---
name: transilienceai/attack-path-stitcher
source: https://app.decimal.ai/s/transilienceai-attack-path-stitcher@1/SKILL.md
source_sha256: a370be9659e3
---

# Attack Path Stitcher

The Validation Run task (#3) produces confirmed findings *per asset*. Real attacker risk lives in **chains**: a finding on asset A leaks credentials that enable a finding on asset B that pivots into asset C. This skill builds that graph.

Mounted onto cloud-agent task #6.

## Trigger

Cron daily (default 03:00 UTC). May also re-run after a Validation Run task batch completes.

## Workflow

1. **Load inputs.**
   - `validated/*.json` — every confirmed finding across all engagements.
   - `artifacts/org-surface.json` — the org-wide surface graph (assets, services, network zones, trust relationships).
   - `findings/finding-NNN/evidence/raw-source.txt` — for credential / token extraction during stitching.
2. **Build asset nodes.** One node per asset in `org-surface.json`, attributed with: `tier`, `services`, `network_zone`, `trust_relationships`.
3. **Build edges** — one edge per detected pivot. See `reference/edge-detectors.md` for the seven detectors:
   - Credential reuse (creds leaked on A reused as auth on B)
   - Shared secret / API key (same secret appears in two assets' evidence)
   - Trust-zone transitive access (A in zone X has implicit reach to B in zone X)
   - AD path hops (kerberoast / DC sync / RBCD chains)
   - Cloud IAM role chains (assume-role from compromised asset)
   - SSRF → internal asset reach (A's SSRF reaches B's internal endpoint)
   - Supply-chain (A is a dependency of B per `source-code-scanning` SBOM)
4. **Compute reachability closure.** For each tier-`crown_jewel` node, BFS backwards through edges to find every external-facing node that can reach it. Mark these as "entry points".
5. **Write graph** to `artifacts/attack-paths.json` plus a human DOT file `artifacts/attack-paths.dot` (renderable with Graphviz).

Implementation runs through `tools/chain-merger.py` which handles the graph construction. The skill provides the *rules* the tool consults; the tool does the iteration.

## Output

```
{OUTPUT_DIR}/
  artifacts/
    attack-paths.json     # nodes, edges, entry_points, crown_jewel_paths
    attack-paths.dot      # Graphviz source
    attack-paths.md       # ranked list of distinct paths (human read)
```

`attack-paths.json` schema:

```json
{
  "generated_at": "2026-05-13T03:00:00Z",
  "nodes": [
    {"id": "asset42", "tier": "revenue", "services": ["http/443"], "zone": "dmz",
     "external": true, "findings": ["finding-012", "finding-018"], "max_cvss": 9.8}
  ],
  "edges": [
    {"src": "asset42", "dst": "asset77", "detector": "credential-reuse",
     "via_findings": ["finding-012", "finding-019"],
     "evidence": "credential (userpass) present in evidence of asset42 and asset77",
     "feasibility": 1.0}
  ],
  "entry_points": ["asset42", "asset05"],
  "confirmed_paths": [
    {"jewel": "asset99", "paths": [
      {"hops": ["asset42", "asset77", "asset99"],
       "edges": [{"src":"asset42","dst":"asset77","detector":"credential-reuse","feasibility":1.0,"via_findings":["finding-012"]},
                 {"src":"asset77","dst":"asset99","detector":"ssrf-reach","feasibility":1.0,"via_findings":["finding-024"]}],
       "feasibility": 1.0, "max_cvss": 9.8, "path_class": "confirmed"}
    ]}
  ],
  "inferred_paths": [
    {"jewel": "asset99", "paths": [
      {"hops": ["asset05", "asset99"], "edges": [...],
       "feasibility": 0.5, "max_cvss": 7.5, "path_class": "inferred"}
    ]}
  ],
  "truncation": {
    "edge_cap_hit": false, "depth_truncated_count": 0,
    "topn_dropped_count": 0, "max_depth": 8, "edge_cap": 50000
  }
}
```

**Crucial for RFP §3.3 compliance**: `confirmed_paths` contains ONLY paths where every edge has feasibility 1.0 AND every edge cites at least one validated finding. These are the "confirmed attack paths" the RFP requires. `inferred_paths` carries topology / supply-chain hops with no PoC evidence — surfaced for analyst review but excluded from remediation SLA buckets by `risk-prioritiser`.

## Rules

1. **Edges require evidence.** An edge is only written if at least one finding's evidence corroborates the pivot. No speculative edges.
2. **Bi-directional ≠ assumed.** If A reaches B, do not infer B reaches A. Each direction needs its own evidence.
3. **Deduplicate by `(src, dst, detector)`.** Multiple findings that enable the same hop merge into one edge with `via_findings` listing all of them.
4. **Feasibility ∈ {1.0, 0.5, 0.25}.** Reliable PoC re-run = 1.0; conditional (race, timing, specific user) = 0.5; theoretical (logically follows but never demonstrated) = 0.25.
5. **Limit path enumeration.** For each crown-jewel, return top-10 paths per class (confirmed + inferred separately) sorted by `feasibility × max_cvss / hop_count`. Full graph is in `attack-paths.json` for downstream prioritisation.
6. **Read-only.** Stitcher never re-fires PoCs and never touches `findings/`. It only reads.
7. **Bound graph size.** Stop edge construction at 50,000 edges; cap path-search depth at `--max-depth` (default 8 hops). Emit `truncation.edge_cap_hit`, `truncation.depth_truncated_count`, and `truncation.topn_dropped_count` in the JSON so downstream consumers can detect silent path loss.
8. **Confirmed vs inferred is non-negotiable.** A path appears in `confirmed_paths` only if every edge has feasibility 1.0 AND every edge has a non-empty `via_findings`. Trust-zone-only, shared-secret-only, and supply-chain-only chains land in `inferred_paths`. This split is the contract that lets the RFP-§3.3 claim "confirmed attack paths" stand.
9. **Schema enforcement on input.** `tools/chain-merger.py` drops `validated/{id}.json` rows missing `finding_id` or `asset`, or whose `verdict != "VALID"`, with stderr WARNs. Upstream validator must comply with the schema in `projects/rfp-3.2/task-03-validation-run.md`.

## References

- `reference/edge-detectors.md` — the 7 detector rules with concrete signal patterns.
- `projects/rfp-3.3/task-06-attack-path-stitcher.md` — cloud-agent runtime contract.