attackmap 0.3.2__tar.gz → 0.4.1__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 (99) hide show
  1. {attackmap-0.3.2/src/attackmap.egg-info → attackmap-0.4.1}/PKG-INFO +73 -26
  2. {attackmap-0.3.2 → attackmap-0.4.1}/README.md +72 -25
  3. {attackmap-0.3.2 → attackmap-0.4.1}/pyproject.toml +1 -1
  4. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/__init__.py +1 -1
  5. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/cli.py +98 -5
  6. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/crypto.py +51 -0
  7. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/exploitability.py +113 -4
  8. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/llm_review.py +12 -3
  9. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/progress.py +99 -1
  10. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/report.py +50 -0
  11. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/review_prompts.py +148 -0
  12. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/scanner.py +112 -2
  13. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/srcpaths.py +5 -2
  14. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/taint.py +341 -21
  15. {attackmap-0.3.2 → attackmap-0.4.1/src/attackmap.egg-info}/PKG-INFO +73 -26
  16. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap.egg-info/SOURCES.txt +2 -0
  17. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_crypto.py +29 -0
  18. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_exploitability.py +75 -0
  19. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_hunt.py +36 -0
  20. attackmap-0.4.1/tests/test_pr_comment.py +49 -0
  21. attackmap-0.4.1/tests/test_progress.py +183 -0
  22. attackmap-0.4.1/tests/test_remediate.py +65 -0
  23. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_scanner.py +120 -0
  24. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_srcpaths.py +17 -0
  25. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_taint.py +255 -0
  26. attackmap-0.3.2/tests/test_progress.py +0 -97
  27. {attackmap-0.3.2 → attackmap-0.4.1}/LICENSE +0 -0
  28. {attackmap-0.3.2 → attackmap-0.4.1}/setup.cfg +0 -0
  29. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/analyzer.py +0 -0
  30. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/analyzer_contracts.py +0 -0
  31. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/analyzers.py +0 -0
  32. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/anomalies.py +0 -0
  33. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/asset_model.py +0 -0
  34. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/attack_taxonomy.py +0 -0
  35. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/authz.py +0 -0
  36. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/config_scanner.py +0 -0
  37. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/context_pack.py +0 -0
  38. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/control_model.py +0 -0
  39. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/cve.py +0 -0
  40. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/defensive_review.py +0 -0
  41. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/detection_opportunities.py +0 -0
  42. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/diagrams.py +0 -0
  43. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/diff.py +0 -0
  44. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/graph.py +0 -0
  45. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/insights.py +0 -0
  46. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/merge.py +0 -0
  47. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/models.py +0 -0
  48. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/recon_models.py +0 -0
  49. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/recon_to_analysis.py +0 -0
  50. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/review_eval.py +0 -0
  51. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/review_json.py +0 -0
  52. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/sarif.py +0 -0
  53. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/sbom.py +0 -0
  54. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/sdk/__init__.py +0 -0
  55. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/sdk/contracts.py +0 -0
  56. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/sdk/models.py +0 -0
  57. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/security_overlay.py +0 -0
  58. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/suggest.py +0 -0
  59. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/threat_model.py +0 -0
  60. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/topology.py +0 -0
  61. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/weaknesses.py +0 -0
  62. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/webhardening.py +0 -0
  63. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap.egg-info/dependency_links.txt +0 -0
  64. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap.egg-info/entry_points.txt +0 -0
  65. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap.egg-info/requires.txt +0 -0
  66. {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap.egg-info/top_level.txt +0 -0
  67. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_analyzer.py +0 -0
  68. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_analyzer_metadata_schema.py +0 -0
  69. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_analyzers.py +0 -0
  70. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_anomalies.py +0 -0
  71. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_artifact_schemas.py +0 -0
  72. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_authz.py +0 -0
  73. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_config_scanner.py +0 -0
  74. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_context_pack.py +0 -0
  75. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_cve.py +0 -0
  76. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_defensive_review.py +0 -0
  77. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_diagrams.py +0 -0
  78. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_diff.py +0 -0
  79. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_graph.py +0 -0
  80. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_injection_sinks.py +0 -0
  81. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_llm_review.py +0 -0
  82. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_merge.py +0 -0
  83. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_provenance.py +0 -0
  84. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_recon_to_analysis.py +0 -0
  85. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_report.py +0 -0
  86. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_review_eval.py +0 -0
  87. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_review_json.py +0 -0
  88. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_review_prompts.py +0 -0
  89. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_sarif.py +0 -0
  90. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_sbom.py +0 -0
  91. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_security_overlay.py +0 -0
  92. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_shared_contract_imports.py +0 -0
  93. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_signal_v2.py +0 -0
  94. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_suggest.py +0 -0
  95. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_threat_intel.py +0 -0
  96. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_threat_model.py +0 -0
  97. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_topology.py +0 -0
  98. {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_weaknesses.py +0 -0
  99. {attackmap-0.3.2 → attackmap-0.4.1}/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.1
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.1"
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.1"
@@ -25,9 +25,9 @@ from .diff import (
25
25
  )
26
26
  from .graph import build_graph
27
27
  from .llm_review import LlmReviewError, generate_llm_review
28
- from .progress import ScanProgress
28
+ from .progress import create_progress
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,16 @@ 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
+ progress_format: str = typer.Option(
128
+ "auto",
129
+ "--progress-format",
130
+ help="Progress reporting: 'auto' (TTY bar when stderr is a terminal), 'json' (newline-delimited JSON events on stderr, for GUI/tool front-ends), or 'none'. --no-progress is equivalent to 'none'.",
131
+ ),
132
+ pr_comment: str | None = typer.Option(
133
+ None,
134
+ "--pr-comment",
135
+ 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.",
136
+ ),
107
137
  ) -> None:
108
138
  repo_path = Path(path).resolve()
109
139
  if not repo_path.exists():
@@ -123,8 +153,10 @@ def analyze(
123
153
  except ValueError as exc:
124
154
  raise typer.BadParameter(str(exc)) from exc
125
155
 
156
+ if progress_format not in {"auto", "tty", "json", "none"}:
157
+ raise typer.BadParameter("--progress-format must be one of: auto, json, none.")
126
158
  active_analyzers = resolve_run_analyzers(repo_path, analyzers=selected_analyzers)
127
- scan_progress = ScanProgress(enabled=not no_progress)
159
+ scan_progress = create_progress(progress_format, no_progress=no_progress)
128
160
  scan = analyze_repository(repo_path, analyzers=active_analyzers, progress=scan_progress)
129
161
  if cve and scan.dependencies:
130
162
  typer.echo(f"Checking {len(scan.dependencies)} dependencies against OSV.dev…")
@@ -170,6 +202,7 @@ def analyze(
170
202
  typer.echo(f"Reports written to: {Path(output).resolve()}")
171
203
 
172
204
  diff_exit_code = 0
205
+ diff = None
173
206
  if baseline is not None:
174
207
  baseline_path = Path(baseline)
175
208
  try:
@@ -199,6 +232,12 @@ def analyze(
199
232
  typer.echo(f" - {t}", err=True)
200
233
  diff_exit_code = 1
201
234
 
235
+ if pr_comment is not None:
236
+ pr_path = Path(pr_comment)
237
+ pr_path.parent.mkdir(parents=True, exist_ok=True)
238
+ pr_path.write_text(render_pr_comment(findings, diff), encoding="utf-8")
239
+ typer.echo(f"PR comment written to: {pr_path.resolve()}")
240
+
202
241
  if llm:
203
242
  try:
204
243
  effort_value = None
@@ -263,8 +302,10 @@ def analyze(
263
302
  f"Invalid --llm-backend '{llm_backend}'. Use one of: auto, api, cli."
264
303
  )
265
304
  typer.echo("")
305
+ hunt_mode = "hunt_verify" if verify else "hunt"
266
306
  typer.echo(
267
- f"Hunting for vulnerability hypotheses via Claude (backend={llm_backend}, may take a minute)..."
307
+ f"Hunting for vulnerability hypotheses via Claude "
308
+ f"({'adjudicated against source, ' if verify else ''}backend={llm_backend}, may take a minute)..."
268
309
  )
269
310
  hunt_result = generate_llm_review(
270
311
  scan,
@@ -274,7 +315,7 @@ def analyze(
274
315
  model=llm_model,
275
316
  effort=hunt_effort_value, # type: ignore[arg-type]
276
317
  backend=llm_backend, # type: ignore[arg-type]
277
- mode="hunt",
318
+ mode=hunt_mode, # type: ignore[arg-type]
278
319
  )
279
320
  except LlmReviewError as exc:
280
321
  typer.echo(f"Vulnerability hunt skipped: {exc}", err=True)
@@ -301,6 +342,58 @@ def analyze(
301
342
  f"(backend={hunt_result.backend})"
302
343
  )
303
344
 
345
+ if remediate:
346
+ try:
347
+ rem_effort_value = None
348
+ if llm_effort is not None:
349
+ if llm_effort not in {"low", "medium", "high", "xhigh", "max"}:
350
+ raise typer.BadParameter(
351
+ f"Invalid --llm-effort '{llm_effort}'. Use one of: low, medium, high, xhigh, max."
352
+ )
353
+ rem_effort_value = llm_effort # type: ignore[assignment]
354
+ if llm_backend not in {"auto", "api", "cli"}:
355
+ raise typer.BadParameter(
356
+ f"Invalid --llm-backend '{llm_backend}'. Use one of: auto, api, cli."
357
+ )
358
+ typer.echo("")
359
+ typer.echo(
360
+ f"Generating remediation suggestions via Claude (backend={llm_backend}, may take a minute)..."
361
+ )
362
+ rem_result = generate_llm_review(
363
+ scan,
364
+ attack_surfaces,
365
+ findings,
366
+ attack_paths,
367
+ model=llm_model,
368
+ effort=rem_effort_value, # type: ignore[arg-type]
369
+ backend=llm_backend, # type: ignore[arg-type]
370
+ mode="remediate",
371
+ )
372
+ except LlmReviewError as exc:
373
+ typer.echo(f"Remediation skipped: {exc}", err=True)
374
+ else:
375
+ output_path = Path(output)
376
+ rem_md_path = output_path / "remediation.md"
377
+ rem_md_path.write_text(REMEDIATION_BANNER + rem_result.markdown + "\n", encoding="utf-8")
378
+ rem_meta_path = output_path / "remediation.meta.json"
379
+ rem_meta_path.write_text(
380
+ json.dumps(
381
+ {
382
+ "backend": rem_result.backend,
383
+ "model": rem_result.model,
384
+ "stop_reason": rem_result.stop_reason,
385
+ "usage": rem_result.usage,
386
+ },
387
+ indent=2,
388
+ )
389
+ + "\n",
390
+ encoding="utf-8",
391
+ )
392
+ typer.echo(
393
+ f"Remediation suggestions written to: {rem_md_path.resolve()} "
394
+ f"(backend={rem_result.backend})"
395
+ )
396
+
304
397
  if diff_exit_code:
305
398
  raise typer.Exit(code=diff_exit_code)
306
399
 
@@ -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