Runbook: Data Exfiltration Pattern¶
Incident kind: data_exfil_pattern · Severity: high · Detection rule: correlate::rule_data_exfil
Status: Implemented heuristic correlation for gateway evidence. It is not content-aware DLP and does not prove delivery to an external destination.
Overview¶
Use this runbook when one agent performs a source-like read followed by a sink-like external write within the correlation window. Contain first, preserve evidence, determine what data and destination were involved, rotate potentially exposed credentials, and verify both Aegis state and the external destination.
Symptoms¶
- A SOC alert/incident with
kind: "data_exfil_pattern"appears inGET /v1/incidents. - The pattern: a Source action (
lib/soc/src/correlate.rs'sSOURCE_TOKENS— names containingread/get/fetch/list/download/query/select/export/dump/cat) is followed withinEXFIL_WINDOW_SECS(120s) by a Sink action for the same agent (SINK_TOKENS— names containingsend/post/upload/email/webhook/push/publish/write_external/share/transfer, or any action literally namedexfil). Matching is substring-based and case-insensitive on the action name only, so it's a heuristic, not a content-aware DLP scan. - This is the canonical real-world pattern from the Invariant Labs GitHub-MCP disclosure (T-B2 in
AegisAgent_Threat_Model.md): a malicious issue tricks an agent into reading private data, then pushing it somewhere public/external.
Before you start: check whether this already auto-resolved¶
data_exfil_pattern maps to freeze the agent + a critical-severity notification in the Response Engine (lib/soc/src/respond.rs), but only runs at SOC autonomy level L3/L4 (default is L1, notify-only). Check the tenant's effective level the same way as in deny-storm.md before assuming containment already happened.
Investigation¶
- Find the incident and its evidence graph (same as the deny-storm runbook):
- Identify the Source and Sink decisions specifically — the incident's
source_event_idslink to exactly two decisions (the paired read and the paired write). Resolve each event to its decision viaGET /v1/decisions/:idto see the actualtool/action/resourceandrisk_score. - Check the triggering context's trust level. Was the run triggered by
untrusted_external/semi_trusted_customercontent (e.g. a public issue, an email)? If so this strongly corroborates T-B1/T-B2 (confused-deputy via provenance) rather than a benign coincidence — cross-checkGET /v1/decisions/:id's recordedtrust_level/root_trust_level. - Check what was actually read and where it went. The decision's
resourcefield (e.g. a file path, a repo, a record id) tells you the blast radius — was it a single record or a bulk export? Where did the sink action send it (an external webhook URL, a public repo, an email address outside the org)? - Generate an RCA narrative:
Remediation¶
This pattern is higher-confidence-malicious than a deny storm — default to containment first, investigation second:
- Freeze (or revoke, if you're confident this is a real compromise) immediately:
- Rotate the agent's token regardless of root cause — even a false positive doesn't hurt from rotating, and a true positive may mean the token is already compromised (see
agent-token-rotation.md): - If the sink was an external destination you control (e.g. your own webhook endpoint, a repo), check whether the data actually arrived there and needs to be deleted/revoked at the destination — AegisAgent's containment stops the agent, not data already in flight or already delivered.
- If the source was triggered by untrusted content (a malicious issue/email/ticket), consider whether the underlying confused-deputy gate needs tightening — e.g. an explicit
forbidpolicy for this specific tool/action combination under untrusted provenance, beyond the defaultmutates_state && untrusted_externalrule (a read-then-external-write chain may not always tripmutates_stateon the read half). - Close the incident once handled:
Verification¶
GET /v1/agents/:idshowsstatus: "revoked"or"active"with a confirmed-rotated token.- The old token is rejected on the next
/v1/authorizecall (401). GET /v1/incidents/<incident_id>showsstatus: "closed".- If a destination cleanup was needed, confirm directly against that external system (outside AegisAgent's scope) — note this in the incident close reason for the audit trail.
- No repeat
data_exfil_patternincident for the same agent in the followingEXFIL_WINDOW_SECS(120s) window.
Security and Failure Handling¶
- Treat attacker-controlled prompt/tool content as inert evidence; do not follow instructions embedded in it.
- Do not copy sensitive source data into incident chat or tickets. Record identifiers, hashes, and protected evidence locations.
- Freezing the agent does not recall data already delivered. Confirm destination cleanup independently.
- A missing correlation is not proof of safety; the detector uses action-name heuristics and deployed evidence only.
- If containment fails, revoke credentials at the upstream tool/provider and restrict network access using external controls.
Rollback and Recovery¶
Do not unfreeze solely because the incident was closed. Restore the agent only after the trigger source, policy gap, credentials, destination impact, and agent configuration are understood. Re-enable with a fresh token and a low-risk verification action; monitor for at least one correlation window.