An instance can carry the tenant of the client it serves, for clients sharing
one multi-tenant manager. endpoint_count uses it, for the count and the fleet
total beside it, when the block leaves its tenant filter empty; a tenant named
on the block still wins.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TgjV26rz6VcP262dX4vBrD
An instance can carry the account IDs of the client it serves, for clients
sharing one multi-tenant console. count_agents, get_threats, get_alerts and
get_unified_alerts use them when the block leaves its account filter empty;
an account named on the block still wins.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TgjV26rz6VcP262dX4vBrD
Its description gave "⚠ SLA ALERT" as the default, which read as a
value to type: a French mail ended up with an English box title. It now
says to leave the field empty for the badge in the mail language.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BJu1jvaug4qY1kMgz8wx41
description and callout_text declare format: markdown, which Riposte
0.322+ edits in a text area that keeps line breaks; older versions
ignore it and \n still works. README documents format: multiline.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BJu1jvaug4qY1kMgz8wx41
The playbook editor's input field is a single <input>, which drops the
newlines of a pasted text: the whole description reached the script as
one line starting with ## and rendered as a single heading. description
and callout_text without a real line break now take the two characters
\n as one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BJu1jvaug4qY1kMgz8wx41
The runner execs a script without __file__ or __name__: the Test button
failed on __file__ and render-mail-template never reached main(). The
test no longer imports the renderer, and the renderer calls main()
unconditionally like the other integrations.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BJu1jvaug4qY1kMgz8wx41
render-mail-template turns a template, a subject and a markdown
description into the dark ACKnowledge SOC mail (header badge, optional
callout and action button, signature with the logo, SOC or client
footer, fr/en). Nothing is sent: subject, html_body and text_body go to
exchange-send-mail or mail-sender, which embed the data-URI logo.
Eight templates (sla_breach, incident_notification, incident_update,
incident_closure, action_required, escalation, threat_bulletin, info).
The markdown covers GFM tables, code blocks and {{level}} pills; raw
HTML in the inputs is escaped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BJu1jvaug4qY1kMgz8wx41
fetch_rapid7_alerts opens the alert InsightIDR Essential posts as a JSON
string in a single payload field (JSON or form body) and returns one flat
item per relayed message; the correlation id is the dedup key. Own mapper
and alert type, written from a real delivery.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xa1QDbsPTYiVREv2Cvs3Zn
Mail Sender speaks SMTP and Defender for Office 365 only reads and
purges, so nothing in the catalogue could send from an Exchange Online
mailbox with an app registration (tenant ID, client ID, client secret).
send_mail posts to /users/{mailbox}/sendMail: text or HTML, inline
data-URI images as cid attachments, CC/BCC, Reply-To, importance and
saveToSentItems. Over 3 MB of attachments the message goes through a
draft and upload sessions instead, since sendMail caps the request at
4 MB; that path needs Mail.ReadWrite. reply_mail and forward_mail take
the Graph id or the internet Message-ID (resolved with a $filter, which
needs Mail.ReadBasic.All). test_connection reads the token's roles and
fails when Mail.Send is missing, without sending anything.
Exercised against a stubbed Graph: request bodies, the draft path with a
7 MB file in three ranges and no bearer on the upload URL, and Riposte's
ParseManifest. Not yet tried against a real tenant.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The generic mapper now fills the OCSF fields a relayed message has a
place for: metadata.logged_time, log_provider and log_name, the checks
the delivery passed as metadata.labels (relay:hmac…), and raw_data. The
sender's address goes to unmapped.relay.sender_ip rather than
src_endpoint.ip: it is the vendor's cloud, and as an entity it would
correlate every alert of that vendor. metadata.product.name no longer
holds the source id.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xa1QDbsPTYiVREv2Cvs3Zn
Riposte accepts no inbound connection, so a vendor that can only push
(Rapid7 InsightIDR Essential basic detection rules) posts to the relay,
and an alert rule on this integration reads one relay source back over
outbound HTTPS.
fetch_messages takes the rule's watermark as `since` and walks every
page within the run, since the next watermark is this run's instant. A
window holding more than `limit` fails the run instead of truncating, so
the watermark stays put. relay_status and test_connection tell a 404
from the proxy (this Riposte's address is not listed) from a refused
token. The generic mapper keeps the raw body, the sender's address and
the checks the delivery passed (custom:webhook_verified).
Tested against a local relay: 2503 messages over three pages, a
millisecond watermark, the limit, unknown source and bad token.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xa1QDbsPTYiVREv2Cvs3Zn
The alerts PATCH answers 200 to a refused update (errors, resources_affected
0) and the command reported it as a success. It now exits in error with
Falcon's reasons and the ids sent; an empty update is refused up front.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VBnLYwQbUCaf5vpv77N14Q
An alert only summarises its detection; addresses, user, process and
command line live in the events tagged with its short id. with_events
runs one event search per alert (alert_short_ids, first_seen_at - 1 min
to now, as Sekoia's XSOAR integration does), in parallel within the
60 s command budget, and nests the dotted ECS names. A search not done
in time leaves events_pending + events_job_uuid: the alert still goes
out, as the rule's watermark would not offer it again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VBnLYwQbUCaf5vpv77N14Q
On a real tenant the Unified Detections raised from ingested FortiGate
logs are product 'thirdparty'; 'ngsiem' matched nothing. The command now
reads thirdparty and ngsiem by default (products input), and its mapper
is rewritten from real payloads: source product/vendor, matched host,
connection endpoints and ports, vendor signature id, Falcon priority,
console link, and a description built from what fired on which connection.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VBnLYwQbUCaf5vpv77N14Q
search_detections and search_ngsiem_detections now return a query block
(FQL sent, Falcon's total, errors) next to resources, so an empty run can
be told apart from a wrong region or an API client that sees no alerts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NG-SIEM correlation rules file their detections in the unified Alerts
API under product 'ngsiem'. search_detections defaults to epp/ldt and
maps device/process/file blocks those detections do not carry.
New search_ngsiem_detections command (product clause always kept, filter
ANDed, created_from watermark), its own first-cut mapper on the fields
every unified alert carries, and a CrowdStrike NG-SIEM Detection type.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HarfangLab answers a 500 now and then on a call that passes when sent
again. Each script's request() now retries up to three times (1, 2, 4 s):
GET on 429/500/502/503/504, writes only on 429/502/503 so an isolation,
a kill or a job is never sent twice.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Given up to 50 keys and an instant, returns the issues updated since with
their status, new comments (author id, plain-text body, own comments
flagged) and status transitions, plus the instance's own account. JQL dates
are converted to the account's time zone; malformed keys are never queried;
a deleted key falls back to per-key reads instead of failing the batch.
get_comments gains order, start_at and author_id, and decodes HTML entities.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The endpoints API has no filter on operational_status, so the endpoints
the other filters match are read 100 at a time and counted by the script.
Lets an agent reporting playbook count the agents in error, as
SentinelOne's operationalStatesNin does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LaiNEwG2W5Y9fMo4qXcesr
Wraith spent ten calls on a real unified alert and still lost the host:
it took the UAM asset id for an agent id and the raw agent list ran past
what a tool result carries. investigate_alert reads the alert, its
detail, the host's agent (by id or UUID, then name), the other alerts
and threats on that host around the detection, and the file SHA1's
reputation, blocklist and fleet-wide threats, summarised in one answer.
The operator sheet now starts with it (budget 8).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RhL2gy6Sx6Y3FEsyBE3mZ
Riposte gives a tool result 8000 characters; two full summaries ran to 11903
and were cut at 3903, the asset list half gone. The answer now keeps only the
fields an authentication question is answered from (destination and source
asset and address, domain, result, logon type, service) and lists up to 40
new destination assets in full: about 2.5 KB for 1000 events.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RhL2gy6Sx6Y3FEsyBE3mZ
The keyword search matches the account name anywhere in a line, so it brought
other accounts' logons: on a real run the only "new" event reported was
another user's failed password. Only events whose account/user field is the
user (DOMAIN\\user and user@domain normalized) are summarized now, with
keyword_matches and user_matches both reported. A first authentication is
recognized as Rapid7 flags it, a boolean new_authentication field, as well as
the word. The event limit cannot be set below 10000 (the agent passed 1000).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RhL2gy6Sx6Y3FEsyBE3mZ
A new read command answers what a behaviour authentication alert asks —
which assets the user authenticated to around the alert, and which were new —
in one call: it resolves the Asset Authentication log set, builds the
keyword query and the 24 h window itself, and summarizes all events and,
apart, those marked new_authentication. Across real runs gpt-oss mangled log
set ids, computed the window a day off, put a filter inside the LEQL (HTTP
400) and read ten lines out of a thousand; this leaves it nothing to get
wrong. Static calls it first for such alerts; list_investigation_alerts
points to it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RhL2gy6Sx6Y3FEsyBE3mZ
query_log_set takes only="field=value[, …]", applied to the events' fields
after the search (case ignored), and says how many matched before it. Static
runs a second summary of the same query restricted to what the alert is about
— for a "new assets" alert, the new authentications — and names their
destination assets: the first real run summarized 978 events but left the 64
new ones mixed in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RhL2gy6Sx6Y3FEsyBE3mZ
summarize=true returns, over every matching event, each field's most frequent
values with counts, the first and last event time and five samples, instead of
the raw events. On a real run Static got 1000 raw events, could read about
ten, and reported the hosts and accounts of those ten; 300 events now fit in
about 1.5 KB. Static passes summarize always and quotes the returned window.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RhL2gy6Sx6Y3FEsyBE3mZ
It could only read a sandbox report by MD5 — no web logs — so it added noise to
the crew lead's routing without answering an investigation question. Riposte
retires the installed agent on the next update of the integration.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RhL2gy6Sx6Y3FEsyBE3mZ
get_investigations_full dropped every alert without an RRN, so a behaviour
(UBA) investigation arrived with alerts: [] and alert_count: 0 and its
reading worked from a title alone. Those alerts are now kept under
alerts_without_rrn as the investigation endpoint returns them, counted in
alert_count, rendered in summary_markdown with where their evidence lives,
and lift first_alert_type / first_alert_source / first_alert_id when they are
all the investigation has.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RhL2gy6Sx6Y3FEsyBE3mZ
query_log_set takes anchor_time with hours_before / minutes_after and computes
the window itself, and returns it as text: on a real run the agent computed
"24 hours before 2026-10-11T00:04Z" as ending on the 10th and reported a
day-old window. Static passes the alert's first seen time instead of dates,
and lists domain controllers apart from the new assets.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RhL2gy6Sx6Y3FEsyBE3mZ
query_log_set accepts log_set_name and resolves the id itself (exact name, or
the one set whose name contains it; otherwise refused with the existing
names): on a real run the model mangled a 36-character id it copied. Static
queries by name, retries a 0-event keyword search case-insensitively, takes
the 24 hours before a behaviour alert (they aggregate hours of activity), and
always looks for the user's other investigations.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RhL2gy6Sx6Y3FEsyBE3mZ
list_investigation_alerts now marks each alert with alert_rrn, evidence_api
and next: a behaviour alert with no RRN says, in the output the agent reads,
that the Alert Triage commands answer 404 for it and that its evidence is in
the authentication logs. Static still called them when only its instructions
said so. Its sheet gets 14 calls spent in a stated order, authentication log
sets for lateral-movement alerts (never file access), and a window built from
first and last seen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RhL2gy6Sx6Y3FEsyBE3mZ
agents/operator.yaml: read-only commands, analyst_l2 ceiling, and the procedure
learned on a real UBA alert (start from the investigation's alerts, plain-UUID
alerts have no Alert Triage evidence, LEQL keyword search in parentheses,
search_investigation takes start_time/end_time). README documents agents/.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RhL2gy6Sx6Y3FEsyBE3mZ
crt.sh often answers 502 for minutes at a time. When it is down,
sub-domain and exact searches are now answered by SSLMate Cert Spotter,
which reads the same CT logs; its rows are reshaped into crt.sh's so the
output keeps its shape, plus 'source' and 'complete'. starts_with has no
fallback (Cert Spotter cannot search by prefix).
Riposte kills an integration script at 60 s, and the old retry loop
(4 x 120 s) could never finish. Every call now takes its timeout from a
55 s budget, with 15 s kept for the fallback; only 5xx are retried.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmWT9wtWHGtZpMS64Hp4q5
Three keyless sources for the questions an analyst asks first:
- circl-hashlookup 1.0.0: is this hash known software (NSRL, distro
packages, Windows updates)? `score` 0 for a known file, 100 when CIRCL
flags it KnownMalicious, so an enrichment rule with benign 0 marks it
benign; an unknown hash is an HTTP 404, read as "no record". Single and
bulk lookups; base_url for a self-hosted instance.
- rdap 1.0.0: who registered a domain, IP block or AS, and when — with
age_days for the phishing question. Authoritative server from the IANA
bootstrap, rdap.org as fallback; a host name walks up to its registered
domain; GDPR placeholders are not reported as registrants; a TLD with no
RDAP (.ch) is an error, not a fake "unregistered".
- crtsh 1.0.0: Certificate Transparency — sub-domains with first/last seen,
issuers, recent certificates, and a starts_with mode for look-alikes of a
brand (crt.sh no longer searches with a leading wildcard). Retries the
502/503/504 its shared front throws under load.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmWT9wtWHGtZpMS64Hp4q5
The get_security_events and get_alerts mappers set an incident's
description to alert_type alone — "sigma", "yara" — which says nothing
an analyst can act on. It now takes msg, the rule's own account of what
it detects and what to check, and falls back to the alert type only on
alerts that carry no message. Folded into the unreleased 1.7.1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXFMuDDLohs4qnL12mBJoP
HarfangLab's alert and threat list endpoints echo, on every call, the
catalogue of every filterable field — about 50 KB, unrelated to the
results. get_security_events, get_alerts and get_threats now drop it
before printing, so a fetch carries the alerts and little else.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CXFMuDDLohs4qnL12mBJoP
The previous commit said MTTD was retired. Migration 0256 did retire it,
but 0257 restored it at once: what stays is its anchor, the moment
Riposte records the alert. The OCSF `time` field is still not read to
date detection — that part was right.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Eight mappers (Cortex XDR, CrowdStrike, Rapid7) still called the OCSF
`time` field "the MTTD anchor" that Riposte reads to stamp detected_at.
Riposte retired MTTD in migration 0256 and stamps detection at
ingestion for every alert; the mappings stay, as the alert's own record
of when the source saw the event. Comments only: no mapping changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- incident source is 'Sekoia XDR'; Sekoia's `source` (the alert's source
address) goes to src_endpoint.ip/.hostname, `target` to dst_endpoint;
- description: the alert's sentence with {rule:<uuid>} replaced by the rule
name; the rule documentation (`details`) moves to analytic.desc;
- every TTP mapped into attacks; kill_chain_short_id no longer written as
an ATT&CK tactic uid;
- OCSF times in epoch ms; finding_info.types, analytic.category,
metadata.product added.
Checked with Riposte's mapper preview on the alert and a variant (IPv4
source, named target, one TTP): no expression error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since HarfangLab merged alerts and security events, a threat's events
are the alerts whose threat_key is the threat id — the filter the
vendor's XSOAR integration uses. get_alerts could filter on it but
stops at one page of 100; the new threat_security_events command pages
through up to max_results and returns the threat's total with a
truncated flag.
threat_status, threat_level and alert_tag offer their status and level
as drop-downs, and threat_status now reads as the place to close a
threat or mark it a false positive, HarfangLab's only verdict.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Title and description both read the incident's description, so every
ticket showed the same sentence twice. The title is now the rule that
fired, with "(+N other alerts)" when several are folded in, or the
incident's console name; the description adds what each distinct rule
means.
network_artifacts[0] no longer overwrites the alert's own flow: the
artifacts are the fallback, and the destination country comes from the
artifact matching the alert's remote IP. Also maps the rule description
and id, and the XDR assignee.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Falcon records a verdict as a tag — true_positive, false_positive or
ignored, which the console's Resolution column reads — and update_status
only takes new, in_progress, reopened and closed, so resolve_detection
had no way to record one. Its new verdict input adds the tag after
removing the other two, add_tag never replacing one; status and verdict
are drop-downs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhDQSYZd1mFtRsXK4ixtyB
create_issue and edit_issue declare options_from on project_key,
issue_type_name and priority, and link_issues on link_type, so the
playbook editor lists the Jira site's own values. Three read commands
feed them: list_projects, list_issue_types (per project) and
list_priorities. The README documents enum and options_from.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhDQSYZd1mFtRsXK4ixtyB
cortex-xdr-update-incident declares the API's nine status values and
the four severities, so the playbook editor offers them in a drop-down
and the command bar refuses a misspelt one. resolved_other joins the
list; resolved_auto leaves it, Cortex XDR setting it itself.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhDQSYZd1mFtRsXK4ixtyB