attackmap 0.4.1__tar.gz → 0.4.3__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.
- {attackmap-0.4.1/src/attackmap.egg-info → attackmap-0.4.3}/PKG-INFO +13 -2
- {attackmap-0.4.1 → attackmap-0.4.3}/README.md +11 -1
- {attackmap-0.4.1 → attackmap-0.4.3}/pyproject.toml +2 -1
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/__init__.py +1 -1
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/anomalies.py +22 -2
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/cli.py +83 -33
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/llm_review.py +285 -10
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/scanner.py +3 -4
- {attackmap-0.4.1 → attackmap-0.4.3/src/attackmap.egg-info}/PKG-INFO +13 -2
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap.egg-info/requires.txt +1 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_anomalies.py +26 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_llm_review.py +284 -2
- {attackmap-0.4.1 → attackmap-0.4.3}/LICENSE +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/setup.cfg +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/analyzer.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/analyzer_contracts.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/analyzers.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/asset_model.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/attack_taxonomy.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/authz.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/config_scanner.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/context_pack.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/control_model.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/crypto.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/cve.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/defensive_review.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/detection_opportunities.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/diagrams.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/diff.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/exploitability.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/graph.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/insights.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/merge.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/models.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/progress.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/recon_models.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/recon_to_analysis.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/report.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/review_eval.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/review_json.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/review_prompts.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/sarif.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/sbom.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/sdk/__init__.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/sdk/contracts.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/sdk/models.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/security_overlay.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/srcpaths.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/suggest.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/taint.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/threat_model.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/topology.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/weaknesses.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap/webhardening.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap.egg-info/SOURCES.txt +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap.egg-info/dependency_links.txt +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap.egg-info/entry_points.txt +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/src/attackmap.egg-info/top_level.txt +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_analyzer.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_analyzer_metadata_schema.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_analyzers.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_artifact_schemas.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_authz.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_config_scanner.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_context_pack.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_crypto.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_cve.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_defensive_review.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_diagrams.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_diff.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_exploitability.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_graph.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_hunt.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_injection_sinks.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_merge.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_pr_comment.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_progress.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_provenance.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_recon_to_analysis.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_remediate.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_report.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_review_eval.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_review_json.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_review_prompts.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_sarif.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_sbom.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_scanner.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_security_overlay.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_shared_contract_imports.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_signal_v2.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_srcpaths.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_suggest.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_taint.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_threat_intel.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_threat_model.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_topology.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_weaknesses.py +0 -0
- {attackmap-0.4.1 → attackmap-0.4.3}/tests/test_webhardening.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: attackmap
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
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>
|
|
@@ -36,6 +36,7 @@ Requires-Dist: pydantic>=2.7.0
|
|
|
36
36
|
Requires-Dist: networkx>=3.3
|
|
37
37
|
Provides-Extra: llm
|
|
38
38
|
Requires-Dist: anthropic>=0.40.0; extra == "llm"
|
|
39
|
+
Requires-Dist: openai>=1.60.0; extra == "llm"
|
|
39
40
|
Provides-Extra: all
|
|
40
41
|
Requires-Dist: attackmap[llm]; extra == "all"
|
|
41
42
|
Requires-Dist: attackmap-analyzer-python>=0.1.0; extra == "all"
|
|
@@ -105,6 +106,12 @@ to use your existing Pro/Max subscription:
|
|
|
105
106
|
attackmap analyze /path/to/repo --output reports --llm
|
|
106
107
|
```
|
|
107
108
|
|
|
109
|
+
Prefer OpenAI? Add `--llm-provider openai` to run the same review/hunt/remediate
|
|
110
|
+
phases on OpenAI. Set `OPENAI_API_KEY` for the Responses API, or log in once with
|
|
111
|
+
the [`codex` CLI](https://developers.openai.com/codex) to use your subscription
|
|
112
|
+
(`--llm-backend cli`). Default model is `gpt-5-codex`; `--llm-model` accepts any
|
|
113
|
+
model ID verbatim.
|
|
114
|
+
|
|
108
115
|
Read `reports/defensive-review.md` (heuristic) and `reports/defensive-review-llm.md`
|
|
109
116
|
(LLM-narrated) side by side.
|
|
110
117
|
|
|
@@ -482,7 +489,9 @@ trust-boundary violations, and more.
|
|
|
482
489
|
|
|
483
490
|
**4. LLM narrative review.** With `--llm`, Claude Opus generates a final review
|
|
484
491
|
from the structured evidence pack. The model is forced to cite real
|
|
485
|
-
surface/asset/control IDs, so it can't invent findings.
|
|
492
|
+
surface/asset/control IDs, so it can't invent findings. Add `--llm-provider
|
|
493
|
+
openai` to run the same phase on OpenAI/Codex instead (`gpt-5-codex` by default,
|
|
494
|
+
via `OPENAI_API_KEY` or the `codex` CLI) — the grounding contract is identical.
|
|
486
495
|
|
|
487
496
|
**5. Vulnerability-hypothesis hunting (`--hunt`).** The honest core of the
|
|
488
497
|
"find the unknown" ask. `attackmap analyze . --hunt` has Claude reason over the
|
|
@@ -542,6 +551,8 @@ attackmap analyze <path> --module python --module rust # only these analyzers
|
|
|
542
551
|
attackmap analyze <path> --cve # cross-reference SBOM against OSV.dev
|
|
543
552
|
attackmap analyze <path> --llm # add LLM narrative (auto-resolve auth)
|
|
544
553
|
attackmap analyze <path> --llm --llm-backend cli # force Claude CLI
|
|
554
|
+
attackmap analyze <path> --llm --llm-provider openai # use OpenAI/Codex (OPENAI_API_KEY or `codex` CLI)
|
|
555
|
+
attackmap analyze <path> --llm --llm-provider openai --llm-model gpt-5.5 # any OpenAI model ID, passed through
|
|
545
556
|
attackmap analyze <path> --hunt # LLM vulnerability-hypothesis hunt (leads to confirm)
|
|
546
557
|
attackmap analyze <path> --hunt --verify # adjudicate each lead vs. source (confirmed/refuted/review)
|
|
547
558
|
attackmap analyze <path> --remediate # LLM review-first fix suggestions (remediation.md)
|
|
@@ -46,6 +46,12 @@ to use your existing Pro/Max subscription:
|
|
|
46
46
|
attackmap analyze /path/to/repo --output reports --llm
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
Prefer OpenAI? Add `--llm-provider openai` to run the same review/hunt/remediate
|
|
50
|
+
phases on OpenAI. Set `OPENAI_API_KEY` for the Responses API, or log in once with
|
|
51
|
+
the [`codex` CLI](https://developers.openai.com/codex) to use your subscription
|
|
52
|
+
(`--llm-backend cli`). Default model is `gpt-5-codex`; `--llm-model` accepts any
|
|
53
|
+
model ID verbatim.
|
|
54
|
+
|
|
49
55
|
Read `reports/defensive-review.md` (heuristic) and `reports/defensive-review-llm.md`
|
|
50
56
|
(LLM-narrated) side by side.
|
|
51
57
|
|
|
@@ -423,7 +429,9 @@ trust-boundary violations, and more.
|
|
|
423
429
|
|
|
424
430
|
**4. LLM narrative review.** With `--llm`, Claude Opus generates a final review
|
|
425
431
|
from the structured evidence pack. The model is forced to cite real
|
|
426
|
-
surface/asset/control IDs, so it can't invent findings.
|
|
432
|
+
surface/asset/control IDs, so it can't invent findings. Add `--llm-provider
|
|
433
|
+
openai` to run the same phase on OpenAI/Codex instead (`gpt-5-codex` by default,
|
|
434
|
+
via `OPENAI_API_KEY` or the `codex` CLI) — the grounding contract is identical.
|
|
427
435
|
|
|
428
436
|
**5. Vulnerability-hypothesis hunting (`--hunt`).** The honest core of the
|
|
429
437
|
"find the unknown" ask. `attackmap analyze . --hunt` has Claude reason over the
|
|
@@ -483,6 +491,8 @@ attackmap analyze <path> --module python --module rust # only these analyzers
|
|
|
483
491
|
attackmap analyze <path> --cve # cross-reference SBOM against OSV.dev
|
|
484
492
|
attackmap analyze <path> --llm # add LLM narrative (auto-resolve auth)
|
|
485
493
|
attackmap analyze <path> --llm --llm-backend cli # force Claude CLI
|
|
494
|
+
attackmap analyze <path> --llm --llm-provider openai # use OpenAI/Codex (OPENAI_API_KEY or `codex` CLI)
|
|
495
|
+
attackmap analyze <path> --llm --llm-provider openai --llm-model gpt-5.5 # any OpenAI model ID, passed through
|
|
486
496
|
attackmap analyze <path> --hunt # LLM vulnerability-hypothesis hunt (leads to confirm)
|
|
487
497
|
attackmap analyze <path> --hunt --verify # adjudicate each lead vs. source (confirmed/refuted/review)
|
|
488
498
|
attackmap analyze <path> --remediate # LLM review-first fix suggestions (remediation.md)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "attackmap"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.3"
|
|
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"
|
|
@@ -56,6 +56,7 @@ dependencies = [
|
|
|
56
56
|
[project.optional-dependencies]
|
|
57
57
|
llm = [
|
|
58
58
|
"anthropic>=0.40.0",
|
|
59
|
+
"openai>=1.60.0",
|
|
59
60
|
]
|
|
60
61
|
all = [
|
|
61
62
|
"attackmap[llm]",
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__all__ = ["__version__"]
|
|
2
|
-
__version__ = "0.4.
|
|
2
|
+
__version__ = "0.4.3"
|
|
@@ -36,10 +36,16 @@ from __future__ import annotations
|
|
|
36
36
|
import re
|
|
37
37
|
from dataclasses import dataclass
|
|
38
38
|
from pathlib import Path
|
|
39
|
+
from typing import TYPE_CHECKING
|
|
39
40
|
|
|
40
41
|
from .models import Anomaly, Route, ScanResult
|
|
41
42
|
from .srcpaths import is_test_file
|
|
42
43
|
|
|
44
|
+
if TYPE_CHECKING:
|
|
45
|
+
from .progress import JsonScanProgress, ScanProgress
|
|
46
|
+
|
|
47
|
+
_Progress = ScanProgress | JsonScanProgress
|
|
48
|
+
|
|
43
49
|
|
|
44
50
|
@dataclass
|
|
45
51
|
class _SignalCtx:
|
|
@@ -106,8 +112,17 @@ _MIN_GROUP = 3
|
|
|
106
112
|
_SCAFFOLD_RE = re.compile(r"^(?:api|rest|graphql|gql|v\d+(?:beta\d*|alpha\d*)?)$", re.IGNORECASE)
|
|
107
113
|
|
|
108
114
|
|
|
109
|
-
def find_anomalies(
|
|
110
|
-
|
|
115
|
+
def find_anomalies(
|
|
116
|
+
scan: ScanResult,
|
|
117
|
+
root: str | Path | None = None,
|
|
118
|
+
progress: "_Progress | None" = None,
|
|
119
|
+
) -> list[Anomaly]:
|
|
120
|
+
"""Return within-repo consistency outliers for the routes in ``scan``.
|
|
121
|
+
|
|
122
|
+
On a large route surface the per-cohort source-window reads dominate this
|
|
123
|
+
pass, so when a ``progress`` reporter is supplied it drives a determinate
|
|
124
|
+
bar over the cohorts (rather than an open-ended spinner).
|
|
125
|
+
"""
|
|
111
126
|
root_path = Path(root or scan.root).resolve()
|
|
112
127
|
|
|
113
128
|
routes = [r for r in scan.routes if not is_test_file(r.file)]
|
|
@@ -129,8 +144,13 @@ def find_anomalies(scan: ScanResult, root: str | Path | None = None) -> list[Ano
|
|
|
129
144
|
|
|
130
145
|
ctx = _SignalCtx(root_path, line_cache, file_route_lines)
|
|
131
146
|
|
|
147
|
+
if progress is not None:
|
|
148
|
+
progress.begin(len(groups), "Anomaly / outlier detection")
|
|
149
|
+
|
|
132
150
|
out: list[Anomaly] = []
|
|
133
151
|
for key, members in groups.items():
|
|
152
|
+
if progress is not None:
|
|
153
|
+
progress.advance(key)
|
|
134
154
|
if len(members) < _MIN_GROUP:
|
|
135
155
|
continue
|
|
136
156
|
if not _is_route_like_cohort(key, members):
|
|
@@ -69,20 +69,30 @@ def analyze(
|
|
|
69
69
|
"--llm",
|
|
70
70
|
help="Generate a narrative defensive review by calling Claude with the evidence pack. Auth resolves automatically: ANTHROPIC_API_KEY → ANTHROPIC_AUTH_TOKEN → `claude` CLI (subscription auth). Force a backend with --llm-backend.",
|
|
71
71
|
),
|
|
72
|
+
llm_provider: str = typer.Option(
|
|
73
|
+
"claude",
|
|
74
|
+
"--llm-provider",
|
|
75
|
+
help="LLM provider for --llm/--hunt/--remediate: 'claude' (default, Anthropic) or 'openai' (OpenAI/Codex).",
|
|
76
|
+
),
|
|
72
77
|
llm_model: str | None = typer.Option(
|
|
73
78
|
None,
|
|
74
79
|
"--llm-model",
|
|
75
|
-
help="
|
|
80
|
+
help="Model ID for --llm. Claude provider: e.g. claude-opus-4-8 (default), claude-fable-5, claude-sonnet-5, claude-opus-4-7. OpenAI provider: e.g. gpt-5-codex (default), gpt-5.5, gpt-5.4, gpt-5.4-mini — any model ID is passed through verbatim.",
|
|
76
81
|
),
|
|
77
82
|
llm_effort: str | None = typer.Option(
|
|
78
83
|
None,
|
|
79
84
|
"--llm-effort",
|
|
80
|
-
help="Effort for --llm: low|medium|high|xhigh|max. Defaults to high.",
|
|
85
|
+
help="Effort for --llm: low|medium|high|xhigh|max. Defaults to high. For OpenAI, xhigh/max clamp to high (Responses API reasoning effort tops out at high).",
|
|
81
86
|
),
|
|
82
87
|
llm_backend: str = typer.Option(
|
|
83
88
|
"auto",
|
|
84
89
|
"--llm-backend",
|
|
85
|
-
help="Which backend --llm uses: 'auto'
|
|
90
|
+
help="Which backend --llm uses. Claude: 'auto' tries ANTHROPIC_API_KEY → ANTHROPIC_AUTH_TOKEN → `claude` CLI; 'api' forces the SDK; 'cli' forces the `claude` CLI. OpenAI: 'auto' tries OPENAI_API_KEY then the `codex` CLI; 'api' forces the OpenAI SDK; 'cli' forces `codex exec` (your `codex login` subscription).",
|
|
91
|
+
),
|
|
92
|
+
llm_speed: str = typer.Option(
|
|
93
|
+
"standard",
|
|
94
|
+
"--llm-speed",
|
|
95
|
+
help="LLM output speed: 'standard' (default) or 'fast'. Fast mode (~2.5x output speed, premium price) applies only to Opus 4.8/4.7 via the API backend; other models/backends fall back to standard.",
|
|
86
96
|
),
|
|
87
97
|
hunt: bool = typer.Option(
|
|
88
98
|
False,
|
|
@@ -155,12 +165,27 @@ def analyze(
|
|
|
155
165
|
|
|
156
166
|
if progress_format not in {"auto", "tty", "json", "none"}:
|
|
157
167
|
raise typer.BadParameter("--progress-format must be one of: auto, json, none.")
|
|
168
|
+
if llm_speed not in {"standard", "fast"}:
|
|
169
|
+
raise typer.BadParameter("--llm-speed must be one of: standard, fast.")
|
|
170
|
+
if llm_provider not in {"claude", "openai"}:
|
|
171
|
+
raise typer.BadParameter("--llm-provider must be one of: claude, openai.")
|
|
172
|
+
# Provider-aware label for the "via X" progress/echo strings.
|
|
173
|
+
llm_display = "Codex" if llm_provider == "openai" else "Claude"
|
|
174
|
+
if llm_provider == "openai" and llm_speed == "fast" and (llm or hunt or remediate):
|
|
175
|
+
typer.echo(
|
|
176
|
+
"Note: --llm-speed fast is Claude-only; ignoring it for the OpenAI provider.",
|
|
177
|
+
err=True,
|
|
178
|
+
)
|
|
158
179
|
active_analyzers = resolve_run_analyzers(repo_path, analyzers=selected_analyzers)
|
|
159
180
|
scan_progress = create_progress(progress_format, no_progress=no_progress)
|
|
160
181
|
scan = analyze_repository(repo_path, analyzers=active_analyzers, progress=scan_progress)
|
|
161
182
|
if cve and scan.dependencies:
|
|
162
183
|
typer.echo(f"Checking {len(scan.dependencies)} dependencies against OSV.dev…")
|
|
163
|
-
|
|
184
|
+
scan_progress.stage(f"Checking {len(scan.dependencies)} dependencies against OSV.dev")
|
|
185
|
+
try:
|
|
186
|
+
vulns, cve_summary = query_vulnerabilities(scan.dependencies)
|
|
187
|
+
finally:
|
|
188
|
+
scan_progress.done()
|
|
164
189
|
scan.vulnerabilities = vulns
|
|
165
190
|
typer.echo(
|
|
166
191
|
f"CVE lookup: {len(vulns)} vulnerabilit"
|
|
@@ -255,17 +280,25 @@ def analyze(
|
|
|
255
280
|
|
|
256
281
|
typer.echo("")
|
|
257
282
|
typer.echo(
|
|
258
|
-
f"Generating narrative review via
|
|
283
|
+
f"Generating narrative review via {llm_display} (backend={llm_backend}, may take a minute)..."
|
|
259
284
|
)
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
attack_surfaces,
|
|
263
|
-
findings,
|
|
264
|
-
attack_paths,
|
|
265
|
-
model=llm_model,
|
|
266
|
-
effort=effort_value, # type: ignore[arg-type]
|
|
267
|
-
backend=llm_backend, # type: ignore[arg-type]
|
|
285
|
+
scan_progress.stage(
|
|
286
|
+
f"{llm_display} is writing the defensive review (backend={llm_backend})"
|
|
268
287
|
)
|
|
288
|
+
try:
|
|
289
|
+
result = generate_llm_review(
|
|
290
|
+
scan,
|
|
291
|
+
attack_surfaces,
|
|
292
|
+
findings,
|
|
293
|
+
attack_paths,
|
|
294
|
+
model=llm_model,
|
|
295
|
+
effort=effort_value, # type: ignore[arg-type]
|
|
296
|
+
backend=llm_backend, # type: ignore[arg-type]
|
|
297
|
+
speed=llm_speed, # type: ignore[arg-type]
|
|
298
|
+
provider=llm_provider, # type: ignore[arg-type]
|
|
299
|
+
)
|
|
300
|
+
finally:
|
|
301
|
+
scan_progress.done()
|
|
269
302
|
except LlmReviewError as exc:
|
|
270
303
|
typer.echo(f"LLM review skipped: {exc}", err=True)
|
|
271
304
|
else:
|
|
@@ -304,19 +337,28 @@ def analyze(
|
|
|
304
337
|
typer.echo("")
|
|
305
338
|
hunt_mode = "hunt_verify" if verify else "hunt"
|
|
306
339
|
typer.echo(
|
|
307
|
-
f"Hunting for vulnerability hypotheses via
|
|
340
|
+
f"Hunting for vulnerability hypotheses via {llm_display} "
|
|
308
341
|
f"({'adjudicated against source, ' if verify else ''}backend={llm_backend}, may take a minute)..."
|
|
309
342
|
)
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
findings,
|
|
314
|
-
attack_paths,
|
|
315
|
-
model=llm_model,
|
|
316
|
-
effort=hunt_effort_value, # type: ignore[arg-type]
|
|
317
|
-
backend=llm_backend, # type: ignore[arg-type]
|
|
318
|
-
mode=hunt_mode, # type: ignore[arg-type]
|
|
343
|
+
scan_progress.stage(
|
|
344
|
+
f"{llm_display} is hunting exploit-chain hypotheses"
|
|
345
|
+
f"{' + verifying against source' if verify else ''} (backend={llm_backend})"
|
|
319
346
|
)
|
|
347
|
+
try:
|
|
348
|
+
hunt_result = generate_llm_review(
|
|
349
|
+
scan,
|
|
350
|
+
attack_surfaces,
|
|
351
|
+
findings,
|
|
352
|
+
attack_paths,
|
|
353
|
+
model=llm_model,
|
|
354
|
+
effort=hunt_effort_value, # type: ignore[arg-type]
|
|
355
|
+
backend=llm_backend, # type: ignore[arg-type]
|
|
356
|
+
mode=hunt_mode, # type: ignore[arg-type]
|
|
357
|
+
speed=llm_speed, # type: ignore[arg-type]
|
|
358
|
+
provider=llm_provider, # type: ignore[arg-type]
|
|
359
|
+
)
|
|
360
|
+
finally:
|
|
361
|
+
scan_progress.done()
|
|
320
362
|
except LlmReviewError as exc:
|
|
321
363
|
typer.echo(f"Vulnerability hunt skipped: {exc}", err=True)
|
|
322
364
|
else:
|
|
@@ -357,18 +399,26 @@ def analyze(
|
|
|
357
399
|
)
|
|
358
400
|
typer.echo("")
|
|
359
401
|
typer.echo(
|
|
360
|
-
f"Generating remediation suggestions via
|
|
402
|
+
f"Generating remediation suggestions via {llm_display} (backend={llm_backend}, may take a minute)..."
|
|
361
403
|
)
|
|
362
|
-
|
|
363
|
-
|
|
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",
|
|
404
|
+
scan_progress.stage(
|
|
405
|
+
f"{llm_display} is drafting remediation suggestions (backend={llm_backend})"
|
|
371
406
|
)
|
|
407
|
+
try:
|
|
408
|
+
rem_result = generate_llm_review(
|
|
409
|
+
scan,
|
|
410
|
+
attack_surfaces,
|
|
411
|
+
findings,
|
|
412
|
+
attack_paths,
|
|
413
|
+
model=llm_model,
|
|
414
|
+
effort=rem_effort_value, # type: ignore[arg-type]
|
|
415
|
+
backend=llm_backend, # type: ignore[arg-type]
|
|
416
|
+
mode="remediate",
|
|
417
|
+
speed=llm_speed, # type: ignore[arg-type]
|
|
418
|
+
provider=llm_provider, # type: ignore[arg-type]
|
|
419
|
+
)
|
|
420
|
+
finally:
|
|
421
|
+
scan_progress.done()
|
|
372
422
|
except LlmReviewError as exc:
|
|
373
423
|
typer.echo(f"Remediation skipped: {exc}", err=True)
|
|
374
424
|
else:
|