attackmap 0.3.2__tar.gz → 0.4.0__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.
Files changed (98) hide show
  1. {attackmap-0.3.2/src/attackmap.egg-info → attackmap-0.4.0}/PKG-INFO +73 -26
  2. {attackmap-0.3.2 → attackmap-0.4.0}/README.md +72 -25
  3. {attackmap-0.3.2 → attackmap-0.4.0}/pyproject.toml +1 -1
  4. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/__init__.py +1 -1
  5. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/cli.py +89 -3
  6. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/crypto.py +51 -0
  7. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/exploitability.py +113 -4
  8. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/llm_review.py +12 -3
  9. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/report.py +50 -0
  10. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/review_prompts.py +148 -0
  11. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/scanner.py +112 -2
  12. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/srcpaths.py +5 -2
  13. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/taint.py +341 -21
  14. {attackmap-0.3.2 → attackmap-0.4.0/src/attackmap.egg-info}/PKG-INFO +73 -26
  15. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap.egg-info/SOURCES.txt +2 -0
  16. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_crypto.py +29 -0
  17. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_exploitability.py +75 -0
  18. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_hunt.py +36 -0
  19. attackmap-0.4.0/tests/test_pr_comment.py +49 -0
  20. attackmap-0.4.0/tests/test_remediate.py +65 -0
  21. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_scanner.py +120 -0
  22. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_srcpaths.py +17 -0
  23. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_taint.py +255 -0
  24. {attackmap-0.3.2 → attackmap-0.4.0}/LICENSE +0 -0
  25. {attackmap-0.3.2 → attackmap-0.4.0}/setup.cfg +0 -0
  26. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/analyzer.py +0 -0
  27. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/analyzer_contracts.py +0 -0
  28. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/analyzers.py +0 -0
  29. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/anomalies.py +0 -0
  30. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/asset_model.py +0 -0
  31. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/attack_taxonomy.py +0 -0
  32. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/authz.py +0 -0
  33. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/config_scanner.py +0 -0
  34. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/context_pack.py +0 -0
  35. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/control_model.py +0 -0
  36. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/cve.py +0 -0
  37. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/defensive_review.py +0 -0
  38. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/detection_opportunities.py +0 -0
  39. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/diagrams.py +0 -0
  40. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/diff.py +0 -0
  41. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/graph.py +0 -0
  42. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/insights.py +0 -0
  43. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/merge.py +0 -0
  44. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/models.py +0 -0
  45. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/progress.py +0 -0
  46. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/recon_models.py +0 -0
  47. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/recon_to_analysis.py +0 -0
  48. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/review_eval.py +0 -0
  49. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/review_json.py +0 -0
  50. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/sarif.py +0 -0
  51. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/sbom.py +0 -0
  52. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/sdk/__init__.py +0 -0
  53. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/sdk/contracts.py +0 -0
  54. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/sdk/models.py +0 -0
  55. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/security_overlay.py +0 -0
  56. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/suggest.py +0 -0
  57. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/threat_model.py +0 -0
  58. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/topology.py +0 -0
  59. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/weaknesses.py +0 -0
  60. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap/webhardening.py +0 -0
  61. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap.egg-info/dependency_links.txt +0 -0
  62. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap.egg-info/entry_points.txt +0 -0
  63. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap.egg-info/requires.txt +0 -0
  64. {attackmap-0.3.2 → attackmap-0.4.0}/src/attackmap.egg-info/top_level.txt +0 -0
  65. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_analyzer.py +0 -0
  66. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_analyzer_metadata_schema.py +0 -0
  67. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_analyzers.py +0 -0
  68. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_anomalies.py +0 -0
  69. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_artifact_schemas.py +0 -0
  70. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_authz.py +0 -0
  71. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_config_scanner.py +0 -0
  72. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_context_pack.py +0 -0
  73. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_cve.py +0 -0
  74. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_defensive_review.py +0 -0
  75. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_diagrams.py +0 -0
  76. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_diff.py +0 -0
  77. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_graph.py +0 -0
  78. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_injection_sinks.py +0 -0
  79. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_llm_review.py +0 -0
  80. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_merge.py +0 -0
  81. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_progress.py +0 -0
  82. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_provenance.py +0 -0
  83. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_recon_to_analysis.py +0 -0
  84. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_report.py +0 -0
  85. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_review_eval.py +0 -0
  86. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_review_json.py +0 -0
  87. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_review_prompts.py +0 -0
  88. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_sarif.py +0 -0
  89. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_sbom.py +0 -0
  90. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_security_overlay.py +0 -0
  91. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_shared_contract_imports.py +0 -0
  92. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_signal_v2.py +0 -0
  93. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_suggest.py +0 -0
  94. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_threat_intel.py +0 -0
  95. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_threat_model.py +0 -0
  96. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_topology.py +0 -0
  97. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_weaknesses.py +0 -0
  98. {attackmap-0.3.2 → attackmap-0.4.0}/tests/test_webhardening.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: attackmap
3
- Version: 0.3.2
3
+ Version: 0.4.0
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>
@@ -75,7 +75,7 @@ managers who need to triage an unfamiliar codebase.
75
75
  [![Python: 3.11+](https://img.shields.io/badge/Python-3.11%2B-blue.svg)](https://www.python.org/)
76
76
  [![PyPI](https://img.shields.io/pypi/v/attackmap.svg)](https://pypi.org/project/attackmap/)
77
77
 
78
- > **Status: beta (v0.3.2).** Core engine and 14 analyzer plugins are published
78
+ > **Status: beta (v0.4.0).** Core engine and 14 analyzer plugins are published
79
79
  > to PyPI, Homebrew, and GHCR and validated against real-world codebases.
80
80
  > AttackMap is heuristic by design — findings are confidence-tiered evidence,
81
81
  > not proof. See [Project status](#project-status) for what's solid and what's
@@ -205,6 +205,46 @@ jobs:
205
205
  category: attackmap
206
206
  ```
207
207
 
208
+ ### PR bot (reusable Action + summary comment)
209
+
210
+ AttackMap ships a composite **GitHub Action** (`action.yml`) that installs it,
211
+ scans, uploads SARIF (for inline annotations on the PR's *Files changed* tab),
212
+ and renders a Markdown **PR summary comment** (new/resolved findings, gate
213
+ status, and the top "most exploitable now"). Wire it up to post the comment on
214
+ each PR:
215
+
216
+ ```yaml
217
+ name: AttackMap PR review
218
+ on: pull_request
219
+ permissions:
220
+ contents: read
221
+ security-events: write # SARIF upload
222
+ pull-requests: write # summary comment
223
+ jobs:
224
+ attackmap:
225
+ runs-on: ubuntu-latest
226
+ steps:
227
+ - uses: actions/checkout@v4
228
+ - id: scan
229
+ uses: mlaify/AttackMap@v1
230
+ with:
231
+ path: .
232
+ fail-on-new-high: "true" # optional gate (needs a baseline)
233
+ - uses: actions/github-script@v7
234
+ if: always()
235
+ with:
236
+ script: |
237
+ const fs = require('fs');
238
+ const body = fs.readFileSync('${{ steps.scan.outputs.pr-comment-file }}', 'utf8');
239
+ await github.rest.issues.createComment({
240
+ owner: context.repo.owner, repo: context.repo.repo,
241
+ issue_number: context.issue.number, body,
242
+ });
243
+ ```
244
+
245
+ The comment renderer is also available standalone: `attackmap analyze .
246
+ --pr-comment pr.md` (add `--baseline prev.json` to include the diff).
247
+
208
248
  ### Diff mode (PR gating)
209
249
 
210
250
  For a lighter CI integration than Code Scanning — a bot comment, a JSON delta,
@@ -358,16 +398,16 @@ same sink two hops away. The score is:
358
398
  is shown. Contributing factors: sink danger (SQLi/RCE/deserialization highest),
359
399
  exposure (public/internal/unknown), auth at the entry route, reachability
360
400
  (fewer hops = higher), and data sensitivity at the sink (a co-located secret or
361
- datastore), plus insecure-crypto / web-hardening gaps on the path as
362
- amplifiers.
401
+ datastore), plus insecure-crypto / web-hardening gaps and **known-vulnerable
402
+ dependencies imported on the path** (from `--cve`, amplified by CVE severity)
403
+ as amplifiers.
363
404
 
364
405
  Scores land on the relevant taint findings (`exploitability` + `exploitability_tier`)
365
406
  and in a ranked **`attackmap-exploitability.md`** report plus the `exploitability`
366
407
  array in `attackmap-report.json`; the console summary leads with the top few. For
367
- example, `public + no-auth + taint-to-eval (0 hops)` scores 90/100 (CRITICAL).
368
- (Dependency CVEs are manifest-level, not attributable to a specific source path,
369
- so they inform the report but aren't folded into a path's score — path-attributed
370
- CVE fusion is future work.)
408
+ example, `public + no-auth + taint-to-eval (0 hops)` scores 90/100 (CRITICAL), and
409
+ a route reaching a sink through a known-vulnerable library version scores higher
410
+ still.
371
411
 
372
412
  ### SBOM inventory
373
413
 
@@ -503,6 +543,9 @@ attackmap analyze <path> --cve # cross-reference SBOM against OSV.dev
503
543
  attackmap analyze <path> --llm # add LLM narrative (auto-resolve auth)
504
544
  attackmap analyze <path> --llm --llm-backend cli # force Claude CLI
505
545
  attackmap analyze <path> --hunt # LLM vulnerability-hypothesis hunt (leads to confirm)
546
+ attackmap analyze <path> --hunt --verify # adjudicate each lead vs. source (confirmed/refuted/review)
547
+ attackmap analyze <path> --remediate # LLM review-first fix suggestions (remediation.md)
548
+ attackmap analyze <path> --pr-comment pr.md # Markdown PR summary comment for CI
506
549
 
507
550
  # CI / PR diff gating
508
551
  attackmap analyze <path> --baseline prev/attackmap-report.json \
@@ -541,7 +584,7 @@ introduces a new HIGH finding.
541
584
 
542
585
  ## Project status
543
586
 
544
- AttackMap is **beta** (v0.3.2) — published and validated on real codebases, but
587
+ AttackMap is **beta** (v0.4.0) — published and validated on real codebases, but
545
588
  pre-1.0 and heuristic.
546
589
 
547
590
  **Solid today:**
@@ -549,34 +592,38 @@ pre-1.0 and heuristic.
549
592
  - Modular analyzer execution with entry-point discovery; 14 official plugins on PyPI.
550
593
  - Framework-aware route extraction (FastAPI/Flask/Express/Spring/axum/chi/…).
551
594
  - Asset + control modeling, cross-cutting insight engine, chain-aware threat model.
552
- - Injection / data-flow detection: SSRF, SSTI, NoSQL, unsafe deserialization,
553
- eval/exec/shell, SQL, dynamic file open, open redirect request-container-gated
554
- for precision.
595
+ - **Multi-language import-graph taint** **Python, JS/TS, Go (module-path
596
+ resolution), and PHP (PSR-4 autoload)** for injection sinks: SSRF, SSTI,
597
+ NoSQL, unsafe deserialization, eval/exec/shell, SQL (parameterized-query-gated),
598
+ dynamic file open, open redirect. Handler-aware seeding connects a route to
599
+ the module that defines its handler (no import-hub fan-out).
555
600
  - Novel vuln-class detectors: prototype pollution, mass assignment, JWT weakness,
556
601
  XXE, ReDoS, insecure upload, GraphQL exposure.
557
- - BOLA/IDOR authorization detection on path-template routes.
558
- - Insecure-crypto / weak-randomness and web-hardening (CORS/CSRF/cookies/CSP/debug)
559
- detection.
560
- - Anomaly / outlier detection (the odd-one-out among sibling routes) and
561
- **exploitability fusion** — a deterministic, explainable 0–100 "exploitable now"
562
- score that ranks route→sink combinations.
563
- - **`--hunt`**: LLM vulnerability-hypothesis mode evidence-cited, human-verifiable
564
- exploit-chain leads (not detections).
602
+ - BOLA/IDOR authorization detection (with custom-middleware / guard-arg awareness).
603
+ - Insecure-crypto / weak-randomness (Python/JS/Go/PHP) and web-hardening
604
+ (CORS/CSRF/cookies/CSP/debug) detection.
605
+ - Anomaly / outlier detection and **exploitability fusion** a deterministic,
606
+ explainable 0–100 "exploitable now" score that ranks route→sink combinations
607
+ and folds in known-vulnerable dependencies on the path.
608
+ - **`--hunt`** (LLM exploit-chain hypotheses) with **`--verify`** (adjudicate
609
+ each lead CONFIRMED/REFUTED/NEEDS-REVIEW against the actual source), and
610
+ **`--remediate`** (review-first fix suggestions).
565
611
  - SBOM inventory (5 ecosystems) + OSV.dev CVE cross-reference (`--cve`).
566
- - Output: Markdown + JSON + **SARIF 2.1.0** (GitHub Code Scanning) + **Mermaid /
567
- Graphviz** diagrams; **diff/baseline** mode for PR gating; optional LLM narrative.
568
- - Live scan progress bar + ETA; test/spec files excluded from heuristic passes.
612
+ - Output: Markdown + JSON + **SARIF 2.1.0** + **Mermaid / Graphviz** diagrams;
613
+ **diff/baseline** PR gating; a reusable **GitHub Action + PR bot**; optional
614
+ LLM narrative.
615
+ - Live scan progress bar + ETA; test/spec/vendored/generated files excluded from
616
+ heuristic passes.
569
617
  - Distribution: `pip install attackmap[all]`, `brew install mlaify/tap/attackmap`,
570
618
  `docker pull ghcr.io/mlaify/attackmap`.
571
619
 
572
620
  **Still maturing:**
573
621
 
574
- - Taint + BOLA are Python + JS/TS and path-template scoped; query-param / RPC-method
622
+ - Taint is Python/JS/TS/Go/PHP and path-template scoped; query-param / RPC-method
575
623
  authorization and more languages are planned.
576
624
  - The import-graph taint walk approximates call-edges with import-edges — precision
577
625
  over recall; findings are evidence, not proof.
578
- - CVE lookup resolves a best-effort concrete version, not full lockfile ranges, and
579
- isn't yet fused into per-path exploitability scores.
626
+ - CVE lookup resolves a best-effort concrete version, not full lockfile ranges.
580
627
  - Anomaly / exploitability reasoning is route-cohort and taint-chain scoped.
581
628
 
582
629
  ---
@@ -16,7 +16,7 @@ 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.3.2).** Core engine and 14 analyzer plugins are published
19
+ > **Status: beta (v0.4.0).** 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
@@ -146,6 +146,46 @@ jobs:
146
146
  category: attackmap
147
147
  ```
148
148
 
149
+ ### PR bot (reusable Action + summary comment)
150
+
151
+ AttackMap ships a composite **GitHub Action** (`action.yml`) that installs it,
152
+ scans, uploads SARIF (for inline annotations on the PR's *Files changed* tab),
153
+ and renders a Markdown **PR summary comment** (new/resolved findings, gate
154
+ status, and the top "most exploitable now"). Wire it up to post the comment on
155
+ each PR:
156
+
157
+ ```yaml
158
+ name: AttackMap PR review
159
+ on: pull_request
160
+ permissions:
161
+ contents: read
162
+ security-events: write # SARIF upload
163
+ pull-requests: write # summary comment
164
+ jobs:
165
+ attackmap:
166
+ runs-on: ubuntu-latest
167
+ steps:
168
+ - uses: actions/checkout@v4
169
+ - id: scan
170
+ uses: mlaify/AttackMap@v1
171
+ with:
172
+ path: .
173
+ fail-on-new-high: "true" # optional gate (needs a baseline)
174
+ - uses: actions/github-script@v7
175
+ if: always()
176
+ with:
177
+ script: |
178
+ const fs = require('fs');
179
+ const body = fs.readFileSync('${{ steps.scan.outputs.pr-comment-file }}', 'utf8');
180
+ await github.rest.issues.createComment({
181
+ owner: context.repo.owner, repo: context.repo.repo,
182
+ issue_number: context.issue.number, body,
183
+ });
184
+ ```
185
+
186
+ The comment renderer is also available standalone: `attackmap analyze .
187
+ --pr-comment pr.md` (add `--baseline prev.json` to include the diff).
188
+
149
189
  ### Diff mode (PR gating)
150
190
 
151
191
  For a lighter CI integration than Code Scanning — a bot comment, a JSON delta,
@@ -299,16 +339,16 @@ same sink two hops away. The score is:
299
339
  is shown. Contributing factors: sink danger (SQLi/RCE/deserialization highest),
300
340
  exposure (public/internal/unknown), auth at the entry route, reachability
301
341
  (fewer hops = higher), and data sensitivity at the sink (a co-located secret or
302
- datastore), plus insecure-crypto / web-hardening gaps on the path as
303
- amplifiers.
342
+ datastore), plus insecure-crypto / web-hardening gaps and **known-vulnerable
343
+ dependencies imported on the path** (from `--cve`, amplified by CVE severity)
344
+ as amplifiers.
304
345
 
305
346
  Scores land on the relevant taint findings (`exploitability` + `exploitability_tier`)
306
347
  and in a ranked **`attackmap-exploitability.md`** report plus the `exploitability`
307
348
  array in `attackmap-report.json`; the console summary leads with the top few. For
308
- example, `public + no-auth + taint-to-eval (0 hops)` scores 90/100 (CRITICAL).
309
- (Dependency CVEs are manifest-level, not attributable to a specific source path,
310
- so they inform the report but aren't folded into a path's score — path-attributed
311
- CVE fusion is future work.)
349
+ example, `public + no-auth + taint-to-eval (0 hops)` scores 90/100 (CRITICAL), and
350
+ a route reaching a sink through a known-vulnerable library version scores higher
351
+ still.
312
352
 
313
353
  ### SBOM inventory
314
354
 
@@ -444,6 +484,9 @@ attackmap analyze <path> --cve # cross-reference SBOM against OSV.dev
444
484
  attackmap analyze <path> --llm # add LLM narrative (auto-resolve auth)
445
485
  attackmap analyze <path> --llm --llm-backend cli # force Claude CLI
446
486
  attackmap analyze <path> --hunt # LLM vulnerability-hypothesis hunt (leads to confirm)
487
+ attackmap analyze <path> --hunt --verify # adjudicate each lead vs. source (confirmed/refuted/review)
488
+ attackmap analyze <path> --remediate # LLM review-first fix suggestions (remediation.md)
489
+ attackmap analyze <path> --pr-comment pr.md # Markdown PR summary comment for CI
447
490
 
448
491
  # CI / PR diff gating
449
492
  attackmap analyze <path> --baseline prev/attackmap-report.json \
@@ -482,7 +525,7 @@ introduces a new HIGH finding.
482
525
 
483
526
  ## Project status
484
527
 
485
- AttackMap is **beta** (v0.3.2) — published and validated on real codebases, but
528
+ AttackMap is **beta** (v0.4.0) — published and validated on real codebases, but
486
529
  pre-1.0 and heuristic.
487
530
 
488
531
  **Solid today:**
@@ -490,34 +533,38 @@ pre-1.0 and heuristic.
490
533
  - Modular analyzer execution with entry-point discovery; 14 official plugins on PyPI.
491
534
  - Framework-aware route extraction (FastAPI/Flask/Express/Spring/axum/chi/…).
492
535
  - Asset + control modeling, cross-cutting insight engine, chain-aware threat model.
493
- - Injection / data-flow detection: SSRF, SSTI, NoSQL, unsafe deserialization,
494
- eval/exec/shell, SQL, dynamic file open, open redirect request-container-gated
495
- for precision.
536
+ - **Multi-language import-graph taint** **Python, JS/TS, Go (module-path
537
+ resolution), and PHP (PSR-4 autoload)** for injection sinks: SSRF, SSTI,
538
+ NoSQL, unsafe deserialization, eval/exec/shell, SQL (parameterized-query-gated),
539
+ dynamic file open, open redirect. Handler-aware seeding connects a route to
540
+ the module that defines its handler (no import-hub fan-out).
496
541
  - Novel vuln-class detectors: prototype pollution, mass assignment, JWT weakness,
497
542
  XXE, ReDoS, insecure upload, GraphQL exposure.
498
- - BOLA/IDOR authorization detection on path-template routes.
499
- - Insecure-crypto / weak-randomness and web-hardening (CORS/CSRF/cookies/CSP/debug)
500
- detection.
501
- - Anomaly / outlier detection (the odd-one-out among sibling routes) and
502
- **exploitability fusion** — a deterministic, explainable 0–100 "exploitable now"
503
- score that ranks route→sink combinations.
504
- - **`--hunt`**: LLM vulnerability-hypothesis mode evidence-cited, human-verifiable
505
- exploit-chain leads (not detections).
543
+ - BOLA/IDOR authorization detection (with custom-middleware / guard-arg awareness).
544
+ - Insecure-crypto / weak-randomness (Python/JS/Go/PHP) and web-hardening
545
+ (CORS/CSRF/cookies/CSP/debug) detection.
546
+ - Anomaly / outlier detection and **exploitability fusion** a deterministic,
547
+ explainable 0–100 "exploitable now" score that ranks route→sink combinations
548
+ and folds in known-vulnerable dependencies on the path.
549
+ - **`--hunt`** (LLM exploit-chain hypotheses) with **`--verify`** (adjudicate
550
+ each lead CONFIRMED/REFUTED/NEEDS-REVIEW against the actual source), and
551
+ **`--remediate`** (review-first fix suggestions).
506
552
  - SBOM inventory (5 ecosystems) + OSV.dev CVE cross-reference (`--cve`).
507
- - Output: Markdown + JSON + **SARIF 2.1.0** (GitHub Code Scanning) + **Mermaid /
508
- Graphviz** diagrams; **diff/baseline** mode for PR gating; optional LLM narrative.
509
- - Live scan progress bar + ETA; test/spec files excluded from heuristic passes.
553
+ - Output: Markdown + JSON + **SARIF 2.1.0** + **Mermaid / Graphviz** diagrams;
554
+ **diff/baseline** PR gating; a reusable **GitHub Action + PR bot**; optional
555
+ LLM narrative.
556
+ - Live scan progress bar + ETA; test/spec/vendored/generated files excluded from
557
+ heuristic passes.
510
558
  - Distribution: `pip install attackmap[all]`, `brew install mlaify/tap/attackmap`,
511
559
  `docker pull ghcr.io/mlaify/attackmap`.
512
560
 
513
561
  **Still maturing:**
514
562
 
515
- - Taint + BOLA are Python + JS/TS and path-template scoped; query-param / RPC-method
563
+ - Taint is Python/JS/TS/Go/PHP and path-template scoped; query-param / RPC-method
516
564
  authorization and more languages are planned.
517
565
  - The import-graph taint walk approximates call-edges with import-edges — precision
518
566
  over recall; findings are evidence, not proof.
519
- - CVE lookup resolves a best-effort concrete version, not full lockfile ranges, and
520
- isn't yet fused into per-path exploitability scores.
567
+ - CVE lookup resolves a best-effort concrete version, not full lockfile ranges.
521
568
  - Anomaly / exploitability reasoning is route-cohort and taint-chain scoped.
522
569
 
523
570
  ---
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "attackmap"
7
- version = "0.3.2"
7
+ version = "0.4.0"
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.3.2"
2
+ __version__ = "0.4.0"
@@ -27,7 +27,7 @@ from .graph import build_graph
27
27
  from .llm_review import LlmReviewError, generate_llm_review
28
28
  from .progress import ScanProgress
29
29
  from .recon_to_analysis import translate_recon
30
- from .report import render_console_summary, write_reports
30
+ from .report import render_console_summary, render_pr_comment, write_reports
31
31
  from .suggest import detect_ecosystems
32
32
 
33
33
  app = typer.Typer(help="AttackMap: understand your system and map your attack surface.")
@@ -42,6 +42,16 @@ HUNT_BANNER = (
42
42
  "> no CVE assignment, no exploitation instructions.\n\n"
43
43
  )
44
44
 
45
+ # Prepended to remediation.md (#106): suggestions are review-first, not
46
+ # auto-applied.
47
+ REMEDIATION_BANNER = (
48
+ "> 🛠️ **Suggested fixes to review, not auto-applied.**\n"
49
+ "> LLM-proposed remediations grounded in AttackMap's static evidence. Each is\n"
50
+ "> the smallest change to remove a weakness class; review the diff, confirm the\n"
51
+ "> intent (e.g. whether a route should require auth), and run the verification\n"
52
+ "> step before applying. No exploit code.\n\n"
53
+ )
54
+
45
55
 
46
56
  @app.command()
47
57
  def analyze(
@@ -79,6 +89,16 @@ def analyze(
79
89
  "--hunt",
80
90
  help="Vulnerability-hypothesis mode (#80): have Claude reason over the full evidence pack as a red-team analyst and propose ranked, human-verifiable exploit-chain HYPOTHESES (leads, not detections) to vulnerability-hypotheses.md. Uses the same LLM auth/backend as --llm.",
81
91
  ),
92
+ verify: bool = typer.Option(
93
+ False,
94
+ "--verify",
95
+ help="With --hunt: adjudicate each hypothesis against the actual source at cited locations — CONFIRMED / REFUTED / NEEDS HUMAN REVIEW (#hunt-verify). AttackMap feeds the code excerpts to the model.",
96
+ ),
97
+ remediate: bool = typer.Option(
98
+ False,
99
+ "--remediate",
100
+ help="Remediation mode (#106): have Claude propose concrete, review-first fixes per finding (suggested diffs / precise instructions, grounded in evidence) to remediation.md. Uses the same LLM auth/backend as --llm.",
101
+ ),
82
102
  baseline: str | None = typer.Option(
83
103
  None,
84
104
  "--baseline",
@@ -104,6 +124,11 @@ def analyze(
104
124
  "--no-progress",
105
125
  help="Disable the live progress bar / ETA during scanning. Progress auto-disables when stderr isn't a terminal (CI, piped output).",
106
126
  ),
127
+ pr_comment: str | None = typer.Option(
128
+ None,
129
+ "--pr-comment",
130
+ help="Write a Markdown PR summary comment to this path (for the GitHub Action / bot to post). Includes the baseline diff and top exploitability when available.",
131
+ ),
107
132
  ) -> None:
108
133
  repo_path = Path(path).resolve()
109
134
  if not repo_path.exists():
@@ -170,6 +195,7 @@ def analyze(
170
195
  typer.echo(f"Reports written to: {Path(output).resolve()}")
171
196
 
172
197
  diff_exit_code = 0
198
+ diff = None
173
199
  if baseline is not None:
174
200
  baseline_path = Path(baseline)
175
201
  try:
@@ -199,6 +225,12 @@ def analyze(
199
225
  typer.echo(f" - {t}", err=True)
200
226
  diff_exit_code = 1
201
227
 
228
+ if pr_comment is not None:
229
+ pr_path = Path(pr_comment)
230
+ pr_path.parent.mkdir(parents=True, exist_ok=True)
231
+ pr_path.write_text(render_pr_comment(findings, diff), encoding="utf-8")
232
+ typer.echo(f"PR comment written to: {pr_path.resolve()}")
233
+
202
234
  if llm:
203
235
  try:
204
236
  effort_value = None
@@ -263,8 +295,10 @@ def analyze(
263
295
  f"Invalid --llm-backend '{llm_backend}'. Use one of: auto, api, cli."
264
296
  )
265
297
  typer.echo("")
298
+ hunt_mode = "hunt_verify" if verify else "hunt"
266
299
  typer.echo(
267
- f"Hunting for vulnerability hypotheses via Claude (backend={llm_backend}, may take a minute)..."
300
+ f"Hunting for vulnerability hypotheses via Claude "
301
+ f"({'adjudicated against source, ' if verify else ''}backend={llm_backend}, may take a minute)..."
268
302
  )
269
303
  hunt_result = generate_llm_review(
270
304
  scan,
@@ -274,7 +308,7 @@ def analyze(
274
308
  model=llm_model,
275
309
  effort=hunt_effort_value, # type: ignore[arg-type]
276
310
  backend=llm_backend, # type: ignore[arg-type]
277
- mode="hunt",
311
+ mode=hunt_mode, # type: ignore[arg-type]
278
312
  )
279
313
  except LlmReviewError as exc:
280
314
  typer.echo(f"Vulnerability hunt skipped: {exc}", err=True)
@@ -301,6 +335,58 @@ def analyze(
301
335
  f"(backend={hunt_result.backend})"
302
336
  )
303
337
 
338
+ if remediate:
339
+ try:
340
+ rem_effort_value = None
341
+ if llm_effort is not None:
342
+ if llm_effort not in {"low", "medium", "high", "xhigh", "max"}:
343
+ raise typer.BadParameter(
344
+ f"Invalid --llm-effort '{llm_effort}'. Use one of: low, medium, high, xhigh, max."
345
+ )
346
+ rem_effort_value = llm_effort # type: ignore[assignment]
347
+ if llm_backend not in {"auto", "api", "cli"}:
348
+ raise typer.BadParameter(
349
+ f"Invalid --llm-backend '{llm_backend}'. Use one of: auto, api, cli."
350
+ )
351
+ typer.echo("")
352
+ typer.echo(
353
+ f"Generating remediation suggestions via Claude (backend={llm_backend}, may take a minute)..."
354
+ )
355
+ rem_result = generate_llm_review(
356
+ scan,
357
+ attack_surfaces,
358
+ findings,
359
+ attack_paths,
360
+ model=llm_model,
361
+ effort=rem_effort_value, # type: ignore[arg-type]
362
+ backend=llm_backend, # type: ignore[arg-type]
363
+ mode="remediate",
364
+ )
365
+ except LlmReviewError as exc:
366
+ typer.echo(f"Remediation skipped: {exc}", err=True)
367
+ else:
368
+ output_path = Path(output)
369
+ rem_md_path = output_path / "remediation.md"
370
+ rem_md_path.write_text(REMEDIATION_BANNER + rem_result.markdown + "\n", encoding="utf-8")
371
+ rem_meta_path = output_path / "remediation.meta.json"
372
+ rem_meta_path.write_text(
373
+ json.dumps(
374
+ {
375
+ "backend": rem_result.backend,
376
+ "model": rem_result.model,
377
+ "stop_reason": rem_result.stop_reason,
378
+ "usage": rem_result.usage,
379
+ },
380
+ indent=2,
381
+ )
382
+ + "\n",
383
+ encoding="utf-8",
384
+ )
385
+ typer.echo(
386
+ f"Remediation suggestions written to: {rem_md_path.resolve()} "
387
+ f"(backend={rem_result.backend})"
388
+ )
389
+
304
390
  if diff_exit_code:
305
391
  raise typer.Exit(code=diff_exit_code)
306
392
 
@@ -145,6 +145,57 @@ _CRYPTO_PATTERNS: tuple[tuple[str, str, re.Pattern[str]], ...] = (
145
145
  # deprecated protocols
146
146
  _rx(r"PROTOCOL_TLSv1(?:_1)?\b|PROTOCOL_SSLv[23]\b|SSLv3|\bTLSv1\.0\b"),
147
147
  ),
148
+ # --- Go-specific (#102 follow-up) ------------------------------------
149
+ (
150
+ # Go weak hash: md5.New()/md5.Sum(...) / sha1.* over a security value.
151
+ "weak_password_hash",
152
+ "high",
153
+ _rx(rf"\b(?:md5|sha1)\.(?:New|Sum)\s*\([^;\n]*{_SECURITY_CTX}"),
154
+ ),
155
+ (
156
+ "weak_password_hash",
157
+ "high",
158
+ _rx(rf"{_SECURITY_CTX}[^;\n]*\b(?:md5|sha1)\.(?:New|Sum)\s*\("),
159
+ ),
160
+ (
161
+ # Go weak cipher: des.NewCipher / rc4.NewCipher / crypto/des|rc4 import.
162
+ # `.New…(` / `crypto/` context (not prose) makes lowercase safe.
163
+ "weak_cipher",
164
+ "high",
165
+ re.compile(r"\b(?:des|rc4)\.New(?:Cipher|TripleDESCipher)?\s*\(|\bcrypto/(?:des|rc4)\b"),
166
+ ),
167
+ (
168
+ # Go insecure RNG for a security value — math/rand-exclusive funcs only
169
+ # (rand.Int/Read are ambiguous with the secure crypto/rand, so excluded).
170
+ "insecure_random",
171
+ "medium",
172
+ _rx(rf"{_SECURITY_CTX}[^;\n]*\brand\.(?:Intn|Int31n?|Int63n?|Float64|Float32|Perm|Shuffle)\s*\("),
173
+ ),
174
+ (
175
+ "insecure_random",
176
+ "medium",
177
+ _rx(rf"\brand\.(?:Intn|Int31n?|Int63n?|Float64|Float32|Perm|Shuffle)\s*\([^;\n]*{_SECURITY_CTX}"),
178
+ ),
179
+ # --- PHP-specific (#103 follow-up) -----------------------------------
180
+ (
181
+ # PHP mcrypt weak ciphers (mcrypt is removed/insecure) + DES/RC4/Blowfish
182
+ # constants (which the \bDES\b token misses after the `MCRYPT_` prefix).
183
+ "weak_cipher",
184
+ "high",
185
+ re.compile(
186
+ r"\bMCRYPT_(?:DES|3DES|TRIPLEDES|RC4|RC2|BLOWFISH)\b"
187
+ r"|\bmcrypt_(?:encrypt|decrypt|module_open)\s*\("
188
+ ),
189
+ ),
190
+ (
191
+ # PHP disabled TLS verification (cURL / stream context).
192
+ "insecure_tls",
193
+ "high",
194
+ _rx(
195
+ r"CURLOPT_SSL_VERIFY(?:PEER|HOST)\s*(?:,|=>)\s*(?:false|0)\b"
196
+ r"|['\"]verify(?:_peer(?:_name)?)?['\"]\s*=>\s*false"
197
+ ),
198
+ ),
148
199
  )
149
200
 
150
201