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.

Files changed (112) hide show
  1. {attackmap-0.4.10/src/attackmap.egg-info → attackmap-0.4.15}/PKG-INFO +113 -28
  2. {attackmap-0.4.10 → attackmap-0.4.15}/README.md +112 -27
  3. {attackmap-0.4.10 → attackmap-0.4.15}/pyproject.toml +1 -1
  4. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/__init__.py +1 -1
  5. attackmap-0.4.15/src/attackmap/authz.py +448 -0
  6. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/cli.py +81 -0
  7. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/cve.py +11 -1
  8. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/llm_review.py +3 -1
  9. attackmap-0.4.15/src/attackmap/lockfiles.py +387 -0
  10. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/models.py +22 -6
  11. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/review_prompts.py +61 -0
  12. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/sbom.py +31 -3
  13. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/scanner.py +76 -3
  14. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/taint.py +139 -27
  15. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/threat_model.py +30 -5
  16. attackmap-0.4.15/src/attackmap/triage.py +151 -0
  17. {attackmap-0.4.10 → attackmap-0.4.15/src/attackmap.egg-info}/PKG-INFO +113 -28
  18. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap.egg-info/SOURCES.txt +4 -0
  19. attackmap-0.4.15/tests/test_authz.py +472 -0
  20. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_cve.py +91 -0
  21. attackmap-0.4.15/tests/test_lockfiles.py +348 -0
  22. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_scanner.py +79 -0
  23. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_taint.py +119 -0
  24. attackmap-0.4.15/tests/test_triage.py +201 -0
  25. attackmap-0.4.10/src/attackmap/authz.py +0 -196
  26. attackmap-0.4.10/tests/test_authz.py +0 -241
  27. {attackmap-0.4.10 → attackmap-0.4.15}/LICENSE +0 -0
  28. {attackmap-0.4.10 → attackmap-0.4.15}/setup.cfg +0 -0
  29. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/analyzer.py +0 -0
  30. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/analyzer_contracts.py +0 -0
  31. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/analyzers.py +0 -0
  32. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/anomalies.py +0 -0
  33. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/asset_model.py +0 -0
  34. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/attack_taxonomy.py +0 -0
  35. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/config_scanner.py +0 -0
  36. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/context_pack.py +0 -0
  37. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/control_model.py +0 -0
  38. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/crypto.py +0 -0
  39. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/defensive_review.py +0 -0
  40. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/detection_opportunities.py +0 -0
  41. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/diagrams.py +0 -0
  42. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/diff.py +0 -0
  43. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/exploitability.py +0 -0
  44. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/graph.py +0 -0
  45. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/insights.py +0 -0
  46. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/merge.py +0 -0
  47. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/progress.py +0 -0
  48. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/recon_models.py +0 -0
  49. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/recon_to_analysis.py +0 -0
  50. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/report.py +0 -0
  51. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/review_eval.py +0 -0
  52. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/review_json.py +0 -0
  53. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/route_auth_fusion.py +0 -0
  54. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/sarif.py +0 -0
  55. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/sdk/__init__.py +0 -0
  56. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/sdk/contracts.py +0 -0
  57. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/sdk/models.py +0 -0
  58. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/security_overlay.py +0 -0
  59. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/srcpaths.py +0 -0
  60. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/suggest.py +0 -0
  61. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/suppress.py +0 -0
  62. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/topology.py +0 -0
  63. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/weaknesses.py +0 -0
  64. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/webhardening.py +0 -0
  65. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap/workflow_scanner.py +0 -0
  66. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap.egg-info/dependency_links.txt +0 -0
  67. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap.egg-info/entry_points.txt +0 -0
  68. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap.egg-info/requires.txt +0 -0
  69. {attackmap-0.4.10 → attackmap-0.4.15}/src/attackmap.egg-info/top_level.txt +0 -0
  70. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_analyzer.py +0 -0
  71. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_analyzer_metadata_schema.py +0 -0
  72. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_analyzers.py +0 -0
  73. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_anomalies.py +0 -0
  74. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_artifact_schemas.py +0 -0
  75. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_config_scanner.py +0 -0
  76. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_context_pack.py +0 -0
  77. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_crypto.py +0 -0
  78. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_defensive_review.py +0 -0
  79. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_diagrams.py +0 -0
  80. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_diff.py +0 -0
  81. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_exploitability.py +0 -0
  82. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_graph.py +0 -0
  83. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_hunt.py +0 -0
  84. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_injection_sinks.py +0 -0
  85. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_llm_review.py +0 -0
  86. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_merge.py +0 -0
  87. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_modules_cli.py +0 -0
  88. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_pr_comment.py +0 -0
  89. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_progress.py +0 -0
  90. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_provenance.py +0 -0
  91. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_recon_to_analysis.py +0 -0
  92. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_remediate.py +0 -0
  93. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_report.py +0 -0
  94. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_review_eval.py +0 -0
  95. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_review_json.py +0 -0
  96. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_review_prompts.py +0 -0
  97. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_route_auth_fusion.py +0 -0
  98. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_sarif.py +0 -0
  99. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_sbom.py +0 -0
  100. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_security_overlay.py +0 -0
  101. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_shared_contract_imports.py +0 -0
  102. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_signal_v2.py +0 -0
  103. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_srcpaths.py +0 -0
  104. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_suggest.py +0 -0
  105. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_suppress.py +0 -0
  106. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_taint_sanitizer.py +0 -0
  107. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_threat_intel.py +0 -0
  108. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_threat_model.py +0 -0
  109. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_topology.py +0 -0
  110. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_weaknesses.py +0 -0
  111. {attackmap-0.4.10 → attackmap-0.4.15}/tests/test_webhardening.py +0 -0
  112. {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.10
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
  [![Python: 3.11+](https://img.shields.io/badge/Python-3.11%2B-blue.svg)](https://www.python.org/)
78
78
  [![PyPI](https://img.shields.io/pypi/v/attackmap.svg)](https://pypi.org/project/attackmap/)
79
79
 
80
- > **Status: beta (v0.4.0).** Core engine and 14 analyzer plugins are published
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
+ | [![AttackMap macOS app — overview](https://raw.githubusercontent.com/mlaify/AttackMap/main/docs/screenshots/04-overview.png)](https://docs.matthewd.xyz/gui/) | [![AttackMap macOS app — exploitability](https://raw.githubusercontent.com/mlaify/AttackMap/main/docs/screenshots/06-exploitability-ranked.png)](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. It's a heuristic (import-edge ≠ call-edge), so findings
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** in the path (`/users/{id}`,
446
- `/orders/:orderId`, `/docs/<int:doc_id>`), and
447
- 2. it **reaches a datastore** — a DB hint in the same file/module, or a taint
448
- chain from the route to a SQL execute sink, and
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, or a
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 routes (POST/PUT/PATCH/DELETE) are HIGH, reads MEDIUM. Candidates appear
454
- under `scan.authz_candidates`; the ownership-marker scan is the main
455
- false-positive reducer, so a well-scoped handler is not flagged. Path-template
456
- routes today; query-parameter and RPC-method ids are future work.
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}`. Version ranges are kept verbatim
524
- (`^4.16.0`, `>=2,<3`, `latest`) — this slice does not resolve lockfiles.
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
- - **Version resolution is best-effort.** Manifest ranges (`^4.16.0`,
546
- `>=2.28,<3`) resolve to a queryable lower-bound; OSV does the range math.
547
- Unpinned specs (`*`, `latest`) are skipped.
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 import-graph walk
683
- (import-edge ≈ call-edge), not sound interprocedural taint analysis. It
684
- favors precision over recall; findings are evidence, not proof.
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.0) — published and validated on real codebases, but
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), and
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
- - Taint is Python/JS/TS/Go/PHP and path-template scoped; query-param / RPC-method
728
- authorization and more languages are planned.
729
- - The import-graph taint walk approximates call-edges with import-edges — precision
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 resolves a best-effort concrete version, not full lockfile ranges.
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
  [![Python: 3.11+](https://img.shields.io/badge/Python-3.11%2B-blue.svg)](https://www.python.org/)
17
17
  [![PyPI](https://img.shields.io/pypi/v/attackmap.svg)](https://pypi.org/project/attackmap/)
18
18
 
19
- > **Status: beta (v0.4.0).** Core engine and 14 analyzer plugins are published
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
+ | [![AttackMap macOS app — overview](https://raw.githubusercontent.com/mlaify/AttackMap/main/docs/screenshots/04-overview.png)](https://docs.matthewd.xyz/gui/) | [![AttackMap macOS app — exploitability](https://raw.githubusercontent.com/mlaify/AttackMap/main/docs/screenshots/06-exploitability-ranked.png)](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. It's a heuristic (import-edge ≠ call-edge), so findings
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** in the path (`/users/{id}`,
385
- `/orders/:orderId`, `/docs/<int:doc_id>`), and
386
- 2. it **reaches a datastore** — a DB hint in the same file/module, or a taint
387
- chain from the route to a SQL execute sink, and
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, or a
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 routes (POST/PUT/PATCH/DELETE) are HIGH, reads MEDIUM. Candidates appear
393
- under `scan.authz_candidates`; the ownership-marker scan is the main
394
- false-positive reducer, so a well-scoped handler is not flagged. Path-template
395
- routes today; query-parameter and RPC-method ids are future work.
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}`. Version ranges are kept verbatim
463
- (`^4.16.0`, `>=2,<3`, `latest`) — this slice does not resolve lockfiles.
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
- - **Version resolution is best-effort.** Manifest ranges (`^4.16.0`,
485
- `>=2.28,<3`) resolve to a queryable lower-bound; OSV does the range math.
486
- Unpinned specs (`*`, `latest`) are skipped.
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 import-graph walk
622
- (import-edge ≈ call-edge), not sound interprocedural taint analysis. It
623
- favors precision over recall; findings are evidence, not proof.
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.0) — published and validated on real codebases, but
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), and
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
- - Taint is Python/JS/TS/Go/PHP and path-template scoped; query-param / RPC-method
667
- authorization and more languages are planned.
668
- - The import-graph taint walk approximates call-edges with import-edges — precision
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 resolves a best-effort concrete version, not full lockfile ranges.
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.10"
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.10"
2
+ __version__ = "0.4.15"