@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
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Tier 1 gaps found by comparing the pre-scan with real Java legacy applications (Issue #364).
|
|
3
|
+
|
|
4
|
+
Each test is a minimal reproduction of a pattern observed in a public application:
|
|
5
|
+
Apache Roller, Apache Struts 1, JEECG, JeeSite 1.x and OpenMRS 1.9. No third-party code is copied here.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
import tempfile
|
|
12
|
+
import unittest
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Dict, List
|
|
15
|
+
|
|
16
|
+
REPO_ROOT = Path(__file__).resolve().parents[3]
|
|
17
|
+
sys.path.insert(0, str(REPO_ROOT / "assess" / "lsa" / "scripts"))
|
|
18
|
+
|
|
19
|
+
from detect_stack import detect # noqa: E402
|
|
20
|
+
from find_injection import find_injection # noqa: E402
|
|
21
|
+
from lsa_common import iter_source_files, load_config # noqa: E402
|
|
22
|
+
from map_routes import map_routes # noqa: E402
|
|
23
|
+
|
|
24
|
+
CONFIG_XML_HEADER = '<?xml version="1.0"?>\n'
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def write_repo(root: Path, files: Dict[str, str]) -> None:
|
|
28
|
+
for rel, text in files.items():
|
|
29
|
+
path = root / rel
|
|
30
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
31
|
+
path.write_text(text, encoding="utf-8")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def scan_routes(files: Dict[str, str]) -> List[Dict]:
|
|
35
|
+
with tempfile.TemporaryDirectory() as td:
|
|
36
|
+
root = Path(td)
|
|
37
|
+
write_repo(root, files)
|
|
38
|
+
return map_routes(root, load_config(None), workers=1)["candidates"]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def scan_stack(files: Dict[str, str]) -> Dict:
|
|
42
|
+
with tempfile.TemporaryDirectory() as td:
|
|
43
|
+
root = Path(td)
|
|
44
|
+
write_repo(root, files)
|
|
45
|
+
return detect(root, load_config(None))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def scan_injection(files: Dict[str, str]) -> Dict:
|
|
49
|
+
with tempfile.TemporaryDirectory() as td:
|
|
50
|
+
root = Path(td)
|
|
51
|
+
write_repo(root, files)
|
|
52
|
+
return find_injection(root, load_config(None), workers=1)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def scoped_files(files: Dict[str, str]) -> List[str]:
|
|
56
|
+
with tempfile.TemporaryDirectory() as td:
|
|
57
|
+
root = Path(td)
|
|
58
|
+
write_repo(root, files)
|
|
59
|
+
return [rel for _, rel in iter_source_files(root, load_config(None))]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def paths_of(routes: List[Dict], stack: str) -> List[str]:
|
|
63
|
+
return sorted(r["path"] for r in routes if r["stack"] == stack)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def controller(body: str, class_annotations: str = "") -> Dict[str, str]:
|
|
67
|
+
source = (
|
|
68
|
+
"package com.acme.web;\n"
|
|
69
|
+
"@Controller\n"
|
|
70
|
+
f"{class_annotations}\n"
|
|
71
|
+
"public class ItemController {\n"
|
|
72
|
+
f"{body}\n"
|
|
73
|
+
"}\n"
|
|
74
|
+
)
|
|
75
|
+
return {"src/main/java/com/acme/web/ItemController.java": source}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class TestScope(unittest.TestCase):
|
|
79
|
+
def test_java_package_named_like_a_build_directory_is_scanned(self) -> None:
|
|
80
|
+
scanned = scoped_files({
|
|
81
|
+
"src/main/java/com/acme/vendor/VendorController.java": "class A {}\n",
|
|
82
|
+
"src/main/java/com/acme/modules/test/web/TestController.java": "class B {}\n",
|
|
83
|
+
"src/com/acme/build/BuildDao.java": "class C {}\n",
|
|
84
|
+
})
|
|
85
|
+
self.assertEqual(len(scanned), 3)
|
|
86
|
+
|
|
87
|
+
def test_jsp_in_a_web_directory_named_test_is_scanned(self) -> None:
|
|
88
|
+
scanned = scoped_files({"src/main/webapp/test/mailer_test.jsp": "<html/>\n"})
|
|
89
|
+
self.assertEqual(scanned, ["src/main/webapp/test/mailer_test.jsp"])
|
|
90
|
+
|
|
91
|
+
def test_real_test_and_vendor_trees_stay_excluded(self) -> None:
|
|
92
|
+
scanned = scoped_files({
|
|
93
|
+
"src/test/java/com/acme/FooTest.java": "class T {}\n",
|
|
94
|
+
"test/com/acme/BarTest.java": "class U {}\n",
|
|
95
|
+
"target/classes/com/acme/Baz.java": "class V {}\n",
|
|
96
|
+
"src/main/webapp/test/helper.js": "var a = 1;\n",
|
|
97
|
+
"src/main/webapp/static/vendor/lib.js": "var b = 1;\n",
|
|
98
|
+
})
|
|
99
|
+
self.assertEqual(scanned, [])
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class TestStackDetection(unittest.TestCase):
|
|
103
|
+
def test_java_ternary_is_not_dotnet_mvc(self) -> None:
|
|
104
|
+
profile = scan_stack({"src/A.java": "class A { Object c = ok ? other : Controller.NONE; }\n"})
|
|
105
|
+
self.assertNotIn("dotnet-mvc", [s["id"] for s in profile["stacks"]])
|
|
106
|
+
|
|
107
|
+
def test_commented_import_is_not_struts2(self) -> None:
|
|
108
|
+
profile = scan_stack({"src/A.java": "//import com.opensymphony.xwork2.ognl.OgnlUtil;\nclass A {}\n"})
|
|
109
|
+
self.assertNotIn("java-struts2", [s["id"] for s in profile["stacks"]])
|
|
110
|
+
|
|
111
|
+
def test_hibernate_validator_is_not_the_orm(self) -> None:
|
|
112
|
+
profile = scan_stack({"src/A.java": "import org.hibernate.validator.constraints.Length;\nclass A {}\n"})
|
|
113
|
+
self.assertNotIn("java-hibernate", [s["id"] for s in profile["stacks"]])
|
|
114
|
+
self.assertNotIn("Hibernate/JPA", profile["data_access"])
|
|
115
|
+
|
|
116
|
+
def test_mybatis_sql_command_type_is_not_ado_net(self) -> None:
|
|
117
|
+
profile = scan_stack({"src/A.java": "class A { SqlCommandType type; }\n"})
|
|
118
|
+
self.assertNotIn("ADO.NET", profile["data_access"])
|
|
119
|
+
|
|
120
|
+
def test_real_dotnet_and_orm_still_detected(self) -> None:
|
|
121
|
+
profile = scan_stack({
|
|
122
|
+
"Web/HomeController.cs": "class HomeController : Controller { SqlCommand c; }\n",
|
|
123
|
+
"src/A.java": "import org.hibernate.Session;\nclass A {}\n",
|
|
124
|
+
})
|
|
125
|
+
self.assertIn("dotnet-mvc", [s["id"] for s in profile["stacks"]])
|
|
126
|
+
self.assertIn("java-hibernate", [s["id"] for s in profile["stacks"]])
|
|
127
|
+
self.assertIn("ADO.NET", profile["data_access"])
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class TestXmlRoutes(unittest.TestCase):
|
|
131
|
+
def test_commented_out_mappings_are_not_routes(self) -> None:
|
|
132
|
+
web = CONFIG_XML_HEADER + (
|
|
133
|
+
"<web-app>\n<!--\n<servlet><servlet-name>cxf</servlet-name><servlet-class>a.Cxf</servlet-class></servlet>\n"
|
|
134
|
+
"<servlet-mapping><servlet-name>cxf</servlet-name><url-pattern>/cxf/*</url-pattern></servlet-mapping>\n-->\n</web-app>\n"
|
|
135
|
+
)
|
|
136
|
+
struts = CONFIG_XML_HEADER + (
|
|
137
|
+
'<struts-config><action-mappings><action path="/live" type="a.Live"/>\n'
|
|
138
|
+
'<!-- <action path="/dead" type="a.Dead"/> -->\n</action-mappings></struts-config>\n'
|
|
139
|
+
)
|
|
140
|
+
routes = scan_routes({"WEB-INF/web.xml": web, "WEB-INF/struts-config.xml": struts})
|
|
141
|
+
self.assertEqual(paths_of(routes, "java-servlet"), [])
|
|
142
|
+
self.assertEqual(paths_of(routes, "java-struts1"), ["/live.do"])
|
|
143
|
+
|
|
144
|
+
def test_servlet_with_description_and_several_url_patterns(self) -> None:
|
|
145
|
+
web = CONFIG_XML_HEADER + (
|
|
146
|
+
"<web-app><servlet><description>x</description><servlet-name>s</servlet-name><servlet-class>com.acme.Img</servlet-class></servlet>\n"
|
|
147
|
+
"<servlet-mapping><servlet-name>s</servlet-name><url-pattern>/a/*</url-pattern><url-pattern>/b/*</url-pattern></servlet-mapping>\n"
|
|
148
|
+
"<servlet-mapping><servlet-name>s</servlet-name><url-pattern>/c/*</url-pattern></servlet-mapping></web-app>\n"
|
|
149
|
+
)
|
|
150
|
+
servlet = "package com.acme;\npublic class Img extends HttpServlet {\n public void doGet(HttpServletRequest r) {}\n}\n"
|
|
151
|
+
routes = scan_routes({"WEB-INF/web.xml": web, "src/com/acme/Img.java": servlet})
|
|
152
|
+
self.assertEqual(paths_of(routes, "java-servlet"), ["/a/*", "/b/*", "/c/*"])
|
|
153
|
+
self.assertTrue(all(r["handler"]["file"].endswith("Img.java") for r in routes))
|
|
154
|
+
|
|
155
|
+
def test_handler_is_resolved_by_package_not_simple_name(self) -> None:
|
|
156
|
+
struts = CONFIG_XML_HEADER + (
|
|
157
|
+
'<struts-config><action-mappings><action path="/a" type="com.acme.one.ListAction"/>'
|
|
158
|
+
'<action path="/b" type="com.acme.two.ListAction"/>'
|
|
159
|
+
'<action path="/lib" type="org.other.ListAction"/></action-mappings></struts-config>\n'
|
|
160
|
+
)
|
|
161
|
+
routes = scan_routes({
|
|
162
|
+
"WEB-INF/struts-config.xml": struts,
|
|
163
|
+
"src/com/acme/one/ListAction.java": "package com.acme.one;\nclass ListAction {}\n",
|
|
164
|
+
"src/com/acme/two/ListAction.java": "package com.acme.two;\nclass ListAction {}\n",
|
|
165
|
+
})
|
|
166
|
+
handlers = {r["path"]: r["handler"]["file"] for r in routes}
|
|
167
|
+
self.assertEqual(handlers["/a.do"], "src/com/acme/one/ListAction.java")
|
|
168
|
+
self.assertEqual(handlers["/b.do"], "src/com/acme/two/ListAction.java")
|
|
169
|
+
self.assertEqual(handlers["/lib.do"], "WEB-INF/struts-config.xml")
|
|
170
|
+
|
|
171
|
+
def test_struts2_included_file_and_custom_extension(self) -> None:
|
|
172
|
+
main = CONFIG_XML_HEADER + (
|
|
173
|
+
"<struts><include file=\"struts-order.xml\"/>\n"
|
|
174
|
+
'<package name="m" namespace="/m"><action name="home" class="a.Home"/></package></struts>\n'
|
|
175
|
+
)
|
|
176
|
+
included = (
|
|
177
|
+
CONFIG_XML_HEADER
|
|
178
|
+
+ '<!DOCTYPE struts PUBLIC "x" "y">\n<struts><package name="o" namespace="/o"><action name="view" class="a.View"/></package></struts>\n'
|
|
179
|
+
)
|
|
180
|
+
routes = scan_routes({
|
|
181
|
+
"src/main/resources/struts.xml": main,
|
|
182
|
+
"src/main/resources/struts-order.xml": included,
|
|
183
|
+
"src/main/resources/struts.properties": "struts.action.extension=rol\n",
|
|
184
|
+
})
|
|
185
|
+
self.assertEqual(paths_of(routes, "java-struts2"), ["/m/home.rol", "/o/view.rol"])
|
|
186
|
+
|
|
187
|
+
def test_struts2_extension_from_constant_with_blank_alternative(self) -> None:
|
|
188
|
+
xml = CONFIG_XML_HEADER + (
|
|
189
|
+
'<struts><constant name="struts.action.extension" value="do,,"/>\n'
|
|
190
|
+
'<package name="m" namespace="/m"><action name="home" class="a.Home"/></package></struts>\n'
|
|
191
|
+
)
|
|
192
|
+
routes = scan_routes({"src/main/resources/struts.xml": xml})
|
|
193
|
+
self.assertEqual(paths_of(routes, "java-struts2"), ["/m/home.do"])
|
|
194
|
+
|
|
195
|
+
def test_struts1_module_prefix_comes_from_web_xml(self) -> None:
|
|
196
|
+
web = CONFIG_XML_HEADER + (
|
|
197
|
+
"<web-app><servlet><servlet-name>a</servlet-name><servlet-class>org.apache.struts.action.ActionServlet</servlet-class>\n"
|
|
198
|
+
"<init-param><param-name>config/admin</param-name><param-value>/WEB-INF/admin/struts-config.xml</param-value></init-param>\n"
|
|
199
|
+
"</servlet></web-app>\n"
|
|
200
|
+
)
|
|
201
|
+
admin = CONFIG_XML_HEADER + '<struts-config><action-mappings><action path="/edit" type="a.Edit"/></action-mappings></struts-config>\n'
|
|
202
|
+
root = CONFIG_XML_HEADER + '<struts-config><action-mappings><action path="/home" type="a.Home"/></action-mappings></struts-config>\n'
|
|
203
|
+
routes = scan_routes({
|
|
204
|
+
"app/WEB-INF/web.xml": web,
|
|
205
|
+
"app/WEB-INF/admin/struts-config.xml": admin,
|
|
206
|
+
"app/WEB-INF/struts-config.xml": root,
|
|
207
|
+
})
|
|
208
|
+
self.assertEqual(paths_of(routes, "java-struts1"), ["/admin/edit.do", "/home.do"])
|
|
209
|
+
|
|
210
|
+
def test_spring_simple_url_handler_mapping(self) -> None:
|
|
211
|
+
servlet = CONFIG_XML_HEADER + (
|
|
212
|
+
'<beans><bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">\n'
|
|
213
|
+
'<property name="interceptors"><list><bean class="com.acme.Audit"></bean></list></property>\n'
|
|
214
|
+
'<property name="mappings"><props><prop key="/admin/order.form">orderForm</prop></props></property></bean>\n'
|
|
215
|
+
'<bean id="orderForm" class="com.acme.OrderFormController"/>\n</beans>\n'
|
|
216
|
+
)
|
|
217
|
+
routes = scan_routes({
|
|
218
|
+
"WEB-INF/app-servlet.xml": servlet,
|
|
219
|
+
"src/com/acme/OrderFormController.java": 'package com.acme;\nclass OrderFormController { void f() { getParameter("orderId"); } }\n',
|
|
220
|
+
})
|
|
221
|
+
spring = [r for r in routes if r["stack"] == "java-spring-mvc"]
|
|
222
|
+
self.assertEqual([r["path"] for r in spring], ["/admin/order.form"])
|
|
223
|
+
self.assertEqual(spring[0]["handler"]["file"], "src/com/acme/OrderFormController.java")
|
|
224
|
+
self.assertIn("orderId", [p["name"] for p in spring[0]["id_params"]])
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class TestSpringAnnotations(unittest.TestCase):
|
|
228
|
+
def test_annotation_between_modifier_and_return_type(self) -> None:
|
|
229
|
+
routes = scan_routes(controller(
|
|
230
|
+
' @RequestMapping("/q/h")\n public @ResponseBody\n List<Map<String, Object>> list(@RequestParam("itemId") int id) { return null; }\n'
|
|
231
|
+
))
|
|
232
|
+
self.assertEqual(paths_of(routes, "java-spring-mvc"), ["/q/h"])
|
|
233
|
+
self.assertEqual(routes[0]["handler"]["symbol"], "list")
|
|
234
|
+
|
|
235
|
+
def test_array_of_paths_gives_one_route_each(self) -> None:
|
|
236
|
+
routes = scan_routes(controller(
|
|
237
|
+
' @RequestMapping(value = {"list", ""})\n public String list() { return ""; }\n',
|
|
238
|
+
'@RequestMapping(value = "/sys/user")',
|
|
239
|
+
))
|
|
240
|
+
self.assertEqual(paths_of(routes, "java-spring-mvc"), ["/sys/user", "/sys/user/list"])
|
|
241
|
+
|
|
242
|
+
def test_params_is_a_query_string_not_a_path(self) -> None:
|
|
243
|
+
routes = scan_routes(controller(
|
|
244
|
+
' @RequestMapping(params = "doDel")\n public String del() { return ""; }\n'
|
|
245
|
+
' @RequestMapping(value = "/x", params = "action=up")\n public String up() { return ""; }\n',
|
|
246
|
+
'@RequestMapping("/item")',
|
|
247
|
+
))
|
|
248
|
+
self.assertEqual(paths_of(routes, "java-spring-mvc"), ["/item/x?action=up", "/item?doDel"])
|
|
249
|
+
|
|
250
|
+
def test_produces_is_not_a_path(self) -> None:
|
|
251
|
+
routes = scan_routes(controller(
|
|
252
|
+
' @RequestMapping(method = RequestMethod.POST, produces = "application/json")\n public String p() { return ""; }\n',
|
|
253
|
+
'@RequestMapping("/item")',
|
|
254
|
+
))
|
|
255
|
+
self.assertEqual(paths_of(routes, "java-spring-mvc"), ["/item"])
|
|
256
|
+
self.assertEqual(routes[0]["method"], ["POST"])
|
|
257
|
+
|
|
258
|
+
def test_class_literal_in_a_method_does_not_become_the_class_prefix(self) -> None:
|
|
259
|
+
routes = scan_routes(controller(
|
|
260
|
+
' @RequestMapping(value = "export")\n public String e() { new Excel(User.class); return ""; }\n'
|
|
261
|
+
' @RequestMapping(value = "info")\n public String i() { return ""; }\n',
|
|
262
|
+
'@RequestMapping(value = "/sys/user")',
|
|
263
|
+
))
|
|
264
|
+
self.assertEqual(paths_of(routes, "java-spring-mvc"), ["/sys/user/export", "/sys/user/info"])
|
|
265
|
+
|
|
266
|
+
def test_string_constant_path_is_resolved(self) -> None:
|
|
267
|
+
routes = scan_routes(controller(
|
|
268
|
+
' private static final String PATH = "/admin/settings";\n'
|
|
269
|
+
' @RequestMapping(value = PATH + "/save", method = RequestMethod.POST)\n public String s() { return ""; }\n'
|
|
270
|
+
))
|
|
271
|
+
self.assertEqual(paths_of(routes, "java-spring-mvc"), ["/admin/settings/save"])
|
|
272
|
+
|
|
273
|
+
def test_placeholder_is_not_an_id_parameter(self) -> None:
|
|
274
|
+
routes = scan_routes(controller(
|
|
275
|
+
' @RequestMapping(value = "{tenantId}/view")\n public String v() { return ""; }\n',
|
|
276
|
+
'@RequestMapping(value = "${adminPath}/sys/user")',
|
|
277
|
+
))
|
|
278
|
+
self.assertEqual([p["name"] for p in routes[0]["id_params"]], ["tenantId"])
|
|
279
|
+
|
|
280
|
+
def test_commented_out_annotation_is_not_a_route(self) -> None:
|
|
281
|
+
routes = scan_routes(controller(
|
|
282
|
+
' // @RequestMapping(value = "old")\n public String old() { return ""; }\n'
|
|
283
|
+
' /*\n @RequestMapping(value = "dead")\n public String dead() { return ""; }\n */\n'
|
|
284
|
+
' @RequestMapping(value = "live")\n public String live() { return ""; }\n',
|
|
285
|
+
'@RequestMapping("/item")',
|
|
286
|
+
))
|
|
287
|
+
self.assertEqual(paths_of(routes, "java-spring-mvc"), ["/item/live"])
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
class TestJspEntryPoints(unittest.TestCase):
|
|
291
|
+
def test_jsp_under_web_inf_is_not_a_route(self) -> None:
|
|
292
|
+
routes = scan_routes({"app/src/main/webapp/WEB-INF/jsps/editor/Entries.jsp": "<html/>\n"})
|
|
293
|
+
self.assertEqual(paths_of(routes, "java-jsp"), [])
|
|
294
|
+
|
|
295
|
+
def test_public_jsp_in_an_include_like_directory_is_a_route(self) -> None:
|
|
296
|
+
routes = scan_routes({"src/main/webapp/webpage/cgform/config/list.jsp": "<html/>\n"})
|
|
297
|
+
self.assertEqual(paths_of(routes, "java-jsp"), ["/webpage/cgform/config/list.jsp"])
|
|
298
|
+
|
|
299
|
+
def test_path_is_relative_to_the_web_root(self) -> None:
|
|
300
|
+
routes = scan_routes({"app/WebContent/order/detail.jsp": "<html/>\n"})
|
|
301
|
+
self.assertEqual(paths_of(routes, "java-jsp"), ["/order/detail.jsp"])
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class TestInjectionPrecision(unittest.TestCase):
|
|
305
|
+
def test_html_select_builder_is_not_sql(self) -> None:
|
|
306
|
+
result = scan_injection({"src/A.java": (
|
|
307
|
+
"class A {\n"
|
|
308
|
+
" String f(String v) {\n"
|
|
309
|
+
" StringBuffer sb = new StringBuffer();\n"
|
|
310
|
+
' sb.append("<select name=\\"" + v + "\\">");\n'
|
|
311
|
+
' sb.append("<option value=\\"" + v + "\\">" + v);\n'
|
|
312
|
+
" return sb.toString();\n"
|
|
313
|
+
" }\n"
|
|
314
|
+
"}\n"
|
|
315
|
+
)})
|
|
316
|
+
self.assertEqual(result["candidates"], [])
|
|
317
|
+
|
|
318
|
+
def test_freemarker_generator_template_is_not_a_mapper(self) -> None:
|
|
319
|
+
result = scan_injection({"src/main/resources/templates/gen/mapper.xml": (
|
|
320
|
+
'<template><content><![CDATA[\n<mapper namespace="${pkg}">\n<select id="x">\n'
|
|
321
|
+
"SELECT * FROM ${table.name} <#if flag>WHERE a = 1</#if>\n</select></mapper>\n]]></content></template>\n"
|
|
322
|
+
)})
|
|
323
|
+
self.assertEqual(result["candidates"], [])
|
|
324
|
+
|
|
325
|
+
def test_real_mapper_dollar_still_flagged(self) -> None:
|
|
326
|
+
result = scan_injection({"src/ItemDao.xml": (
|
|
327
|
+
'<mapper namespace="a.ItemDao">\n<select id="x">\nSELECT * FROM item ORDER BY ${page.orderBy}\n</select></mapper>\n'
|
|
328
|
+
)})
|
|
329
|
+
self.assertEqual([c["rule"] for c in result["candidates"]], ["mybatis-dollar"])
|
|
330
|
+
|
|
331
|
+
def test_positional_parameter_index_is_suppressed(self) -> None:
|
|
332
|
+
result = scan_injection({"src/A.java": (
|
|
333
|
+
"class A {\n"
|
|
334
|
+
" String f(int size, String sortBy) {\n"
|
|
335
|
+
' StringBuilder q = new StringBuilder("SELECT e FROM Entry e WHERE ");\n'
|
|
336
|
+
' q.append(" AND e.name = ?").append(size);\n'
|
|
337
|
+
' q.append(" ORDER BY ").append(sortBy);\n'
|
|
338
|
+
" return q.toString();\n"
|
|
339
|
+
" }\n"
|
|
340
|
+
"}\n"
|
|
341
|
+
)})
|
|
342
|
+
self.assertEqual([c["line"] for c in result["candidates"]], [5])
|
|
343
|
+
self.assertEqual([s["line"] for s in result["suppressed"]], [4])
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
class TestInjectionRecall(unittest.TestCase):
|
|
347
|
+
def test_operand_on_a_continuation_line_is_flagged(self) -> None:
|
|
348
|
+
result = scan_injection({"src/A.java": (
|
|
349
|
+
"class A {\n"
|
|
350
|
+
" void f(Connection c, String name) throws Exception {\n"
|
|
351
|
+
' c.prepareStatement("INSERT INTO t (a) VALUES("\n'
|
|
352
|
+
" + name + \",?)\");\n"
|
|
353
|
+
" }\n"
|
|
354
|
+
"}\n"
|
|
355
|
+
)})
|
|
356
|
+
self.assertEqual([c["line"] for c in result["candidates"]], [4])
|
|
357
|
+
|
|
358
|
+
def test_leading_operand_call_on_a_continuation_line_is_flagged(self) -> None:
|
|
359
|
+
result = scan_injection({"src/A.java": (
|
|
360
|
+
"class A {\n"
|
|
361
|
+
" int f(Connection c, Name n) {\n"
|
|
362
|
+
' return get(c, "SELECT count(*) FROM concept WHERE locale = \'"\n'
|
|
363
|
+
" + quote(n.getLocale())\n"
|
|
364
|
+
' + "\'");\n'
|
|
365
|
+
" }\n"
|
|
366
|
+
"}\n"
|
|
367
|
+
)})
|
|
368
|
+
self.assertIn(4, [c["line"] for c in result["candidates"]])
|
|
369
|
+
|
|
370
|
+
def test_constant_on_a_continuation_line_is_not_flagged(self) -> None:
|
|
371
|
+
result = scan_injection({"src/A.java": (
|
|
372
|
+
"class A {\n"
|
|
373
|
+
" int f(Connection c) {\n"
|
|
374
|
+
' return get(c, "SELECT count(*) FROM concept WHERE kind = \'"\n'
|
|
375
|
+
" + ConceptNameType.FULLY_SPECIFIED\n"
|
|
376
|
+
' + "\'");\n'
|
|
377
|
+
" }\n"
|
|
378
|
+
"}\n"
|
|
379
|
+
)})
|
|
380
|
+
self.assertEqual(result["candidates"], [])
|
|
381
|
+
|
|
382
|
+
def test_jpql_literal_ending_in_where_is_flagged(self) -> None:
|
|
383
|
+
result = scan_injection({"src/A.java": (
|
|
384
|
+
"class A {\n"
|
|
385
|
+
" String f(StringBuilder whereClause) {\n"
|
|
386
|
+
' return "FROM MediaFile m WHERE " + whereClause;\n'
|
|
387
|
+
" }\n"
|
|
388
|
+
"}\n"
|
|
389
|
+
)})
|
|
390
|
+
self.assertEqual([c["line"] for c in result["candidates"]], [3])
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
class XmlDeclaredHandlerLineTests(unittest.TestCase):
|
|
394
|
+
"""The headless auditor reads 15 lines around handler.line, so it must point at the entry method (#374)."""
|
|
395
|
+
|
|
396
|
+
IMPORTS = "".join(f"import a.b.C{n};\n" for n in range(30))
|
|
397
|
+
|
|
398
|
+
def handler_line(self, config_rel: str, config: str, source_rel: str, source: str) -> int:
|
|
399
|
+
routes = scan_routes({config_rel: CONFIG_XML_HEADER + config, source_rel: source})
|
|
400
|
+
return next(r["handler"]["line"] for r in routes if r["handler"]["file"] == source_rel)
|
|
401
|
+
|
|
402
|
+
def test_struts1_action_points_at_execute(self) -> None:
|
|
403
|
+
source = "package a;\n" + self.IMPORTS + (
|
|
404
|
+
"public class ViewAction extends Action {\n"
|
|
405
|
+
" public ActionForward execute(ActionMapping m, ActionForm f,\n"
|
|
406
|
+
" HttpServletRequest request, HttpServletResponse response) {\n"
|
|
407
|
+
' return load(request.getParameter("id"));\n'
|
|
408
|
+
" }\n"
|
|
409
|
+
"}\n"
|
|
410
|
+
)
|
|
411
|
+
line = self.handler_line(
|
|
412
|
+
"WEB-INF/struts-config.xml",
|
|
413
|
+
'<struts-config><action-mappings><action path="/view" type="a.ViewAction"/></action-mappings></struts-config>',
|
|
414
|
+
"src/a/ViewAction.java", source)
|
|
415
|
+
self.assertEqual(line, 33)
|
|
416
|
+
|
|
417
|
+
def test_struts2_action_points_at_the_declared_method(self) -> None:
|
|
418
|
+
source = "package a;\n" + self.IMPORTS + (
|
|
419
|
+
"public class ViewAction extends ActionSupport {\n"
|
|
420
|
+
" public String execute() { return list(); }\n"
|
|
421
|
+
" public String show() { return load(id); }\n"
|
|
422
|
+
"}\n"
|
|
423
|
+
)
|
|
424
|
+
line = self.handler_line(
|
|
425
|
+
"src/struts.xml",
|
|
426
|
+
'<struts><package name="p"><action name="view" class="a.ViewAction" method="show"/></package></struts>',
|
|
427
|
+
"src/a/ViewAction.java", source)
|
|
428
|
+
self.assertEqual(line, 34)
|
|
429
|
+
|
|
430
|
+
def test_servlet_from_web_xml_points_at_do_post(self) -> None:
|
|
431
|
+
source = "package a;\n" + self.IMPORTS + (
|
|
432
|
+
"public class ViewServlet extends HttpServlet {\n"
|
|
433
|
+
" protected void doPost(HttpServletRequest req, HttpServletResponse res) { load(req); }\n"
|
|
434
|
+
"}\n"
|
|
435
|
+
)
|
|
436
|
+
line = self.handler_line(
|
|
437
|
+
"WEB-INF/web.xml",
|
|
438
|
+
"<web-app><servlet><servlet-name>v</servlet-name><servlet-class>a.ViewServlet</servlet-class></servlet>"
|
|
439
|
+
"<servlet-mapping><servlet-name>v</servlet-name><url-pattern>/view</url-pattern></servlet-mapping></web-app>",
|
|
440
|
+
"src/a/ViewServlet.java", source)
|
|
441
|
+
self.assertEqual(line, 33)
|
|
442
|
+
|
|
443
|
+
def test_class_without_a_known_entry_method_keeps_line_one(self) -> None:
|
|
444
|
+
source = "package a;\npublic class Helper {\n void run() {}\n}\n"
|
|
445
|
+
line = self.handler_line(
|
|
446
|
+
"WEB-INF/struts-config.xml",
|
|
447
|
+
'<struts-config><action-mappings><action path="/h" type="a.Helper"/></action-mappings></struts-config>',
|
|
448
|
+
"src/a/Helper.java", source)
|
|
449
|
+
self.assertEqual(line, 1)
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
if __name__ == "__main__":
|
|
453
|
+
unittest.main()
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Laravel routes map to the controller method they name, not to their declaration (#380)."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import shutil
|
|
7
|
+
import sys
|
|
8
|
+
import tempfile
|
|
9
|
+
import unittest
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
TESTS_DIR = Path(__file__).resolve().parent
|
|
13
|
+
sys.path.insert(0, str(TESTS_DIR.parent / "scripts"))
|
|
14
|
+
from laravel_routes import route_action # noqa: E402
|
|
15
|
+
from lsa_common import load_config # noqa: E402
|
|
16
|
+
from map_routes import map_routes # noqa: E402
|
|
17
|
+
|
|
18
|
+
ROUTES = """<?php
|
|
19
|
+
use App\\Http\\Controllers\\OrderController;
|
|
20
|
+
use Illuminate\\Support\\Facades\\Route;
|
|
21
|
+
|
|
22
|
+
Route::get('/orders/{id}', [OrderController::class, 'show']);
|
|
23
|
+
Route::post('/orders', 'OrderController@store');
|
|
24
|
+
Route::get('/missing', [GoneController::class, 'index']);
|
|
25
|
+
Route::get('/ping', function () { return 'ok'; });
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
CONTROLLER = """<?php
|
|
29
|
+
namespace App\\Http\\Controllers;
|
|
30
|
+
|
|
31
|
+
use App\\Models\\Order;
|
|
32
|
+
use Illuminate\\Http\\Request;
|
|
33
|
+
|
|
34
|
+
class OrderController extends Controller
|
|
35
|
+
{
|
|
36
|
+
public function show($id)
|
|
37
|
+
{
|
|
38
|
+
return Order::find($id);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public function store(Request $request)
|
|
42
|
+
{
|
|
43
|
+
return Order::create(['customer_id' => $request->input('customer_id')]);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TestRouteAction(unittest.TestCase):
|
|
50
|
+
def test_array_and_string_actions(self) -> None:
|
|
51
|
+
self.assertEqual(route_action("Route::get('/a', [OrderController::class, 'show'])"), "OrderController@show")
|
|
52
|
+
self.assertEqual(route_action("Route::get('/a', [\\App\\Http\\Controllers\\OrderController::class, 'show'])"),
|
|
53
|
+
"OrderController@show")
|
|
54
|
+
self.assertEqual(route_action("Route::post('/a', 'App\\Http\\Controllers\\OrderController@store')"),
|
|
55
|
+
"OrderController@store")
|
|
56
|
+
self.assertIsNone(route_action("Route::get('/a', function () { return 1; })"))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class TestLaravelHandlers(unittest.TestCase):
|
|
60
|
+
def setUp(self) -> None:
|
|
61
|
+
self.root = Path(tempfile.mkdtemp(prefix="lsa_laravel_"))
|
|
62
|
+
(self.root / "routes").mkdir()
|
|
63
|
+
(self.root / "app/Http/Controllers").mkdir(parents=True)
|
|
64
|
+
(self.root / "routes/web.php").write_text(ROUTES, encoding="utf-8")
|
|
65
|
+
(self.root / "app/Http/Controllers/OrderController.php").write_text(CONTROLLER, encoding="utf-8")
|
|
66
|
+
|
|
67
|
+
def tearDown(self) -> None:
|
|
68
|
+
shutil.rmtree(self.root, ignore_errors=True)
|
|
69
|
+
|
|
70
|
+
def routes(self) -> dict:
|
|
71
|
+
found = map_routes(self.root, load_config(None), {"php-laravel"}, workers=1)["candidates"]
|
|
72
|
+
return {r["path"]: r for r in found if r["stack"] == "php-laravel"}
|
|
73
|
+
|
|
74
|
+
def test_route_points_at_the_controller_method(self) -> None:
|
|
75
|
+
show = self.routes()["/orders/{id}"]
|
|
76
|
+
self.assertEqual(show["handler"], {"file": "app/Http/Controllers/OrderController.php", "line": 9,
|
|
77
|
+
"symbol": "OrderController@show"})
|
|
78
|
+
self.assertEqual(show["notes"], "declared in routes/web.php:5")
|
|
79
|
+
self.assertIn("id", [p["name"] for p in show["id_params"]])
|
|
80
|
+
|
|
81
|
+
def test_string_action_and_body_inputs(self) -> None:
|
|
82
|
+
store = self.routes()["/orders"]
|
|
83
|
+
self.assertEqual((store["handler"]["file"], store["handler"]["line"]),
|
|
84
|
+
("app/Http/Controllers/OrderController.php", 14))
|
|
85
|
+
self.assertIn("customer_id", [p["name"] for p in store["id_params"]])
|
|
86
|
+
|
|
87
|
+
def test_unresolved_routes_keep_their_declaration(self) -> None:
|
|
88
|
+
routes = self.routes()
|
|
89
|
+
self.assertEqual(routes["/missing"]["handler"], {"file": "routes/web.php", "line": 7,
|
|
90
|
+
"symbol": "GoneController@index"})
|
|
91
|
+
self.assertEqual(routes["/ping"]["handler"]["file"], "routes/web.php")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
if __name__ == "__main__":
|
|
95
|
+
unittest.main()
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "operate/persona-suite.schema.json",
|
|
4
4
|
"title": "Komagu test suite v2 (persona matrix)",
|
|
5
|
-
"description": "Personas and, per route, the personas allowed to call it. Every other persona must be refused (BFLA). Tokens come only from environment variables.",
|
|
5
|
+
"description": "Personas and, per route, the personas allowed to call it. Every other persona must be refused (BFLA). Tokens come only from environment variables; session cookies come from an environment variable or a file.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"required": [
|
|
8
8
|
"version",
|
|
@@ -29,6 +29,15 @@
|
|
|
29
29
|
"pattern": "^[A-Za-z_][A-Za-z0-9_]*$",
|
|
30
30
|
"description": "Environment variable that holds this persona's bearer token. Omit for an anonymous persona."
|
|
31
31
|
},
|
|
32
|
+
"cookie_env": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"pattern": "^[A-Za-z_][A-Za-z0-9_]*$",
|
|
35
|
+
"description": "Environment variable that holds this persona's Cookie value, e.g. JSESSIONID=... Use for session-based apps instead of token_env."
|
|
36
|
+
},
|
|
37
|
+
"cookie_file": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"description": "Path to a file that holds this persona's Cookie value."
|
|
40
|
+
},
|
|
32
41
|
"description": {
|
|
33
42
|
"type": "string"
|
|
34
43
|
}
|
|
@@ -75,7 +84,7 @@
|
|
|
75
84
|
},
|
|
76
85
|
"payload": {
|
|
77
86
|
"type": "object",
|
|
78
|
-
"description": "
|
|
87
|
+
"description": "Body fields for writes, encoded as set by body_format."
|
|
79
88
|
},
|
|
80
89
|
"allow": {
|
|
81
90
|
"type": "array",
|
|
@@ -106,6 +115,33 @@
|
|
|
106
115
|
"description": "Request that returns the stored object, e.g. 'GET /api/profile' (default: GET on the route path)."
|
|
107
116
|
}
|
|
108
117
|
}
|
|
118
|
+
},
|
|
119
|
+
"body_format": {
|
|
120
|
+
"enum": [
|
|
121
|
+
"json",
|
|
122
|
+
"form"
|
|
123
|
+
],
|
|
124
|
+
"default": "json",
|
|
125
|
+
"description": "Body encoding. form sends application/x-www-form-urlencoded."
|
|
126
|
+
},
|
|
127
|
+
"csrf": {
|
|
128
|
+
"type": "object",
|
|
129
|
+
"required": [
|
|
130
|
+
"page",
|
|
131
|
+
"field"
|
|
132
|
+
],
|
|
133
|
+
"additionalProperties": false,
|
|
134
|
+
"description": "Fetch this page before each write and send the token found in the named field.",
|
|
135
|
+
"properties": {
|
|
136
|
+
"page": {
|
|
137
|
+
"type": "string",
|
|
138
|
+
"pattern": "^/"
|
|
139
|
+
},
|
|
140
|
+
"field": {
|
|
141
|
+
"type": "string",
|
|
142
|
+
"minLength": 1
|
|
143
|
+
}
|
|
144
|
+
}
|
|
109
145
|
}
|
|
110
146
|
}
|
|
111
147
|
}
|
|
@@ -10,6 +10,7 @@ RUN dotnet publish examples/member-portal/backend-dotnet/MemberPortal.Backend.cs
|
|
|
10
10
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
|
|
11
11
|
WORKDIR /app
|
|
12
12
|
COPY --from=build /app/publish .
|
|
13
|
-
RUN mkdir -p /app/data
|
|
13
|
+
RUN mkdir -p /app/data && chown app:app /app/data
|
|
14
14
|
EXPOSE 5001
|
|
15
|
+
USER app
|
|
15
16
|
ENTRYPOINT ["dotnet", "MemberPortal.Backend.dll"]
|
|
@@ -9,5 +9,7 @@ RUN gradle -p examples/member-portal/backend-spring bootJar --no-daemon
|
|
|
9
9
|
FROM eclipse-temurin:17-jre-alpine AS final
|
|
10
10
|
WORKDIR /app
|
|
11
11
|
COPY --from=build /home/gradle/src/examples/member-portal/backend-spring/build/libs/*.jar app.jar
|
|
12
|
+
RUN addgroup -S komagu && adduser -S -G komagu komagu
|
|
12
13
|
EXPOSE 5002
|
|
14
|
+
USER komagu
|
|
13
15
|
ENTRYPOINT ["java", "-jar", "app.jar"]
|