Skip to content

Commit fa8aadf

Browse files
authored
Merge: v2.7 AI-native detection engine (87% recall, 3 benchmarks)
v2.7: AI-native detection engine — 87% recall across 3 benchmarks
2 parents 8a1fe6f + 6e87ab9 commit fa8aadf

80 files changed

Lines changed: 13373 additions & 505 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude-plugin/marketplace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
{
88
"name": "vuln-scout",
99
"source": "./whitebox-pentest",
10-
"description": "AI-powered whitebox penetration testing plugin for Claude Code. 9 languages, 27 skills, 7 autonomous agents."
10+
"description": "AI-powered whitebox penetration testing plugin for Claude Code. 9 languages, 27 skills, 8 autonomous agents."
1111
}
1212
]
1313
}

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,10 @@ joern-workspace/
3333
__pycache__/
3434
*.pyc
3535

36+
# VulnScout scan artifacts
37+
.vuln-scout-cache/
38+
.codeql/
39+
*.jsonl
40+
41+
# Node modules
42+
node_modules/

README.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,16 @@ python3 scripts/create_cpg.py /path/to/code
6464
# Batch-verify findings with Joern CPG analysis
6565
python3 scripts/batch_verify.py --findings .claude/findings.json --cpg .joern/*.cpg
6666

67+
# Render HTML or Markdown from an existing findings artifact
68+
python3 scripts/report.py .claude/findings.json --format html --output security-report.html
69+
6770
# CI gate: fail on high-severity findings
6871
python3 scripts/scan_orchestrator.py . --tools semgrep --fail-on high --format sarif --output findings.sarif
6972
```
7073

7174
## What You Get
7275

73-
### 9 Commands
76+
### 13 Commands
7477

7578
| Command | What it does |
7679
|---------|-------------|
@@ -82,9 +85,13 @@ python3 scripts/scan_orchestrator.py . --tools semgrep --fail-on high --format s
8285
| `/whitebox-pentest:scope` | Handle large codebases with smart compression |
8386
| `/whitebox-pentest:propagate` | Found one bug? Find every instance of the pattern |
8487
| `/whitebox-pentest:verify` | CPG-based false positive elimination |
85-
| `/whitebox-pentest:report` | Render Markdown, JSON, or SARIF from the shared findings artifact |
88+
| `/whitebox-pentest:report` | Render Markdown, JSON, SARIF, or HTML from the shared findings artifact |
89+
| `/whitebox-pentest:diff` | Compare security posture between git refs and highlight regressions |
90+
| `/whitebox-pentest:auto-fix` | Auto-remediate verified findings with generated patches |
91+
| `/whitebox-pentest:create-rule` | Generate a custom Semgrep rule from a confirmed vulnerability pattern |
92+
| `/whitebox-pentest:mutate` | Mutation-test security controls to find detection gaps |
8693

87-
### 7 Autonomous Agents
94+
### 8 Autonomous Agents
8895

8996
Agents run independently and return detailed analysis:
9097

@@ -95,6 +102,7 @@ Agents run independently and return detailed analysis:
95102
- **poc-developer** -- Proof of concept development
96103
- **patch-advisor** -- Specific remediation with code patches
97104
- **false-positive-verifier** -- Evidence-based verification with NEEDS_REVIEW resolution path
105+
- **attack-researcher** -- Autonomous attack vector exploration beyond pattern matching
98106

99107
### 15 Joern CPG Verification Scripts
100108

docs/superpowers/specs/2026-03-25-vulnscout-improvements-design.md

Lines changed: 485 additions & 0 deletions
Large diffs are not rendered by default.

tests/test_cli_workflows.py

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
from __future__ import annotations
2+
3+
import importlib.util
4+
import subprocess
5+
import sys
6+
import tempfile
7+
import unittest
8+
from pathlib import Path
9+
from unittest import mock
10+
11+
12+
ROOT = Path(__file__).resolve().parents[1]
13+
SCRIPTS_DIR = ROOT / "whitebox-pentest" / "scripts"
14+
FIXTURES_DIR = ROOT / "tests" / "fixtures" / "artifacts"
15+
16+
sys.path.insert(0, str(SCRIPTS_DIR))
17+
18+
19+
def load_module(name: str, path: Path):
20+
spec = importlib.util.spec_from_file_location(name, path)
21+
module = importlib.util.module_from_spec(spec)
22+
sys.modules[name] = module
23+
assert spec.loader is not None
24+
spec.loader.exec_module(module)
25+
return module
26+
27+
28+
scan_orchestrator = load_module("scan_orchestrator_cli", SCRIPTS_DIR / "scan_orchestrator.py")
29+
run_diff = load_module("run_diff_cli", SCRIPTS_DIR / "run_diff.py")
30+
31+
32+
class ScanCliParityTests(unittest.TestCase):
33+
def test_scan_help_includes_supported_public_flags(self):
34+
help_text = scan_orchestrator.build_arg_parser().format_help()
35+
36+
self.assertIn("--workspace", help_text)
37+
self.assertIn("--no-claude-analysis", help_text)
38+
self.assertNotIn("--scope", help_text)
39+
40+
def test_resolve_workspace_finds_nested_workspace(self):
41+
with tempfile.TemporaryDirectory() as tmpdir:
42+
repo = Path(tmpdir)
43+
workspace = repo / "services" / "api"
44+
workspace.mkdir(parents=True)
45+
(workspace / "package.json").write_text("{}\n")
46+
47+
resolved = scan_orchestrator.resolve_workspace(repo, "api")
48+
49+
self.assertEqual(resolved, workspace.resolve())
50+
51+
def test_resolve_target_path_rejects_scope_snapshot(self):
52+
with tempfile.TemporaryDirectory() as tmpdir:
53+
scope_file = Path(tmpdir) / ".claude" / "scope-api.md"
54+
scope_file.parent.mkdir(parents=True)
55+
scope_file.write_text("# saved scope\n")
56+
57+
with self.assertRaisesRegex(ValueError, "scanner input"):
58+
scan_orchestrator.resolve_target_path(str(scope_file))
59+
60+
61+
class ReportCliTests(unittest.TestCase):
62+
def test_report_cli_renders_markdown_and_html(self):
63+
artifact = FIXTURES_DIR / "sample-findings.json"
64+
script = SCRIPTS_DIR / "report.py"
65+
66+
with tempfile.TemporaryDirectory() as tmpdir:
67+
md_path = Path(tmpdir) / "report.md"
68+
html_path = Path(tmpdir) / "report.html"
69+
70+
md_result = subprocess.run(
71+
[sys.executable, str(script), str(artifact), "--format", "md", "--output", str(md_path)],
72+
capture_output=True,
73+
text=True,
74+
)
75+
html_result = subprocess.run(
76+
[sys.executable, str(script), str(artifact), "--format", "html", "--output", str(html_path)],
77+
capture_output=True,
78+
text=True,
79+
)
80+
81+
self.assertEqual(md_result.returncode, 0, md_result.stderr)
82+
self.assertEqual(html_result.returncode, 0, html_result.stderr)
83+
self.assertIn("VulnScout Scan Report", md_path.read_text())
84+
self.assertIn("<html", html_path.read_text().lower())
85+
86+
def test_report_cli_fail_on_returns_exit_2(self):
87+
artifact = FIXTURES_DIR / "sample-findings.json"
88+
script = SCRIPTS_DIR / "report.py"
89+
90+
result = subprocess.run(
91+
[sys.executable, str(script), str(artifact), "--format", "json", "--fail-on", "high"],
92+
capture_output=True,
93+
text=True,
94+
)
95+
96+
self.assertEqual(result.returncode, 2)
97+
self.assertIn('"summary"', result.stdout)
98+
99+
100+
class RunDiffCliTests(unittest.TestCase):
101+
def test_run_diff_markdown_output(self):
102+
baseline = {
103+
"findings": [
104+
{
105+
"stable_key": "a",
106+
"id": "VSCOUT-0001",
107+
"type": "xss",
108+
"severity": "low",
109+
"file": "app.py",
110+
"verdict": "unverified",
111+
"kind": "finding",
112+
}
113+
],
114+
"entry_points": [],
115+
}
116+
current = {
117+
"findings": [
118+
{
119+
"stable_key": "a",
120+
"id": "VSCOUT-0001",
121+
"type": "xss",
122+
"severity": "high",
123+
"file": "app.py",
124+
"verdict": "verified",
125+
"kind": "finding",
126+
},
127+
{
128+
"stable_key": "b",
129+
"id": "VSCOUT-0002",
130+
"type": "sql-injection",
131+
"severity": "critical",
132+
"file": "db.py",
133+
"verdict": "unverified",
134+
"kind": "finding",
135+
},
136+
],
137+
"entry_points": [{"method": "GET", "path": "/admin"}],
138+
}
139+
140+
with tempfile.TemporaryDirectory() as tmpdir:
141+
output_path = Path(tmpdir) / "security-diff.md"
142+
argv = [
143+
"run_diff.py",
144+
"--base",
145+
"base",
146+
"--head",
147+
"head",
148+
"--format",
149+
"md",
150+
"--output",
151+
str(output_path),
152+
]
153+
154+
with mock.patch.object(run_diff, "_scan_ref", side_effect=[baseline, current]):
155+
with mock.patch.object(sys, "argv", argv):
156+
exit_code = run_diff.main()
157+
158+
text = output_path.read_text()
159+
self.assertEqual(exit_code, 0)
160+
self.assertIn("# VulnScout Security Diff", text)
161+
self.assertIn("## Changed Findings", text)
162+
self.assertIn("severity low -> high", text)
163+
self.assertNotIn('"new_findings"', text)
164+
165+
166+
if __name__ == "__main__":
167+
unittest.main()

0 commit comments

Comments
 (0)