attackmap 0.4.25__tar.gz → 0.4.26__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of attackmap might be problematic. Click here for more details.

Files changed (119) hide show
  1. {attackmap-0.4.25/src/attackmap.egg-info → attackmap-0.4.26}/PKG-INFO +27 -9
  2. {attackmap-0.4.25 → attackmap-0.4.26}/README.md +26 -8
  3. {attackmap-0.4.25 → attackmap-0.4.26}/pyproject.toml +1 -1
  4. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/lockfiles.py +21 -4
  5. {attackmap-0.4.25 → attackmap-0.4.26/src/attackmap.egg-info}/PKG-INFO +27 -9
  6. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_lockfiles.py +54 -0
  7. {attackmap-0.4.25 → attackmap-0.4.26}/LICENSE +0 -0
  8. {attackmap-0.4.25 → attackmap-0.4.26}/setup.cfg +0 -0
  9. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/__init__.py +0 -0
  10. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/analyzer.py +0 -0
  11. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/analyzer_contracts.py +0 -0
  12. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/analyzers.py +0 -0
  13. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/anomalies.py +0 -0
  14. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/asset_model.py +0 -0
  15. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/attack_taxonomy.py +0 -0
  16. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/authz.py +0 -0
  17. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/cli.py +0 -0
  18. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/config_scanner.py +0 -0
  19. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/context_pack.py +0 -0
  20. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/contracts.py +0 -0
  21. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/control_model.py +0 -0
  22. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/crossrepo.py +0 -0
  23. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/crypto.py +0 -0
  24. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/cve.py +0 -0
  25. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/defensive_review.py +0 -0
  26. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/detection_opportunities.py +0 -0
  27. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/diagrams.py +0 -0
  28. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/diff.py +0 -0
  29. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/exploitability.py +0 -0
  30. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/fleet.py +0 -0
  31. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/graph.py +0 -0
  32. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/hunt_harness.py +0 -0
  33. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/insights.py +0 -0
  34. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/llm_review.py +0 -0
  35. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/merge.py +0 -0
  36. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/models.py +0 -0
  37. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/progress.py +0 -0
  38. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/recon_models.py +0 -0
  39. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/recon_to_analysis.py +0 -0
  40. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/report.py +0 -0
  41. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/review_eval.py +0 -0
  42. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/review_json.py +0 -0
  43. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/review_prompts.py +0 -0
  44. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/route_auth_fusion.py +0 -0
  45. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/sarif.py +0 -0
  46. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/sbom.py +0 -0
  47. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/scanner.py +0 -0
  48. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/sdk/__init__.py +0 -0
  49. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/sdk/contracts.py +0 -0
  50. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/sdk/models.py +0 -0
  51. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/security_overlay.py +0 -0
  52. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/srcpaths.py +0 -0
  53. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/suggest.py +0 -0
  54. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/suppress.py +0 -0
  55. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/taint.py +0 -0
  56. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/threat_model.py +0 -0
  57. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/topology.py +0 -0
  58. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/triage.py +0 -0
  59. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/weaknesses.py +0 -0
  60. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/webhardening.py +0 -0
  61. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap/workflow_scanner.py +0 -0
  62. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap.egg-info/SOURCES.txt +0 -0
  63. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap.egg-info/dependency_links.txt +0 -0
  64. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap.egg-info/entry_points.txt +0 -0
  65. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap.egg-info/requires.txt +0 -0
  66. {attackmap-0.4.25 → attackmap-0.4.26}/src/attackmap.egg-info/top_level.txt +0 -0
  67. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_analyzer.py +0 -0
  68. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_analyzer_metadata_schema.py +0 -0
  69. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_analyzers.py +0 -0
  70. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_anomalies.py +0 -0
  71. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_artifact_schemas.py +0 -0
  72. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_authz.py +0 -0
  73. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_config_scanner.py +0 -0
  74. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_context_pack.py +0 -0
  75. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_contracts.py +0 -0
  76. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_crossrepo.py +0 -0
  77. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_crypto.py +0 -0
  78. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_cve.py +0 -0
  79. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_defensive_review.py +0 -0
  80. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_diagrams.py +0 -0
  81. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_diff.py +0 -0
  82. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_exploitability.py +0 -0
  83. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_fleet.py +0 -0
  84. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_graph.py +0 -0
  85. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_hunt.py +0 -0
  86. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_hunt_harness.py +0 -0
  87. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_injection_sinks.py +0 -0
  88. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_llm_review.py +0 -0
  89. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_merge.py +0 -0
  90. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_modules_cli.py +0 -0
  91. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_pr_comment.py +0 -0
  92. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_progress.py +0 -0
  93. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_provenance.py +0 -0
  94. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_recall.py +0 -0
  95. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_recon_to_analysis.py +0 -0
  96. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_remediate.py +0 -0
  97. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_report.py +0 -0
  98. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_review_eval.py +0 -0
  99. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_review_json.py +0 -0
  100. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_review_prompts.py +0 -0
  101. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_route_auth_fusion.py +0 -0
  102. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_sarif.py +0 -0
  103. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_sbom.py +0 -0
  104. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_scanner.py +0 -0
  105. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_security_overlay.py +0 -0
  106. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_shared_contract_imports.py +0 -0
  107. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_signal_v2.py +0 -0
  108. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_srcpaths.py +0 -0
  109. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_suggest.py +0 -0
  110. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_suppress.py +0 -0
  111. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_taint.py +0 -0
  112. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_taint_sanitizer.py +0 -0
  113. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_threat_intel.py +0 -0
  114. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_threat_model.py +0 -0
  115. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_topology.py +0 -0
  116. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_triage.py +0 -0
  117. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_weaknesses.py +0 -0
  118. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_webhardening.py +0 -0
  119. {attackmap-0.4.25 → attackmap-0.4.26}/tests/test_workflow_scanner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: attackmap
3
- Version: 0.4.25
3
+ Version: 0.4.26
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>
@@ -790,10 +790,12 @@ scaffolding, testing, and publishing instructions is in
790
790
 
791
791
  ```bash
792
792
  attackmap analyze <path> # run a review on a repository
793
+ attackmap analyze repoA repoB gateway # multi-repo fleet scan (per-repo reports + fleet summary)
793
794
  attackmap analyze <path> --output dir # write outputs to `dir/`
794
795
  attackmap analyze <path> --format json # json | markdown | all (default)
795
796
  attackmap analyze <path> --module python --module rust # only these analyzers
796
797
  attackmap analyze <path> --cve # cross-reference SBOM against OSV.dev
798
+ attackmap analyze <path> --recall # widen taint discovery (deeper hops, capability-reach); extra reach marked SPECULATIVE
797
799
  attackmap analyze <path> --llm # add LLM narrative (auto-resolve auth)
798
800
  attackmap analyze <path> --llm --llm-backend cli # force Claude CLI
799
801
  attackmap analyze <path> --llm --llm-provider openai # use OpenAI/Codex (OPENAI_API_KEY or `codex` CLI)
@@ -803,6 +805,8 @@ attackmap analyze <path> --hunt --verify # adjudicate each lead vs. source (conf
803
805
  attackmap analyze <path> --hunt --verify --verify-votes 3 # majority vote of N independent skeptics (default 3; 1 = single pass)
804
806
  attackmap analyze <path> --hunt --verify --hunt-lenses 4 # N lens-specialised generation passes, deduped, then verified
805
807
  attackmap analyze <path> --hunt --verify --hunt-rounds 4 # loop-until-dry generation (completeness critic seeds each round)
808
+ attackmap analyze <path> --hunt --verify --hunt-budget 500000 # cap total hunt output tokens across rounds
809
+ attackmap analyze <path> --recall --hunt --verify # widen the net, then adjudicate the speculative reach
806
810
  attackmap analyze <path> --remediate # LLM review-first fix suggestions (remediation.md)
807
811
  attackmap analyze <path> --triage # cluster/dedupe/rank existing findings (triage.md; deterministic fallback)
808
812
  attackmap analyze <path> --pr-comment pr.md # Markdown PR summary comment for CI
@@ -849,7 +853,7 @@ introduces a new HIGH finding.
849
853
 
850
854
  ## Project status
851
855
 
852
- AttackMap is **beta** (v0.4.18) — published and validated on real codebases, but
856
+ AttackMap is **beta** (v0.4.25) — published and validated on real codebases, but
853
857
  pre-1.0 and heuristic.
854
858
 
855
859
  **Solid today:**
@@ -867,13 +871,27 @@ pre-1.0 and heuristic.
867
871
  - BOLA/IDOR authorization detection (with custom-middleware / guard-arg awareness).
868
872
  - Insecure-crypto / weak-randomness (Python/JS/Go/PHP) and web-hardening
869
873
  (CORS/CSRF/cookies/CSP/debug) detection.
870
- - Anomaly / outlier detection and **exploitability fusion** — a deterministic,
871
- explainable 0–100 "exploitable now" score that ranks route→sink combinations
872
- and folds in known-vulnerable dependencies on the path.
873
- - **`--hunt`** (LLM exploit-chain hypotheses) with **`--verify`** (adjudicate
874
- each lead CONFIRMED/REFUTED/NEEDS-REVIEW against the actual source),
875
- **`--remediate`** (review-first fix suggestions), and **`--triage`**
876
- (cluster/dedupe/rank existing findings, with a deterministic fallback).
874
+ - Anomaly / outlier detection — including **signature-free invariant mining**
875
+ (flag the handler that reaches a dangerous sink without the guard its peers
876
+ apply) — and **exploitability fusion**, a deterministic, explainable 0–100
877
+ "exploitable now" score that ranks route→sink combinations and folds in
878
+ known-vulnerable dependencies on the path.
879
+ - **`--hunt`** (LLM exploit-chain hypotheses) with a **verify jury** —
880
+ **`--verify`** adjudicates each lead CONFIRMED/REFUTED/NEEDS-REVIEW against the
881
+ actual source; **`--verify-votes`** runs N independent skeptics, **`--hunt-lenses`**
882
+ fans generation across failure-mode specialists, **`--hunt-rounds`** loops
883
+ until dry with a completeness critic, **`--hunt-budget`** caps token spend —
884
+ plus **`--remediate`** (review-first fixes) and **`--triage`**
885
+ (cluster/dedupe/rank existing findings, deterministic fallback).
886
+ - **`--recall`** — verifier-gated aggressive discovery: widens the taint walk
887
+ (deeper hops, capability-reach enumeration of every reach to exec/fs/net/
888
+ deserialize/template/SQL) and marks the extra reach SPECULATIVE, kept out of
889
+ `--fail-on-new-high` and left for `--hunt --verify` to adjudicate.
890
+ - **Cross-repo / fleet analysis** — `attackmap analyze repoA repoB …` scans a
891
+ fleet, links one repo's outbound HTTP calls to another's routes, and surfaces
892
+ bugs that live in the seams: cross-boundary (confused-deputy) flows,
893
+ trust-assumption gaps, and the sibling service that omits a control its peers
894
+ enforce. All cross-repo findings are speculative until verified.
877
895
  - SBOM inventory (5 ecosystems) + OSV.dev CVE cross-reference (`--cve`).
878
896
  - Output: Markdown + JSON + **SARIF 2.1.0** + **Mermaid / Graphviz** diagrams;
879
897
  **diff/baseline** PR gating; a reusable **GitHub Action + PR bot**; optional
@@ -729,10 +729,12 @@ scaffolding, testing, and publishing instructions is in
729
729
 
730
730
  ```bash
731
731
  attackmap analyze <path> # run a review on a repository
732
+ attackmap analyze repoA repoB gateway # multi-repo fleet scan (per-repo reports + fleet summary)
732
733
  attackmap analyze <path> --output dir # write outputs to `dir/`
733
734
  attackmap analyze <path> --format json # json | markdown | all (default)
734
735
  attackmap analyze <path> --module python --module rust # only these analyzers
735
736
  attackmap analyze <path> --cve # cross-reference SBOM against OSV.dev
737
+ attackmap analyze <path> --recall # widen taint discovery (deeper hops, capability-reach); extra reach marked SPECULATIVE
736
738
  attackmap analyze <path> --llm # add LLM narrative (auto-resolve auth)
737
739
  attackmap analyze <path> --llm --llm-backend cli # force Claude CLI
738
740
  attackmap analyze <path> --llm --llm-provider openai # use OpenAI/Codex (OPENAI_API_KEY or `codex` CLI)
@@ -742,6 +744,8 @@ attackmap analyze <path> --hunt --verify # adjudicate each lead vs. source (conf
742
744
  attackmap analyze <path> --hunt --verify --verify-votes 3 # majority vote of N independent skeptics (default 3; 1 = single pass)
743
745
  attackmap analyze <path> --hunt --verify --hunt-lenses 4 # N lens-specialised generation passes, deduped, then verified
744
746
  attackmap analyze <path> --hunt --verify --hunt-rounds 4 # loop-until-dry generation (completeness critic seeds each round)
747
+ attackmap analyze <path> --hunt --verify --hunt-budget 500000 # cap total hunt output tokens across rounds
748
+ attackmap analyze <path> --recall --hunt --verify # widen the net, then adjudicate the speculative reach
745
749
  attackmap analyze <path> --remediate # LLM review-first fix suggestions (remediation.md)
746
750
  attackmap analyze <path> --triage # cluster/dedupe/rank existing findings (triage.md; deterministic fallback)
747
751
  attackmap analyze <path> --pr-comment pr.md # Markdown PR summary comment for CI
@@ -788,7 +792,7 @@ introduces a new HIGH finding.
788
792
 
789
793
  ## Project status
790
794
 
791
- AttackMap is **beta** (v0.4.18) — published and validated on real codebases, but
795
+ AttackMap is **beta** (v0.4.25) — published and validated on real codebases, but
792
796
  pre-1.0 and heuristic.
793
797
 
794
798
  **Solid today:**
@@ -806,13 +810,27 @@ pre-1.0 and heuristic.
806
810
  - BOLA/IDOR authorization detection (with custom-middleware / guard-arg awareness).
807
811
  - Insecure-crypto / weak-randomness (Python/JS/Go/PHP) and web-hardening
808
812
  (CORS/CSRF/cookies/CSP/debug) detection.
809
- - Anomaly / outlier detection and **exploitability fusion** — a deterministic,
810
- explainable 0–100 "exploitable now" score that ranks route→sink combinations
811
- and folds in known-vulnerable dependencies on the path.
812
- - **`--hunt`** (LLM exploit-chain hypotheses) with **`--verify`** (adjudicate
813
- each lead CONFIRMED/REFUTED/NEEDS-REVIEW against the actual source),
814
- **`--remediate`** (review-first fix suggestions), and **`--triage`**
815
- (cluster/dedupe/rank existing findings, with a deterministic fallback).
813
+ - Anomaly / outlier detection — including **signature-free invariant mining**
814
+ (flag the handler that reaches a dangerous sink without the guard its peers
815
+ apply) — and **exploitability fusion**, a deterministic, explainable 0–100
816
+ "exploitable now" score that ranks route→sink combinations and folds in
817
+ known-vulnerable dependencies on the path.
818
+ - **`--hunt`** (LLM exploit-chain hypotheses) with a **verify jury** —
819
+ **`--verify`** adjudicates each lead CONFIRMED/REFUTED/NEEDS-REVIEW against the
820
+ actual source; **`--verify-votes`** runs N independent skeptics, **`--hunt-lenses`**
821
+ fans generation across failure-mode specialists, **`--hunt-rounds`** loops
822
+ until dry with a completeness critic, **`--hunt-budget`** caps token spend —
823
+ plus **`--remediate`** (review-first fixes) and **`--triage`**
824
+ (cluster/dedupe/rank existing findings, deterministic fallback).
825
+ - **`--recall`** — verifier-gated aggressive discovery: widens the taint walk
826
+ (deeper hops, capability-reach enumeration of every reach to exec/fs/net/
827
+ deserialize/template/SQL) and marks the extra reach SPECULATIVE, kept out of
828
+ `--fail-on-new-high` and left for `--hunt --verify` to adjudicate.
829
+ - **Cross-repo / fleet analysis** — `attackmap analyze repoA repoB …` scans a
830
+ fleet, links one repo's outbound HTTP calls to another's routes, and surfaces
831
+ bugs that live in the seams: cross-boundary (confused-deputy) flows,
832
+ trust-assumption gaps, and the sibling service that omits a control its peers
833
+ enforce. All cross-repo findings are speculative until verified.
816
834
  - SBOM inventory (5 ecosystems) + OSV.dev CVE cross-reference (`--cve`).
817
835
  - Output: Markdown + JSON + **SARIF 2.1.0** + **Mermaid / Graphviz** diagrams;
818
836
  **diff/baseline** PR gating; a reusable **GitHub Action + PR bot**; optional
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "attackmap"
7
- version = "0.4.25"
7
+ version = "0.4.26"
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"
@@ -263,11 +263,29 @@ def _parse_pnpm_lock(path: Path) -> _Graph | None:
263
263
  import yaml
264
264
  except ImportError: # pragma: no cover - PyYAML is a declared dependency
265
265
  return None
266
- data = yaml.safe_load(path.read_text(encoding="utf-8"))
267
- if not isinstance(data, dict):
266
+ # pnpm-lock is normally a single YAML document, but some files concatenate
267
+ # several with `---` separators (e.g. a merge artifact, or a tool that
268
+ # appends per-workspace lockfiles). Parse every document and merge them, and
269
+ # never let a malformed-YAML error abort the whole scan — a lockfile we can't
270
+ # read just yields no dependency hints.
271
+ try:
272
+ documents = [
273
+ doc
274
+ for doc in yaml.safe_load_all(path.read_text(encoding="utf-8"))
275
+ if isinstance(doc, dict)
276
+ ]
277
+ except yaml.YAMLError:
278
+ return None
279
+ if not documents:
268
280
  return None
269
281
  graph = _Graph(ecosystem="npm")
282
+ for data in documents:
283
+ _absorb_pnpm_document(data, graph)
284
+ return graph
270
285
 
286
+
287
+ def _absorb_pnpm_document(data: dict, graph: _Graph) -> None:
288
+ """Fold one parsed pnpm-lock YAML document into ``graph`` (roots + packages)."""
271
289
  # Roots: importers.'.'.{dependencies,devDependencies} (v6+) or top-level.
272
290
  importers = data.get("importers")
273
291
  root_sections = []
@@ -285,7 +303,7 @@ def _parse_pnpm_lock(path: Path) -> _Graph | None:
285
303
 
286
304
  packages = data.get("packages")
287
305
  if not isinstance(packages, dict):
288
- return graph
306
+ return
289
307
  for raw_key, meta in packages.items():
290
308
  m = _PNPM_PKG_KEY_RE.match(str(raw_key))
291
309
  if not m:
@@ -302,7 +320,6 @@ def _parse_pnpm_lock(path: Path) -> _Graph | None:
302
320
  children.update(deps.keys())
303
321
  if children:
304
322
  graph.edges.setdefault(name, set()).update(children)
305
- return graph
306
323
 
307
324
 
308
325
  # --- pypi: poetry.lock / uv.lock; cargo: Cargo.lock (all TOML [[package]]) --
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: attackmap
3
- Version: 0.4.25
3
+ Version: 0.4.26
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>
@@ -790,10 +790,12 @@ scaffolding, testing, and publishing instructions is in
790
790
 
791
791
  ```bash
792
792
  attackmap analyze <path> # run a review on a repository
793
+ attackmap analyze repoA repoB gateway # multi-repo fleet scan (per-repo reports + fleet summary)
793
794
  attackmap analyze <path> --output dir # write outputs to `dir/`
794
795
  attackmap analyze <path> --format json # json | markdown | all (default)
795
796
  attackmap analyze <path> --module python --module rust # only these analyzers
796
797
  attackmap analyze <path> --cve # cross-reference SBOM against OSV.dev
798
+ attackmap analyze <path> --recall # widen taint discovery (deeper hops, capability-reach); extra reach marked SPECULATIVE
797
799
  attackmap analyze <path> --llm # add LLM narrative (auto-resolve auth)
798
800
  attackmap analyze <path> --llm --llm-backend cli # force Claude CLI
799
801
  attackmap analyze <path> --llm --llm-provider openai # use OpenAI/Codex (OPENAI_API_KEY or `codex` CLI)
@@ -803,6 +805,8 @@ attackmap analyze <path> --hunt --verify # adjudicate each lead vs. source (conf
803
805
  attackmap analyze <path> --hunt --verify --verify-votes 3 # majority vote of N independent skeptics (default 3; 1 = single pass)
804
806
  attackmap analyze <path> --hunt --verify --hunt-lenses 4 # N lens-specialised generation passes, deduped, then verified
805
807
  attackmap analyze <path> --hunt --verify --hunt-rounds 4 # loop-until-dry generation (completeness critic seeds each round)
808
+ attackmap analyze <path> --hunt --verify --hunt-budget 500000 # cap total hunt output tokens across rounds
809
+ attackmap analyze <path> --recall --hunt --verify # widen the net, then adjudicate the speculative reach
806
810
  attackmap analyze <path> --remediate # LLM review-first fix suggestions (remediation.md)
807
811
  attackmap analyze <path> --triage # cluster/dedupe/rank existing findings (triage.md; deterministic fallback)
808
812
  attackmap analyze <path> --pr-comment pr.md # Markdown PR summary comment for CI
@@ -849,7 +853,7 @@ introduces a new HIGH finding.
849
853
 
850
854
  ## Project status
851
855
 
852
- AttackMap is **beta** (v0.4.18) — published and validated on real codebases, but
856
+ AttackMap is **beta** (v0.4.25) — published and validated on real codebases, but
853
857
  pre-1.0 and heuristic.
854
858
 
855
859
  **Solid today:**
@@ -867,13 +871,27 @@ pre-1.0 and heuristic.
867
871
  - BOLA/IDOR authorization detection (with custom-middleware / guard-arg awareness).
868
872
  - Insecure-crypto / weak-randomness (Python/JS/Go/PHP) and web-hardening
869
873
  (CORS/CSRF/cookies/CSP/debug) detection.
870
- - Anomaly / outlier detection and **exploitability fusion** — a deterministic,
871
- explainable 0–100 "exploitable now" score that ranks route→sink combinations
872
- and folds in known-vulnerable dependencies on the path.
873
- - **`--hunt`** (LLM exploit-chain hypotheses) with **`--verify`** (adjudicate
874
- each lead CONFIRMED/REFUTED/NEEDS-REVIEW against the actual source),
875
- **`--remediate`** (review-first fix suggestions), and **`--triage`**
876
- (cluster/dedupe/rank existing findings, with a deterministic fallback).
874
+ - Anomaly / outlier detection — including **signature-free invariant mining**
875
+ (flag the handler that reaches a dangerous sink without the guard its peers
876
+ apply) — and **exploitability fusion**, a deterministic, explainable 0–100
877
+ "exploitable now" score that ranks route→sink combinations and folds in
878
+ known-vulnerable dependencies on the path.
879
+ - **`--hunt`** (LLM exploit-chain hypotheses) with a **verify jury** —
880
+ **`--verify`** adjudicates each lead CONFIRMED/REFUTED/NEEDS-REVIEW against the
881
+ actual source; **`--verify-votes`** runs N independent skeptics, **`--hunt-lenses`**
882
+ fans generation across failure-mode specialists, **`--hunt-rounds`** loops
883
+ until dry with a completeness critic, **`--hunt-budget`** caps token spend —
884
+ plus **`--remediate`** (review-first fixes) and **`--triage`**
885
+ (cluster/dedupe/rank existing findings, deterministic fallback).
886
+ - **`--recall`** — verifier-gated aggressive discovery: widens the taint walk
887
+ (deeper hops, capability-reach enumeration of every reach to exec/fs/net/
888
+ deserialize/template/SQL) and marks the extra reach SPECULATIVE, kept out of
889
+ `--fail-on-new-high` and left for `--hunt --verify` to adjudicate.
890
+ - **Cross-repo / fleet analysis** — `attackmap analyze repoA repoB …` scans a
891
+ fleet, links one repo's outbound HTTP calls to another's routes, and surfaces
892
+ bugs that live in the seams: cross-boundary (confused-deputy) flows,
893
+ trust-assumption gaps, and the sibling service that omits a control its peers
894
+ enforce. All cross-repo findings are speculative until verified.
877
895
  - SBOM inventory (5 ecosystems) + OSV.dev CVE cross-reference (`--cve`).
878
896
  - Output: Markdown + JSON + **SARIF 2.1.0** + **Mermaid / Graphviz** diagrams;
879
897
  **diff/baseline** PR gating; a reusable **GitHub Action + PR bot**; optional
@@ -211,6 +211,60 @@ packages:
211
211
  assert by_name["minimist"].version == "1.2.5"
212
212
 
213
213
 
214
+ def test_pnpm_lock_multi_document_merges_all(tmp_path: Path) -> None:
215
+ # Some pnpm-lock.yaml files concatenate several YAML documents with `---`
216
+ # separators (seen in the wild, e.g. bluesky-social/atproto). A single-doc
217
+ # load raises yaml.ComposerError and previously aborted the whole scan
218
+ # (regression). We must parse every document and merge them.
219
+ (tmp_path / "pnpm-lock.yaml").write_text(
220
+ """---
221
+ lockfileVersion: '9.0'
222
+ importers:
223
+ .:
224
+ dependencies:
225
+ lodash:
226
+ specifier: ^4.17.0
227
+ version: 4.17.21
228
+ packages:
229
+ lodash@4.17.21:
230
+ resolution: {integrity: sha512-abc}
231
+ ---
232
+ lockfileVersion: '9.0'
233
+ importers:
234
+ .:
235
+ dependencies:
236
+ express:
237
+ specifier: ^4.18.0
238
+ version: 4.18.2
239
+ packages:
240
+ express@4.18.2:
241
+ resolution: {integrity: sha512-def}
242
+ """,
243
+ encoding="utf-8",
244
+ )
245
+ hints, superseded = parse_lockfiles(tmp_path)
246
+ assert ("npm", ".") in superseded
247
+ by_name = _by_name(hints)
248
+ # Both documents contribute their packages and roots.
249
+ assert by_name["lodash"].version == "4.17.21"
250
+ assert by_name["express"].version == "4.18.2"
251
+ assert by_name["lodash"].direct is True
252
+ assert by_name["express"].direct is True
253
+
254
+
255
+ def test_pnpm_lock_malformed_yaml_yields_no_hints_without_crashing(tmp_path: Path) -> None:
256
+ # A lockfile we can't parse must never abort the scan — it just yields
257
+ # no dependency hints.
258
+ (tmp_path / "pnpm-lock.yaml").write_text(
259
+ "lockfileVersion: '9.0'\npackages:\n - : : broken\n bad indent\n",
260
+ encoding="utf-8",
261
+ )
262
+ # The key property: the call returns (a list) instead of raising a
263
+ # yaml.YAMLError that would abort the scan.
264
+ hints, superseded = parse_lockfiles(tmp_path)
265
+ assert isinstance(hints, list)
266
+
267
+
214
268
  def test_uv_local_project_excluded_and_deps_direct(tmp_path: Path) -> None:
215
269
  # The project package (editable source) is not an SBOM entry; its deps
216
270
  # are the direct dependencies.
File without changes
File without changes
File without changes