attackmap 0.4.10__tar.gz → 0.4.21__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of attackmap might be problematic. Click here for more details.
- {attackmap-0.4.10/src/attackmap.egg-info → attackmap-0.4.21}/PKG-INFO +169 -28
- {attackmap-0.4.10 → attackmap-0.4.21}/README.md +168 -27
- {attackmap-0.4.10 → attackmap-0.4.21}/pyproject.toml +1 -1
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/__init__.py +1 -1
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/analyzers.py +37 -18
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/anomalies.py +214 -8
- attackmap-0.4.21/src/attackmap/authz.py +451 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/cli.py +210 -39
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/cve.py +11 -1
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/exploitability.py +3 -1
- attackmap-0.4.21/src/attackmap/hunt_harness.py +451 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/llm_review.py +36 -7
- attackmap-0.4.21/src/attackmap/lockfiles.py +387 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/models.py +45 -7
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/review_prompts.py +233 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/sbom.py +31 -3
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/scanner.py +81 -5
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/taint.py +330 -47
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/threat_model.py +93 -7
- attackmap-0.4.21/src/attackmap/triage.py +151 -0
- {attackmap-0.4.10 → attackmap-0.4.21/src/attackmap.egg-info}/PKG-INFO +169 -28
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap.egg-info/SOURCES.txt +7 -0
- attackmap-0.4.21/tests/test_anomalies.py +663 -0
- attackmap-0.4.21/tests/test_authz.py +472 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_cve.py +91 -0
- attackmap-0.4.21/tests/test_hunt_harness.py +558 -0
- attackmap-0.4.21/tests/test_lockfiles.py +348 -0
- attackmap-0.4.21/tests/test_recall.py +310 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_scanner.py +79 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_taint.py +119 -0
- attackmap-0.4.21/tests/test_triage.py +201 -0
- attackmap-0.4.10/src/attackmap/authz.py +0 -196
- attackmap-0.4.10/tests/test_anomalies.py +0 -397
- attackmap-0.4.10/tests/test_authz.py +0 -241
- {attackmap-0.4.10 → attackmap-0.4.21}/LICENSE +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/setup.cfg +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/analyzer.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/analyzer_contracts.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/asset_model.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/attack_taxonomy.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/config_scanner.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/context_pack.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/control_model.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/crypto.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/defensive_review.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/detection_opportunities.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/diagrams.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/diff.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/graph.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/insights.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/merge.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/progress.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/recon_models.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/recon_to_analysis.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/report.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/review_eval.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/review_json.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/route_auth_fusion.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/sarif.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/sdk/__init__.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/sdk/contracts.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/sdk/models.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/security_overlay.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/srcpaths.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/suggest.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/suppress.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/topology.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/weaknesses.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/webhardening.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap/workflow_scanner.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap.egg-info/dependency_links.txt +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap.egg-info/entry_points.txt +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap.egg-info/requires.txt +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/src/attackmap.egg-info/top_level.txt +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_analyzer.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_analyzer_metadata_schema.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_analyzers.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_artifact_schemas.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_config_scanner.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_context_pack.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_crypto.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_defensive_review.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_diagrams.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_diff.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_exploitability.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_graph.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_hunt.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_injection_sinks.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_llm_review.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_merge.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_modules_cli.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_pr_comment.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_progress.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_provenance.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_recon_to_analysis.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_remediate.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_report.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_review_eval.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_review_json.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_review_prompts.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_route_auth_fusion.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_sarif.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_sbom.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_security_overlay.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_shared_contract_imports.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_signal_v2.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_srcpaths.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_suggest.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_suppress.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_taint_sanitizer.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_threat_intel.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_threat_model.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_topology.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_weaknesses.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_webhardening.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.21}/tests/test_workflow_scanner.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: attackmap
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.21
|
|
4
4
|
Summary: AI-assisted defensive security analyzer for codebases — scans a repository, models assets and controls, finds cross-cutting weaknesses, and generates an evidence-grounded review with MITRE ATT&CK mappings and detection-engineering hints.
|
|
5
5
|
Author: AttackMap Contributors
|
|
6
6
|
Author-email: Matthew Davis <matthewd@matthewd.xyz>
|
|
@@ -77,12 +77,24 @@ managers who need to triage an unfamiliar codebase.
|
|
|
77
77
|
[](https://www.python.org/)
|
|
78
78
|
[](https://pypi.org/project/attackmap/)
|
|
79
79
|
|
|
80
|
-
> **Status: beta (v0.4.
|
|
80
|
+
> **Status: beta (v0.4.18).** Core engine and 14 analyzer plugins are published
|
|
81
81
|
> to PyPI, Homebrew, and GHCR and validated against real-world codebases.
|
|
82
82
|
> AttackMap is heuristic by design — findings are confidence-tiered evidence,
|
|
83
83
|
> not proof. See [Project status](#project-status) for what's solid and what's
|
|
84
84
|
> still maturing.
|
|
85
85
|
|
|
86
|
+
### The macOS app
|
|
87
|
+
|
|
88
|
+
Prefer a GUI? AttackMap ships a native macOS front-end that drives this CLI and
|
|
89
|
+
renders every result view — install with `brew install --cask mlaify/tap/attackmap-app`
|
|
90
|
+
(the cask depends on the `attackmap` formula, so this pulls the CLI too).
|
|
91
|
+
|
|
92
|
+
| Overview | Exploitability |
|
|
93
|
+
|---|---|
|
|
94
|
+
| [](https://docs.matthewd.xyz/gui/) | [](https://docs.matthewd.xyz/gui/) |
|
|
95
|
+
|
|
96
|
+
*Scanning OWASP Juice Shop. Full walkthrough in the [macOS app docs](https://docs.matthewd.xyz/gui/); source at [mlaify/AttackMap-mac](https://github.com/mlaify/AttackMap-mac).*
|
|
97
|
+
|
|
86
98
|
---
|
|
87
99
|
|
|
88
100
|
## Quickstart
|
|
@@ -187,6 +199,7 @@ Every `attackmap analyze` run writes:
|
|
|
187
199
|
| `defensive-review-llm.md` *(with `--llm`)* | Claude-narrated review |
|
|
188
200
|
| `defensive-review-llm.meta.json` *(with `--llm`)* | Backend, model, token usage |
|
|
189
201
|
| `vulnerability-hypotheses.md` *(with `--hunt`)* | LLM-generated, evidence-cited exploit-chain **hypotheses** to confirm (leads, not detections) |
|
|
202
|
+
| `triage.md` *(with `--triage`)* | Clustered, de-duplicated, ranked shortlist of the existing findings (LLM-backed; deterministic fallback) |
|
|
190
203
|
|
|
191
204
|
### GitHub Code Scanning integration
|
|
192
205
|
|
|
@@ -345,7 +358,11 @@ left on).
|
|
|
345
358
|
|
|
346
359
|
Sinks that are only dangerous with attacker-controlled input (SSRF, SSTI, NoSQL,
|
|
347
360
|
open redirect, `open`) are gated on a request-shaped identifier in the call — a constant URL or
|
|
348
|
-
template is not flagged.
|
|
361
|
+
template is not flagged. The walk is **call-graph-aware** (#138): for Python and
|
|
362
|
+
JS/TS an import edge is kept only when the imported symbol is actually used in
|
|
363
|
+
the file, so a dead import no longer fans a route out to that module's sinks
|
|
364
|
+
(the plain import-graph remains the fallback for namespace/star/side-effect
|
|
365
|
+
imports and for Go/PHP). It's still a heuristic (use ≉ proven call), so findings
|
|
349
366
|
are evidence, not proof; confidence tapers with hop distance. Chains appear in
|
|
350
367
|
`attackmap-report.json` under `scan.taint_chains`.
|
|
351
368
|
|
|
@@ -367,6 +384,27 @@ excluded from all the heuristic passes above by default, since dangerous
|
|
|
367
384
|
patterns in test scaffolding are rarely real exposure. Set
|
|
368
385
|
`ATTACKMAP_INCLUDE_TESTS=1` to scan them too (e.g. for test-quality reviews).
|
|
369
386
|
|
|
387
|
+
**Recall mode (`--recall`).** Aggressiveness is only useful *behind* a verifier —
|
|
388
|
+
so `--recall` widens taint discovery and marks the extra reach **speculative**
|
|
389
|
+
instead of asserting it. It:
|
|
390
|
+
|
|
391
|
+
- raises the max import-hop depth (2 → 4) and per-route visit budget;
|
|
392
|
+
- surfaces reaches the default pass conservatively hides, like a
|
|
393
|
+
static-literal-looking `eval`/`exec`/deserialize argument;
|
|
394
|
+
- **enumerates capability-reach**: every reach to a powerful capability —
|
|
395
|
+
network (`requests`/`httpx`/`axios`/`fetch`/`urlopen`), template
|
|
396
|
+
(`render_template_string`/`Template`), filesystem (`open`), redirect
|
|
397
|
+
(`redirect`/`res.redirect`) — even with **no known-bad pattern**, i.e. the
|
|
398
|
+
bare call without a request-derived argument (a genuine request-derived sink
|
|
399
|
+
still fires once, as a confirmed finding, not a duplicate).
|
|
400
|
+
|
|
401
|
+
Speculative chains carry downgraded confidence, get their own LOW-severity
|
|
402
|
+
finding (`SPECULATIVE (recall mode)…`), are excluded from every asserted
|
|
403
|
+
downstream pass (exploitability, BOLA, attack paths), and are kept **out of
|
|
404
|
+
`--fail-on-new-high`** — they are leads for `--hunt --verify` / `--triage` to
|
|
405
|
+
adjudicate, not detections. Default behavior is unchanged; pair `--recall` with
|
|
406
|
+
`--hunt --verify` to widen the net and then adjudicate what it catches.
|
|
407
|
+
|
|
370
408
|
### Web hardening gaps
|
|
371
409
|
|
|
372
410
|
Route- and config-level checks for common web misconfigurations, each an
|
|
@@ -403,6 +441,32 @@ identifier; cipher/ECB tokens are matched case-sensitively so algorithm names
|
|
|
403
441
|
aren't confused with prose (e.g. the French word "des"). Results appear under
|
|
404
442
|
`scan.crypto_weaknesses`.
|
|
405
443
|
|
|
444
|
+
### Hard-coded secret detection
|
|
445
|
+
|
|
446
|
+
Two layers, precision-first. **Typed provider-signature detectors** recognize the
|
|
447
|
+
distinctive shapes of leaked keys and fire at high confidence with a
|
|
448
|
+
provider-specific `kind`:
|
|
449
|
+
|
|
450
|
+
| Provider | Prefix / shape |
|
|
451
|
+
|---|---|
|
|
452
|
+
| AWS | `AKIA…` / `ASIA…` |
|
|
453
|
+
| GitHub | `ghp_` / `gho_` / `ghu_` / `ghs_` / `ghr_` |
|
|
454
|
+
| OpenAI | `sk-…` / `sk-proj-…` / `sk-svcacct-…` |
|
|
455
|
+
| Anthropic | `sk-ant-…` |
|
|
456
|
+
| GitLab / npm | `glpat-…` / `npm_…` |
|
|
457
|
+
| Stripe / Slack / Google | `sk_live_…` / `xox[bpsare]-…` / `AIza…` |
|
|
458
|
+
| SendGrid / Mailgun / Twilio | `SG.…` / `key-…` / `AC…` SID |
|
|
459
|
+
| PEM / JWT | `-----BEGIN … PRIVATE KEY-----` / `eyJ….eyJ….…` (header verified) |
|
|
460
|
+
|
|
461
|
+
Below the typed layer, a **Shannon-entropy fallback** catches high-entropy
|
|
462
|
+
literals that don't match a known provider. It is calibrated to stay quiet:
|
|
463
|
+
pure-hex hashes, URLs, version/number strings, base64/base32 alphabet constants
|
|
464
|
+
(#96), Subresource-Integrity / lockfile digests (`sha384-…`), and UUIDs are
|
|
465
|
+
suppressed, and a typed match always takes precedence over the entropy hit for
|
|
466
|
+
the same span. Values are redacted (`ghp_…6789`) before they reach any report —
|
|
467
|
+
the raw secret never appears. No liveness verification is performed. Results
|
|
468
|
+
appear as `scan.secret_hints`.
|
|
469
|
+
|
|
406
470
|
### GitHub Actions / CI workflow security
|
|
407
471
|
|
|
408
472
|
CI config is a real attack surface. AttackMap parses `.github/workflows/*.yml`
|
|
@@ -442,18 +506,22 @@ adds the general mutating-endpoint case, with the resolved chain as evidence.
|
|
|
442
506
|
OWASP API Security #1. AttackMap flags a route as a BOLA/IDOR candidate when it
|
|
443
507
|
composes three signals it already has:
|
|
444
508
|
|
|
445
|
-
1. the route takes a **resource id**
|
|
446
|
-
`/orders/:orderId
|
|
447
|
-
|
|
448
|
-
|
|
509
|
+
1. the route takes a **resource id** — via a **path** template (`/users/{id}`,
|
|
510
|
+
`/orders/:orderId`), an id-bearing **query parameter** (`?orderId=`), an
|
|
511
|
+
**RPC method** (XRPC `/xrpc/…getRecord`, tRPC `user.byId`), or a **GraphQL
|
|
512
|
+
field** (`user(id: ID!)`) — and
|
|
513
|
+
2. it **reaches a datastore** — a DB hint in the same file/module, a taint chain
|
|
514
|
+
from the route to a SQL execute sink, or (for RPC/GraphQL) an object-access
|
|
515
|
+
operation, and
|
|
449
516
|
3. **no ownership/authorization check** is visible near the handler
|
|
450
|
-
(`current_user`, `request.user`, `authorize`, a policy/guard,
|
|
451
|
-
`filter_by(user_id=…)`-style scoped query
|
|
517
|
+
(`current_user`, `request.user`, `authorize`, a policy/guard, a
|
|
518
|
+
`filter_by(user_id=…)`-style scoped query, or a GraphQL `@auth`/`@hasRole`
|
|
519
|
+
schema directive on the field).
|
|
452
520
|
|
|
453
|
-
Write
|
|
454
|
-
under `scan.authz_candidates
|
|
455
|
-
false-positive reducer, so a
|
|
456
|
-
|
|
521
|
+
Write operations (POST/PUT/PATCH/DELETE, GraphQL mutations) are HIGH, reads
|
|
522
|
+
MEDIUM. Candidates appear under `scan.authz_candidates` (each carrying its
|
|
523
|
+
`surface`); the ownership-marker scan is the main false-positive reducer, so a
|
|
524
|
+
well-scoped handler or an `@auth`-guarded field is not flagged.
|
|
457
525
|
|
|
458
526
|
### Anomaly / outlier detection
|
|
459
527
|
|
|
@@ -469,6 +537,20 @@ within each cohort the odd-one-out is reported:
|
|
|
469
537
|
| `auth_outlier` | siblings carry an auth/authorization signal near the handler; this route doesn't | HIGH |
|
|
470
538
|
| `validation_outlier` | among a cohort's state-changing handlers, peers validate input and this one shows no validation marker | LOW |
|
|
471
539
|
| `method_outlier` | a lone state-changing method in an otherwise read-only cohort | MEDIUM |
|
|
540
|
+
| `invariant_violation` | a mined-invariant outlier: among handlers that reach the same dangerous sink kind, the majority guard the request *before* the sink and this one doesn't — evidence cites the mined rule | HIGH |
|
|
541
|
+
|
|
542
|
+
The `invariant_violation` kind is the **mined-invariant** pass (#149a): its
|
|
543
|
+
cohort is not a route-path prefix but the set of handlers that reach the same
|
|
544
|
+
dangerous sink (from the taint pass). When a strong majority guard the request
|
|
545
|
+
before the sink, that pattern is mined as an implicit invariant and the handler
|
|
546
|
+
that reaches the same sink kind with no preceding guard is flagged, citing the
|
|
547
|
+
mined rule (e.g. "9 of 10 handlers that reach a database (SQL execute) sink
|
|
548
|
+
apply an auth/validation guard before it"). It reasons only about **same-file**
|
|
549
|
+
flows, where guard-before-sink ordering is actually verifiable (a cross-file
|
|
550
|
+
sink could be called before an `authorize(...)` that textually follows it), and
|
|
551
|
+
counts one handler per declaration regardless of how many verbs it exposes.
|
|
552
|
+
Sanitized chains are excluded so only genuinely undefended flows form the cohort
|
|
553
|
+
— it measures the code against its own norm and needs no signature.
|
|
472
554
|
|
|
473
555
|
Everything is peer-relative and **confidence scales with how consistent the
|
|
474
556
|
cohort is** — a lone deviation among many agreeing siblings is likelier a mistake
|
|
@@ -520,8 +602,27 @@ of the common manifest files:
|
|
|
520
602
|
| Composer (PHP) | `composer.json` (require + require-dev; platform reqs skipped) |
|
|
521
603
|
|
|
522
604
|
Each entry appears in `attackmap-report.json` under `scan.dependencies` with
|
|
523
|
-
`{name, version, ecosystem, file, dev}`.
|
|
524
|
-
(`^4.16.0`, `>=2,<3`, `latest`)
|
|
605
|
+
`{name, version, ecosystem, file, dev, resolved, direct, via}`. Manifest ranges
|
|
606
|
+
are kept verbatim (`^4.16.0`, `>=2,<3`, `latest`).
|
|
607
|
+
|
|
608
|
+
**Lockfile resolution (#143).** When a lockfile is present it is parsed for
|
|
609
|
+
*exact* resolved versions and the full **transitive** tree — where most
|
|
610
|
+
known-vulnerable dependencies actually live. Resolved entries set
|
|
611
|
+
`resolved=true`, mark `direct`/transitive, and carry a `via` resolution path
|
|
612
|
+
(`express > body-parser > qs`). A lockfile supersedes the range-only manifest
|
|
613
|
+
in its **own directory** (so a monorepo's per-service lockfiles don't shadow
|
|
614
|
+
each other).
|
|
615
|
+
|
|
616
|
+
| Lockfile | Ecosystem |
|
|
617
|
+
|---|---|
|
|
618
|
+
| `package-lock.json` (v1/v2/v3), `pnpm-lock.yaml` | npm |
|
|
619
|
+
| `poetry.lock`, `uv.lock` | PyPI |
|
|
620
|
+
| `Cargo.lock` | Cargo |
|
|
621
|
+
|
|
622
|
+
Go needs no lockfile pass: `go.mod` already pins exact versions and flags
|
|
623
|
+
`// indirect` (transitive) deps, so it's treated as resolved directly.
|
|
624
|
+
(`go.sum` is a checksum history, not the build list, so it is not used for
|
|
625
|
+
inventory.)
|
|
525
626
|
|
|
526
627
|
### CVE cross-reference (opt-in)
|
|
527
628
|
|
|
@@ -542,9 +643,12 @@ attackmap analyze . --cve
|
|
|
542
643
|
the network.
|
|
543
644
|
- **Offline-tolerant.** If the network's unavailable but the cache is warm,
|
|
544
645
|
cached results still surface; only fresh queries are skipped.
|
|
545
|
-
- **
|
|
546
|
-
|
|
547
|
-
|
|
646
|
+
- **Exact when a lockfile exists.** Lockfile-resolved dependencies (direct and
|
|
647
|
+
transitive) are queried at their pinned version, and a vulnerable transitive
|
|
648
|
+
dep is reported with its resolution path. For range-only manifests with no
|
|
649
|
+
lockfile, version resolution is best-effort — ranges (`^4.16.0`, `>=2.28,<3`)
|
|
650
|
+
resolve to a queryable lower-bound and OSV does the range math; unpinned specs
|
|
651
|
+
(`*`, `latest`) are skipped.
|
|
548
652
|
|
|
549
653
|
The structured vulnerability list is available under
|
|
550
654
|
`scan.vulnerabilities` in `attackmap-report.json` with
|
|
@@ -595,9 +699,35 @@ to confirm, not detections.** The same grounding contract as the review applies
|
|
|
595
699
|
assignment, no exploit code, confidence-tiered, and each lead lists exactly what
|
|
596
700
|
a human must verify. Uses the same auth/backend resolution as `--llm`.
|
|
597
701
|
|
|
702
|
+
With `--verify`, each hypothesis is adjudicated against the actual source at its
|
|
703
|
+
cited locations. **`--verify-votes N`** (default 3) turns that into a jury: **N
|
|
704
|
+
independent skeptics** each adjudicate the same fixed hypothesis list. A lead is
|
|
705
|
+
**CONFIRMED only on a strict majority** (NEEDS_REVIEW only when a majority flags
|
|
706
|
+
the evidence as insufficient); ties and uncertainty are **REFUTED** — so a lead
|
|
707
|
+
only one skeptic would confirm is dropped. **`--hunt-lenses N`** additionally
|
|
708
|
+
fans generation out into N independent passes, each specialised in one failure
|
|
709
|
+
mode (auth-bypass, TOCTOU/race, IDOR, deserialization, SSRF-to-internal,
|
|
710
|
+
secret-misuse), and dedupes the leads across passes before verifying — more
|
|
711
|
+
unique novel leads than a single generalist pass. **`--hunt-rounds N`** loops
|
|
712
|
+
generation for up to N rounds, accumulating new (deduped) leads while a
|
|
713
|
+
completeness critic seeds each next round with untried angles, and stops early
|
|
714
|
+
once a round finds nothing new; **`--hunt-budget T`** caps the multi-round output
|
|
715
|
+
tokens.
|
|
716
|
+
This is the verifier the unknown-bug initiative
|
|
717
|
+
([`docs/unknown-bug-epic.md`](docs/unknown-bug-epic.md)) is built on;
|
|
718
|
+
`--verify-votes 1` is the classic single pass.
|
|
719
|
+
|
|
598
720
|
Layered on top: **MITRE ATT&CK technique mappings** on every insight and
|
|
599
721
|
**detection opportunities** (Sigma/KQL/Splunk-style hints) for each weakness.
|
|
600
722
|
|
|
723
|
+
**6. Triage (`--triage`).** Where `--hunt` finds *new* leads, `--triage`
|
|
724
|
+
distills the *existing* heuristic findings: `attackmap analyze . --triage` has
|
|
725
|
+
the LLM cluster them by root cause, de-duplicate, and rank into a prioritized
|
|
726
|
+
shortlist that cites real finding IDs (it organizes, it never invents). Output
|
|
727
|
+
goes to `triage.md`. When no LLM backend is available it degrades to a
|
|
728
|
+
**deterministic**, score-ordered, clustered shortlist — reproducible enough to
|
|
729
|
+
diff across runs — rather than erroring.
|
|
730
|
+
|
|
601
731
|
---
|
|
602
732
|
|
|
603
733
|
## Supported ecosystems
|
|
@@ -645,7 +775,11 @@ attackmap analyze <path> --llm --llm-provider openai # use OpenAI/Codex (OPE
|
|
|
645
775
|
attackmap analyze <path> --llm --llm-provider openai --llm-model gpt-5.5 # any OpenAI model ID, passed through
|
|
646
776
|
attackmap analyze <path> --hunt # LLM vulnerability-hypothesis hunt (leads to confirm)
|
|
647
777
|
attackmap analyze <path> --hunt --verify # adjudicate each lead vs. source (confirmed/refuted/review)
|
|
778
|
+
attackmap analyze <path> --hunt --verify --verify-votes 3 # majority vote of N independent skeptics (default 3; 1 = single pass)
|
|
779
|
+
attackmap analyze <path> --hunt --verify --hunt-lenses 4 # N lens-specialised generation passes, deduped, then verified
|
|
780
|
+
attackmap analyze <path> --hunt --verify --hunt-rounds 4 # loop-until-dry generation (completeness critic seeds each round)
|
|
648
781
|
attackmap analyze <path> --remediate # LLM review-first fix suggestions (remediation.md)
|
|
782
|
+
attackmap analyze <path> --triage # cluster/dedupe/rank existing findings (triage.md; deterministic fallback)
|
|
649
783
|
attackmap analyze <path> --pr-comment pr.md # Markdown PR summary comment for CI
|
|
650
784
|
|
|
651
785
|
# CI / PR diff gating
|
|
@@ -679,9 +813,10 @@ introduces a new HIGH finding.
|
|
|
679
813
|
Dependabot go deeper on transitive resolution and lockfiles. AttackMap's
|
|
680
814
|
value is folding the CVE signal into an architecture-aware narrative
|
|
681
815
|
("this public route reaches this vulnerable ORM").
|
|
682
|
-
- **A true taint engine.** The data-flow pass is a heuristic
|
|
683
|
-
|
|
684
|
-
|
|
816
|
+
- **A true taint engine.** The data-flow pass is a heuristic call-graph-refined
|
|
817
|
+
import-graph walk (edges pruned to used symbols, but symbol *use* ≉ a proven
|
|
818
|
+
data-carrying call), not sound interprocedural taint analysis. It favors
|
|
819
|
+
precision over recall; findings are evidence, not proof.
|
|
685
820
|
- **Exhaustive.** AttackMap is heuristic by design. Findings are confidence-tiered
|
|
686
821
|
with explicit guardrails for stale signals.
|
|
687
822
|
|
|
@@ -689,7 +824,7 @@ introduces a new HIGH finding.
|
|
|
689
824
|
|
|
690
825
|
## Project status
|
|
691
826
|
|
|
692
|
-
AttackMap is **beta** (v0.4.
|
|
827
|
+
AttackMap is **beta** (v0.4.18) — published and validated on real codebases, but
|
|
693
828
|
pre-1.0 and heuristic.
|
|
694
829
|
|
|
695
830
|
**Solid today:**
|
|
@@ -711,8 +846,9 @@ pre-1.0 and heuristic.
|
|
|
711
846
|
explainable 0–100 "exploitable now" score that ranks route→sink combinations
|
|
712
847
|
and folds in known-vulnerable dependencies on the path.
|
|
713
848
|
- **`--hunt`** (LLM exploit-chain hypotheses) with **`--verify`** (adjudicate
|
|
714
|
-
each lead CONFIRMED/REFUTED/NEEDS-REVIEW against the actual source),
|
|
715
|
-
**`--remediate`** (review-first fix suggestions)
|
|
849
|
+
each lead CONFIRMED/REFUTED/NEEDS-REVIEW against the actual source),
|
|
850
|
+
**`--remediate`** (review-first fix suggestions), and **`--triage`**
|
|
851
|
+
(cluster/dedupe/rank existing findings, with a deterministic fallback).
|
|
716
852
|
- SBOM inventory (5 ecosystems) + OSV.dev CVE cross-reference (`--cve`).
|
|
717
853
|
- Output: Markdown + JSON + **SARIF 2.1.0** + **Mermaid / Graphviz** diagrams;
|
|
718
854
|
**diff/baseline** PR gating; a reusable **GitHub Action + PR bot**; optional
|
|
@@ -724,11 +860,16 @@ pre-1.0 and heuristic.
|
|
|
724
860
|
|
|
725
861
|
**Still maturing:**
|
|
726
862
|
|
|
727
|
-
-
|
|
728
|
-
|
|
729
|
-
|
|
863
|
+
- BOLA/IDOR authorization now covers path templates, query parameters, RPC
|
|
864
|
+
methods (XRPC/tRPC), and GraphQL fields; deeper resolver-level GraphQL
|
|
865
|
+
ownership analysis and more languages are planned.
|
|
866
|
+
- The taint walk is call-graph-aware for Python/JS/TS (import edges are pruned to
|
|
867
|
+
symbols actually used in the file) but still approximates: symbol *use* is not a
|
|
868
|
+
proven data-carrying call, and Go/PHP keep the plain import-graph. Precision
|
|
730
869
|
over recall; findings are evidence, not proof.
|
|
731
|
-
- CVE lookup
|
|
870
|
+
- CVE lookup uses exact lockfile-resolved versions (direct + transitive) when a
|
|
871
|
+
lockfile is present; range-only manifests without a lockfile still fall back to
|
|
872
|
+
a best-effort concrete version.
|
|
732
873
|
- Anomaly / exploitability reasoning is route-cohort and taint-chain scoped.
|
|
733
874
|
|
|
734
875
|
---
|
|
@@ -16,12 +16,24 @@ managers who need to triage an unfamiliar codebase.
|
|
|
16
16
|
[](https://www.python.org/)
|
|
17
17
|
[](https://pypi.org/project/attackmap/)
|
|
18
18
|
|
|
19
|
-
> **Status: beta (v0.4.
|
|
19
|
+
> **Status: beta (v0.4.18).** Core engine and 14 analyzer plugins are published
|
|
20
20
|
> to PyPI, Homebrew, and GHCR and validated against real-world codebases.
|
|
21
21
|
> AttackMap is heuristic by design — findings are confidence-tiered evidence,
|
|
22
22
|
> not proof. See [Project status](#project-status) for what's solid and what's
|
|
23
23
|
> still maturing.
|
|
24
24
|
|
|
25
|
+
### The macOS app
|
|
26
|
+
|
|
27
|
+
Prefer a GUI? AttackMap ships a native macOS front-end that drives this CLI and
|
|
28
|
+
renders every result view — install with `brew install --cask mlaify/tap/attackmap-app`
|
|
29
|
+
(the cask depends on the `attackmap` formula, so this pulls the CLI too).
|
|
30
|
+
|
|
31
|
+
| Overview | Exploitability |
|
|
32
|
+
|---|---|
|
|
33
|
+
| [](https://docs.matthewd.xyz/gui/) | [](https://docs.matthewd.xyz/gui/) |
|
|
34
|
+
|
|
35
|
+
*Scanning OWASP Juice Shop. Full walkthrough in the [macOS app docs](https://docs.matthewd.xyz/gui/); source at [mlaify/AttackMap-mac](https://github.com/mlaify/AttackMap-mac).*
|
|
36
|
+
|
|
25
37
|
---
|
|
26
38
|
|
|
27
39
|
## Quickstart
|
|
@@ -126,6 +138,7 @@ Every `attackmap analyze` run writes:
|
|
|
126
138
|
| `defensive-review-llm.md` *(with `--llm`)* | Claude-narrated review |
|
|
127
139
|
| `defensive-review-llm.meta.json` *(with `--llm`)* | Backend, model, token usage |
|
|
128
140
|
| `vulnerability-hypotheses.md` *(with `--hunt`)* | LLM-generated, evidence-cited exploit-chain **hypotheses** to confirm (leads, not detections) |
|
|
141
|
+
| `triage.md` *(with `--triage`)* | Clustered, de-duplicated, ranked shortlist of the existing findings (LLM-backed; deterministic fallback) |
|
|
129
142
|
|
|
130
143
|
### GitHub Code Scanning integration
|
|
131
144
|
|
|
@@ -284,7 +297,11 @@ left on).
|
|
|
284
297
|
|
|
285
298
|
Sinks that are only dangerous with attacker-controlled input (SSRF, SSTI, NoSQL,
|
|
286
299
|
open redirect, `open`) are gated on a request-shaped identifier in the call — a constant URL or
|
|
287
|
-
template is not flagged.
|
|
300
|
+
template is not flagged. The walk is **call-graph-aware** (#138): for Python and
|
|
301
|
+
JS/TS an import edge is kept only when the imported symbol is actually used in
|
|
302
|
+
the file, so a dead import no longer fans a route out to that module's sinks
|
|
303
|
+
(the plain import-graph remains the fallback for namespace/star/side-effect
|
|
304
|
+
imports and for Go/PHP). It's still a heuristic (use ≉ proven call), so findings
|
|
288
305
|
are evidence, not proof; confidence tapers with hop distance. Chains appear in
|
|
289
306
|
`attackmap-report.json` under `scan.taint_chains`.
|
|
290
307
|
|
|
@@ -306,6 +323,27 @@ excluded from all the heuristic passes above by default, since dangerous
|
|
|
306
323
|
patterns in test scaffolding are rarely real exposure. Set
|
|
307
324
|
`ATTACKMAP_INCLUDE_TESTS=1` to scan them too (e.g. for test-quality reviews).
|
|
308
325
|
|
|
326
|
+
**Recall mode (`--recall`).** Aggressiveness is only useful *behind* a verifier —
|
|
327
|
+
so `--recall` widens taint discovery and marks the extra reach **speculative**
|
|
328
|
+
instead of asserting it. It:
|
|
329
|
+
|
|
330
|
+
- raises the max import-hop depth (2 → 4) and per-route visit budget;
|
|
331
|
+
- surfaces reaches the default pass conservatively hides, like a
|
|
332
|
+
static-literal-looking `eval`/`exec`/deserialize argument;
|
|
333
|
+
- **enumerates capability-reach**: every reach to a powerful capability —
|
|
334
|
+
network (`requests`/`httpx`/`axios`/`fetch`/`urlopen`), template
|
|
335
|
+
(`render_template_string`/`Template`), filesystem (`open`), redirect
|
|
336
|
+
(`redirect`/`res.redirect`) — even with **no known-bad pattern**, i.e. the
|
|
337
|
+
bare call without a request-derived argument (a genuine request-derived sink
|
|
338
|
+
still fires once, as a confirmed finding, not a duplicate).
|
|
339
|
+
|
|
340
|
+
Speculative chains carry downgraded confidence, get their own LOW-severity
|
|
341
|
+
finding (`SPECULATIVE (recall mode)…`), are excluded from every asserted
|
|
342
|
+
downstream pass (exploitability, BOLA, attack paths), and are kept **out of
|
|
343
|
+
`--fail-on-new-high`** — they are leads for `--hunt --verify` / `--triage` to
|
|
344
|
+
adjudicate, not detections. Default behavior is unchanged; pair `--recall` with
|
|
345
|
+
`--hunt --verify` to widen the net and then adjudicate what it catches.
|
|
346
|
+
|
|
309
347
|
### Web hardening gaps
|
|
310
348
|
|
|
311
349
|
Route- and config-level checks for common web misconfigurations, each an
|
|
@@ -342,6 +380,32 @@ identifier; cipher/ECB tokens are matched case-sensitively so algorithm names
|
|
|
342
380
|
aren't confused with prose (e.g. the French word "des"). Results appear under
|
|
343
381
|
`scan.crypto_weaknesses`.
|
|
344
382
|
|
|
383
|
+
### Hard-coded secret detection
|
|
384
|
+
|
|
385
|
+
Two layers, precision-first. **Typed provider-signature detectors** recognize the
|
|
386
|
+
distinctive shapes of leaked keys and fire at high confidence with a
|
|
387
|
+
provider-specific `kind`:
|
|
388
|
+
|
|
389
|
+
| Provider | Prefix / shape |
|
|
390
|
+
|---|---|
|
|
391
|
+
| AWS | `AKIA…` / `ASIA…` |
|
|
392
|
+
| GitHub | `ghp_` / `gho_` / `ghu_` / `ghs_` / `ghr_` |
|
|
393
|
+
| OpenAI | `sk-…` / `sk-proj-…` / `sk-svcacct-…` |
|
|
394
|
+
| Anthropic | `sk-ant-…` |
|
|
395
|
+
| GitLab / npm | `glpat-…` / `npm_…` |
|
|
396
|
+
| Stripe / Slack / Google | `sk_live_…` / `xox[bpsare]-…` / `AIza…` |
|
|
397
|
+
| SendGrid / Mailgun / Twilio | `SG.…` / `key-…` / `AC…` SID |
|
|
398
|
+
| PEM / JWT | `-----BEGIN … PRIVATE KEY-----` / `eyJ….eyJ….…` (header verified) |
|
|
399
|
+
|
|
400
|
+
Below the typed layer, a **Shannon-entropy fallback** catches high-entropy
|
|
401
|
+
literals that don't match a known provider. It is calibrated to stay quiet:
|
|
402
|
+
pure-hex hashes, URLs, version/number strings, base64/base32 alphabet constants
|
|
403
|
+
(#96), Subresource-Integrity / lockfile digests (`sha384-…`), and UUIDs are
|
|
404
|
+
suppressed, and a typed match always takes precedence over the entropy hit for
|
|
405
|
+
the same span. Values are redacted (`ghp_…6789`) before they reach any report —
|
|
406
|
+
the raw secret never appears. No liveness verification is performed. Results
|
|
407
|
+
appear as `scan.secret_hints`.
|
|
408
|
+
|
|
345
409
|
### GitHub Actions / CI workflow security
|
|
346
410
|
|
|
347
411
|
CI config is a real attack surface. AttackMap parses `.github/workflows/*.yml`
|
|
@@ -381,18 +445,22 @@ adds the general mutating-endpoint case, with the resolved chain as evidence.
|
|
|
381
445
|
OWASP API Security #1. AttackMap flags a route as a BOLA/IDOR candidate when it
|
|
382
446
|
composes three signals it already has:
|
|
383
447
|
|
|
384
|
-
1. the route takes a **resource id**
|
|
385
|
-
`/orders/:orderId
|
|
386
|
-
|
|
387
|
-
|
|
448
|
+
1. the route takes a **resource id** — via a **path** template (`/users/{id}`,
|
|
449
|
+
`/orders/:orderId`), an id-bearing **query parameter** (`?orderId=`), an
|
|
450
|
+
**RPC method** (XRPC `/xrpc/…getRecord`, tRPC `user.byId`), or a **GraphQL
|
|
451
|
+
field** (`user(id: ID!)`) — and
|
|
452
|
+
2. it **reaches a datastore** — a DB hint in the same file/module, a taint chain
|
|
453
|
+
from the route to a SQL execute sink, or (for RPC/GraphQL) an object-access
|
|
454
|
+
operation, and
|
|
388
455
|
3. **no ownership/authorization check** is visible near the handler
|
|
389
|
-
(`current_user`, `request.user`, `authorize`, a policy/guard,
|
|
390
|
-
`filter_by(user_id=…)`-style scoped query
|
|
456
|
+
(`current_user`, `request.user`, `authorize`, a policy/guard, a
|
|
457
|
+
`filter_by(user_id=…)`-style scoped query, or a GraphQL `@auth`/`@hasRole`
|
|
458
|
+
schema directive on the field).
|
|
391
459
|
|
|
392
|
-
Write
|
|
393
|
-
under `scan.authz_candidates
|
|
394
|
-
false-positive reducer, so a
|
|
395
|
-
|
|
460
|
+
Write operations (POST/PUT/PATCH/DELETE, GraphQL mutations) are HIGH, reads
|
|
461
|
+
MEDIUM. Candidates appear under `scan.authz_candidates` (each carrying its
|
|
462
|
+
`surface`); the ownership-marker scan is the main false-positive reducer, so a
|
|
463
|
+
well-scoped handler or an `@auth`-guarded field is not flagged.
|
|
396
464
|
|
|
397
465
|
### Anomaly / outlier detection
|
|
398
466
|
|
|
@@ -408,6 +476,20 @@ within each cohort the odd-one-out is reported:
|
|
|
408
476
|
| `auth_outlier` | siblings carry an auth/authorization signal near the handler; this route doesn't | HIGH |
|
|
409
477
|
| `validation_outlier` | among a cohort's state-changing handlers, peers validate input and this one shows no validation marker | LOW |
|
|
410
478
|
| `method_outlier` | a lone state-changing method in an otherwise read-only cohort | MEDIUM |
|
|
479
|
+
| `invariant_violation` | a mined-invariant outlier: among handlers that reach the same dangerous sink kind, the majority guard the request *before* the sink and this one doesn't — evidence cites the mined rule | HIGH |
|
|
480
|
+
|
|
481
|
+
The `invariant_violation` kind is the **mined-invariant** pass (#149a): its
|
|
482
|
+
cohort is not a route-path prefix but the set of handlers that reach the same
|
|
483
|
+
dangerous sink (from the taint pass). When a strong majority guard the request
|
|
484
|
+
before the sink, that pattern is mined as an implicit invariant and the handler
|
|
485
|
+
that reaches the same sink kind with no preceding guard is flagged, citing the
|
|
486
|
+
mined rule (e.g. "9 of 10 handlers that reach a database (SQL execute) sink
|
|
487
|
+
apply an auth/validation guard before it"). It reasons only about **same-file**
|
|
488
|
+
flows, where guard-before-sink ordering is actually verifiable (a cross-file
|
|
489
|
+
sink could be called before an `authorize(...)` that textually follows it), and
|
|
490
|
+
counts one handler per declaration regardless of how many verbs it exposes.
|
|
491
|
+
Sanitized chains are excluded so only genuinely undefended flows form the cohort
|
|
492
|
+
— it measures the code against its own norm and needs no signature.
|
|
411
493
|
|
|
412
494
|
Everything is peer-relative and **confidence scales with how consistent the
|
|
413
495
|
cohort is** — a lone deviation among many agreeing siblings is likelier a mistake
|
|
@@ -459,8 +541,27 @@ of the common manifest files:
|
|
|
459
541
|
| Composer (PHP) | `composer.json` (require + require-dev; platform reqs skipped) |
|
|
460
542
|
|
|
461
543
|
Each entry appears in `attackmap-report.json` under `scan.dependencies` with
|
|
462
|
-
`{name, version, ecosystem, file, dev}`.
|
|
463
|
-
(`^4.16.0`, `>=2,<3`, `latest`)
|
|
544
|
+
`{name, version, ecosystem, file, dev, resolved, direct, via}`. Manifest ranges
|
|
545
|
+
are kept verbatim (`^4.16.0`, `>=2,<3`, `latest`).
|
|
546
|
+
|
|
547
|
+
**Lockfile resolution (#143).** When a lockfile is present it is parsed for
|
|
548
|
+
*exact* resolved versions and the full **transitive** tree — where most
|
|
549
|
+
known-vulnerable dependencies actually live. Resolved entries set
|
|
550
|
+
`resolved=true`, mark `direct`/transitive, and carry a `via` resolution path
|
|
551
|
+
(`express > body-parser > qs`). A lockfile supersedes the range-only manifest
|
|
552
|
+
in its **own directory** (so a monorepo's per-service lockfiles don't shadow
|
|
553
|
+
each other).
|
|
554
|
+
|
|
555
|
+
| Lockfile | Ecosystem |
|
|
556
|
+
|---|---|
|
|
557
|
+
| `package-lock.json` (v1/v2/v3), `pnpm-lock.yaml` | npm |
|
|
558
|
+
| `poetry.lock`, `uv.lock` | PyPI |
|
|
559
|
+
| `Cargo.lock` | Cargo |
|
|
560
|
+
|
|
561
|
+
Go needs no lockfile pass: `go.mod` already pins exact versions and flags
|
|
562
|
+
`// indirect` (transitive) deps, so it's treated as resolved directly.
|
|
563
|
+
(`go.sum` is a checksum history, not the build list, so it is not used for
|
|
564
|
+
inventory.)
|
|
464
565
|
|
|
465
566
|
### CVE cross-reference (opt-in)
|
|
466
567
|
|
|
@@ -481,9 +582,12 @@ attackmap analyze . --cve
|
|
|
481
582
|
the network.
|
|
482
583
|
- **Offline-tolerant.** If the network's unavailable but the cache is warm,
|
|
483
584
|
cached results still surface; only fresh queries are skipped.
|
|
484
|
-
- **
|
|
485
|
-
|
|
486
|
-
|
|
585
|
+
- **Exact when a lockfile exists.** Lockfile-resolved dependencies (direct and
|
|
586
|
+
transitive) are queried at their pinned version, and a vulnerable transitive
|
|
587
|
+
dep is reported with its resolution path. For range-only manifests with no
|
|
588
|
+
lockfile, version resolution is best-effort — ranges (`^4.16.0`, `>=2.28,<3`)
|
|
589
|
+
resolve to a queryable lower-bound and OSV does the range math; unpinned specs
|
|
590
|
+
(`*`, `latest`) are skipped.
|
|
487
591
|
|
|
488
592
|
The structured vulnerability list is available under
|
|
489
593
|
`scan.vulnerabilities` in `attackmap-report.json` with
|
|
@@ -534,9 +638,35 @@ to confirm, not detections.** The same grounding contract as the review applies
|
|
|
534
638
|
assignment, no exploit code, confidence-tiered, and each lead lists exactly what
|
|
535
639
|
a human must verify. Uses the same auth/backend resolution as `--llm`.
|
|
536
640
|
|
|
641
|
+
With `--verify`, each hypothesis is adjudicated against the actual source at its
|
|
642
|
+
cited locations. **`--verify-votes N`** (default 3) turns that into a jury: **N
|
|
643
|
+
independent skeptics** each adjudicate the same fixed hypothesis list. A lead is
|
|
644
|
+
**CONFIRMED only on a strict majority** (NEEDS_REVIEW only when a majority flags
|
|
645
|
+
the evidence as insufficient); ties and uncertainty are **REFUTED** — so a lead
|
|
646
|
+
only one skeptic would confirm is dropped. **`--hunt-lenses N`** additionally
|
|
647
|
+
fans generation out into N independent passes, each specialised in one failure
|
|
648
|
+
mode (auth-bypass, TOCTOU/race, IDOR, deserialization, SSRF-to-internal,
|
|
649
|
+
secret-misuse), and dedupes the leads across passes before verifying — more
|
|
650
|
+
unique novel leads than a single generalist pass. **`--hunt-rounds N`** loops
|
|
651
|
+
generation for up to N rounds, accumulating new (deduped) leads while a
|
|
652
|
+
completeness critic seeds each next round with untried angles, and stops early
|
|
653
|
+
once a round finds nothing new; **`--hunt-budget T`** caps the multi-round output
|
|
654
|
+
tokens.
|
|
655
|
+
This is the verifier the unknown-bug initiative
|
|
656
|
+
([`docs/unknown-bug-epic.md`](docs/unknown-bug-epic.md)) is built on;
|
|
657
|
+
`--verify-votes 1` is the classic single pass.
|
|
658
|
+
|
|
537
659
|
Layered on top: **MITRE ATT&CK technique mappings** on every insight and
|
|
538
660
|
**detection opportunities** (Sigma/KQL/Splunk-style hints) for each weakness.
|
|
539
661
|
|
|
662
|
+
**6. Triage (`--triage`).** Where `--hunt` finds *new* leads, `--triage`
|
|
663
|
+
distills the *existing* heuristic findings: `attackmap analyze . --triage` has
|
|
664
|
+
the LLM cluster them by root cause, de-duplicate, and rank into a prioritized
|
|
665
|
+
shortlist that cites real finding IDs (it organizes, it never invents). Output
|
|
666
|
+
goes to `triage.md`. When no LLM backend is available it degrades to a
|
|
667
|
+
**deterministic**, score-ordered, clustered shortlist — reproducible enough to
|
|
668
|
+
diff across runs — rather than erroring.
|
|
669
|
+
|
|
540
670
|
---
|
|
541
671
|
|
|
542
672
|
## Supported ecosystems
|
|
@@ -584,7 +714,11 @@ attackmap analyze <path> --llm --llm-provider openai # use OpenAI/Codex (OPE
|
|
|
584
714
|
attackmap analyze <path> --llm --llm-provider openai --llm-model gpt-5.5 # any OpenAI model ID, passed through
|
|
585
715
|
attackmap analyze <path> --hunt # LLM vulnerability-hypothesis hunt (leads to confirm)
|
|
586
716
|
attackmap analyze <path> --hunt --verify # adjudicate each lead vs. source (confirmed/refuted/review)
|
|
717
|
+
attackmap analyze <path> --hunt --verify --verify-votes 3 # majority vote of N independent skeptics (default 3; 1 = single pass)
|
|
718
|
+
attackmap analyze <path> --hunt --verify --hunt-lenses 4 # N lens-specialised generation passes, deduped, then verified
|
|
719
|
+
attackmap analyze <path> --hunt --verify --hunt-rounds 4 # loop-until-dry generation (completeness critic seeds each round)
|
|
587
720
|
attackmap analyze <path> --remediate # LLM review-first fix suggestions (remediation.md)
|
|
721
|
+
attackmap analyze <path> --triage # cluster/dedupe/rank existing findings (triage.md; deterministic fallback)
|
|
588
722
|
attackmap analyze <path> --pr-comment pr.md # Markdown PR summary comment for CI
|
|
589
723
|
|
|
590
724
|
# CI / PR diff gating
|
|
@@ -618,9 +752,10 @@ introduces a new HIGH finding.
|
|
|
618
752
|
Dependabot go deeper on transitive resolution and lockfiles. AttackMap's
|
|
619
753
|
value is folding the CVE signal into an architecture-aware narrative
|
|
620
754
|
("this public route reaches this vulnerable ORM").
|
|
621
|
-
- **A true taint engine.** The data-flow pass is a heuristic
|
|
622
|
-
|
|
623
|
-
|
|
755
|
+
- **A true taint engine.** The data-flow pass is a heuristic call-graph-refined
|
|
756
|
+
import-graph walk (edges pruned to used symbols, but symbol *use* ≉ a proven
|
|
757
|
+
data-carrying call), not sound interprocedural taint analysis. It favors
|
|
758
|
+
precision over recall; findings are evidence, not proof.
|
|
624
759
|
- **Exhaustive.** AttackMap is heuristic by design. Findings are confidence-tiered
|
|
625
760
|
with explicit guardrails for stale signals.
|
|
626
761
|
|
|
@@ -628,7 +763,7 @@ introduces a new HIGH finding.
|
|
|
628
763
|
|
|
629
764
|
## Project status
|
|
630
765
|
|
|
631
|
-
AttackMap is **beta** (v0.4.
|
|
766
|
+
AttackMap is **beta** (v0.4.18) — published and validated on real codebases, but
|
|
632
767
|
pre-1.0 and heuristic.
|
|
633
768
|
|
|
634
769
|
**Solid today:**
|
|
@@ -650,8 +785,9 @@ pre-1.0 and heuristic.
|
|
|
650
785
|
explainable 0–100 "exploitable now" score that ranks route→sink combinations
|
|
651
786
|
and folds in known-vulnerable dependencies on the path.
|
|
652
787
|
- **`--hunt`** (LLM exploit-chain hypotheses) with **`--verify`** (adjudicate
|
|
653
|
-
each lead CONFIRMED/REFUTED/NEEDS-REVIEW against the actual source),
|
|
654
|
-
**`--remediate`** (review-first fix suggestions)
|
|
788
|
+
each lead CONFIRMED/REFUTED/NEEDS-REVIEW against the actual source),
|
|
789
|
+
**`--remediate`** (review-first fix suggestions), and **`--triage`**
|
|
790
|
+
(cluster/dedupe/rank existing findings, with a deterministic fallback).
|
|
655
791
|
- SBOM inventory (5 ecosystems) + OSV.dev CVE cross-reference (`--cve`).
|
|
656
792
|
- Output: Markdown + JSON + **SARIF 2.1.0** + **Mermaid / Graphviz** diagrams;
|
|
657
793
|
**diff/baseline** PR gating; a reusable **GitHub Action + PR bot**; optional
|
|
@@ -663,11 +799,16 @@ pre-1.0 and heuristic.
|
|
|
663
799
|
|
|
664
800
|
**Still maturing:**
|
|
665
801
|
|
|
666
|
-
-
|
|
667
|
-
|
|
668
|
-
|
|
802
|
+
- BOLA/IDOR authorization now covers path templates, query parameters, RPC
|
|
803
|
+
methods (XRPC/tRPC), and GraphQL fields; deeper resolver-level GraphQL
|
|
804
|
+
ownership analysis and more languages are planned.
|
|
805
|
+
- The taint walk is call-graph-aware for Python/JS/TS (import edges are pruned to
|
|
806
|
+
symbols actually used in the file) but still approximates: symbol *use* is not a
|
|
807
|
+
proven data-carrying call, and Go/PHP keep the plain import-graph. Precision
|
|
669
808
|
over recall; findings are evidence, not proof.
|
|
670
|
-
- CVE lookup
|
|
809
|
+
- CVE lookup uses exact lockfile-resolved versions (direct + transitive) when a
|
|
810
|
+
lockfile is present; range-only manifests without a lockfile still fall back to
|
|
811
|
+
a best-effort concrete version.
|
|
671
812
|
- Anomaly / exploitability reasoning is route-cohort and taint-chain scoped.
|
|
672
813
|
|
|
673
814
|
---
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "attackmap"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.21"
|
|
8
8
|
description = "AI-assisted defensive security analyzer for codebases — scans a repository, models assets and controls, finds cross-cutting weaknesses, and generates an evidence-grounded review with MITRE ATT&CK mappings and detection-engineering hints."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__all__ = ["__version__"]
|
|
2
|
-
__version__ = "0.4.
|
|
2
|
+
__version__ = "0.4.21"
|