@hoanghainh/komagu 2.0.0 → 2.1.0
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.
- package/CHANGELOG.md +20 -0
- package/README.md +1 -1
- package/assess/lsa/examples/eval-run-4/ground-truth.json +823 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/ai-run-stats.json +58 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/coverage.json +85 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/findings/priv-privilege-001.json +822 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/findings/priv-privilege-002.json +393 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/findings.json +4477 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/mass-assignment.candidates.json +232 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/routes/routes-001.json +425 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/routes/routes-002.json +333 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/routes/routes-003.json +400 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/routes/routes-004.json +403 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/routes/routes-005.json +398 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/routes/routes-006.json +63 -0
- package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/score.json +120 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/ai-run-stats.json +55 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/coverage.json +50 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/findings/priv-privilege-001.json +613 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/findings/priv-privilege-002.json +287 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/findings.json +4870 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/mass-assignment.candidates.json +232 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/routes/routes-001.json +425 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/routes/routes-002.json +333 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/routes/routes-003.json +400 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/routes/routes-004.json +403 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/routes/routes-005.json +398 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/routes/routes-006.json +63 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/score.json +107 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/ai-run-stats.json +58 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/coverage.json +92 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/findings/priv-privilege-001.json +645 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/findings/priv-privilege-002.json +222 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/findings.json +4859 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/mass-assignment.candidates.json +232 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/routes/routes-001.json +425 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/routes/routes-002.json +333 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/routes/routes-003.json +400 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/routes/routes-004.json +403 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/routes/routes-005.json +398 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/routes/routes-006.json +63 -0
- package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/score.json +121 -0
- package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/coverage.json +111 -0
- package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/findings/priv-privilege-001.json +6 -0
- package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/findings/priv-privilege-002.json +344 -0
- package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/findings.json +3544 -0
- package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/mass-assignment.candidates.json +232 -0
- package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/routes/routes-001.json +425 -0
- package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/routes/routes-002.json +333 -0
- package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/routes/routes-003.json +400 -0
- package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/routes/routes-004.json +403 -0
- package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/routes/routes-005.json +398 -0
- package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/routes/routes-006.json +63 -0
- package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/score.json +123 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/ai-run-stats.json +50 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/coverage.json +42 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/findings/priv-privilege-001.json +540 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/findings/priv-privilege-002.json +238 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/findings.json +4383 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/findings.sarif +8729 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/mass-assignment.candidates.json +232 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/report.ja.md +1911 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/report.md +1911 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/routes/routes-001.json +425 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/routes/routes-002.json +333 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/routes/routes-003.json +400 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/routes/routes-004.json +403 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/routes/routes-005.json +398 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/routes/routes-006.json +63 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/score.json +101 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/stack-profile.json +226 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/ai-run-stats.json +54 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/coverage.json +66 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/findings/priv-privilege-001.json +440 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/findings/priv-privilege-002.json +229 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/findings.json +3424 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/mass-assignment.candidates.json +232 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/routes/routes-001.json +425 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/routes/routes-002.json +333 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/routes/routes-003.json +400 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/routes/routes-004.json +403 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/routes/routes-005.json +398 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/routes/routes-006.json +63 -0
- package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/score.json +120 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/ai-run-stats.json +88 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/coverage.json +36 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/findings/priv-privilege-001.json +530 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/findings/priv-privilege-002.json +248 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/findings.json +4766 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/findings.sarif +9326 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/mass-assignment.candidates.json +232 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/report.ja.md +2011 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/report.md +2011 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/routes/routes-001.json +425 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/routes/routes-002.json +333 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/routes/routes-003.json +400 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/routes/routes-004.json +403 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/routes/routes-005.json +398 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/routes/routes-006.json +63 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/score.json +80 -0
- package/assess/lsa/examples/eval-run-5/gptoss-a/stack-profile.json +226 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/ai-run-stats.json +83 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/coverage.json +44 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/findings/priv-privilege-001.json +579 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/findings/priv-privilege-002.json +268 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/findings.json +3913 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/mass-assignment.candidates.json +232 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/routes/routes-001.json +425 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/routes/routes-002.json +333 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/routes/routes-003.json +400 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/routes/routes-004.json +403 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/routes/routes-005.json +398 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/routes/routes-006.json +63 -0
- package/assess/lsa/examples/eval-run-5/gptoss-b/score.json +94 -0
- package/assess/lsa/examples/eval-run-5/ground-truth.json +823 -0
- package/assess/lsa/knowledge/injection-patterns.md +8 -1
- package/assess/lsa/knowledge/stacks/nextjs.md +62 -0
- package/assess/lsa/prompts/35-privilege-auditor.md +3 -1
- package/assess/lsa/prompts/_shared-rules.md +1 -0
- package/assess/lsa/scripts/detect_stack.py +5 -1
- package/assess/lsa/scripts/find_injection.py +34 -5
- package/assess/lsa/scripts/lsa_common.py +9 -2
- package/assess/lsa/scripts/make_batches.py +2 -1
- package/assess/lsa/scripts/map_routes.py +10 -2
- package/assess/lsa/scripts/nextjs_routes.py +236 -0
- package/assess/lsa/templates/audit-config.json +1 -1
- package/assess/lsa/tests/EVAL-RESULTS.md +156 -0
- package/assess/lsa/tests/score_findings.py +58 -0
- package/assess/lsa/tests/test_default_scope.py +86 -0
- package/assess/lsa/tests/test_eval_run_4.py +73 -0
- package/assess/lsa/tests/test_eval_run_5.py +73 -0
- package/assess/lsa/tests/test_js_tagged_sql.py +112 -0
- package/assess/lsa/tests/test_nextjs.py +198 -0
- package/assess/lsa/tests/test_privilege_tier2.py +30 -0
- package/operate/ai/anchoring.py +130 -0
- package/operate/ai/client.py +13 -0
- package/operate/ai/context.py +105 -0
- package/operate/ai/runner.py +197 -69
- package/operate/cli/__init__.py +1 -1
- package/operate/cli/commands/scan.py +1 -1
- package/operate/tests/test_ai_anchoring.py +184 -0
- package/operate/tests/test_headless_ai.py +148 -1
- package/package.json +1 -1
|
@@ -74,6 +74,67 @@ class TestLLMClient(unittest.TestCase):
|
|
|
74
74
|
self.assertIn("Failed to connect to LLM endpoint", str(ctx.exception))
|
|
75
75
|
|
|
76
76
|
|
|
77
|
+
class TestSanitizeFinding(unittest.TestCase):
|
|
78
|
+
"""Format repairs for model output that would otherwise fail finding.schema.json (#286)."""
|
|
79
|
+
|
|
80
|
+
def test_impact_list_becomes_string(self) -> None:
|
|
81
|
+
f = HeadlessAIRunner._sanitize_finding({"category": "mass_assignment", "subtype": "whole_object_binding",
|
|
82
|
+
"impact": ["role escalation", "balance change"]})
|
|
83
|
+
self.assertEqual(f["impact"], "role escalation; balance change")
|
|
84
|
+
|
|
85
|
+
def test_verdict_in_subtype_moves_to_status(self) -> None:
|
|
86
|
+
f = HeadlessAIRunner._sanitize_finding({"category": "mass_assignment", "subtype": "needs_review", "status": "unverified"})
|
|
87
|
+
self.assertEqual(f["subtype"], "whole_object_binding")
|
|
88
|
+
self.assertEqual(f["status"], "needs_review")
|
|
89
|
+
|
|
90
|
+
def test_unknown_bfla_subtype_falls_back(self) -> None:
|
|
91
|
+
f = HeadlessAIRunner._sanitize_finding({"category": "bfla", "subtype": "admin_only", "status": "unverified"})
|
|
92
|
+
self.assertEqual((f["subtype"], f["status"]), ("missing_role_check", "unverified"))
|
|
93
|
+
|
|
94
|
+
def test_null_evidence_line_and_missing_status_are_repaired(self) -> None:
|
|
95
|
+
f = HeadlessAIRunner._sanitize_finding({"category": "mass_assignment", "subtype": "whole_object_binding",
|
|
96
|
+
"location": {"file": "a.py", "start_line": 7},
|
|
97
|
+
"evidence": [{"file": "a.py", "line": None, "why": "binds body"}]})
|
|
98
|
+
self.assertEqual(f["evidence"][0]["line"], 7)
|
|
99
|
+
self.assertEqual(f["status"], "unverified")
|
|
100
|
+
|
|
101
|
+
def test_valid_subtype_is_kept(self) -> None:
|
|
102
|
+
f = HeadlessAIRunner._sanitize_finding({"category": "bfla", "subtype": "role_from_request"})
|
|
103
|
+
self.assertEqual(f["subtype"], "role_from_request")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class TestVerificationContext(unittest.TestCase):
|
|
107
|
+
"""The headless verifier gets the code it judges, including server-side includes (#286)."""
|
|
108
|
+
|
|
109
|
+
def setUp(self) -> None:
|
|
110
|
+
self.tmp = Path(tempfile.mkdtemp(prefix="komagu_verify_ctx_"))
|
|
111
|
+
(self.tmp / "inc").mkdir()
|
|
112
|
+
(self.tmp / "admin").mkdir()
|
|
113
|
+
(self.tmp / "inc" / "auth.php").write_text("<?php\n// Login check only\nsession_start();\n", encoding="utf-8")
|
|
114
|
+
(self.tmp / "admin" / "delete_user.php").write_text(
|
|
115
|
+
"<?php\nrequire_once '../inc/auth.php';\n$db->prepare('DELETE FROM users WHERE id = ?');\n", encoding="utf-8")
|
|
116
|
+
|
|
117
|
+
def tearDown(self) -> None:
|
|
118
|
+
shutil.rmtree(self.tmp, ignore_errors=True)
|
|
119
|
+
|
|
120
|
+
def test_included_files_are_resolved(self) -> None:
|
|
121
|
+
from operate.ai.runner import _included_files
|
|
122
|
+
self.assertEqual(_included_files(self.tmp, "admin/delete_user.php"), ["inc/auth.php"])
|
|
123
|
+
self.assertEqual(_included_files(self.tmp, "missing.php"), [])
|
|
124
|
+
|
|
125
|
+
def test_context_has_site_and_included_code(self) -> None:
|
|
126
|
+
from operate.ai.runner import _verification_context
|
|
127
|
+
ctx = _verification_context(self.tmp, {"location": {"file": "admin/delete_user.php", "start_line": 3}})
|
|
128
|
+
self.assertIn("DELETE FROM users", ctx["location"])
|
|
129
|
+
self.assertEqual(ctx["included_files"][0]["file"], "inc/auth.php")
|
|
130
|
+
self.assertIn("Login check only", ctx["included_files"][0]["code"])
|
|
131
|
+
|
|
132
|
+
def test_include_outside_target_is_ignored(self) -> None:
|
|
133
|
+
from operate.ai.runner import _included_files
|
|
134
|
+
(self.tmp / "x.php").write_text("<?php include '../../etc/passwd';\n", encoding="utf-8")
|
|
135
|
+
self.assertEqual(_included_files(self.tmp, "x.php"), [])
|
|
136
|
+
|
|
137
|
+
|
|
77
138
|
class TestHeadlessAIRunner(unittest.TestCase):
|
|
78
139
|
"""End-to-end tests for HeadlessAIRunner and multi-agent pipeline."""
|
|
79
140
|
|
|
@@ -109,7 +170,7 @@ module.exports = app;
|
|
|
109
170
|
# Mock LLM response dispatcher
|
|
110
171
|
def mock_agent_responses(messages, model):
|
|
111
172
|
user_msg = messages[-1]["content"]
|
|
112
|
-
if "Auth Model" in user_msg or "Pre-scan Stack Profile" in user_msg:
|
|
173
|
+
if ("Auth Model" in user_msg or "Pre-scan Stack Profile" in user_msg) and "Findings to Verify" not in user_msg:
|
|
113
174
|
return "# Mock Application Auth Model\n\n- Auth: Express session\n- Ownership: missing on /api/orders/:id\n"
|
|
114
175
|
if "dynamic query string concatenation or interpolation" in user_msg:
|
|
115
176
|
return """```json
|
|
@@ -196,6 +257,7 @@ module.exports = app;
|
|
|
196
257
|
```"""
|
|
197
258
|
return '{"status": "ok"}'
|
|
198
259
|
|
|
260
|
+
self.mock_agent_responses = mock_agent_responses
|
|
199
261
|
self.server, self.thread, self.base_url = start_mock_llm_server(
|
|
200
262
|
port=0, response_generator=mock_agent_responses
|
|
201
263
|
)
|
|
@@ -204,6 +266,91 @@ module.exports = app;
|
|
|
204
266
|
self.server.server_close()
|
|
205
267
|
shutil.rmtree(self.temp_dir, ignore_errors=True)
|
|
206
268
|
|
|
269
|
+
def _prescan(self) -> None:
|
|
270
|
+
import subprocess
|
|
271
|
+
|
|
272
|
+
prescan_script = PROJECT_ROOT / "assess" / "lsa" / "scripts" / "run_prescan.py"
|
|
273
|
+
res = subprocess.run(
|
|
274
|
+
[sys.executable, str(prescan_script), str(self.target_dir), str(self.out_dir)],
|
|
275
|
+
capture_output=True,
|
|
276
|
+
text=True,
|
|
277
|
+
)
|
|
278
|
+
self.assertEqual(res.returncode, 0, f"Pre-scan failed: {res.stderr}")
|
|
279
|
+
|
|
280
|
+
def test_run_stats_record_tokens_and_phases(self) -> None:
|
|
281
|
+
self._prescan()
|
|
282
|
+
client = LLMClient(endpoint=self.base_url, model="mock-model", api_key="test")
|
|
283
|
+
HeadlessAIRunner(self.target_dir, self.out_dir, client).run()
|
|
284
|
+
|
|
285
|
+
stats = json.loads((self.out_dir / "ai-run-stats.json").read_text(encoding="utf-8"))
|
|
286
|
+
self.assertEqual(stats["model"], "mock-model")
|
|
287
|
+
self.assertGreater(stats["totals"]["calls"], 0)
|
|
288
|
+
# the mock server reports 100 prompt / 50 completion tokens per call
|
|
289
|
+
self.assertEqual(stats["totals"]["prompt_tokens"], 100 * stats["totals"]["calls"])
|
|
290
|
+
self.assertEqual(stats["totals"]["completion_tokens"], 50 * stats["totals"]["calls"])
|
|
291
|
+
self.assertEqual(sum(ph["calls"] for ph in stats["phases"].values()), stats["totals"]["calls"])
|
|
292
|
+
self.assertIn("5-finding-verifier", stats["phases"])
|
|
293
|
+
# the mock answers the BOLA prompt with the auth-model markdown; the retry gets plain JSON (#319)
|
|
294
|
+
self.assertEqual(stats["retries"], {"4a-bola-auditor": ["routes-001"]})
|
|
295
|
+
self.assertEqual(stats["parse_failures"], {})
|
|
296
|
+
self.assertEqual(stats["anchoring"]["unanchored"], [])
|
|
297
|
+
|
|
298
|
+
def test_unparseable_verifier_reply_leaves_findings_unverified(self) -> None:
|
|
299
|
+
"""A broken verifier reply must not confirm findings (#286)."""
|
|
300
|
+
self.server.server_close()
|
|
301
|
+
|
|
302
|
+
verifier_prompts = []
|
|
303
|
+
|
|
304
|
+
def broken_verifier(messages, model):
|
|
305
|
+
if any("Findings to Verify & Challenge" in m["content"] for m in messages):
|
|
306
|
+
verifier_prompts.append(messages[-1]["content"])
|
|
307
|
+
return "I think these all look vulnerable."
|
|
308
|
+
return self.mock_agent_responses(messages, model)
|
|
309
|
+
|
|
310
|
+
self.server, self.thread, self.base_url = start_mock_llm_server(port=0, response_generator=broken_verifier)
|
|
311
|
+
self._prescan()
|
|
312
|
+
client = LLMClient(endpoint=self.base_url, model="mock-model", api_key="test")
|
|
313
|
+
logs = []
|
|
314
|
+
HeadlessAIRunner(self.target_dir, self.out_dir, client, step_callback=lambda st, msg: logs.append((st, msg))).run()
|
|
315
|
+
|
|
316
|
+
findings = json.loads((self.out_dir / "findings.json").read_text(encoding="utf-8"))["findings"]
|
|
317
|
+
self.assertGreaterEqual(len(findings), 1)
|
|
318
|
+
self.assertNotIn("confirmed", [f.get("status") for f in findings])
|
|
319
|
+
coverage = json.loads((self.out_dir / "coverage.json").read_text(encoding="utf-8"))
|
|
320
|
+
self.assertEqual(sorted(coverage["findings_without_verification"]), sorted(f["finding_id"] for f in findings))
|
|
321
|
+
stats = json.loads((self.out_dir / "ai-run-stats.json").read_text(encoding="utf-8"))
|
|
322
|
+
self.assertEqual(stats["parse_failures"]["5-finding-verifier"], ["verified-1"])
|
|
323
|
+
self.assertEqual(stats["retries"]["5-finding-verifier"], ["verified-1"])
|
|
324
|
+
# the verifier sees the code and the auth model, and is told not to assume unseen controls
|
|
325
|
+
self.assertEqual(len(verifier_prompts), 2, "one retry after the unparseable reply")
|
|
326
|
+
self.assertIn("Answer again with the JSON object only", verifier_prompts[1])
|
|
327
|
+
self.assertIn("code_context", verifier_prompts[0])
|
|
328
|
+
self.assertIn("Mock Application Auth Model", verifier_prompts[0])
|
|
329
|
+
self.assertIn("Never assume a control", verifier_prompts[0])
|
|
330
|
+
raw = self.out_dir / "ai-raw" / "5-finding-verifier-verified-1.txt"
|
|
331
|
+
self.assertEqual(raw.read_text(encoding="utf-8"), "I think these all look vulnerable.")
|
|
332
|
+
self.assertTrue(any(st == "warn" and "verified-1" in msg for st, msg in logs))
|
|
333
|
+
|
|
334
|
+
def test_failed_batch_call_is_recorded_and_run_continues(self) -> None:
|
|
335
|
+
"""A timeout on one batch must not abort the run or lose earlier phases (#286)."""
|
|
336
|
+
|
|
337
|
+
class TimeoutOnInjection(LLMClient):
|
|
338
|
+
def chat(self, messages, temperature=0.1, max_tokens=None): # noqa: ANN001
|
|
339
|
+
if "dynamic query string concatenation or interpolation" in messages[-1]["content"]:
|
|
340
|
+
raise RuntimeError("timed out")
|
|
341
|
+
return super().chat(messages, temperature=temperature, max_tokens=max_tokens)
|
|
342
|
+
|
|
343
|
+
self._prescan()
|
|
344
|
+
client = TimeoutOnInjection(endpoint=self.base_url, model="mock-model", api_key="test")
|
|
345
|
+
result = HeadlessAIRunner(self.target_dir, self.out_dir, client).run()
|
|
346
|
+
self.assertEqual(result["status"], "success")
|
|
347
|
+
|
|
348
|
+
stats = json.loads((self.out_dir / "ai-run-stats.json").read_text(encoding="utf-8"))
|
|
349
|
+
self.assertEqual(stats["call_errors"], {"4b-injection-auditor": {"injection-001": "timed out"}})
|
|
350
|
+
self.assertNotIn("4b-injection-auditor", stats["parse_failures"])
|
|
351
|
+
self.assertIn("5-finding-verifier", stats["phases"])
|
|
352
|
+
self.assertTrue((self.out_dir / "findings.json").is_file())
|
|
353
|
+
|
|
207
354
|
def test_headless_ai_runner_full_cycle(self) -> None:
|
|
208
355
|
# 1. Run deterministic pre-scan to populate OUT
|
|
209
356
|
prescan_script = PROJECT_ROOT / "assess" / "lsa" / "scripts" / "run_prescan.py"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hoanghainh/komagu",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Security-First Legacy Modernization Platform CLI (LSA Kit, IAM Perimeter, Cross-User Testing)",
|
|
5
5
|
"main": "remediate/authz/nodejs/src/index.js",
|
|
6
6
|
"types": "remediate/authz/nodejs/src/index.d.ts",
|