attackmap 0.4.10__tar.gz → 0.4.15__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.15}/PKG-INFO +113 -28
- {attackmap-0.4.10 → attackmap-0.4.15}/README.md +112 -27
- {attackmap-0.4.10 → attackmap-0.4.15}/pyproject.toml +1 -1
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/__init__.py +1 -1
- attackmap-0.4.15/src/attackmap/authz.py +448 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/cli.py +81 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/cve.py +11 -1
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/llm_review.py +3 -1
- attackmap-0.4.15/src/attackmap/lockfiles.py +387 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/models.py +22 -6
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/review_prompts.py +61 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/sbom.py +31 -3
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/scanner.py +76 -3
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/taint.py +139 -27
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/threat_model.py +30 -5
- attackmap-0.4.15/src/attackmap/triage.py +151 -0
- {attackmap-0.4.10 → attackmap-0.4.15/src/attackmap.egg-info}/PKG-INFO +113 -28
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap.egg-info/SOURCES.txt +4 -0
- attackmap-0.4.15/tests/test_authz.py +472 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_cve.py +91 -0
- attackmap-0.4.15/tests/test_lockfiles.py +348 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_scanner.py +79 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_taint.py +119 -0
- attackmap-0.4.15/tests/test_triage.py +201 -0
- attackmap-0.4.10/src/attackmap/authz.py +0 -196
- attackmap-0.4.10/tests/test_authz.py +0 -241
- {attackmap-0.4.10 → attackmap-0.4.15}/LICENSE +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/setup.cfg +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/analyzer.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/analyzer_contracts.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/analyzers.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/anomalies.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/asset_model.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/attack_taxonomy.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/config_scanner.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/context_pack.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/control_model.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/crypto.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/defensive_review.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/detection_opportunities.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/diagrams.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/diff.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/exploitability.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/graph.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/insights.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/merge.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/progress.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/recon_models.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/recon_to_analysis.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/report.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/review_eval.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/review_json.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/route_auth_fusion.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/sarif.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/sdk/__init__.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/sdk/contracts.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/sdk/models.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/security_overlay.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/srcpaths.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/suggest.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/suppress.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/topology.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/weaknesses.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/webhardening.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/workflow_scanner.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap.egg-info/dependency_links.txt +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap.egg-info/entry_points.txt +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap.egg-info/requires.txt +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap.egg-info/top_level.txt +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_analyzer.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_analyzer_metadata_schema.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_analyzers.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_anomalies.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_artifact_schemas.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_config_scanner.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_context_pack.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_crypto.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_defensive_review.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_diagrams.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_diff.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_exploitability.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_graph.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_hunt.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_injection_sinks.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_llm_review.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_merge.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_modules_cli.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_pr_comment.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_progress.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_provenance.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_recon_to_analysis.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_remediate.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_report.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_review_eval.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_review_json.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_review_prompts.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_route_auth_fusion.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_sarif.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_sbom.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_security_overlay.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_shared_contract_imports.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_signal_v2.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_srcpaths.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_suggest.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_suppress.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_taint_sanitizer.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_threat_intel.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_threat_model.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_topology.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_weaknesses.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_webhardening.py +0 -0
- {attackmap-0.4.10 → attackmap-0.4.15}/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.15
|
|
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.15).** 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
|
|
|
@@ -403,6 +420,32 @@ identifier; cipher/ECB tokens are matched case-sensitively so algorithm names
|
|
|
403
420
|
aren't confused with prose (e.g. the French word "des"). Results appear under
|
|
404
421
|
`scan.crypto_weaknesses`.
|
|
405
422
|
|
|
423
|
+
### Hard-coded secret detection
|
|
424
|
+
|
|
425
|
+
Two layers, precision-first. **Typed provider-signature detectors** recognize the
|
|
426
|
+
distinctive shapes of leaked keys and fire at high confidence with a
|
|
427
|
+
provider-specific `kind`:
|
|
428
|
+
|
|
429
|
+
| Provider | Prefix / shape |
|
|
430
|
+
|---|---|
|
|
431
|
+
| AWS | `AKIA…` / `ASIA…` |
|
|
432
|
+
| GitHub | `ghp_` / `gho_` / `ghu_` / `ghs_` / `ghr_` |
|
|
433
|
+
| OpenAI | `sk-…` / `sk-proj-…` / `sk-svcacct-…` |
|
|
434
|
+
| Anthropic | `sk-ant-…` |
|
|
435
|
+
| GitLab / npm | `glpat-…` / `npm_…` |
|
|
436
|
+
| Stripe / Slack / Google | `sk_live_…` / `xox[bpsare]-…` / `AIza…` |
|
|
437
|
+
| SendGrid / Mailgun / Twilio | `SG.…` / `key-…` / `AC…` SID |
|
|
438
|
+
| PEM / JWT | `-----BEGIN … PRIVATE KEY-----` / `eyJ….eyJ….…` (header verified) |
|
|
439
|
+
|
|
440
|
+
Below the typed layer, a **Shannon-entropy fallback** catches high-entropy
|
|
441
|
+
literals that don't match a known provider. It is calibrated to stay quiet:
|
|
442
|
+
pure-hex hashes, URLs, version/number strings, base64/base32 alphabet constants
|
|
443
|
+
(#96), Subresource-Integrity / lockfile digests (`sha384-…`), and UUIDs are
|
|
444
|
+
suppressed, and a typed match always takes precedence over the entropy hit for
|
|
445
|
+
the same span. Values are redacted (`ghp_…6789`) before they reach any report —
|
|
446
|
+
the raw secret never appears. No liveness verification is performed. Results
|
|
447
|
+
appear as `scan.secret_hints`.
|
|
448
|
+
|
|
406
449
|
### GitHub Actions / CI workflow security
|
|
407
450
|
|
|
408
451
|
CI config is a real attack surface. AttackMap parses `.github/workflows/*.yml`
|
|
@@ -442,18 +485,22 @@ adds the general mutating-endpoint case, with the resolved chain as evidence.
|
|
|
442
485
|
OWASP API Security #1. AttackMap flags a route as a BOLA/IDOR candidate when it
|
|
443
486
|
composes three signals it already has:
|
|
444
487
|
|
|
445
|
-
1. the route takes a **resource id**
|
|
446
|
-
`/orders/:orderId
|
|
447
|
-
|
|
448
|
-
|
|
488
|
+
1. the route takes a **resource id** — via a **path** template (`/users/{id}`,
|
|
489
|
+
`/orders/:orderId`), an id-bearing **query parameter** (`?orderId=`), an
|
|
490
|
+
**RPC method** (XRPC `/xrpc/…getRecord`, tRPC `user.byId`), or a **GraphQL
|
|
491
|
+
field** (`user(id: ID!)`) — and
|
|
492
|
+
2. it **reaches a datastore** — a DB hint in the same file/module, a taint chain
|
|
493
|
+
from the route to a SQL execute sink, or (for RPC/GraphQL) an object-access
|
|
494
|
+
operation, and
|
|
449
495
|
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
|
|
496
|
+
(`current_user`, `request.user`, `authorize`, a policy/guard, a
|
|
497
|
+
`filter_by(user_id=…)`-style scoped query, or a GraphQL `@auth`/`@hasRole`
|
|
498
|
+
schema directive on the field).
|
|
452
499
|
|
|
453
|
-
Write
|
|
454
|
-
under `scan.authz_candidates
|
|
455
|
-
false-positive reducer, so a
|
|
456
|
-
|
|
500
|
+
Write operations (POST/PUT/PATCH/DELETE, GraphQL mutations) are HIGH, reads
|
|
501
|
+
MEDIUM. Candidates appear under `scan.authz_candidates` (each carrying its
|
|
502
|
+
`surface`); the ownership-marker scan is the main false-positive reducer, so a
|
|
503
|
+
well-scoped handler or an `@auth`-guarded field is not flagged.
|
|
457
504
|
|
|
458
505
|
### Anomaly / outlier detection
|
|
459
506
|
|
|
@@ -520,8 +567,27 @@ of the common manifest files:
|
|
|
520
567
|
| Composer (PHP) | `composer.json` (require + require-dev; platform reqs skipped) |
|
|
521
568
|
|
|
522
569
|
Each entry appears in `attackmap-report.json` under `scan.dependencies` with
|
|
523
|
-
`{name, version, ecosystem, file, dev}`.
|
|
524
|
-
(`^4.16.0`, `>=2,<3`, `latest`)
|
|
570
|
+
`{name, version, ecosystem, file, dev, resolved, direct, via}`. Manifest ranges
|
|
571
|
+
are kept verbatim (`^4.16.0`, `>=2,<3`, `latest`).
|
|
572
|
+
|
|
573
|
+
**Lockfile resolution (#143).** When a lockfile is present it is parsed for
|
|
574
|
+
*exact* resolved versions and the full **transitive** tree — where most
|
|
575
|
+
known-vulnerable dependencies actually live. Resolved entries set
|
|
576
|
+
`resolved=true`, mark `direct`/transitive, and carry a `via` resolution path
|
|
577
|
+
(`express > body-parser > qs`). A lockfile supersedes the range-only manifest
|
|
578
|
+
in its **own directory** (so a monorepo's per-service lockfiles don't shadow
|
|
579
|
+
each other).
|
|
580
|
+
|
|
581
|
+
| Lockfile | Ecosystem |
|
|
582
|
+
|---|---|
|
|
583
|
+
| `package-lock.json` (v1/v2/v3), `pnpm-lock.yaml` | npm |
|
|
584
|
+
| `poetry.lock`, `uv.lock` | PyPI |
|
|
585
|
+
| `Cargo.lock` | Cargo |
|
|
586
|
+
|
|
587
|
+
Go needs no lockfile pass: `go.mod` already pins exact versions and flags
|
|
588
|
+
`// indirect` (transitive) deps, so it's treated as resolved directly.
|
|
589
|
+
(`go.sum` is a checksum history, not the build list, so it is not used for
|
|
590
|
+
inventory.)
|
|
525
591
|
|
|
526
592
|
### CVE cross-reference (opt-in)
|
|
527
593
|
|
|
@@ -542,9 +608,12 @@ attackmap analyze . --cve
|
|
|
542
608
|
the network.
|
|
543
609
|
- **Offline-tolerant.** If the network's unavailable but the cache is warm,
|
|
544
610
|
cached results still surface; only fresh queries are skipped.
|
|
545
|
-
- **
|
|
546
|
-
|
|
547
|
-
|
|
611
|
+
- **Exact when a lockfile exists.** Lockfile-resolved dependencies (direct and
|
|
612
|
+
transitive) are queried at their pinned version, and a vulnerable transitive
|
|
613
|
+
dep is reported with its resolution path. For range-only manifests with no
|
|
614
|
+
lockfile, version resolution is best-effort — ranges (`^4.16.0`, `>=2.28,<3`)
|
|
615
|
+
resolve to a queryable lower-bound and OSV does the range math; unpinned specs
|
|
616
|
+
(`*`, `latest`) are skipped.
|
|
548
617
|
|
|
549
618
|
The structured vulnerability list is available under
|
|
550
619
|
`scan.vulnerabilities` in `attackmap-report.json` with
|
|
@@ -598,6 +667,14 @@ a human must verify. Uses the same auth/backend resolution as `--llm`.
|
|
|
598
667
|
Layered on top: **MITRE ATT&CK technique mappings** on every insight and
|
|
599
668
|
**detection opportunities** (Sigma/KQL/Splunk-style hints) for each weakness.
|
|
600
669
|
|
|
670
|
+
**6. Triage (`--triage`).** Where `--hunt` finds *new* leads, `--triage`
|
|
671
|
+
distills the *existing* heuristic findings: `attackmap analyze . --triage` has
|
|
672
|
+
the LLM cluster them by root cause, de-duplicate, and rank into a prioritized
|
|
673
|
+
shortlist that cites real finding IDs (it organizes, it never invents). Output
|
|
674
|
+
goes to `triage.md`. When no LLM backend is available it degrades to a
|
|
675
|
+
**deterministic**, score-ordered, clustered shortlist — reproducible enough to
|
|
676
|
+
diff across runs — rather than erroring.
|
|
677
|
+
|
|
601
678
|
---
|
|
602
679
|
|
|
603
680
|
## Supported ecosystems
|
|
@@ -646,6 +723,7 @@ attackmap analyze <path> --llm --llm-provider openai --llm-model gpt-5.5 # any
|
|
|
646
723
|
attackmap analyze <path> --hunt # LLM vulnerability-hypothesis hunt (leads to confirm)
|
|
647
724
|
attackmap analyze <path> --hunt --verify # adjudicate each lead vs. source (confirmed/refuted/review)
|
|
648
725
|
attackmap analyze <path> --remediate # LLM review-first fix suggestions (remediation.md)
|
|
726
|
+
attackmap analyze <path> --triage # cluster/dedupe/rank existing findings (triage.md; deterministic fallback)
|
|
649
727
|
attackmap analyze <path> --pr-comment pr.md # Markdown PR summary comment for CI
|
|
650
728
|
|
|
651
729
|
# CI / PR diff gating
|
|
@@ -679,9 +757,10 @@ introduces a new HIGH finding.
|
|
|
679
757
|
Dependabot go deeper on transitive resolution and lockfiles. AttackMap's
|
|
680
758
|
value is folding the CVE signal into an architecture-aware narrative
|
|
681
759
|
("this public route reaches this vulnerable ORM").
|
|
682
|
-
- **A true taint engine.** The data-flow pass is a heuristic
|
|
683
|
-
|
|
684
|
-
|
|
760
|
+
- **A true taint engine.** The data-flow pass is a heuristic call-graph-refined
|
|
761
|
+
import-graph walk (edges pruned to used symbols, but symbol *use* ≉ a proven
|
|
762
|
+
data-carrying call), not sound interprocedural taint analysis. It favors
|
|
763
|
+
precision over recall; findings are evidence, not proof.
|
|
685
764
|
- **Exhaustive.** AttackMap is heuristic by design. Findings are confidence-tiered
|
|
686
765
|
with explicit guardrails for stale signals.
|
|
687
766
|
|
|
@@ -689,7 +768,7 @@ introduces a new HIGH finding.
|
|
|
689
768
|
|
|
690
769
|
## Project status
|
|
691
770
|
|
|
692
|
-
AttackMap is **beta** (v0.4.
|
|
771
|
+
AttackMap is **beta** (v0.4.15) — published and validated on real codebases, but
|
|
693
772
|
pre-1.0 and heuristic.
|
|
694
773
|
|
|
695
774
|
**Solid today:**
|
|
@@ -711,8 +790,9 @@ pre-1.0 and heuristic.
|
|
|
711
790
|
explainable 0–100 "exploitable now" score that ranks route→sink combinations
|
|
712
791
|
and folds in known-vulnerable dependencies on the path.
|
|
713
792
|
- **`--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)
|
|
793
|
+
each lead CONFIRMED/REFUTED/NEEDS-REVIEW against the actual source),
|
|
794
|
+
**`--remediate`** (review-first fix suggestions), and **`--triage`**
|
|
795
|
+
(cluster/dedupe/rank existing findings, with a deterministic fallback).
|
|
716
796
|
- SBOM inventory (5 ecosystems) + OSV.dev CVE cross-reference (`--cve`).
|
|
717
797
|
- Output: Markdown + JSON + **SARIF 2.1.0** + **Mermaid / Graphviz** diagrams;
|
|
718
798
|
**diff/baseline** PR gating; a reusable **GitHub Action + PR bot**; optional
|
|
@@ -724,11 +804,16 @@ pre-1.0 and heuristic.
|
|
|
724
804
|
|
|
725
805
|
**Still maturing:**
|
|
726
806
|
|
|
727
|
-
-
|
|
728
|
-
|
|
729
|
-
|
|
807
|
+
- BOLA/IDOR authorization now covers path templates, query parameters, RPC
|
|
808
|
+
methods (XRPC/tRPC), and GraphQL fields; deeper resolver-level GraphQL
|
|
809
|
+
ownership analysis and more languages are planned.
|
|
810
|
+
- The taint walk is call-graph-aware for Python/JS/TS (import edges are pruned to
|
|
811
|
+
symbols actually used in the file) but still approximates: symbol *use* is not a
|
|
812
|
+
proven data-carrying call, and Go/PHP keep the plain import-graph. Precision
|
|
730
813
|
over recall; findings are evidence, not proof.
|
|
731
|
-
- CVE lookup
|
|
814
|
+
- CVE lookup uses exact lockfile-resolved versions (direct + transitive) when a
|
|
815
|
+
lockfile is present; range-only manifests without a lockfile still fall back to
|
|
816
|
+
a best-effort concrete version.
|
|
732
817
|
- Anomaly / exploitability reasoning is route-cohort and taint-chain scoped.
|
|
733
818
|
|
|
734
819
|
---
|
|
@@ -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.15).** 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
|
|
|
@@ -342,6 +359,32 @@ identifier; cipher/ECB tokens are matched case-sensitively so algorithm names
|
|
|
342
359
|
aren't confused with prose (e.g. the French word "des"). Results appear under
|
|
343
360
|
`scan.crypto_weaknesses`.
|
|
344
361
|
|
|
362
|
+
### Hard-coded secret detection
|
|
363
|
+
|
|
364
|
+
Two layers, precision-first. **Typed provider-signature detectors** recognize the
|
|
365
|
+
distinctive shapes of leaked keys and fire at high confidence with a
|
|
366
|
+
provider-specific `kind`:
|
|
367
|
+
|
|
368
|
+
| Provider | Prefix / shape |
|
|
369
|
+
|---|---|
|
|
370
|
+
| AWS | `AKIA…` / `ASIA…` |
|
|
371
|
+
| GitHub | `ghp_` / `gho_` / `ghu_` / `ghs_` / `ghr_` |
|
|
372
|
+
| OpenAI | `sk-…` / `sk-proj-…` / `sk-svcacct-…` |
|
|
373
|
+
| Anthropic | `sk-ant-…` |
|
|
374
|
+
| GitLab / npm | `glpat-…` / `npm_…` |
|
|
375
|
+
| Stripe / Slack / Google | `sk_live_…` / `xox[bpsare]-…` / `AIza…` |
|
|
376
|
+
| SendGrid / Mailgun / Twilio | `SG.…` / `key-…` / `AC…` SID |
|
|
377
|
+
| PEM / JWT | `-----BEGIN … PRIVATE KEY-----` / `eyJ….eyJ….…` (header verified) |
|
|
378
|
+
|
|
379
|
+
Below the typed layer, a **Shannon-entropy fallback** catches high-entropy
|
|
380
|
+
literals that don't match a known provider. It is calibrated to stay quiet:
|
|
381
|
+
pure-hex hashes, URLs, version/number strings, base64/base32 alphabet constants
|
|
382
|
+
(#96), Subresource-Integrity / lockfile digests (`sha384-…`), and UUIDs are
|
|
383
|
+
suppressed, and a typed match always takes precedence over the entropy hit for
|
|
384
|
+
the same span. Values are redacted (`ghp_…6789`) before they reach any report —
|
|
385
|
+
the raw secret never appears. No liveness verification is performed. Results
|
|
386
|
+
appear as `scan.secret_hints`.
|
|
387
|
+
|
|
345
388
|
### GitHub Actions / CI workflow security
|
|
346
389
|
|
|
347
390
|
CI config is a real attack surface. AttackMap parses `.github/workflows/*.yml`
|
|
@@ -381,18 +424,22 @@ adds the general mutating-endpoint case, with the resolved chain as evidence.
|
|
|
381
424
|
OWASP API Security #1. AttackMap flags a route as a BOLA/IDOR candidate when it
|
|
382
425
|
composes three signals it already has:
|
|
383
426
|
|
|
384
|
-
1. the route takes a **resource id**
|
|
385
|
-
`/orders/:orderId
|
|
386
|
-
|
|
387
|
-
|
|
427
|
+
1. the route takes a **resource id** — via a **path** template (`/users/{id}`,
|
|
428
|
+
`/orders/:orderId`), an id-bearing **query parameter** (`?orderId=`), an
|
|
429
|
+
**RPC method** (XRPC `/xrpc/…getRecord`, tRPC `user.byId`), or a **GraphQL
|
|
430
|
+
field** (`user(id: ID!)`) — and
|
|
431
|
+
2. it **reaches a datastore** — a DB hint in the same file/module, a taint chain
|
|
432
|
+
from the route to a SQL execute sink, or (for RPC/GraphQL) an object-access
|
|
433
|
+
operation, and
|
|
388
434
|
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
|
|
435
|
+
(`current_user`, `request.user`, `authorize`, a policy/guard, a
|
|
436
|
+
`filter_by(user_id=…)`-style scoped query, or a GraphQL `@auth`/`@hasRole`
|
|
437
|
+
schema directive on the field).
|
|
391
438
|
|
|
392
|
-
Write
|
|
393
|
-
under `scan.authz_candidates
|
|
394
|
-
false-positive reducer, so a
|
|
395
|
-
|
|
439
|
+
Write operations (POST/PUT/PATCH/DELETE, GraphQL mutations) are HIGH, reads
|
|
440
|
+
MEDIUM. Candidates appear under `scan.authz_candidates` (each carrying its
|
|
441
|
+
`surface`); the ownership-marker scan is the main false-positive reducer, so a
|
|
442
|
+
well-scoped handler or an `@auth`-guarded field is not flagged.
|
|
396
443
|
|
|
397
444
|
### Anomaly / outlier detection
|
|
398
445
|
|
|
@@ -459,8 +506,27 @@ of the common manifest files:
|
|
|
459
506
|
| Composer (PHP) | `composer.json` (require + require-dev; platform reqs skipped) |
|
|
460
507
|
|
|
461
508
|
Each entry appears in `attackmap-report.json` under `scan.dependencies` with
|
|
462
|
-
`{name, version, ecosystem, file, dev}`.
|
|
463
|
-
(`^4.16.0`, `>=2,<3`, `latest`)
|
|
509
|
+
`{name, version, ecosystem, file, dev, resolved, direct, via}`. Manifest ranges
|
|
510
|
+
are kept verbatim (`^4.16.0`, `>=2,<3`, `latest`).
|
|
511
|
+
|
|
512
|
+
**Lockfile resolution (#143).** When a lockfile is present it is parsed for
|
|
513
|
+
*exact* resolved versions and the full **transitive** tree — where most
|
|
514
|
+
known-vulnerable dependencies actually live. Resolved entries set
|
|
515
|
+
`resolved=true`, mark `direct`/transitive, and carry a `via` resolution path
|
|
516
|
+
(`express > body-parser > qs`). A lockfile supersedes the range-only manifest
|
|
517
|
+
in its **own directory** (so a monorepo's per-service lockfiles don't shadow
|
|
518
|
+
each other).
|
|
519
|
+
|
|
520
|
+
| Lockfile | Ecosystem |
|
|
521
|
+
|---|---|
|
|
522
|
+
| `package-lock.json` (v1/v2/v3), `pnpm-lock.yaml` | npm |
|
|
523
|
+
| `poetry.lock`, `uv.lock` | PyPI |
|
|
524
|
+
| `Cargo.lock` | Cargo |
|
|
525
|
+
|
|
526
|
+
Go needs no lockfile pass: `go.mod` already pins exact versions and flags
|
|
527
|
+
`// indirect` (transitive) deps, so it's treated as resolved directly.
|
|
528
|
+
(`go.sum` is a checksum history, not the build list, so it is not used for
|
|
529
|
+
inventory.)
|
|
464
530
|
|
|
465
531
|
### CVE cross-reference (opt-in)
|
|
466
532
|
|
|
@@ -481,9 +547,12 @@ attackmap analyze . --cve
|
|
|
481
547
|
the network.
|
|
482
548
|
- **Offline-tolerant.** If the network's unavailable but the cache is warm,
|
|
483
549
|
cached results still surface; only fresh queries are skipped.
|
|
484
|
-
- **
|
|
485
|
-
|
|
486
|
-
|
|
550
|
+
- **Exact when a lockfile exists.** Lockfile-resolved dependencies (direct and
|
|
551
|
+
transitive) are queried at their pinned version, and a vulnerable transitive
|
|
552
|
+
dep is reported with its resolution path. For range-only manifests with no
|
|
553
|
+
lockfile, version resolution is best-effort — ranges (`^4.16.0`, `>=2.28,<3`)
|
|
554
|
+
resolve to a queryable lower-bound and OSV does the range math; unpinned specs
|
|
555
|
+
(`*`, `latest`) are skipped.
|
|
487
556
|
|
|
488
557
|
The structured vulnerability list is available under
|
|
489
558
|
`scan.vulnerabilities` in `attackmap-report.json` with
|
|
@@ -537,6 +606,14 @@ a human must verify. Uses the same auth/backend resolution as `--llm`.
|
|
|
537
606
|
Layered on top: **MITRE ATT&CK technique mappings** on every insight and
|
|
538
607
|
**detection opportunities** (Sigma/KQL/Splunk-style hints) for each weakness.
|
|
539
608
|
|
|
609
|
+
**6. Triage (`--triage`).** Where `--hunt` finds *new* leads, `--triage`
|
|
610
|
+
distills the *existing* heuristic findings: `attackmap analyze . --triage` has
|
|
611
|
+
the LLM cluster them by root cause, de-duplicate, and rank into a prioritized
|
|
612
|
+
shortlist that cites real finding IDs (it organizes, it never invents). Output
|
|
613
|
+
goes to `triage.md`. When no LLM backend is available it degrades to a
|
|
614
|
+
**deterministic**, score-ordered, clustered shortlist — reproducible enough to
|
|
615
|
+
diff across runs — rather than erroring.
|
|
616
|
+
|
|
540
617
|
---
|
|
541
618
|
|
|
542
619
|
## Supported ecosystems
|
|
@@ -585,6 +662,7 @@ attackmap analyze <path> --llm --llm-provider openai --llm-model gpt-5.5 # any
|
|
|
585
662
|
attackmap analyze <path> --hunt # LLM vulnerability-hypothesis hunt (leads to confirm)
|
|
586
663
|
attackmap analyze <path> --hunt --verify # adjudicate each lead vs. source (confirmed/refuted/review)
|
|
587
664
|
attackmap analyze <path> --remediate # LLM review-first fix suggestions (remediation.md)
|
|
665
|
+
attackmap analyze <path> --triage # cluster/dedupe/rank existing findings (triage.md; deterministic fallback)
|
|
588
666
|
attackmap analyze <path> --pr-comment pr.md # Markdown PR summary comment for CI
|
|
589
667
|
|
|
590
668
|
# CI / PR diff gating
|
|
@@ -618,9 +696,10 @@ introduces a new HIGH finding.
|
|
|
618
696
|
Dependabot go deeper on transitive resolution and lockfiles. AttackMap's
|
|
619
697
|
value is folding the CVE signal into an architecture-aware narrative
|
|
620
698
|
("this public route reaches this vulnerable ORM").
|
|
621
|
-
- **A true taint engine.** The data-flow pass is a heuristic
|
|
622
|
-
|
|
623
|
-
|
|
699
|
+
- **A true taint engine.** The data-flow pass is a heuristic call-graph-refined
|
|
700
|
+
import-graph walk (edges pruned to used symbols, but symbol *use* ≉ a proven
|
|
701
|
+
data-carrying call), not sound interprocedural taint analysis. It favors
|
|
702
|
+
precision over recall; findings are evidence, not proof.
|
|
624
703
|
- **Exhaustive.** AttackMap is heuristic by design. Findings are confidence-tiered
|
|
625
704
|
with explicit guardrails for stale signals.
|
|
626
705
|
|
|
@@ -628,7 +707,7 @@ introduces a new HIGH finding.
|
|
|
628
707
|
|
|
629
708
|
## Project status
|
|
630
709
|
|
|
631
|
-
AttackMap is **beta** (v0.4.
|
|
710
|
+
AttackMap is **beta** (v0.4.15) — published and validated on real codebases, but
|
|
632
711
|
pre-1.0 and heuristic.
|
|
633
712
|
|
|
634
713
|
**Solid today:**
|
|
@@ -650,8 +729,9 @@ pre-1.0 and heuristic.
|
|
|
650
729
|
explainable 0–100 "exploitable now" score that ranks route→sink combinations
|
|
651
730
|
and folds in known-vulnerable dependencies on the path.
|
|
652
731
|
- **`--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)
|
|
732
|
+
each lead CONFIRMED/REFUTED/NEEDS-REVIEW against the actual source),
|
|
733
|
+
**`--remediate`** (review-first fix suggestions), and **`--triage`**
|
|
734
|
+
(cluster/dedupe/rank existing findings, with a deterministic fallback).
|
|
655
735
|
- SBOM inventory (5 ecosystems) + OSV.dev CVE cross-reference (`--cve`).
|
|
656
736
|
- Output: Markdown + JSON + **SARIF 2.1.0** + **Mermaid / Graphviz** diagrams;
|
|
657
737
|
**diff/baseline** PR gating; a reusable **GitHub Action + PR bot**; optional
|
|
@@ -663,11 +743,16 @@ pre-1.0 and heuristic.
|
|
|
663
743
|
|
|
664
744
|
**Still maturing:**
|
|
665
745
|
|
|
666
|
-
-
|
|
667
|
-
|
|
668
|
-
|
|
746
|
+
- BOLA/IDOR authorization now covers path templates, query parameters, RPC
|
|
747
|
+
methods (XRPC/tRPC), and GraphQL fields; deeper resolver-level GraphQL
|
|
748
|
+
ownership analysis and more languages are planned.
|
|
749
|
+
- The taint walk is call-graph-aware for Python/JS/TS (import edges are pruned to
|
|
750
|
+
symbols actually used in the file) but still approximates: symbol *use* is not a
|
|
751
|
+
proven data-carrying call, and Go/PHP keep the plain import-graph. Precision
|
|
669
752
|
over recall; findings are evidence, not proof.
|
|
670
|
-
- CVE lookup
|
|
753
|
+
- CVE lookup uses exact lockfile-resolved versions (direct + transitive) when a
|
|
754
|
+
lockfile is present; range-only manifests without a lockfile still fall back to
|
|
755
|
+
a best-effort concrete version.
|
|
671
756
|
- Anomaly / exploitability reasoning is route-cohort and taint-chain scoped.
|
|
672
757
|
|
|
673
758
|
---
|
|
@@ -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.15"
|
|
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.15"
|