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.
- {attackmap-0.3.2/src/attackmap.egg-info → attackmap-0.4.1}/PKG-INFO +73 -26
- {attackmap-0.3.2 → attackmap-0.4.1}/README.md +72 -25
- {attackmap-0.3.2 → attackmap-0.4.1}/pyproject.toml +1 -1
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/__init__.py +1 -1
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/cli.py +98 -5
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/crypto.py +51 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/exploitability.py +113 -4
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/llm_review.py +12 -3
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/progress.py +99 -1
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/report.py +50 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/review_prompts.py +148 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/scanner.py +112 -2
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/srcpaths.py +5 -2
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/taint.py +341 -21
- {attackmap-0.3.2 → attackmap-0.4.1/src/attackmap.egg-info}/PKG-INFO +73 -26
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap.egg-info/SOURCES.txt +2 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_crypto.py +29 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_exploitability.py +75 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_hunt.py +36 -0
- attackmap-0.4.1/tests/test_pr_comment.py +49 -0
- attackmap-0.4.1/tests/test_progress.py +183 -0
- attackmap-0.4.1/tests/test_remediate.py +65 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_scanner.py +120 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_srcpaths.py +17 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_taint.py +255 -0
- attackmap-0.3.2/tests/test_progress.py +0 -97
- {attackmap-0.3.2 → attackmap-0.4.1}/LICENSE +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/setup.cfg +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/analyzer.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/analyzer_contracts.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/analyzers.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/anomalies.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/asset_model.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/attack_taxonomy.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/authz.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/config_scanner.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/context_pack.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/control_model.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/cve.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/defensive_review.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/detection_opportunities.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/diagrams.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/diff.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/graph.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/insights.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/merge.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/models.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/recon_models.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/recon_to_analysis.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/review_eval.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/review_json.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/sarif.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/sbom.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/sdk/__init__.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/sdk/contracts.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/sdk/models.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/security_overlay.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/suggest.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/threat_model.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/topology.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/weaknesses.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap/webhardening.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap.egg-info/dependency_links.txt +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap.egg-info/entry_points.txt +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap.egg-info/requires.txt +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/src/attackmap.egg-info/top_level.txt +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_analyzer.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_analyzer_metadata_schema.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_analyzers.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_anomalies.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_artifact_schemas.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_authz.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_config_scanner.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_context_pack.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_cve.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_defensive_review.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_diagrams.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_diff.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_graph.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_injection_sinks.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_llm_review.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_merge.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_provenance.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_recon_to_analysis.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_report.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_review_eval.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_review_json.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_review_prompts.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_sarif.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_sbom.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_security_overlay.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_shared_contract_imports.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_signal_v2.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_suggest.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_threat_intel.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_threat_model.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_topology.py +0 -0
- {attackmap-0.3.2 → attackmap-0.4.1}/tests/test_weaknesses.py +0 -0
- {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
|
+
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
|
[](https://www.python.org/)
|
|
76
76
|
[](https://pypi.org/project/attackmap/)
|
|
77
77
|
|
|
78
|
-
> **Status: beta (v0.
|
|
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
|
|
362
|
-
|
|
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
|
-
|
|
369
|
-
|
|
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.
|
|
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
|
-
-
|
|
553
|
-
|
|
554
|
-
|
|
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
|
|
558
|
-
- Insecure-crypto / weak-randomness and web-hardening
|
|
559
|
-
detection.
|
|
560
|
-
- Anomaly / outlier detection
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
- **`--hunt
|
|
564
|
-
|
|
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**
|
|
567
|
-
|
|
568
|
-
|
|
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
|
|
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
|
|
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
|
[](https://www.python.org/)
|
|
17
17
|
[](https://pypi.org/project/attackmap/)
|
|
18
18
|
|
|
19
|
-
> **Status: beta (v0.
|
|
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
|
|
303
|
-
|
|
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
|
-
|
|
310
|
-
|
|
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.
|
|
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
|
-
-
|
|
494
|
-
|
|
495
|
-
|
|
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
|
|
499
|
-
- Insecure-crypto / weak-randomness and web-hardening
|
|
500
|
-
detection.
|
|
501
|
-
- Anomaly / outlier detection
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
- **`--hunt
|
|
505
|
-
|
|
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**
|
|
508
|
-
|
|
509
|
-
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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 =
|
|
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
|
|
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=
|
|
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
|
|