467 of the 1592 commands carried no `risk:`. An unset risk is read as
destructive by the agent approval gate, so half the catalog's write side sat
behind the same prompt — and, in HarfangLab's case, 39 pure lookups did too.
A gate that stops everything stops being read as a signal, which is the
argument already made for the toolkit and for sentinelone (df27876).
The catalog is now 1027 read, 261 safe_write, 304 destructive, with nothing
defaulted. The line follows the one sentinelone drew: impact on the estate,
not reversibility — isolate-agent is reversible and is the canonical
destructive example.
read only queries; changes nothing outside Riposte
safe_write additive or reversible inside the tool's own case/enrichment
plane: comments, tickets, tags, alert statuses, threat-intel
records, sandbox submissions, collection jobs, outbound messages
destructive changes the estate or weakens a control: containment, blocklists
and allowlists, exclusions and detection rules, every delete,
identity and credential operations, code on a host, generic
API escape hatches
Three calls that were not obvious, and are now applied throughout:
- Undoing a containment is tiered like the containment it lifts. Un-isolating
a host or un-banning an IP mid-incident is its own decision, not a return to
a neutral state. The catalog contradicted itself here — crowdstrike's
lift_containment was destructive while harfanglab's deisolate_from_threat
was safe_write — so deisolate_from_threat and unquarantine_file are
re-tiered to destructive, the only two existing tiers this pass changes.
- Retrieving a live secret is destructive: cyberark get_password, delinea
get_secret, vault read_secret, beyondtrust get_credential. It is a GET, but
the tier describes the stake, not the HTTP verb.
- Sending outward to humans stays safe_write — slack, teams, twilio, telegram,
mail. Irreversible, but it is the collaboration plane, not the estate.
An object created but not yet referenced by a rule is safe_write; editing one
a rule already reads is destructive. That is why fortigate create_address and
panos create_custom_url_category are safe_write while update_address_group and
edit_custom_url_category are not.
The template and README never documented the field, which is how 467 commands
came to omit it. Both now carry the tier table and these rules, so the next
integration declares a tier instead of inheriting one.
111 packages get a MINOR bump and a changelog entry: an instance compares
versions to offer an update, so an unbumped manifest would leave the tiers
sitting in the catalog and never reaching the estates that need them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A saved Jira filter could always be ingested: "filter = 12345" is ordinary JQL,
and the fetch's own created >= watermark clause sits on top of it happily. What
was missing was the id. Nothing in the product named the filters an instance
holds, so the only way to reach one was to open Jira, find the filter, and read
the number out of the browser's address bar — which is enough friction that the
workflow may as well not exist, and the alternative is retyping a JQL query the
SOC already maintains in Jira.
list_filters returns each filter's id, name, JQL and owner, plus jql_reference:
the clause to paste, already written. Cloud reads filter/search, which pages
and can match a name server-side; Data Center reads filter/favourite, present
across versions where filter/search is not, and pages and matches locally. Both
response shapes — a paginated envelope, a bare array — were checked against
what the API actually returns.
get_incidents and query_issues now say a filter can go in their query box, and
which command hands over the id.
Every input carried a description, and most of them restated the field name:
"Comment ID" for comment_id, "Board ID" for board_id, "JQL query string" for
query. That is a label, not help. An operator filling get_incidents for the
first time has no way to learn from it that the fetch appends its own
created >= watermark clause and an ORDER BY, that writing either breaks it,
that the watermark box is filled by the alert rule rather than by hand, or that
a run makes a single call and silently drops whatever exceeds max.
103 input fields rewritten to answer two questions instead: what goes in this
box, and where do I get it. Formats are shown by example rather than named —
2026-09-30 rather than "yyyy-mm-dd", soc,phishing rather than "comma-separated"
— and every field holding an id now names the command that returns it:
get_id_by_attribute for an assignee, board_list for a board, get_comments for
a comment, get_issue for an attachment, get_link_types for a link type and for
which issue belongs on which side of it.
Fields that replace rather than append say so, since labels and components
quietly drop whatever is left out.
A personal access token silently outranked Basic credentials. Anything left in
pat — an email pasted into the wrong field, a token from a decommissioned Data
Center — became the Authorization header, and Atlassian answered "Client must
be authenticated to access this resource." That sentence names no field, so the
search starts at the URL, the API version, the token itself: everywhere except
the one field that caused it.
Basic authentication now wins whenever username and api_key are both set, and
pat serves the rest, so a token left behind cannot outrank credentials someone
has just filled in. A lone username, or a lone api_key, is not a pair and does
not claim the request. A 401 or 403 says which method was used and whether the
other was configured and ignored.
Three configurations that cannot work are refused with the reason rather than
sent to Jira:
- no usable credential, where an empty username/api_key pair was being
base64-encoded into a valid-looking header;
- the Cloud gateway URL with an empty cloud_id, which addresses no site, so
every path under it is a dead link and Jira replies with an HTML 404 that
explains nothing;
- a cloud_id set against any other host, where it is not an address at all,
only a path segment that does not exist.
Credentials are stripped before use: one pasted with a trailing newline
corrupts the header and reads back as a wrong password.
Error bodies are condensed instead of dumped. Jira's 404 page is four hundred
lines of AUI markup, stylesheet links and inline scripts around one sentence,
and it was going into the test modal whole; what surfaces now is the <title>
and the readable text, capped. A 2xx that is not JSON — a login page, an SSO
redirect, a proxy standing in for the API — reports what answered and the URL
the redirects landed on, in place of a JSON parser offset.
Config descriptions now say which fields pair with which deployment, and which
credential is used when both are present.
Applied to all 27 scripts; the seven shared helper blocks stay identical across
them.
Jira Cloud and On-Prem/Data Center (27 commands): JQL ingestion with an
OCSF mapper and JQL query, full issue lifecycle (create/get/edit/delete,
transitions, assign), comments, remote web links and issue links,
attachments (upload/download/delete), field and user lookups, and agile
boards/sprints/epics. Basic (email + API token) or Personal Access Token
authentication; API v3 + ADF bodies on Cloud, v2 on On-Prem.
Stdlib-only, no extra Python dependencies.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>