attackmap 0.4.6__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.6/src/attackmap.egg-info → attackmap-0.4.15}/PKG-INFO +207 -28
- {attackmap-0.4.6 → attackmap-0.4.15}/README.md +205 -27
- {attackmap-0.4.6 → attackmap-0.4.15}/pyproject.toml +2 -1
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/__init__.py +1 -1
- attackmap-0.4.15/src/attackmap/authz.py +448 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/cli.py +118 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/cve.py +11 -1
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/exploitability.py +4 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/llm_review.py +3 -1
- attackmap-0.4.15/src/attackmap/lockfiles.py +387 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/merge.py +4 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/models.py +59 -6
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/report.py +20 -1
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/review_prompts.py +61 -0
- attackmap-0.4.15/src/attackmap/route_auth_fusion.py +299 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/sarif.py +55 -36
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/sbom.py +31 -3
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/scanner.py +83 -3
- attackmap-0.4.15/src/attackmap/suppress.py +418 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/taint.py +218 -35
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/threat_model.py +135 -6
- attackmap-0.4.15/src/attackmap/triage.py +151 -0
- attackmap-0.4.15/src/attackmap/workflow_scanner.py +328 -0
- {attackmap-0.4.6 → attackmap-0.4.15/src/attackmap.egg-info}/PKG-INFO +207 -28
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap.egg-info/SOURCES.txt +12 -1
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap.egg-info/requires.txt +1 -0
- attackmap-0.4.15/tests/test_authz.py +472 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_cve.py +91 -0
- attackmap-0.4.15/tests/test_lockfiles.py +348 -0
- attackmap-0.4.15/tests/test_route_auth_fusion.py +249 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_scanner.py +79 -0
- attackmap-0.4.15/tests/test_suppress.py +340 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_taint.py +119 -0
- attackmap-0.4.15/tests/test_taint_sanitizer.py +122 -0
- attackmap-0.4.15/tests/test_triage.py +201 -0
- attackmap-0.4.15/tests/test_workflow_scanner.py +416 -0
- attackmap-0.4.6/src/attackmap/authz.py +0 -196
- attackmap-0.4.6/tests/test_authz.py +0 -241
- {attackmap-0.4.6 → attackmap-0.4.15}/LICENSE +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/setup.cfg +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/analyzer.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/analyzer_contracts.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/analyzers.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/anomalies.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/asset_model.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/attack_taxonomy.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/config_scanner.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/context_pack.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/control_model.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/crypto.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/defensive_review.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/detection_opportunities.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/diagrams.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/diff.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/graph.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/insights.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/progress.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/recon_models.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/recon_to_analysis.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/review_eval.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/review_json.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/sdk/__init__.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/sdk/contracts.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/sdk/models.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/security_overlay.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/srcpaths.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/suggest.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/topology.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/weaknesses.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap/webhardening.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap.egg-info/dependency_links.txt +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap.egg-info/entry_points.txt +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/src/attackmap.egg-info/top_level.txt +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_analyzer.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_analyzer_metadata_schema.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_analyzers.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_anomalies.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_artifact_schemas.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_config_scanner.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_context_pack.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_crypto.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_defensive_review.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_diagrams.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_diff.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_exploitability.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_graph.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_hunt.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_injection_sinks.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_llm_review.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_merge.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_modules_cli.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_pr_comment.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_progress.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_provenance.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_recon_to_analysis.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_remediate.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_report.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_review_eval.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_review_json.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_review_prompts.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_sarif.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_sbom.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_security_overlay.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_shared_contract_imports.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_signal_v2.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_srcpaths.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_suggest.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_threat_intel.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_threat_model.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_topology.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_weaknesses.py +0 -0
- {attackmap-0.4.6 → attackmap-0.4.15}/tests/test_webhardening.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>
|
|
@@ -34,6 +34,7 @@ License-File: LICENSE
|
|
|
34
34
|
Requires-Dist: typer>=0.12.3
|
|
35
35
|
Requires-Dist: pydantic>=2.7.0
|
|
36
36
|
Requires-Dist: networkx>=3.3
|
|
37
|
+
Requires-Dist: PyYAML>=6.0
|
|
37
38
|
Provides-Extra: llm
|
|
38
39
|
Requires-Dist: anthropic>=0.40.0; extra == "llm"
|
|
39
40
|
Requires-Dist: openai>=1.60.0; extra == "llm"
|
|
@@ -76,12 +77,24 @@ managers who need to triage an unfamiliar codebase.
|
|
|
76
77
|
[](https://www.python.org/)
|
|
77
78
|
[](https://pypi.org/project/attackmap/)
|
|
78
79
|
|
|
79
|
-
> **Status: beta (v0.4.
|
|
80
|
+
> **Status: beta (v0.4.15).** Core engine and 14 analyzer plugins are published
|
|
80
81
|
> to PyPI, Homebrew, and GHCR and validated against real-world codebases.
|
|
81
82
|
> AttackMap is heuristic by design — findings are confidence-tiered evidence,
|
|
82
83
|
> not proof. See [Project status](#project-status) for what's solid and what's
|
|
83
84
|
> still maturing.
|
|
84
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
|
+
|
|
85
98
|
---
|
|
86
99
|
|
|
87
100
|
## Quickstart
|
|
@@ -186,6 +199,7 @@ Every `attackmap analyze` run writes:
|
|
|
186
199
|
| `defensive-review-llm.md` *(with `--llm`)* | Claude-narrated review |
|
|
187
200
|
| `defensive-review-llm.meta.json` *(with `--llm`)* | Backend, model, token usage |
|
|
188
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) |
|
|
189
203
|
|
|
190
204
|
### GitHub Code Scanning integration
|
|
191
205
|
|
|
@@ -271,6 +285,48 @@ on unrelated commits, so a finding that persists across scans has the same id
|
|
|
271
285
|
in both. The diff has three sections — **New**, **Persisted**, **Resolved** —
|
|
272
286
|
which drop cleanly into a PR comment.
|
|
273
287
|
|
|
288
|
+
### Suppressing findings
|
|
289
|
+
|
|
290
|
+
Silence a residual false positive without going blind to new signal. Two
|
|
291
|
+
mechanisms, both honored across every pass:
|
|
292
|
+
|
|
293
|
+
**Repo-level baseline** — `.attackmap-suppress.yaml` at the repo root:
|
|
294
|
+
|
|
295
|
+
```yaml
|
|
296
|
+
version: 1
|
|
297
|
+
suppress:
|
|
298
|
+
# by stable finding id (the 16-hex id in attackmap-report.json)
|
|
299
|
+
- id: 1a2b3c4d5e6f7a8b
|
|
300
|
+
reason: accepted risk, tracked in JIRA-1234
|
|
301
|
+
|
|
302
|
+
# by rule (a slug of the finding title — same string as the SARIF ruleId),
|
|
303
|
+
# optionally scoped to paths
|
|
304
|
+
- rule: hard-coded-secret-literals-were-found-in-source-or-config
|
|
305
|
+
reason: example keys in docs, never shipped
|
|
306
|
+
paths: ["docs/**", "examples/**"]
|
|
307
|
+
|
|
308
|
+
# by path only — any finding whose evidence is *entirely* within these globs
|
|
309
|
+
- path: "vendor/**"
|
|
310
|
+
reason: third-party code, out of scope
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
A `path` selector matches a finding only when **every** file its evidence cites
|
|
314
|
+
falls under the glob, so a finding that also touches live code is never hidden.
|
|
315
|
+
`*` spans path separators (`vendor/*` covers the whole subtree).
|
|
316
|
+
|
|
317
|
+
**Inline directives** — a comment on the flagged line, in any language:
|
|
318
|
+
|
|
319
|
+
```python
|
|
320
|
+
API_KEY = "AKIA…" # attackmap:ignore[hard-coded-secret-literals-were-found-in-source-or-config] staging only
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Suppressed findings are **not dropped**: they're excluded from
|
|
324
|
+
`--fail-on-new-high` and the console summary, but retained in
|
|
325
|
+
`attackmap-report.json` under `suppressed_findings` (with reasons) and marked
|
|
326
|
+
with a SARIF `suppressions` array so GitHub Code Scanning shows them as
|
|
327
|
+
suppressed. Suppression counts print in the run summary. Use `--no-suppress`
|
|
328
|
+
for a full unfiltered audit, or `--suppress-file PATH` to point elsewhere.
|
|
329
|
+
|
|
274
330
|
### Data-flow / injection detection
|
|
275
331
|
|
|
276
332
|
A lightweight taint pass (Python + JS/TS) walks the import graph up to two hops
|
|
@@ -302,10 +358,27 @@ left on).
|
|
|
302
358
|
|
|
303
359
|
Sinks that are only dangerous with attacker-controlled input (SSRF, SSTI, NoSQL,
|
|
304
360
|
open redirect, `open`) are gated on a request-shaped identifier in the call — a constant URL or
|
|
305
|
-
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
|
|
306
366
|
are evidence, not proof; confidence tapers with hop distance. Chains appear in
|
|
307
367
|
`attackmap-report.json` under `scan.taint_chains`.
|
|
308
368
|
|
|
369
|
+
**Sanitizer awareness.** When a sink-appropriate neutralizer is present in the
|
|
370
|
+
sink file — `shlex.quote` / `escapeshellarg` (shell), `secure_filename` / path
|
|
371
|
+
allow-listers (`open`), `is_safe_url` (redirect), `markupsafe.escape` (SSTI),
|
|
372
|
+
driver escapers (SQL), mongo-sanitize (NoSQL) — the chain is marked
|
|
373
|
+
`sanitized`, its confidence is downgraded well below the finding threshold, and
|
|
374
|
+
the neutralizer is recorded in `sanitizer_evidence`. Sanitized chains stay in
|
|
375
|
+
`scan.taint_chains` for audit but don't raise a HIGH finding or earn an
|
|
376
|
+
exploitability score. Detection is file-granular (matching the import-walk's
|
|
377
|
+
granularity), so it trades a little recall for precision. To extend the table,
|
|
378
|
+
add a high-signal, sink-appropriate `(label, regex)` entry to
|
|
379
|
+
`_SANITIZER_PATTERNS` in [`taint.py`](src/attackmap/taint.py) — keep it specific
|
|
380
|
+
(a vague `validate(` would hide real bugs).
|
|
381
|
+
|
|
309
382
|
Test and spec files (`tests/`, `__tests__/`, `*.test.*`, `test_*.py`, …) are
|
|
310
383
|
excluded from all the heuristic passes above by default, since dangerous
|
|
311
384
|
patterns in test scaffolding are rarely real exposure. Set
|
|
@@ -347,23 +420,87 @@ identifier; cipher/ECB tokens are matched case-sensitively so algorithm names
|
|
|
347
420
|
aren't confused with prose (e.g. the French word "des"). Results appear under
|
|
348
421
|
`scan.crypto_weaknesses`.
|
|
349
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
|
+
|
|
449
|
+
### GitHub Actions / CI workflow security
|
|
450
|
+
|
|
451
|
+
CI config is a real attack surface. AttackMap parses `.github/workflows/*.yml`
|
|
452
|
+
and flags supply-chain and code-execution risks, each a finding with an ATT&CK
|
|
453
|
+
mapping:
|
|
454
|
+
|
|
455
|
+
| Kind | Catches | Severity |
|
|
456
|
+
|---|---|---|
|
|
457
|
+
| `script_injection` | attacker-controlled context (`github.event.*.{title,body,message,…}`, `github.head_ref`) interpolated into a `run:` step | HIGH |
|
|
458
|
+
| `pr_target_checkout` | `pull_request_target` + checkout of the PR head ref (untrusted code with secrets in scope) | HIGH |
|
|
459
|
+
| `unpinned_action` | `uses: org/action@tag\|branch` instead of a pinned commit SHA | branch = MEDIUM, semver tag = LOW |
|
|
460
|
+
| `secret_in_run` | `${{ secrets.* }}` expanded into a shell step (pass via `env:` instead) | MEDIUM |
|
|
461
|
+
| `broad_permissions` | `permissions: write-all` at workflow or job level | MEDIUM |
|
|
462
|
+
| `self_hosted_pr` | self-hosted runner on a `pull_request`/`pull_request_target` trigger | HIGH / MEDIUM |
|
|
463
|
+
|
|
464
|
+
A hardened workflow — SHA-pinned actions, scoped `permissions:`, secrets via
|
|
465
|
+
`env:`, no untrusted-context interpolation — produces nothing. Results appear
|
|
466
|
+
under `scan.workflow_issues`.
|
|
467
|
+
|
|
468
|
+
### Unauthenticated state-changing routes
|
|
469
|
+
|
|
470
|
+
Raw `auth_hints` are signals, not conclusions — a monorepo emits hundreds. A
|
|
471
|
+
fusion pass turns them into one precise finding: the public, state-changing
|
|
472
|
+
(`POST/PUT/PATCH/DELETE`) routes with **no auth control on their own chain**. It
|
|
473
|
+
resolves the control *per route* rather than by file proximity:
|
|
474
|
+
|
|
475
|
+
- **Express/Koa/Fastify** — auth middleware in the route's arguments, or a global `app.use(...)` / `router.use(...)`.
|
|
476
|
+
- **FastAPI/Flask** — an auth decorator, `dependencies=[Depends(...)]`, `Depends(<auth>)` / `Security(...)` in the signature, or a router-level dependency.
|
|
477
|
+
- **Spring** — `@PreAuthorize` / `@Secured` / `@RolesAllowed` on the method or controller, or a global "authenticated" policy.
|
|
478
|
+
|
|
479
|
+
Routes behind a control produce nothing, and the sensitive categories
|
|
480
|
+
(webhook/admin/upload/auth) keep their own dedicated findings — so this only
|
|
481
|
+
adds the general mutating-endpoint case, with the resolved chain as evidence.
|
|
482
|
+
|
|
350
483
|
### Broken object-level authorization (BOLA / IDOR)
|
|
351
484
|
|
|
352
485
|
OWASP API Security #1. AttackMap flags a route as a BOLA/IDOR candidate when it
|
|
353
486
|
composes three signals it already has:
|
|
354
487
|
|
|
355
|
-
1. the route takes a **resource id**
|
|
356
|
-
`/orders/:orderId
|
|
357
|
-
|
|
358
|
-
|
|
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
|
|
359
495
|
3. **no ownership/authorization check** is visible near the handler
|
|
360
|
-
(`current_user`, `request.user`, `authorize`, a policy/guard,
|
|
361
|
-
`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).
|
|
362
499
|
|
|
363
|
-
Write
|
|
364
|
-
under `scan.authz_candidates
|
|
365
|
-
false-positive reducer, so a
|
|
366
|
-
|
|
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.
|
|
367
504
|
|
|
368
505
|
### Anomaly / outlier detection
|
|
369
506
|
|
|
@@ -430,8 +567,27 @@ of the common manifest files:
|
|
|
430
567
|
| Composer (PHP) | `composer.json` (require + require-dev; platform reqs skipped) |
|
|
431
568
|
|
|
432
569
|
Each entry appears in `attackmap-report.json` under `scan.dependencies` with
|
|
433
|
-
`{name, version, ecosystem, file, dev}`.
|
|
434
|
-
(`^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.)
|
|
435
591
|
|
|
436
592
|
### CVE cross-reference (opt-in)
|
|
437
593
|
|
|
@@ -452,9 +608,12 @@ attackmap analyze . --cve
|
|
|
452
608
|
the network.
|
|
453
609
|
- **Offline-tolerant.** If the network's unavailable but the cache is warm,
|
|
454
610
|
cached results still surface; only fresh queries are skipped.
|
|
455
|
-
- **
|
|
456
|
-
|
|
457
|
-
|
|
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.
|
|
458
617
|
|
|
459
618
|
The structured vulnerability list is available under
|
|
460
619
|
`scan.vulnerabilities` in `attackmap-report.json` with
|
|
@@ -508,6 +667,14 @@ a human must verify. Uses the same auth/backend resolution as `--llm`.
|
|
|
508
667
|
Layered on top: **MITRE ATT&CK technique mappings** on every insight and
|
|
509
668
|
**detection opportunities** (Sigma/KQL/Splunk-style hints) for each weakness.
|
|
510
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
|
+
|
|
511
678
|
---
|
|
512
679
|
|
|
513
680
|
## Supported ecosystems
|
|
@@ -556,12 +723,17 @@ attackmap analyze <path> --llm --llm-provider openai --llm-model gpt-5.5 # any
|
|
|
556
723
|
attackmap analyze <path> --hunt # LLM vulnerability-hypothesis hunt (leads to confirm)
|
|
557
724
|
attackmap analyze <path> --hunt --verify # adjudicate each lead vs. source (confirmed/refuted/review)
|
|
558
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)
|
|
559
727
|
attackmap analyze <path> --pr-comment pr.md # Markdown PR summary comment for CI
|
|
560
728
|
|
|
561
729
|
# CI / PR diff gating
|
|
562
730
|
attackmap analyze <path> --baseline prev/attackmap-report.json \
|
|
563
731
|
--diff-output reports/attackmap-diff.md --fail-on-new-high
|
|
564
732
|
|
|
733
|
+
# Suppression (.attackmap-suppress.yaml + inline `attackmap:ignore` directives)
|
|
734
|
+
attackmap analyze <path> --no-suppress # full audit, ignore all suppressions
|
|
735
|
+
attackmap analyze <path> --suppress-file cfg.yaml # override baseline location
|
|
736
|
+
|
|
565
737
|
# Plugin discovery
|
|
566
738
|
attackmap suggest ./repo # recommend plugins for a repo shape
|
|
567
739
|
attackmap suggest ./repo --install # and pip-install the missing ones
|
|
@@ -585,9 +757,10 @@ introduces a new HIGH finding.
|
|
|
585
757
|
Dependabot go deeper on transitive resolution and lockfiles. AttackMap's
|
|
586
758
|
value is folding the CVE signal into an architecture-aware narrative
|
|
587
759
|
("this public route reaches this vulnerable ORM").
|
|
588
|
-
- **A true taint engine.** The data-flow pass is a heuristic
|
|
589
|
-
|
|
590
|
-
|
|
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.
|
|
591
764
|
- **Exhaustive.** AttackMap is heuristic by design. Findings are confidence-tiered
|
|
592
765
|
with explicit guardrails for stale signals.
|
|
593
766
|
|
|
@@ -595,7 +768,7 @@ introduces a new HIGH finding.
|
|
|
595
768
|
|
|
596
769
|
## Project status
|
|
597
770
|
|
|
598
|
-
AttackMap is **beta** (v0.4.
|
|
771
|
+
AttackMap is **beta** (v0.4.15) — published and validated on real codebases, but
|
|
599
772
|
pre-1.0 and heuristic.
|
|
600
773
|
|
|
601
774
|
**Solid today:**
|
|
@@ -617,8 +790,9 @@ pre-1.0 and heuristic.
|
|
|
617
790
|
explainable 0–100 "exploitable now" score that ranks route→sink combinations
|
|
618
791
|
and folds in known-vulnerable dependencies on the path.
|
|
619
792
|
- **`--hunt`** (LLM exploit-chain hypotheses) with **`--verify`** (adjudicate
|
|
620
|
-
each lead CONFIRMED/REFUTED/NEEDS-REVIEW against the actual source),
|
|
621
|
-
**`--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).
|
|
622
796
|
- SBOM inventory (5 ecosystems) + OSV.dev CVE cross-reference (`--cve`).
|
|
623
797
|
- Output: Markdown + JSON + **SARIF 2.1.0** + **Mermaid / Graphviz** diagrams;
|
|
624
798
|
**diff/baseline** PR gating; a reusable **GitHub Action + PR bot**; optional
|
|
@@ -630,11 +804,16 @@ pre-1.0 and heuristic.
|
|
|
630
804
|
|
|
631
805
|
**Still maturing:**
|
|
632
806
|
|
|
633
|
-
-
|
|
634
|
-
|
|
635
|
-
|
|
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
|
|
636
813
|
over recall; findings are evidence, not proof.
|
|
637
|
-
- 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.
|
|
638
817
|
- Anomaly / exploitability reasoning is route-cohort and taint-chain scoped.
|
|
639
818
|
|
|
640
819
|
---
|