@hoanghainh/komagu 2.4.0 → 2.6.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 +41 -0
- package/LICENSE +202 -0
- package/README.md +1 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/ai-run-stats.json +87 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/auth-model.md +120 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/batches/index.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/batches/injection-001.json +383 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/batches/injection-002.json +205 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/batches/privilege-001.json +838 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/batches/privilege-002.json +124 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/batches/routes-001.json +778 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/batches/routes-002.json +546 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/batches/routes-003.json +661 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/batches/routes-004.json +659 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/batches/routes-005.json +612 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/batches/routes-006.json +106 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/coverage.json +19 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/bola-routes-001.json +516 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/bola-routes-002.json +332 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/bola-routes-003.json +218 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/bola-routes-004.json +561 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/bola-routes-005.json +316 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/bola-routes-006.json +14 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/inj-injection-001.json +782 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/inj-injection-002.json +418 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/priv-privilege-001.json +627 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/priv-privilege-002.json +253 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/verified-1.json +68 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/verified-10.json +68 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/verified-11.json +61 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/verified-12.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/verified-13.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/verified-14.json +66 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/verified-15.json +69 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/verified-16.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/verified-17.json +61 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/verified-18.json +69 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/verified-19.json +64 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/verified-2.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/verified-20.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/verified-21.json +61 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/verified-22.json +50 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/verified-3.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/verified-4.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/verified-5.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/verified-6.json +70 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/verified-7.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/verified-8.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings/verified-9.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/findings.json +4857 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/mass-assignment.candidates.json +232 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/report.ja.md +2112 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/report.md +2112 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/routes/routes-001.json +425 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/routes/routes-002.json +333 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/routes/routes-003.json +400 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/routes/routes-004.json +403 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/routes/routes-005.json +398 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/routes/routes-006.json +63 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/score.json +82 -0
- package/assess/lsa/examples/eval-run-6/gptoss-a/stack-profile.json +225 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/ai-run-stats.json +91 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/auth-model.md +120 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/batches/index.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/batches/injection-001.json +383 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/batches/injection-002.json +205 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/batches/privilege-001.json +838 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/batches/privilege-002.json +124 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/batches/routes-001.json +778 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/batches/routes-002.json +546 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/batches/routes-003.json +661 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/batches/routes-004.json +659 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/batches/routes-005.json +612 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/batches/routes-006.json +106 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/coverage.json +21 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/bola-routes-001.json +593 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/bola-routes-002.json +367 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/bola-routes-003.json +207 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/bola-routes-004.json +477 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/bola-routes-005.json +341 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/bola-routes-006.json +14 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/inj-injection-001.json +821 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/inj-injection-002.json +410 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/priv-privilege-001.json +649 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/priv-privilege-002.json +222 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/verified-1.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/verified-10.json +67 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/verified-11.json +68 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/verified-12.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/verified-13.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/verified-14.json +64 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/verified-15.json +70 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/verified-16.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/verified-17.json +61 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/verified-18.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/verified-19.json +65 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/verified-2.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/verified-20.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/verified-21.json +62 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/verified-22.json +50 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/verified-3.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/verified-4.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/verified-5.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/verified-6.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/verified-7.json +64 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/verified-8.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings/verified-9.json +60 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/findings.json +4966 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/mass-assignment.candidates.json +232 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/routes/routes-001.json +425 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/routes/routes-002.json +333 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/routes/routes-003.json +400 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/routes/routes-004.json +403 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/routes/routes-005.json +398 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/routes/routes-006.json +63 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/score.json +87 -0
- package/assess/lsa/examples/eval-run-6/gptoss-b/stack-profile.json +225 -0
- package/assess/lsa/examples/eval-run-6/ground-truth.json +851 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/ai-run-stats.json +96 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/auth-model.md +191 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/batches/index.json +60 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/batches/injection-001.json +383 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/batches/injection-002.json +205 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/batches/privilege-001.json +838 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/batches/privilege-002.json +124 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/batches/routes-001.json +778 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/batches/routes-002.json +546 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/batches/routes-003.json +661 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/batches/routes-004.json +659 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/batches/routes-005.json +612 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/batches/routes-006.json +106 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/coverage.json +31 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/bola-routes-001.json +527 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/bola-routes-002.json +568 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/bola-routes-003.json +467 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/bola-routes-004.json +342 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/bola-routes-005.json +693 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/bola-routes-006.json +16 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/inj-injection-001.json +1123 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/inj-injection-002.json +391 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/priv-privilege-001.json +726 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/priv-privilege-002.json +306 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/verified-1.json +137 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/verified-10.json +106 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/verified-11.json +81 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/verified-12.json +135 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/verified-13.json +135 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/verified-14.json +135 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/verified-15.json +110 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/verified-16.json +116 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/verified-17.json +141 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/verified-18.json +135 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/verified-19.json +140 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/verified-2.json +135 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/verified-20.json +82 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/verified-21.json +74 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/verified-22.json +99 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/verified-3.json +140 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/verified-4.json +145 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/verified-5.json +135 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/verified-6.json +135 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/verified-7.json +87 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/verified-8.json +125 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings/verified-9.json +135 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/findings.json +6989 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/mass-assignment.candidates.json +232 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/memory.log +36 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/routes/routes-001.json +425 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/routes/routes-002.json +333 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/routes/routes-003.json +400 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/routes/routes-004.json +403 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/routes/routes-005.json +398 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/routes/routes-006.json +63 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/score.json +106 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-nothink/stack-profile.json +225 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-partial/auth-model.md +517 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-partial/findings/bola-routes-001.json +5 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-partial/findings/bola-routes-002.json +428 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-partial/findings/bola-routes-003.json +388 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-partial/findings/bola-routes-004.json +463 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-partial/findings/bola-routes-005.json +5 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-partial/findings/bola-routes-006.json +67 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-partial/findings/inj-injection-001.json +964 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-partial/findings.partial.json +1872 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-partial/memory.log +194 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-partial/score.partial.json +108 -0
- package/assess/lsa/examples/eval-run-6/qwen-16k-partial/stack-profile.json +225 -0
- package/assess/lsa/scripts/detect_stack.py +13 -5
- package/assess/lsa/scripts/find_injection.py +35 -2
- package/assess/lsa/scripts/java_routes.py +323 -0
- package/assess/lsa/scripts/laravel_routes.py +102 -0
- package/assess/lsa/scripts/lsa_common.py +72 -2
- package/assess/lsa/scripts/map_routes.py +40 -161
- package/assess/lsa/scripts/merge_findings.py +2 -0
- package/assess/lsa/tests/EVAL-RESULTS.md +126 -0
- package/assess/lsa/tests/test_eval_run_6.py +104 -0
- package/assess/lsa/tests/test_java_legacy_realistic.py +453 -0
- package/assess/lsa/tests/test_laravel_routes.py +95 -0
- package/contracts/schemas/persona-suite.schema.json +38 -2
- package/examples/member-portal/backend-dotnet/Dockerfile +2 -1
- package/examples/member-portal/backend-spring/Dockerfile +2 -0
- package/examples/member-portal/docker-compose.yml +1 -1
- package/examples/member-portal/frontend/Dockerfile +3 -2
- package/examples/member-portal/frontend/package-lock.json +24 -656
- package/examples/member-portal/frontend/package.json +1 -1
- package/operate/ai/client.py +22 -9
- package/operate/ai/runner.py +24 -4
- package/operate/cli/__init__.py +1 -1
- package/operate/cli/commands/fix.py +4 -0
- package/operate/cli/commands/leak_check.py +75 -0
- package/operate/cli/commands/protect.py +32 -3
- package/operate/cli/commands/scan.py +8 -1
- package/operate/cli/commands/test.py +131 -15
- package/operate/cli/main.py +5 -3
- package/operate/cross_user_tester/persona_matrix.py +118 -25
- package/operate/cross_user_tester/runner.py +190 -7
- package/operate/cross_user_tester/session_config.py +180 -0
- package/operate/cross_user_tester/session_http.py +288 -0
- package/operate/devsecops/leak_check.py +314 -0
- package/operate/remediator/runner.py +18 -1
- package/operate/remediator/strategies.py +2 -2
- package/operate/simulate/scenarios.py +2 -2
- package/operate/tests/mock_legacy_session_app.py +160 -0
- package/operate/tests/test_ai_reasoning_effort.py +65 -0
- package/operate/tests/test_ai_route_inventory.py +53 -0
- package/operate/tests/test_cross_user_session.py +565 -0
- package/operate/tests/test_docker_packaging.py +3 -3
- package/operate/tests/test_headless_ai.py +6 -0
- package/operate/tests/test_leak_check.py +250 -0
- package/operate/tests/test_legacy_session_edge.py +192 -0
- package/operate/tests/test_pilot_safeguards.py +245 -0
- package/operate/tests/test_remediator_java_legacy.py +330 -0
- package/package.json +2 -1
- package/remediate/authz/patcher.py +120 -11
- package/remediate/authz/tests/test_bola_patcher.py +4 -1
- package/remediate/edge/coraza/Caddyfile +1 -1
- package/remediate/edge/coraza/Dockerfile +9 -0
- package/remediate/edge/coraza/rules/coraza-crs-custom.conf +12 -0
- package/remediate/edge/coraza/virtual_patcher.py +24 -0
- package/remediate/edge/docker-compose.yml +7 -2
- package/remediate/edge/kong/kong-legacy-session.yaml +51 -0
- package/remediate/edge/mock-backend/Dockerfile +2 -0
- package/remediate/edge/tests/test_edge_security.py +21 -1
- package/remediate/resilience/terraform/backup_worm.tf +39 -1
- package/remediate/resilience/terraform/main.tf +15 -3
- package/remediate/resilience/terraform/variables.tf +11 -0
- package/remediate/resilience/tests/test_resilience.py +16 -0
|
@@ -19,8 +19,12 @@ from pathlib import Path
|
|
|
19
19
|
from typing import Dict, List, Optional, Tuple
|
|
20
20
|
|
|
21
21
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
22
|
-
from
|
|
23
|
-
|
|
22
|
+
from java_routes import (build_class_index, collect_spring_beans, entry_method_line, java_declared, # noqa: E402
|
|
23
|
+
struts2_action_extension,
|
|
24
|
+
struts_modules, xml_declared)
|
|
25
|
+
from lsa_common import (JAVA_WEB_ROOT, KIT_VERSION, call_args, compile_id_patterns, is_id_name, # noqa: E402
|
|
26
|
+
iter_source_files, line_of, load_config, read_text, split_top, write_json)
|
|
27
|
+
from laravel_routes import resolve_laravel_handlers, route_action # noqa: E402
|
|
24
28
|
from nextjs_routes import middleware_note, nextjs_declared # noqa: E402
|
|
25
29
|
from privilege import annotate_privilege # noqa: E402
|
|
26
30
|
|
|
@@ -250,8 +254,6 @@ def segments(text: str, decl_rx: re.Pattern) -> List[Tuple[re.Match, int, int]]:
|
|
|
250
254
|
# Declared-route extractors
|
|
251
255
|
# --------------------------------------------------------------------------------------
|
|
252
256
|
PHP_METHOD = re.compile(r"^[ \t]*(?:(public|protected|private)\s+)?(?:static\s+)?function\s+&?(\w+)\s*\(", re.M)
|
|
253
|
-
JAVA_METHOD_ANN = re.compile(r"@(RequestMapping|GetMapping|PostMapping|PutMapping|DeleteMapping|PatchMapping|Path)\b\s*(\(([^)]*)\))?")
|
|
254
|
-
JAVA_HTTP_ANN = re.compile(r"@(GET|POST|PUT|DELETE|PATCH)\b")
|
|
255
257
|
CS_ACTION = re.compile(r"^[ \t]*public\s+(?!record\b|class\b)(?:virtual\s+|async\s+|override\s+|static\s+)*[A-Za-z0-9_<>., \[\]?]+\s+(\w+)\s*\(([^)]*)\)", re.M)
|
|
256
258
|
VB_ACTION = re.compile(r"^[ \t]*Public\s+(?:Overridable\s+|Async\s+)*Function\s+(\w+)\s*\(([^)]*)\)\s+As\s+[\w.(<>) ]+", re.M | re.I)
|
|
257
259
|
WEBMETHOD = re.compile(r"(\[WebMethod[^\]]*\]|<WebMethod[^>]*>|\[OperationContract[^\]]*\]|<OperationContract[^>]*>)[\s\S]{0,300}?"
|
|
@@ -267,7 +269,7 @@ def php_declared(c: Collector, rel: str, text: str, stacks: set) -> bool:
|
|
|
267
269
|
end = text.find(";", m.end())
|
|
268
270
|
stmt = text[m.start(): end if end > 0 else m.end() + 200]
|
|
269
271
|
c.add(stack="php-laravel", kind="declared_route", method=[verb.upper()], path="/" + path.lstrip("/"),
|
|
270
|
-
handler={"file": rel, "line": line, "symbol":
|
|
272
|
+
handler={"file": rel, "line": line, "symbol": route_action(stmt)}, id_params=c.path_params(path),
|
|
271
273
|
guard_hints=c.guards(stmt, line) + c.guards(text[:m.start()][-3000:], 1)[:2],
|
|
272
274
|
data_hints=[], notes="handler is in the controller named in this route")
|
|
273
275
|
found = True
|
|
@@ -305,107 +307,6 @@ def php_declared(c: Collector, rel: str, text: str, stacks: set) -> bool:
|
|
|
305
307
|
return found
|
|
306
308
|
|
|
307
309
|
|
|
308
|
-
def java_declared(c: Collector, rel: str, text: str) -> bool:
|
|
309
|
-
found = False
|
|
310
|
-
cls_prefix = ""
|
|
311
|
-
cm = re.search(r"@(RequestMapping|Path)\s*\(\s*(?:value\s*=\s*)?\"([^\"]*)\"[^)]*\)\s*(?:@\w+(?:\([^)]*\))?\s*)*(?:public\s+)?(?:abstract\s+)?class", text)
|
|
312
|
-
if cm:
|
|
313
|
-
cls_prefix = cm.group(2)
|
|
314
|
-
is_servlet = re.search(r"extends\s+HttpServlet", text)
|
|
315
|
-
if is_servlet:
|
|
316
|
-
ws = re.search(r"@WebServlet\s*\(\s*(?:urlPatterns\s*=\s*|value\s*=\s*)?\{?\s*\"([^\"]+)\"", text)
|
|
317
|
-
for m in re.finditer(r"(?:public|protected)\s+void\s+(doGet|doPost|doPut|doDelete|service)\s*\(", text):
|
|
318
|
-
start = m.start()
|
|
319
|
-
nxt = re.search(r"\n\s*(?:public|protected|private)\s+[\w<>\[\]]+\s+\w+\s*\(", text[m.end():])
|
|
320
|
-
body = text[start: m.end() + (nxt.start() if nxt else len(text))]
|
|
321
|
-
line = line_of(text, start)
|
|
322
|
-
verb = {"doGet": "GET", "doPost": "POST", "doPut": "PUT", "doDelete": "DELETE"}.get(m.group(1), "ANY")
|
|
323
|
-
c.add(stack="java-servlet", kind="declared_route", method=[verb],
|
|
324
|
-
path=ws.group(1) if ws else "<see web.xml servlet-mapping>",
|
|
325
|
-
handler={"file": rel, "line": line, "symbol": m.group(1)},
|
|
326
|
-
id_params=c.inputs("java", body, line), guard_hints=c.guards(body, line),
|
|
327
|
-
data_hints=c.data_ops(body, line), notes="" if ws else "resolve path from web.xml")
|
|
328
|
-
found = True
|
|
329
|
-
return found
|
|
330
|
-
anns = list(JAVA_METHOD_ANN.finditer(text))
|
|
331
|
-
for m in anns:
|
|
332
|
-
if cm and m.start() == cm.start():
|
|
333
|
-
continue
|
|
334
|
-
after = text[m.end(): m.end() + 600]
|
|
335
|
-
# skip class-level annotation
|
|
336
|
-
if re.match(r"\s*(?:@\w+(?:\([^)]*\))?\s*)*(?:public\s+)?(?:abstract\s+)?class\b", after):
|
|
337
|
-
continue
|
|
338
|
-
sig = re.search(r"(?:(?:public|protected)\s+[\w<>\[\],.? ]+|fun|(?:override|suspend)\s+fun)\s+(\w+)\s*\(", after)
|
|
339
|
-
if not sig:
|
|
340
|
-
continue
|
|
341
|
-
before = text[:m.start()]
|
|
342
|
-
cls_m = list(re.finditer(r"@(?:RequestMapping|Path)\s*\(\s*(?:value\s*=\s*|path\s*=\s*)?\"([^\"]*)\"[^)]*\)[\s\S]{0,300}?(?:class\b)", before))
|
|
343
|
-
method_cls_prefix = cls_m[-1].group(1) if cls_m else cls_prefix
|
|
344
|
-
args = m.group(3) or ""
|
|
345
|
-
pv = re.search(r"(?:value\s*=\s*|path\s*=\s*)?\{?\s*\"([^\"]*)\"", args)
|
|
346
|
-
sub = pv.group(1) if pv else ""
|
|
347
|
-
verbs = re.findall(r"RequestMethod\.(\w+)", args)
|
|
348
|
-
ann = m.group(1)
|
|
349
|
-
if ann.endswith("Mapping") and ann != "RequestMapping":
|
|
350
|
-
verbs = [ann[:-7].upper()]
|
|
351
|
-
if ann == "Path":
|
|
352
|
-
hv = JAVA_HTTP_ANN.search(text[max(0, m.start() - 200): m.end() + 200])
|
|
353
|
-
verbs = [hv.group(1)] if hv else ["ANY"]
|
|
354
|
-
path = "/" + "/".join(p.strip("/") for p in (method_cls_prefix, sub) if p.strip("/"))
|
|
355
|
-
nxt = re.search(r"\n\s*@(RequestMapping|GetMapping|PostMapping|PutMapping|DeleteMapping|PatchMapping|Path)\b(?!Variable|Param)", text[m.end():])
|
|
356
|
-
body = text[m.start(): m.end() + (nxt.start() if nxt else len(text))]
|
|
357
|
-
line = line_of(text, m.start())
|
|
358
|
-
c.add(stack="java-spring-mvc" if ann != "Path" else "java-jaxrs", kind="declared_route", method=verbs or ["ANY"],
|
|
359
|
-
path=path, handler={"file": rel, "line": line, "symbol": sig.group(1)},
|
|
360
|
-
id_params=c.path_params(path) + c.inputs("java", body, line),
|
|
361
|
-
guard_hints=c.guards(text[:m.start()][-1500:], 1)[:2] + c.guards(body, line),
|
|
362
|
-
data_hints=c.data_ops(body, line), notes="")
|
|
363
|
-
found = True
|
|
364
|
-
# Struts 1 / 2 Action classes without annotations are mapped from XML (see xml_declared)
|
|
365
|
-
return found
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
def xml_declared(c: Collector, rel: str, text: str, class_index: Dict[str, str]) -> bool:
|
|
369
|
-
found = False
|
|
370
|
-
if rel.endswith("web.xml"):
|
|
371
|
-
servlets = dict(re.findall(r"<servlet>\s*<servlet-name>\s*([^<]+?)\s*</servlet-name>[\s\S]*?<servlet-class>\s*([^<]+?)\s*</servlet-class>", text))
|
|
372
|
-
for m in re.finditer(r"<servlet-mapping>\s*<servlet-name>\s*([^<]+?)\s*</servlet-name>\s*<url-pattern>\s*([^<]+?)\s*</url-pattern>", text):
|
|
373
|
-
name, pattern = m.group(1), m.group(2)
|
|
374
|
-
cls = servlets.get(name, "")
|
|
375
|
-
hfile = class_index.get(cls.split(".")[-1], rel)
|
|
376
|
-
c.add(stack="java-servlet", kind="declared_route", method=["ANY"], path=pattern,
|
|
377
|
-
handler={"file": hfile, "line": 1, "symbol": cls or name}, id_params=[], guard_hints=[],
|
|
378
|
-
data_hints=[], notes=f"mapped in {rel}:{line_of(text, m.start())}")
|
|
379
|
-
found = True
|
|
380
|
-
if "struts-config" in rel:
|
|
381
|
-
for m in re.finditer(r"<action\b([^>]*)>", text):
|
|
382
|
-
attrs = dict(re.findall(r"(\w+)\s*=\s*\"([^\"]*)\"", m.group(1)))
|
|
383
|
-
if "path" not in attrs:
|
|
384
|
-
continue
|
|
385
|
-
cls = attrs.get("type", "")
|
|
386
|
-
hfile = class_index.get(cls.split(".")[-1], rel)
|
|
387
|
-
note = f"declared in {rel}:{line_of(text, m.start())}"
|
|
388
|
-
if attrs.get("parameter"):
|
|
389
|
-
note += f"; DispatchAction parameter '{attrs['parameter']}' -> one endpoint per method"
|
|
390
|
-
c.add(stack="java-struts1", kind="declared_route", method=["ANY"], path=attrs["path"] + ".do",
|
|
391
|
-
handler={"file": hfile, "line": 1, "symbol": cls}, id_params=[], guard_hints=[], data_hints=[], notes=note)
|
|
392
|
-
found = True
|
|
393
|
-
if rel.endswith("struts.xml"):
|
|
394
|
-
for pm in re.finditer(r"<package\b([^>]*)>([\s\S]*?)</package>", text):
|
|
395
|
-
pattrs = dict(re.findall(r"(\w+)\s*=\s*\"([^\"]*)\"", pm.group(1)))
|
|
396
|
-
ns = pattrs.get("namespace", "/").rstrip("/")
|
|
397
|
-
for am in re.finditer(r"<action\b([^>]*)>", pm.group(2)):
|
|
398
|
-
attrs = dict(re.findall(r"(\w+)\s*=\s*\"([^\"]*)\"", am.group(1)))
|
|
399
|
-
cls = attrs.get("class", "")
|
|
400
|
-
hfile = class_index.get(cls.split(".")[-1], rel)
|
|
401
|
-
c.add(stack="java-struts2", kind="declared_route", method=["ANY"], path=f"{ns}/{attrs.get('name', '?')}.action",
|
|
402
|
-
handler={"file": hfile, "line": 1, "symbol": f"{cls}#{attrs.get('method', 'execute')}"},
|
|
403
|
-
id_params=[], guard_hints=[], data_hints=[],
|
|
404
|
-
notes=f"declared in {rel}:{line_of(text, pm.start() + am.start())}; action setters are inputs")
|
|
405
|
-
found = True
|
|
406
|
-
return found
|
|
407
|
-
|
|
408
|
-
|
|
409
310
|
def dotnet_declared(c: Collector, rel: str, text: str) -> bool:
|
|
410
311
|
found = False
|
|
411
312
|
cm = re.search(r"(?:\[ApiController\][\s\S]{0,100}?)?class\s+(\w+?)Controller\s*(?::\s*(\w+))?|Class\s+(\w+?)Controller[\s\S]{0,80}?Inherits\s+(\w+)", text, re.I)
|
|
@@ -560,48 +461,6 @@ GO_FRAMEWORKS = [("go-gin", "github.com/gin-gonic/gin"), ("go-echo", "github.com
|
|
|
560
461
|
GO_PATTERN = re.compile(r"^(?:([A-Z]+)\s+)?(/\S*)$")
|
|
561
462
|
|
|
562
463
|
|
|
563
|
-
def call_args(text: str, open_idx: int, limit: int = 4000) -> Tuple[str, int]:
|
|
564
|
-
"""Return (argument text, index after the closing paren) for the call whose '(' is at open_idx."""
|
|
565
|
-
depth, i, end = 0, open_idx, min(len(text), open_idx + limit)
|
|
566
|
-
while i < end:
|
|
567
|
-
ch = text[i]
|
|
568
|
-
if ch in "\"`":
|
|
569
|
-
close = text.find(ch, i + 1)
|
|
570
|
-
while ch == '"' and close != -1 and text[close - 1] == "\\":
|
|
571
|
-
close = text.find(ch, close + 1)
|
|
572
|
-
i = close if close != -1 else end
|
|
573
|
-
elif ch in "([{":
|
|
574
|
-
depth += 1
|
|
575
|
-
elif ch in ")]}":
|
|
576
|
-
depth -= 1
|
|
577
|
-
if depth == 0:
|
|
578
|
-
return text[open_idx + 1:i], i + 1
|
|
579
|
-
i += 1
|
|
580
|
-
return text[open_idx + 1:end], end
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
def split_top(args: str) -> List[str]:
|
|
584
|
-
"""Split call arguments on top-level commas."""
|
|
585
|
-
parts, depth, cur, quote = [], 0, [], None
|
|
586
|
-
for ch in args:
|
|
587
|
-
if quote:
|
|
588
|
-
quote = None if ch == quote else quote
|
|
589
|
-
elif ch in "\"`":
|
|
590
|
-
quote = ch
|
|
591
|
-
elif ch in "([{":
|
|
592
|
-
depth += 1
|
|
593
|
-
elif ch in ")]}":
|
|
594
|
-
depth -= 1
|
|
595
|
-
elif ch == "," and depth == 0:
|
|
596
|
-
parts.append("".join(cur).strip())
|
|
597
|
-
cur = []
|
|
598
|
-
continue
|
|
599
|
-
cur.append(ch)
|
|
600
|
-
if "".join(cur).strip():
|
|
601
|
-
parts.append("".join(cur).strip())
|
|
602
|
-
return parts
|
|
603
|
-
|
|
604
|
-
|
|
605
464
|
def guards_before(c: Collector, text: str, start: int, end: int, span: int = 500) -> List[Dict]:
|
|
606
465
|
ws = max(0, start - span)
|
|
607
466
|
return c.guards(text[ws:end], line_of(text, ws))
|
|
@@ -712,6 +571,9 @@ def file_endpoint(c: Collector, rel: str, text: str, lang: str, ext: str, target
|
|
|
712
571
|
wr = wr.strip("/") + "/"
|
|
713
572
|
if rel.startswith(wr):
|
|
714
573
|
path = "/" + rel[len(wr):]
|
|
574
|
+
root = JAVA_WEB_ROOT.search(rel) if lang == "java" and path == "/" + rel else None
|
|
575
|
+
if root:
|
|
576
|
+
path = "/" + rel[root.end():]
|
|
715
577
|
stack = {"php": "php", "java": "java-jsp", "dotnet": "dotnet-webforms" if ext in (".aspx",) else "dotnet-handlers",
|
|
716
578
|
"asp": "classic-asp", "perl": "perl-cgi", "cfml": "coldfusion"}.get(lang, lang)
|
|
717
579
|
if ext in (".asmx", ".svc"):
|
|
@@ -775,7 +637,11 @@ def _scan_route_file_batch(args: Tuple[List[Tuple[str, str]], Dict, set, str]) -
|
|
|
775
637
|
declared = go_declared(c, rel, t)
|
|
776
638
|
if declared or ext not in FILE_ENDPOINT_EXTS:
|
|
777
639
|
continue
|
|
778
|
-
if
|
|
640
|
+
if ext == ".jsp":
|
|
641
|
+
# Tomcat serves any JSP outside WEB-INF, whatever its directory is called; those inside are only forwarded to
|
|
642
|
+
if "/WEB-INF/" in "/" + rel:
|
|
643
|
+
continue
|
|
644
|
+
elif INCLUDE_DIR_RE.search(rel.rsplit("/", 1)[0] + "/" if "/" in rel else ""):
|
|
779
645
|
continue
|
|
780
646
|
if framework_php and lang == "php" and re.search(r"(^|/)(application|system|app|vendor|routes|resources|storage|bootstrap)/", rel):
|
|
781
647
|
continue
|
|
@@ -791,15 +657,22 @@ def map_routes(target: Path, config: Dict, stack_ids: Optional[set] = None, work
|
|
|
791
657
|
files = list(iter_source_files(target, config))
|
|
792
658
|
stack_ids = stack_ids or set()
|
|
793
659
|
framework_php = bool(stack_ids & {"php-codeigniter", "php-cakephp", "php-laravel", "php-zend1"})
|
|
794
|
-
class_index =
|
|
660
|
+
class_index = build_class_index(rel for _, rel in files)
|
|
795
661
|
texts: Dict[str, str] = {}
|
|
796
662
|
|
|
797
|
-
# 1. XML pre-pass (web.xml /
|
|
798
|
-
for p, rel in files
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
663
|
+
# 1. XML pre-pass (web.xml / Struts / Spring declarative mappings)
|
|
664
|
+
xml_texts = {rel: read_text(p, enc) for p, rel in files if p.suffix.lower() == ".xml"}
|
|
665
|
+
modules: Dict[str, str] = {}
|
|
666
|
+
beans: Dict[str, str] = {}
|
|
667
|
+
for rel, t in xml_texts.items():
|
|
668
|
+
if rel.endswith("web.xml"):
|
|
669
|
+
modules.update(struts_modules(rel, t))
|
|
670
|
+
if "<bean" in t:
|
|
671
|
+
beans.update(collect_spring_beans(t))
|
|
672
|
+
extension = struts2_action_extension(target, config, xml_texts.values(), enc)
|
|
673
|
+
for rel, t in xml_texts.items():
|
|
674
|
+
if "<servlet" in t or "<action" in t or "UrlHandlerMapping" in t:
|
|
675
|
+
xml_declared(c, rel, t, class_index, modules, beans, extension)
|
|
803
676
|
|
|
804
677
|
xml_items = list(c.items)
|
|
805
678
|
|
|
@@ -865,20 +738,26 @@ def map_routes(target: Path, config: Dict, stack_ids: Optional[set] = None, work
|
|
|
865
738
|
# post-pass 1: routes declared in XML -> enrich from the handler class file
|
|
866
739
|
for it in c.items:
|
|
867
740
|
hf = it["handler"]["file"]
|
|
741
|
+
if it["kind"] == "declared_route" and it["handler"]["line"] == 1 and hf in texts and hf.endswith(".java"):
|
|
742
|
+
it["handler"]["line"] = entry_method_line(texts[hf], it["handler"].get("symbol")) or 1
|
|
868
743
|
if it["kind"] == "declared_route" and not it["id_params"] and hf in texts and hf.endswith(".java"):
|
|
869
744
|
t = texts[hf]
|
|
870
745
|
it["id_params"] = c.inputs("java", t)
|
|
871
746
|
it["guard_hints"] = it["guard_hints"] or c.guards(t)
|
|
872
747
|
it["data_hints"] = it["data_hints"] or c.data_ops(t)
|
|
748
|
+
# post-pass 1b: Laravel route declarations -> the controller method they name (#380)
|
|
749
|
+
c.items = resolve_laravel_handlers(c.items, target, enc, c, [rel for _, rel in files])
|
|
873
750
|
# post-pass 2: servlet classes without @WebServlet -> take the path from web.xml
|
|
874
|
-
xml_paths
|
|
875
|
-
|
|
751
|
+
xml_paths: Dict[str, List[str]] = {}
|
|
752
|
+
for it in c.items:
|
|
753
|
+
if it["stack"] == "java-servlet" and it["notes"].startswith("mapped in "):
|
|
754
|
+
xml_paths.setdefault(it["handler"]["file"], []).append(it["path"])
|
|
876
755
|
keep = []
|
|
877
756
|
for it in c.items:
|
|
878
757
|
if it["path"] == "<see web.xml servlet-mapping>" and it["handler"]["file"] in xml_paths:
|
|
879
|
-
it
|
|
880
|
-
|
|
881
|
-
|
|
758
|
+
keep.extend(dict(it, path=mapped, notes="path from web.xml") for mapped in xml_paths[it["handler"]["file"]])
|
|
759
|
+
else:
|
|
760
|
+
keep.append(it)
|
|
882
761
|
mapped_with_methods = {it["handler"]["file"] for it in keep if it["notes"] == "path from web.xml"}
|
|
883
762
|
keep = [it for it in keep if not (it["notes"].startswith("mapped in ") and it["handler"]["file"] in mapped_with_methods)]
|
|
884
763
|
for n, it in enumerate(keep, 1):
|
|
@@ -403,6 +403,8 @@ def main() -> int:
|
|
|
403
403
|
"injection_candidates_without_verdict", "bfla_routes_without_verdict",
|
|
404
404
|
"mass_candidates_without_verdict", "validation_errors",
|
|
405
405
|
"new_issues_from_verifier"))
|
|
406
|
+
for error in cov.get("validation_errors") or []:
|
|
407
|
+
print(f"validation error (finding not merged): {error}", file=sys.stderr)
|
|
406
408
|
print(json.dumps({"summary": summary, "coverage_gaps": gaps}, indent=2))
|
|
407
409
|
return 1 if gaps else 0
|
|
408
410
|
|
|
@@ -333,3 +333,129 @@ and their tests still score against them.
|
|
|
333
333
|
Run 5 rescored against the new ground truth (for comparison only; findings unchanged): BOLA 28 / 29
|
|
334
334
|
(run A) and 16 / 29 (run B), precision 0.859 and 0.826, still no false positive on SAFE markers. Every
|
|
335
335
|
new marker was already reported by both runs; Tier 1 lists all of them as BOLA route candidates.
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## Run 6 — 2026-11-11, platform 2.5.0, kit 0.3.1 (#323 part 4: new harness, 121-item ground truth, redaction)
|
|
340
|
+
|
|
341
|
+
Issue #323. First run on the harness from #334 (no code fields in 4a / 4b templates, quote repair,
|
|
342
|
+
findings from flagged verdicts), the fixtures and ground truth from #336 (121 items: 29 BOLA, 34
|
|
343
|
+
SQL/NoSQL injection, 10 BFLA, 15 mass assignment, 33 SAFE), and secret redaction of code sent to the
|
|
344
|
+
model (v2.4.0). Outputs are archived in [`assess/lsa/examples/eval-run-6/`](../examples/eval-run-6/).
|
|
345
|
+
Runs 4–5 are not directly comparable; the Run 5 rows below are the Run 5 findings rescored against the
|
|
346
|
+
new ground truth.
|
|
347
|
+
|
|
348
|
+
| Item | Value |
|
|
349
|
+
|---|---|
|
|
350
|
+
| Pipeline | `komagu scan <repo> --no-cache --headless-ai http://localhost:11434/v1 --model <model> --ai-timeout 3600` from `main` at v2.5.0 |
|
|
351
|
+
| Hosting | Ollama 0.40.16 on the same Apple M4 Pro, 24 GB RAM, 100% GPU, local only; `gpt-oss:20b` on the llama.cpp runner with 32,768 context (as in Runs 4–5) |
|
|
352
|
+
| Scoring | `tests/score_findings.py --verdicts` (tolerance 3 lines) |
|
|
353
|
+
| Machine | No other heavy work while measuring; up to 5.7 GB swap in use during the `gpt-oss` runs |
|
|
354
|
+
|
|
355
|
+
### Results after verification (`gpt-oss:20b`)
|
|
356
|
+
|
|
357
|
+
| Run | BOLA | Injection | BFLA marker | Mass marker | BFLA verdict | Mass verdict | FP on safe code | Precision | Rejected | Retries · repaired · parse fail | From verdicts | Min · calls · tokens |
|
|
358
|
+
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
359
|
+
| Run 5 A, rescored | 28/29 | 34/34 | 10/10 | 10/15 | 10/10 | 15/15 | 0 | 0.859 | 2 | 0 · — · 0 | — | 83 · 32 · 328K |
|
|
360
|
+
| Run 5 B, rescored | 16/29 | 32/34 | 9/10 | 14/15 | 10/10 | 15/15 | 0 | 0.826 | 3 | 1 · — · 1 | — | 86 · 31 · 369K |
|
|
361
|
+
| **Run 6 A** | **26/29** | **34/34** | **10/10** | **14/15** | **10/10** | **15/15** | **1** | **0.813** | 2 | 0 · 0 · 0 | 9 (5 BOLA, 4 mass) | 40 · 33 · 315K |
|
|
362
|
+
| **Run 6 B** | **26/29** | **34/34** | **10/10** | **14/15** | **10/10** | **14/15** | **1** | **0.787** | 1 | 1 · 0 · 0 | 14 (5 BOLA, 9 mass) | 56 · 34 · 373K |
|
|
363
|
+
|
|
364
|
+
- **The two runs now agree.** BOLA is 26 / 29 in both (Run 5: 28 and 16). No batch was lost: no parse
|
|
365
|
+
failure, no call error, no batch needed the quote repair, and the one retry (B, `injection-001`)
|
|
366
|
+
succeeded. The `routes-001` batch that failed in Run 4 B and Run 5 B now parses.
|
|
367
|
+
- **Mass assignment:** 14 / 15 as located findings in both runs (Run 5: 10 and 14). Findings written from
|
|
368
|
+
flagged verdicts closed the gap: the four candidates run A of Run 5 judged `vulnerable` without a
|
|
369
|
+
finding (`profile.php` ×2, `CustomerController.cs` ×2) are findings in both runs.
|
|
370
|
+
`php-plain/register.php:4` is still missed. In A the verifier rejected it because the INSERT "only"
|
|
371
|
+
writes `email`, `name` and `role`, missing that `extract($_POST)` can overwrite `$role` (the
|
|
372
|
+
fixture change in #336); in B the auditor did not flag it.
|
|
373
|
+
- **BFLA:** 10 / 10 in both runs, located and by verdict.
|
|
374
|
+
- **Misses:** `perl-cgi/cgi-bin/order.cgi:19` (both runs, missed since Run 4), `node-express/routes/orders.js:29`
|
|
375
|
+
(both), `java-legacy/.../OrderServlet.java:15` (A), `python-flask/app.py:12` (B).
|
|
376
|
+
- **False positives on SAFE markers: 1 per run** (Run 5: 0), both injection:
|
|
377
|
+
- A: `classic-asp/shop/member.asp:6`. The `UPDATE` starts on line 6, whose `name` value is escaped;
|
|
378
|
+
the concatenated `member_id` is on line 7, which is also reported. The finding is anchored one line
|
|
379
|
+
early rather than describing a new flaw, but it is scored as a false positive.
|
|
380
|
+
- B: `python-flask/app.py:12`. `order_id` is an `<int:...>` path parameter, so the `%` formatting
|
|
381
|
+
cannot inject. The model reported the route's missing ownership check as SQL injection, so the
|
|
382
|
+
BOLA marker on the same line was missed.
|
|
383
|
+
- **Precision 0.81 / 0.79.** Five of the unmatched findings per run come from verdicts: the BOLA
|
|
384
|
+
auditor flagged the Laravel routes `/register`, `/posts`, `/teams`, `/account/rename` and
|
|
385
|
+
`/account/profile`, and the generated findings point at their declarations in `routes/web.php`
|
|
386
|
+
(lines 6–10). The triage after Run 5 found these routes act on `$request->user()` or touch no
|
|
387
|
+
record, so they are likely false positives. Without them precision would be 0.85 / 0.83.
|
|
388
|
+
- **Duration:** 40 and 56 minutes (Run 5: 83 and 86). The model wrote fewer tokens (84K and 118K
|
|
389
|
+
completion tokens against about 109K and 111K); the Ollama version also changed (0.40.13 → 0.40.16).
|
|
390
|
+
- **`ai-raw/`** is written only for batches that needed a repair or failed to parse. None did, so neither
|
|
391
|
+
run has an `ai-raw/` folder.
|
|
392
|
+
|
|
393
|
+
### Did redaction reduce recall?
|
|
394
|
+
|
|
395
|
+
No, and on this repo it cannot. Running the redaction used for model context
|
|
396
|
+
(`assess/lsa/scripts/redaction.py`, `redact_lines`) over every file of the eval repo changes **0 lines**:
|
|
397
|
+
the fixtures contain no secrets in the patterns it covers. The prompts sent in Run 6 are therefore the
|
|
398
|
+
same code as without redaction. The BOLA difference from Run 5 A (28 → 26) is run-to-run variance (the
|
|
399
|
+
same model put 16 in Run 5 B). This also means the eval repo does not test whether redaction costs
|
|
400
|
+
recall on code that does contain secrets; that is covered only by the unit tests
|
|
401
|
+
(`operate/tests/test_ai_redaction.py`, `test_no_planted_secret_reaches_the_model`).
|
|
402
|
+
|
|
403
|
+
### `qwen3.5:9b` with a 16K context (not completed)
|
|
404
|
+
|
|
405
|
+
To fit the 24 GB machine, `qwen3.5:9b` was run with `num_ctx 16384` (a local tag built
|
|
406
|
+
`FROM qwen3.5:9b` with `PARAMETER num_ctx 16384`, MLX runner), same command and timeout as above. It did
|
|
407
|
+
not finish: the run was stopped by hand after 3 h 14 min, while the second injection batch was still
|
|
408
|
+
running and before the privilege (4c) and verifier (5) phases. The partial output is archived as
|
|
409
|
+
[`eval-run-6/qwen-16k-partial/`](../examples/eval-run-6/qwen-16k-partial/) (raw batch findings, the
|
|
410
|
+
per-minute memory log, and an indicative score of the unverified findings).
|
|
411
|
+
|
|
412
|
+
| Phase / batch | Time | Result |
|
|
413
|
+
|---|---|---|
|
|
414
|
+
| Stack detection | 2 min | done |
|
|
415
|
+
| BOLA `routes-001` (15 routes) | 60 min | **timed out**, empty |
|
|
416
|
+
| BOLA `routes-002` … `routes-004` | 5, 3 and 2 min | 8, 7 and 9 findings |
|
|
417
|
+
| BOLA `routes-005` | 60 min | **timed out**, empty |
|
|
418
|
+
| BOLA `routes-006` | 1 min | 1 finding |
|
|
419
|
+
| Injection `injection-001` | 11 min | 20 findings |
|
|
420
|
+
| Injection `injection-002` | 49 min+ | still running when stopped |
|
|
421
|
+
|
|
422
|
+
- **Memory:** the runner used 8.1 GB for the first hour, then grew to 13, 18 and finally 20 GB (from about
|
|
423
|
+
10:55, after the first timeout). Swap rose from about 4 GB (other work on the machine) to 12.4 GB of
|
|
424
|
+
13.3 GB; the run was stopped there to keep the machine usable. `gpt-oss:20b` stayed under 5.7 GB of
|
|
425
|
+
swap on the same machine.
|
|
426
|
+
- **Timeouts:** a batch that hits `--ai-timeout` is not retried (only empty or unparseable replies are),
|
|
427
|
+
so two of six BOLA batches were lost outright. The quick batches suggest the model can do the task;
|
|
428
|
+
the timed-out ones look like long reasoning that does not end within the context.
|
|
429
|
+
- **Indicative score** (findings before verification, from the batches that finished): BOLA 12/29,
|
|
430
|
+
injection 22/34, BFLA and mass assignment not reached, precision 0.73, no finding on a SAFE marker.
|
|
431
|
+
These numbers are not comparable with the `gpt-oss` rows: two BOLA batches, half the injection
|
|
432
|
+
candidates and the verifier are missing.
|
|
433
|
+
|
|
434
|
+
Thinking looks like the cause: on this 24 GB machine a 16K context with default reasoning is **not**
|
|
435
|
+
a working setting. The run below with reasoning off is.
|
|
436
|
+
|
|
437
|
+
### `qwen3.5:9b` with a 16K context and reasoning off (#382)
|
|
438
|
+
|
|
439
|
+
Same tag, command and timeout, plus `--ai-reasoning-effort none` (#382), which sends
|
|
440
|
+
`"reasoning_effort": "none"` with every request; Ollama's `/v1` endpoint ignores `think: false` but
|
|
441
|
+
honours this field. One run, archived as [`eval-run-6/qwen-16k-nothink/`](../examples/eval-run-6/qwen-16k-nothink/)
|
|
442
|
+
(with `ai-raw/` for the two repaired verifier replies and the per-minute memory log).
|
|
443
|
+
|
|
444
|
+
| Run | BOLA (29) | Injection (34) | BFLA (10) | Mass (15) | BFLA verdict | Mass verdict | SAFE hit | Precision | Time |
|
|
445
|
+
|---|---|---|---|---|---|---|---|---|---|
|
|
446
|
+
| `gpt-oss:20b` A | 26 | 34 | 10 | 14 | 10 | 15 | 1 | 0.813 | 40 min |
|
|
447
|
+
| `gpt-oss:20b` B | 26 | 34 | 10 | 14 | 10 | 14 | 1 | 0.787 | 56 min |
|
|
448
|
+
| `qwen3.5:9b` 16K, reasoning off | 23 | 29 | 6 | 10 | 8 | 15 | 0 | 0.734 | 35 min |
|
|
449
|
+
|
|
450
|
+
- **Completed:** all phases, 33 calls, no timeout, no parse failure or call error, no retry, all findings
|
|
451
|
+
anchored. Two verifier replies were repaired (`verified-1`, `verified-9`); 11 findings were generated
|
|
452
|
+
from flagged verdicts (2 BOLA, 2 BFLA, 7 mass assignment).
|
|
453
|
+
- **Recall** is lower than `gpt-oss:20b` in every category: BFLA and mass assignment are mostly found as
|
|
454
|
+
verdicts (8/10, 15/15) but placed on the right line less often (6/10, 10/15).
|
|
455
|
+
- **Precision 0.73:** no finding on a SAFE marker, but 25 unmatched findings (23 BOLA, 2 BFLA), not yet triaged;
|
|
456
|
+
none is on `php-laravel/routes/web.php`.
|
|
457
|
+
- **Memory and time:** the runner stayed at 14–17 GB and swap peaked at 6.0 GB (about 3.7 GB before the
|
|
458
|
+
run, from other work), against 20 GB and 12.4 GB with reasoning on. 35 minutes in all, of which the
|
|
459
|
+
verifier took 17.
|
|
460
|
+
- **Minimum to run it:** a 24 GB machine is enough with `num_ctx 16384` and `--ai-reasoning-effort none`.
|
|
461
|
+
With reasoning on it needs more; on this evidence at least 32 GB RAM.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Keeps the archived Evaluation Run 6 numbers (Issue #323) in step with the scorer and EVAL-RESULTS.md."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
import tempfile
|
|
11
|
+
import unittest
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
TESTS_DIR = Path(__file__).resolve().parent
|
|
15
|
+
LSA_ROOT = TESTS_DIR.parent
|
|
16
|
+
EVAL_RUN_6_DIR = LSA_ROOT / "examples" / "eval-run-6"
|
|
17
|
+
GROUND_TRUTH = EVAL_RUN_6_DIR / "ground-truth.json"
|
|
18
|
+
RUNS = ("gptoss-a", "gptoss-b", "qwen-16k-nothink")
|
|
19
|
+
QWEN_PARTIAL = EVAL_RUN_6_DIR / "qwen-16k-partial"
|
|
20
|
+
# (BOLA, injection, BFLA, mass assignment) found by location, then BFLA / mass found by auditor verdict
|
|
21
|
+
EXPECTED = {
|
|
22
|
+
"gptoss-a": {"marker": (26, 34, 10, 14), "verdict": (10, 15)},
|
|
23
|
+
"gptoss-b": {"marker": (26, 34, 10, 14), "verdict": (10, 14)},
|
|
24
|
+
"qwen-16k-nothink": {"marker": (23, 29, 6, 10), "verdict": (8, 15)},
|
|
25
|
+
}
|
|
26
|
+
# Findings that land on a SAFE marker, by category (see EVAL-RESULTS.md, Run 6)
|
|
27
|
+
SAFE_HITS = {
|
|
28
|
+
"gptoss-a": {"injection": ["classic-asp/shop/member.asp:6"]},
|
|
29
|
+
"gptoss-b": {"injection": ["python-flask/app.py:12"]},
|
|
30
|
+
"qwen-16k-nothink": {},
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def score(run: str) -> dict:
|
|
35
|
+
proc = subprocess.run(
|
|
36
|
+
[sys.executable, str(TESTS_DIR / "score_findings.py"), "--verdicts", str(GROUND_TRUTH), str(EVAL_RUN_6_DIR / run)],
|
|
37
|
+
capture_output=True, text=True, check=True,
|
|
38
|
+
)
|
|
39
|
+
return json.loads(proc.stdout)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TestEvaluationRun6(unittest.TestCase):
|
|
43
|
+
def test_ground_truth_is_the_121_item_set(self) -> None:
|
|
44
|
+
items = json.loads(GROUND_TRUTH.read_text(encoding="utf-8"))["items"]
|
|
45
|
+
self.assertEqual(len(items), 121)
|
|
46
|
+
|
|
47
|
+
def test_artifacts_exist(self) -> None:
|
|
48
|
+
for run in RUNS:
|
|
49
|
+
for name in ("findings.json", "coverage.json", "score.json", "ai-run-stats.json"):
|
|
50
|
+
self.assertTrue((EVAL_RUN_6_DIR / run / name).is_file(), f"{run}/{name}")
|
|
51
|
+
|
|
52
|
+
def test_archived_scores_match_the_scorer(self) -> None:
|
|
53
|
+
for run in RUNS:
|
|
54
|
+
with self.subTest(run=run):
|
|
55
|
+
archived = json.loads((EVAL_RUN_6_DIR / run / "score.json").read_text(encoding="utf-8"))
|
|
56
|
+
self.assertEqual(score(run), archived)
|
|
57
|
+
|
|
58
|
+
def test_numbers_match_eval_results(self) -> None:
|
|
59
|
+
for run, want in EXPECTED.items():
|
|
60
|
+
with self.subTest(run=run):
|
|
61
|
+
report = score(run)
|
|
62
|
+
pc, vl = report["per_category"], report["verdict_level"]
|
|
63
|
+
got = tuple(pc[c]["found"] for c in ("bola", "injection", "bfla", "mass_assignment"))
|
|
64
|
+
self.assertEqual(got, want["marker"])
|
|
65
|
+
self.assertEqual((vl["bfla"]["found"], vl["mass_assignment"]["found"]), want["verdict"])
|
|
66
|
+
|
|
67
|
+
def test_safe_marker_hits_are_the_documented_ones(self) -> None:
|
|
68
|
+
for run in RUNS:
|
|
69
|
+
with self.subTest(run=run):
|
|
70
|
+
report = score(run)
|
|
71
|
+
for cat, row in report["per_category"].items():
|
|
72
|
+
self.assertEqual(row["false_positive_on_safe_code"], SAFE_HITS[run].get(cat, []), cat)
|
|
73
|
+
for cat, row in report["verdict_level"].items():
|
|
74
|
+
self.assertEqual(row["flagged_safe"], [], cat)
|
|
75
|
+
|
|
76
|
+
def test_no_batch_lost_to_parse_or_call_errors(self) -> None:
|
|
77
|
+
for run in RUNS:
|
|
78
|
+
with self.subTest(run=run):
|
|
79
|
+
stats = json.loads((EVAL_RUN_6_DIR / run / "ai-run-stats.json").read_text(encoding="utf-8"))
|
|
80
|
+
self.assertEqual(stats["parse_failures"], {})
|
|
81
|
+
self.assertEqual(stats["call_errors"], {})
|
|
82
|
+
self.assertEqual(stats["anchoring"]["unanchored"], [])
|
|
83
|
+
|
|
84
|
+
def test_qwen_partial_score_matches_eval_results(self) -> None:
|
|
85
|
+
"""The stopped qwen3.5:9b 16K run: indicative score of its unverified findings (EVAL-RESULTS, Run 6)."""
|
|
86
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
87
|
+
shutil.copy(QWEN_PARTIAL / "findings.partial.json", Path(tmp) / "findings.json")
|
|
88
|
+
proc = subprocess.run(
|
|
89
|
+
[sys.executable, str(TESTS_DIR / "score_findings.py"), str(GROUND_TRUTH), tmp],
|
|
90
|
+
capture_output=True, text=True, check=True,
|
|
91
|
+
)
|
|
92
|
+
report = json.loads(proc.stdout)
|
|
93
|
+
archived = json.loads((QWEN_PARTIAL / "score.partial.json").read_text(encoding="utf-8"))
|
|
94
|
+
self.assertEqual(report, archived)
|
|
95
|
+
pc = report["per_category"]
|
|
96
|
+
got = tuple(pc[c]["found"] for c in ("bola", "injection", "bfla", "mass_assignment"))
|
|
97
|
+
self.assertEqual(got, (12, 22, 0, 0))
|
|
98
|
+
self.assertEqual(report["precision"], 0.733)
|
|
99
|
+
for row in pc.values():
|
|
100
|
+
self.assertEqual(row["false_positive_on_safe_code"], [])
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
if __name__ == "__main__":
|
|
104
|
+
unittest.main()
|