@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,245 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Pilot safeguards: source copy exclude list, deletion record, guard against committing real pilot data (#367)."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import importlib.util
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
11
|
+
import tempfile
|
|
12
|
+
import unittest
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from types import ModuleType
|
|
15
|
+
from typing import Dict, Set
|
|
16
|
+
|
|
17
|
+
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
18
|
+
PILOT = REPO_ROOT / "delivery" / "pilot"
|
|
19
|
+
EXCLUDE_FILE = PILOT / "ams-exclude.sample.txt"
|
|
20
|
+
GUARD_FILE = REPO_ROOT / ".githooks" / "pilot_data_guard.py"
|
|
21
|
+
HAS_GIT_CHECKOUT = (REPO_ROOT / ".git").exists() and shutil.which("git") is not None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def load_guard() -> ModuleType:
|
|
25
|
+
spec = importlib.util.spec_from_file_location("komagu_pilot_data_guard", GUARD_FILE)
|
|
26
|
+
assert spec is not None and spec.loader is not None
|
|
27
|
+
module = importlib.util.module_from_spec(spec)
|
|
28
|
+
spec.loader.exec_module(module)
|
|
29
|
+
return module
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def clean_env() -> Dict[str, str]:
|
|
33
|
+
"""A run started from inside a git hook must not act on the outer repository."""
|
|
34
|
+
return {key: value for key, value in os.environ.items() if not key.startswith("GIT_")}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@unittest.skipUnless(shutil.which("rsync"), "rsync is not installed")
|
|
38
|
+
class TestExcludeList(unittest.TestCase):
|
|
39
|
+
EXCLUDED = (
|
|
40
|
+
"keystore.jks",
|
|
41
|
+
"conf/ssl/app.keystore",
|
|
42
|
+
"conf/ssl/client.p12",
|
|
43
|
+
"src/main/resources/server.key",
|
|
44
|
+
"src/main/resources/application-prod.properties",
|
|
45
|
+
"src/main/resources/application-prod.yml",
|
|
46
|
+
"src/main/resources/application-production.yml",
|
|
47
|
+
"context.xml",
|
|
48
|
+
"conf/context.xml",
|
|
49
|
+
"src/main/webapp/META-INF/context.xml",
|
|
50
|
+
"server.xml",
|
|
51
|
+
"conf/tomcat-users.xml",
|
|
52
|
+
".env",
|
|
53
|
+
"src/test/java/FooTest.java",
|
|
54
|
+
"modules/billing/src/test/resources/seed.sql",
|
|
55
|
+
"test-data/users.csv",
|
|
56
|
+
)
|
|
57
|
+
KEPT = (
|
|
58
|
+
"pom.xml",
|
|
59
|
+
"src/main/java/com/acme/OrderAction.java",
|
|
60
|
+
"src/main/resources/application.properties",
|
|
61
|
+
"src/main/webapp/WEB-INF/web.xml",
|
|
62
|
+
"src/main/webapp/WEB-INF/struts-config.xml",
|
|
63
|
+
"src/testing/Keep.java",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def test_patterns_filter_a_real_copy(self) -> None:
|
|
67
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
68
|
+
source, target = Path(tmp) / "repo", Path(tmp) / "copy"
|
|
69
|
+
for relative in self.EXCLUDED + self.KEPT:
|
|
70
|
+
path = source / relative
|
|
71
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
72
|
+
path.write_text("x", encoding="utf-8")
|
|
73
|
+
subprocess.run(
|
|
74
|
+
["rsync", "-a", f"--exclude-from={EXCLUDE_FILE}", f"{source}/", f"{target}/"],
|
|
75
|
+
check=True,
|
|
76
|
+
capture_output=True,
|
|
77
|
+
)
|
|
78
|
+
copied: Set[str] = {str(p.relative_to(target)) for p in target.rglob("*") if p.is_file()}
|
|
79
|
+
self.assertEqual(copied, set(self.KEPT))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class TestDocuments(unittest.TestCase):
|
|
83
|
+
def test_runbook_uses_the_tracked_exclude_list(self) -> None:
|
|
84
|
+
runbook = (REPO_ROOT / "docs" / "pilot-execution-runbook.md").read_text(encoding="utf-8")
|
|
85
|
+
self.assertIn("ams-exclude.sample.txt", runbook)
|
|
86
|
+
self.assertNotIn("**/context.xml", runbook)
|
|
87
|
+
self.assertIn("komagu leak-check", runbook)
|
|
88
|
+
self.assertIn("data-deletion-report.template.md", runbook)
|
|
89
|
+
|
|
90
|
+
def test_deletion_report_covers_the_commitment_in_both_languages(self) -> None:
|
|
91
|
+
template = (PILOT / "data-deletion-report.template.md").read_text(encoding="utf-8")
|
|
92
|
+
for term in ("削除日時", "Deleted at", "実施者", "Operator", "確認者", "Verifier", "モデルサーバーログ",
|
|
93
|
+
"Model server logs", "一時ディレクトリ", "Temporary directories", "~/pilot/src", "~/pilot/out*"):
|
|
94
|
+
self.assertIn(term, template)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class TestGuardRules(unittest.TestCase):
|
|
98
|
+
guard = load_guard()
|
|
99
|
+
|
|
100
|
+
def test_real_output_paths_are_rejected(self) -> None:
|
|
101
|
+
for path in (
|
|
102
|
+
"delivery/pilot/ai-raw/4a.txt",
|
|
103
|
+
"out/ai-raw/4a.txt",
|
|
104
|
+
"delivery/pilot/client-a-config.json",
|
|
105
|
+
"delivery/pilot/out/findings.json",
|
|
106
|
+
"ams-exclude.txt",
|
|
107
|
+
"delivery/pilot/src-file-list.txt",
|
|
108
|
+
"canaries.txt",
|
|
109
|
+
"notes/canaries-prod.txt",
|
|
110
|
+
"delivery/pilot/data-deletion-report.md",
|
|
111
|
+
"notes/Canaries.txt",
|
|
112
|
+
"out/AI-RAW/4a.txt",
|
|
113
|
+
):
|
|
114
|
+
self.assertIsNotNone(self.guard.path_violation(path), path)
|
|
115
|
+
|
|
116
|
+
def test_known_files_are_accepted(self) -> None:
|
|
117
|
+
for path in (
|
|
118
|
+
"delivery/pilot/pilot-config.json",
|
|
119
|
+
"delivery/pilot/ams-exclude.sample.txt",
|
|
120
|
+
"delivery/pilot/data-deletion-report.template.md",
|
|
121
|
+
"delivery/pilot/audit-out/report.md",
|
|
122
|
+
"operate/ai/runner.py",
|
|
123
|
+
):
|
|
124
|
+
self.assertIsNone(self.guard.path_violation(path), path)
|
|
125
|
+
|
|
126
|
+
def test_fallback_config_passes_and_real_config_fails(self) -> None:
|
|
127
|
+
fallback = (PILOT / "pilot-config.json").read_bytes()
|
|
128
|
+
sample = (PILOT / "pilot-config.sample.json").read_bytes()
|
|
129
|
+
check = self.guard.CONTENT_CHECKS[self.guard.CONFIG]
|
|
130
|
+
self.assertIsNone(check(fallback))
|
|
131
|
+
self.assertIsNotNone(check(sample), "a copy of the sample over the fallback is the likely accident")
|
|
132
|
+
self.assertIsNotNone(check(b"not json"))
|
|
133
|
+
|
|
134
|
+
def test_filled_sample_fails(self) -> None:
|
|
135
|
+
sample = json.loads((PILOT / "pilot-config.sample.json").read_text(encoding="utf-8"))
|
|
136
|
+
check = self.guard.CONTENT_CHECKS[self.guard.SAMPLE_CONFIG]
|
|
137
|
+
self.assertIsNone(check(json.dumps(sample).encode()))
|
|
138
|
+
filled = {**sample, "system": {**sample["system"], "name": "Real system"}}
|
|
139
|
+
self.assertIsNotNone(check(json.dumps(filled).encode()))
|
|
140
|
+
|
|
141
|
+
@unittest.skipUnless(HAS_GIT_CHECKOUT, "needs a git checkout")
|
|
142
|
+
def test_tracked_tree_is_clean(self) -> None:
|
|
143
|
+
completed = subprocess.run(
|
|
144
|
+
["python3", str(GUARD_FILE), "--tree"], cwd=REPO_ROOT, env=clean_env(), capture_output=True, text=True
|
|
145
|
+
)
|
|
146
|
+
self.assertEqual(completed.returncode, 0, completed.stderr)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@unittest.skipUnless(shutil.which("git"), "git is not installed")
|
|
150
|
+
class TestPrePushHook(unittest.TestCase):
|
|
151
|
+
def setUp(self) -> None:
|
|
152
|
+
self.tmp = tempfile.TemporaryDirectory()
|
|
153
|
+
self.addCleanup(self.tmp.cleanup)
|
|
154
|
+
root = Path(self.tmp.name)
|
|
155
|
+
self.repo, self.remote = root / "repo", root / "remote.git"
|
|
156
|
+
self.git_at(root, "init", "--bare", str(self.remote))
|
|
157
|
+
self.git_at(root, "init", str(self.repo))
|
|
158
|
+
shutil.copytree(REPO_ROOT / ".githooks", self.repo / ".githooks")
|
|
159
|
+
self.git("remote", "add", "origin", str(self.remote))
|
|
160
|
+
self.write("delivery/pilot/pilot-config.json", (PILOT / "pilot-config.json").read_text(encoding="utf-8"))
|
|
161
|
+
self.write("delivery/pilot/audit-out/report.md", "fallback\n")
|
|
162
|
+
self.write("README.md", "hello\n")
|
|
163
|
+
self.commit("base")
|
|
164
|
+
self.git("push", "-q", "origin", "HEAD:refs/heads/seed")
|
|
165
|
+
self.git("fetch", "-q", "origin")
|
|
166
|
+
self.git("config", "core.hooksPath", ".githooks")
|
|
167
|
+
self.git("switch", "-q", "-c", "work")
|
|
168
|
+
|
|
169
|
+
def git_at(self, cwd: Path, *args: str) -> subprocess.CompletedProcess:
|
|
170
|
+
return subprocess.run(
|
|
171
|
+
["git", "-c", "user.name=t", "-c", "user.email=t@example.invalid", "-c", "commit.gpgsign=false", *args],
|
|
172
|
+
cwd=cwd, env=clean_env(), check=True, capture_output=True, text=True,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
def git(self, *args: str) -> subprocess.CompletedProcess:
|
|
176
|
+
return self.git_at(self.repo, *args)
|
|
177
|
+
|
|
178
|
+
def write(self, relative: str, content: str) -> None:
|
|
179
|
+
path = self.repo / relative
|
|
180
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
181
|
+
path.write_text(content, encoding="utf-8")
|
|
182
|
+
|
|
183
|
+
def commit(self, message: str) -> None:
|
|
184
|
+
self.git("add", "-A", "-f")
|
|
185
|
+
self.git("commit", "-q", "-m", message)
|
|
186
|
+
|
|
187
|
+
def push(self) -> subprocess.CompletedProcess:
|
|
188
|
+
return subprocess.run(
|
|
189
|
+
["git", "push", "origin", "work"], cwd=self.repo, env=clean_env(), capture_output=True, text=True
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
def assert_blocked(self, expected: str) -> None:
|
|
193
|
+
completed = self.push()
|
|
194
|
+
self.assertNotEqual(completed.returncode, 0, completed.stdout)
|
|
195
|
+
self.assertIn(expected, completed.stderr)
|
|
196
|
+
refs = subprocess.run(["git", "ls-remote", str(self.remote)], capture_output=True, text=True, env=clean_env())
|
|
197
|
+
self.assertNotIn("refs/heads/work", refs.stdout)
|
|
198
|
+
|
|
199
|
+
def test_ordinary_change_is_pushed(self) -> None:
|
|
200
|
+
self.write("README.md", "changed\n")
|
|
201
|
+
self.commit("docs")
|
|
202
|
+
completed = self.push()
|
|
203
|
+
self.assertEqual(completed.returncode, 0, completed.stderr)
|
|
204
|
+
|
|
205
|
+
def test_real_config_over_the_tracked_file_is_blocked(self) -> None:
|
|
206
|
+
self.write("delivery/pilot/pilot-config.json", (PILOT / "pilot-config.sample.json").read_text(encoding="utf-8"))
|
|
207
|
+
self.commit("config")
|
|
208
|
+
self.assert_blocked("delivery/pilot/pilot-config.json")
|
|
209
|
+
|
|
210
|
+
def test_output_removed_in_a_later_commit_is_still_blocked(self) -> None:
|
|
211
|
+
self.write("delivery/pilot/ai-raw/4a.txt", "reply\n")
|
|
212
|
+
self.commit("oops")
|
|
213
|
+
self.git("rm", "-q", "delivery/pilot/ai-raw/4a.txt")
|
|
214
|
+
self.commit("undo")
|
|
215
|
+
self.assert_blocked("ai-raw")
|
|
216
|
+
|
|
217
|
+
def test_change_to_frozen_fallback_output_is_blocked(self) -> None:
|
|
218
|
+
self.write("delivery/pilot/audit-out/report.md", "real client report\n")
|
|
219
|
+
self.commit("report")
|
|
220
|
+
self.assert_blocked("frozen fallback")
|
|
221
|
+
|
|
222
|
+
def test_intended_fallback_edit_needs_the_named_override_only(self) -> None:
|
|
223
|
+
self.write("delivery/pilot/audit-out/report.md", "corrected fallback report\n")
|
|
224
|
+
self.write("delivery/pilot/ai-raw/4a.txt", "reply\n")
|
|
225
|
+
self.commit("edit")
|
|
226
|
+
env = {**clean_env(), "KOMAGU_ALLOW_FALLBACK_EDIT": "1"}
|
|
227
|
+
blocked = subprocess.run(["git", "push", "origin", "work"], cwd=self.repo, env=env, capture_output=True, text=True)
|
|
228
|
+
self.assertNotEqual(blocked.returncode, 0)
|
|
229
|
+
self.assertIn("ai-raw", blocked.stderr)
|
|
230
|
+
self.assertNotIn("frozen fallback", blocked.stderr)
|
|
231
|
+
self.git("rm", "-q", "--cached", "delivery/pilot/ai-raw/4a.txt")
|
|
232
|
+
self.git("commit", "-q", "--amend", "--no-edit")
|
|
233
|
+
allowed = subprocess.run(["git", "push", "origin", "work"], cwd=self.repo, env=env, capture_output=True, text=True)
|
|
234
|
+
self.assertEqual(allowed.returncode, 0, allowed.stderr)
|
|
235
|
+
|
|
236
|
+
def test_push_to_main_is_still_blocked(self) -> None:
|
|
237
|
+
completed = subprocess.run(
|
|
238
|
+
["git", "push", "origin", "work:main"], cwd=self.repo, env=clean_env(), capture_output=True, text=True
|
|
239
|
+
)
|
|
240
|
+
self.assertNotEqual(completed.returncode, 0)
|
|
241
|
+
self.assertIn("direct push to main", completed.stderr)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
if __name__ == "__main__":
|
|
245
|
+
unittest.main()
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Regression tests for #372: `komagu fix` on Java legacy code and non-UTF-8 sources."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import shutil
|
|
8
|
+
import sys
|
|
9
|
+
import tempfile
|
|
10
|
+
import unittest
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
15
|
+
sys.path.insert(0, str(REPO_ROOT))
|
|
16
|
+
|
|
17
|
+
from operate.remediator.models import RemediationPlan
|
|
18
|
+
from operate.remediator.runner import RemediationRunner
|
|
19
|
+
from remediate.authz.patcher import BOLAOwnershipPatcher
|
|
20
|
+
|
|
21
|
+
SHIM = "remediate/legacy_shims/java/OwnershipEnforcer.java"
|
|
22
|
+
GUARD_IMPORT = "import io.komagu.security.spring.RequireOwnership;"
|
|
23
|
+
BOOT3_POM = """<project>
|
|
24
|
+
<parent>
|
|
25
|
+
<groupId>org.springframework.boot</groupId>
|
|
26
|
+
<artifactId>spring-boot-starter-parent</artifactId>
|
|
27
|
+
<version>3.2.1</version>
|
|
28
|
+
</parent>
|
|
29
|
+
</project>
|
|
30
|
+
"""
|
|
31
|
+
BOOT2_POM = BOOT3_POM.replace("3.2.1", "2.7.18")
|
|
32
|
+
MVC_POM = """<project>
|
|
33
|
+
<dependencies>
|
|
34
|
+
<dependency>
|
|
35
|
+
<groupId>org.springframework</groupId>
|
|
36
|
+
<artifactId>spring-webmvc</artifactId>
|
|
37
|
+
<version>4.3.30.RELEASE</version>
|
|
38
|
+
</dependency>
|
|
39
|
+
</dependencies>
|
|
40
|
+
</project>
|
|
41
|
+
"""
|
|
42
|
+
STRUTS_ACTION = (
|
|
43
|
+
"package com.acme.web;\n"
|
|
44
|
+
"\n"
|
|
45
|
+
"public class ViewAction extends Action {\n"
|
|
46
|
+
" public ActionForward execute(ActionMapping m, ActionForm f, HttpServletRequest req, HttpServletResponse res) {\n"
|
|
47
|
+
' req.setAttribute("order", dao.find(req.getParameter("id")));\n'
|
|
48
|
+
' return m.findForward("ok");\n'
|
|
49
|
+
" }\n"
|
|
50
|
+
"}\n"
|
|
51
|
+
)
|
|
52
|
+
SERVLET = (
|
|
53
|
+
"public class OrderServlet extends HttpServlet {\n"
|
|
54
|
+
" protected void doGet(HttpServletRequest req, HttpServletResponse res) {\n"
|
|
55
|
+
' write(res, dao.find(req.getParameter("id")));\n'
|
|
56
|
+
" }\n"
|
|
57
|
+
"}\n"
|
|
58
|
+
)
|
|
59
|
+
SPRING_CONTROLLER = (
|
|
60
|
+
"package com.acme.web;\n"
|
|
61
|
+
"\n"
|
|
62
|
+
"@RestController\n"
|
|
63
|
+
"public class OrderController {\n"
|
|
64
|
+
' @GetMapping("/orders/{id}")\n'
|
|
65
|
+
" public Order getOrder(@PathVariable Long id) {\n"
|
|
66
|
+
" return repository.findById(id).orElseThrow();\n"
|
|
67
|
+
" }\n"
|
|
68
|
+
"\n"
|
|
69
|
+
' @DeleteMapping("/orders/{id}")\n'
|
|
70
|
+
" public Order deleteOrder(@PathVariable Long id) {\n"
|
|
71
|
+
" return repository.deleteById(id);\n"
|
|
72
|
+
" }\n"
|
|
73
|
+
"}\n"
|
|
74
|
+
)
|
|
75
|
+
ORDER_ENDPOINT = {"method": "GET", "path": "/orders/{id}"}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _finding(fid: str, file: str, line: int, endpoint: Optional[Dict[str, str]] = None) -> dict:
|
|
79
|
+
return {
|
|
80
|
+
"finding_id": fid,
|
|
81
|
+
"category": "bola",
|
|
82
|
+
"title": fid,
|
|
83
|
+
"severity": "high",
|
|
84
|
+
"location": {"file": file, "start_line": line, "end_line": line},
|
|
85
|
+
"endpoint": endpoint or ORDER_ENDPOINT,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class JavaFixTestCase(unittest.TestCase):
|
|
90
|
+
def setUp(self) -> None:
|
|
91
|
+
self.tmp = Path(tempfile.mkdtemp(prefix="komagu_java_fix_")).resolve()
|
|
92
|
+
self.addCleanup(shutil.rmtree, self.tmp, ignore_errors=True)
|
|
93
|
+
|
|
94
|
+
def write(self, rel: str, data: bytes) -> Path:
|
|
95
|
+
path = self.tmp / rel
|
|
96
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
97
|
+
path.write_bytes(data)
|
|
98
|
+
return path
|
|
99
|
+
|
|
100
|
+
def run_fix(self, findings: List[dict], dry_run: bool = False) -> List[RemediationPlan]:
|
|
101
|
+
findings_file = self.tmp / "findings.json"
|
|
102
|
+
findings_file.write_text(json.dumps({"findings": findings}), encoding="utf-8")
|
|
103
|
+
return RemediationRunner(self.tmp, findings_file, dry_run=dry_run).run().plans
|
|
104
|
+
|
|
105
|
+
def assert_manual_with_shim(self, plan: RemediationPlan) -> None:
|
|
106
|
+
self.assertEqual(plan.status, "needs manual fix", plan.reason)
|
|
107
|
+
self.assertIn(SHIM, plan.reason)
|
|
108
|
+
self.assertEqual(plan.diff, "")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class TestNotSpringBoot3GoesToManualFix(JavaFixTestCase):
|
|
112
|
+
def assert_untouched_manual(self, source: str, line: int, endpoint: Optional[Dict[str, str]] = None) -> None:
|
|
113
|
+
data = source.encode("utf-8")
|
|
114
|
+
path = self.write("src/Target.java", data)
|
|
115
|
+
plans = self.run_fix([_finding("BOLA-1", "src/Target.java", line, endpoint)])
|
|
116
|
+
self.assert_manual_with_shim(plans[0])
|
|
117
|
+
self.assertEqual(path.read_bytes(), data)
|
|
118
|
+
|
|
119
|
+
def test_struts_action(self) -> None:
|
|
120
|
+
self.assert_untouched_manual(STRUTS_ACTION, 4, {"method": "GET", "path": "/order/View.do"})
|
|
121
|
+
|
|
122
|
+
def test_struts_action_inside_a_spring_boot_3_build(self) -> None:
|
|
123
|
+
self.write("pom.xml", BOOT3_POM.encode("utf-8"))
|
|
124
|
+
self.assert_untouched_manual(STRUTS_ACTION, 4, {"method": "GET", "path": "/order/View.do"})
|
|
125
|
+
|
|
126
|
+
def test_servlet(self) -> None:
|
|
127
|
+
self.assert_untouched_manual(SERVLET, 2)
|
|
128
|
+
|
|
129
|
+
def test_controller_without_a_build_file(self) -> None:
|
|
130
|
+
self.assert_untouched_manual(SPRING_CONTROLLER, 5)
|
|
131
|
+
|
|
132
|
+
def test_controller_on_spring_boot_2(self) -> None:
|
|
133
|
+
self.write("pom.xml", BOOT2_POM.encode("utf-8"))
|
|
134
|
+
self.assert_untouched_manual(SPRING_CONTROLLER, 5)
|
|
135
|
+
|
|
136
|
+
def test_controller_on_plain_spring_mvc(self) -> None:
|
|
137
|
+
self.write("pom.xml", MVC_POM.encode("utf-8"))
|
|
138
|
+
self.assert_untouched_manual(SPRING_CONTROLLER, 5)
|
|
139
|
+
|
|
140
|
+
def test_spring_boot_3_commented_out_in_pom_does_not_count(self) -> None:
|
|
141
|
+
self.write("pom.xml", f"<!-- migration attempt: {BOOT3_POM} -->\n{MVC_POM}".encode("utf-8"))
|
|
142
|
+
self.assert_untouched_manual(SPRING_CONTROLLER, 5)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class TestSpringBoot3ControllerStillPatched(JavaFixTestCase):
|
|
146
|
+
def assert_planned_on_handler(self) -> RemediationPlan:
|
|
147
|
+
self.write("src/OrderController.java", SPRING_CONTROLLER.encode("utf-8"))
|
|
148
|
+
plan = self.run_fix([_finding("BOLA-1", "src/OrderController.java", 5)], dry_run=True)[0]
|
|
149
|
+
self.assertEqual(plan.status, "planned", plan.reason)
|
|
150
|
+
return plan
|
|
151
|
+
|
|
152
|
+
def test_guard_uses_the_real_annotation_package(self) -> None:
|
|
153
|
+
self.write("pom.xml", BOOT3_POM.encode("utf-8"))
|
|
154
|
+
plan = self.assert_planned_on_handler()
|
|
155
|
+
self.assertIn(GUARD_IMPORT, plan.patched_content)
|
|
156
|
+
self.assertIn("import io.komagu.security.authorization.ResourceOperation;", plan.patched_content)
|
|
157
|
+
self.assertIn("@RequireOwnership(entityClass = Order.class, operation = ResourceOperation.READ)", plan.patched_content)
|
|
158
|
+
|
|
159
|
+
def test_version_from_parent_pom_of_a_multi_module_build(self) -> None:
|
|
160
|
+
self.write("pom.xml", BOOT3_POM.encode("utf-8"))
|
|
161
|
+
self.write("web/pom.xml", b"<project><artifactId>web</artifactId></project>\n")
|
|
162
|
+
self.write("web/src/OrderController.java", SPRING_CONTROLLER.encode("utf-8"))
|
|
163
|
+
plan = self.run_fix([_finding("BOLA-1", "web/src/OrderController.java", 5)], dry_run=True)[0]
|
|
164
|
+
self.assertEqual(plan.status, "planned", plan.reason)
|
|
165
|
+
|
|
166
|
+
def test_version_from_gradle_plugin(self) -> None:
|
|
167
|
+
self.write("build.gradle.kts", b'plugins {\n id("org.springframework.boot") version "3.3.0"\n}\n')
|
|
168
|
+
self.assert_planned_on_handler()
|
|
169
|
+
|
|
170
|
+
def test_version_from_gradle_properties(self) -> None:
|
|
171
|
+
self.write("build.gradle", b"plugins { id 'org.springframework.boot' version \"$springBootVersion\" }\n")
|
|
172
|
+
self.write("gradle.properties", b"springBootVersion=3.1.5\n")
|
|
173
|
+
self.assert_planned_on_handler()
|
|
174
|
+
|
|
175
|
+
def test_version_from_maven_property(self) -> None:
|
|
176
|
+
self.write("pom.xml", b"<project><properties><spring-boot.version>3.4.0</spring-boot.version></properties></project>\n")
|
|
177
|
+
self.assert_planned_on_handler()
|
|
178
|
+
|
|
179
|
+
def test_finding_on_the_method_declaration(self) -> None:
|
|
180
|
+
self.write("pom.xml", BOOT3_POM.encode("utf-8"))
|
|
181
|
+
self.write("src/OrderController.java", SPRING_CONTROLLER.encode("utf-8"))
|
|
182
|
+
plan = self.run_fix([_finding("BOLA-1", "src/OrderController.java", 6)], dry_run=True)[0]
|
|
183
|
+
self.assertEqual(plan.status, "planned", plan.reason)
|
|
184
|
+
|
|
185
|
+
def test_finding_inside_the_method_body_is_manual(self) -> None:
|
|
186
|
+
self.write("pom.xml", BOOT3_POM.encode("utf-8"))
|
|
187
|
+
self.write("src/OrderController.java", SPRING_CONTROLLER.encode("utf-8"))
|
|
188
|
+
plan = self.run_fix([_finding("BOLA-1", "src/OrderController.java", 7)], dry_run=True)[0]
|
|
189
|
+
self.assert_manual_with_shim(plan)
|
|
190
|
+
|
|
191
|
+
def test_class_level_mapping_is_manual(self) -> None:
|
|
192
|
+
self.write("pom.xml", BOOT3_POM.encode("utf-8"))
|
|
193
|
+
source = (
|
|
194
|
+
"@RestController\n"
|
|
195
|
+
'@RequestMapping("/orders")\n'
|
|
196
|
+
"public class OrderController {\n"
|
|
197
|
+
' @GetMapping("/{id}")\n'
|
|
198
|
+
" public Order get(@PathVariable Long id) { return repository.find(id); }\n"
|
|
199
|
+
"}\n"
|
|
200
|
+
)
|
|
201
|
+
self.write("src/OrderController.java", source.encode("utf-8"))
|
|
202
|
+
plan = self.run_fix([_finding("BOLA-1", "src/OrderController.java", 2)], dry_run=True)[0]
|
|
203
|
+
self.assert_manual_with_shim(plan)
|
|
204
|
+
|
|
205
|
+
def test_entity_that_would_not_compile_is_manual(self) -> None:
|
|
206
|
+
self.write("pom.xml", BOOT3_POM.encode("utf-8"))
|
|
207
|
+
self.write("src/OrderController.java", SPRING_CONTROLLER.encode("utf-8"))
|
|
208
|
+
plan = self.run_fix(
|
|
209
|
+
[_finding("BOLA-1", "src/OrderController.java", 5, {"method": "GET", "path": "/order/View.do"})],
|
|
210
|
+
dry_run=True,
|
|
211
|
+
)[0]
|
|
212
|
+
self.assert_manual_with_shim(plan)
|
|
213
|
+
self.assertNotIn("View.do.class", plan.patched_content)
|
|
214
|
+
|
|
215
|
+
def test_entity_not_referenced_in_the_file_is_manual(self) -> None:
|
|
216
|
+
self.write("pom.xml", BOOT3_POM.encode("utf-8"))
|
|
217
|
+
self.write("src/OrderController.java", SPRING_CONTROLLER.encode("utf-8"))
|
|
218
|
+
plan = self.run_fix(
|
|
219
|
+
[_finding("BOLA-1", "src/OrderController.java", 5, {"method": "GET", "path": "/invoices/{id}"})],
|
|
220
|
+
dry_run=True,
|
|
221
|
+
)[0]
|
|
222
|
+
self.assert_manual_with_shim(plan)
|
|
223
|
+
|
|
224
|
+
def assert_entity_rejected(self, source: str, line: int, path: str) -> None:
|
|
225
|
+
self.write("pom.xml", BOOT3_POM.encode("utf-8"))
|
|
226
|
+
self.write("src/Thing.java", source.encode("utf-8"))
|
|
227
|
+
plan = self.run_fix([_finding("BOLA-1", "src/Thing.java", line, {"method": "GET", "path": path})], dry_run=True)[0]
|
|
228
|
+
self.assert_manual_with_shim(plan)
|
|
229
|
+
|
|
230
|
+
def test_controller_class_is_not_accepted_as_the_entity(self) -> None:
|
|
231
|
+
source = '@RestController\npublic class Thing {\n @GetMapping("/{id}")\n public Object get(@PathVariable Long id) {\n return null;\n }\n}\n'
|
|
232
|
+
self.assert_entity_rejected(source, 3, "/{id}")
|
|
233
|
+
|
|
234
|
+
def test_entity_mentioned_only_in_a_string_or_comment_is_rejected(self) -> None:
|
|
235
|
+
source = '// Order lookup\n@RestController\npublic class Thing {\n @GetMapping("/orders/{id}")\n public Object get(@PathVariable Long id) {\n throw new RuntimeException("Order not found");\n }\n}\n'
|
|
236
|
+
self.assert_entity_rejected(source, 4, "/orders/{id}")
|
|
237
|
+
|
|
238
|
+
def test_two_findings_in_one_file_are_both_applied_on_their_own_handlers(self) -> None:
|
|
239
|
+
self.write("pom.xml", BOOT3_POM.encode("utf-8"))
|
|
240
|
+
path = self.write("src/OrderController.java", SPRING_CONTROLLER.encode("utf-8"))
|
|
241
|
+
plans = self.run_fix(
|
|
242
|
+
[
|
|
243
|
+
_finding("BOLA-1", "src/OrderController.java", 5),
|
|
244
|
+
_finding("BOLA-2", "src/OrderController.java", 10, {"method": "DELETE", "path": "/orders/{id}"}),
|
|
245
|
+
]
|
|
246
|
+
)
|
|
247
|
+
self.assertEqual([p.status for p in plans], ["applied", "applied"], [p.reason for p in plans])
|
|
248
|
+
lines = path.read_text(encoding="utf-8").splitlines()
|
|
249
|
+
read_guard = lines.index(' @GetMapping("/orders/{id}")') - 1
|
|
250
|
+
delete_guard = lines.index(' @DeleteMapping("/orders/{id}")') - 1
|
|
251
|
+
self.assertIn("ResourceOperation.READ", lines[read_guard])
|
|
252
|
+
self.assertIn("ResourceOperation.DELETE", lines[delete_guard])
|
|
253
|
+
self.assertEqual(sum(line == GUARD_IMPORT for line in lines), 1)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class TestEncodingAndNewlinesPreserved(JavaFixTestCase):
|
|
257
|
+
COMMENT = " // 注文を取得する\r\n"
|
|
258
|
+
|
|
259
|
+
def sjis_controller(self) -> bytes:
|
|
260
|
+
source = SPRING_CONTROLLER.replace("\n", "\r\n").replace(
|
|
261
|
+
' @GetMapping("/orders/{id}")', self.COMMENT + ' @GetMapping("/orders/{id}")'
|
|
262
|
+
)
|
|
263
|
+
return source.encode("cp932")
|
|
264
|
+
|
|
265
|
+
def test_shift_jis_crlf_file_keeps_every_original_byte(self) -> None:
|
|
266
|
+
self.write("pom.xml", BOOT3_POM.encode("utf-8"))
|
|
267
|
+
original = self.sjis_controller()
|
|
268
|
+
path = self.write("src/OrderController.java", original)
|
|
269
|
+
|
|
270
|
+
plan = self.run_fix([_finding("BOLA-1", "src/OrderController.java", 6)])[0]
|
|
271
|
+
|
|
272
|
+
self.assertEqual(plan.status, "applied", plan.reason)
|
|
273
|
+
patched = path.read_bytes()
|
|
274
|
+
self.assertIn(self.COMMENT.encode("cp932"), patched)
|
|
275
|
+
self.assertNotIn(b"\xef\xbf\xbd", patched)
|
|
276
|
+
self.assertEqual(patched.count(b"\n"), patched.count(b"\r\n"))
|
|
277
|
+
remaining = patched.split(b"\r\n")
|
|
278
|
+
for line in (
|
|
279
|
+
b"import io.komagu.security.spring.RequireOwnership;",
|
|
280
|
+
b"import io.komagu.security.authorization.ResourceOperation;",
|
|
281
|
+
b"",
|
|
282
|
+
b" @RequireOwnership(entityClass = Order.class, operation = ResourceOperation.READ)",
|
|
283
|
+
):
|
|
284
|
+
remaining.remove(line)
|
|
285
|
+
self.assertEqual(remaining, original.split(b"\r\n"))
|
|
286
|
+
|
|
287
|
+
def test_diff_only_adds_lines(self) -> None:
|
|
288
|
+
self.write("pom.xml", BOOT3_POM.encode("utf-8"))
|
|
289
|
+
self.write("src/OrderController.java", self.sjis_controller())
|
|
290
|
+
|
|
291
|
+
plan = self.run_fix([_finding("BOLA-1", "src/OrderController.java", 6)], dry_run=True)[0]
|
|
292
|
+
|
|
293
|
+
self.assertEqual(plan.status, "planned", plan.reason)
|
|
294
|
+
body = plan.diff.split("\n")[2:]
|
|
295
|
+
self.assertEqual([line for line in body if line.startswith("-")], [])
|
|
296
|
+
self.assertIn("注文を取得する", plan.patched_content)
|
|
297
|
+
self.assertEqual(plan.patched_content.count("\n"), plan.patched_content.count("\r\n"))
|
|
298
|
+
|
|
299
|
+
def test_shift_jis_struts_action_is_manual_and_untouched(self) -> None:
|
|
300
|
+
original = STRUTS_ACTION.replace("\n", "\r\n").replace("public class", "// 注文\r\npublic class", 1).encode("cp932")
|
|
301
|
+
path = self.write("src/ViewAction.java", original)
|
|
302
|
+
plan = self.run_fix([_finding("BOLA-1", "src/ViewAction.java", 5)])[0]
|
|
303
|
+
self.assert_manual_with_shim(plan)
|
|
304
|
+
self.assertEqual(path.read_bytes(), original)
|
|
305
|
+
|
|
306
|
+
def test_crlf_csharp_patch_stays_crlf(self) -> None:
|
|
307
|
+
source = "namespace Web;\r\npublic class C {\r\n [HttpGet]\r\n public Order Get(Guid id) { return null; }\r\n}\r\n"
|
|
308
|
+
path = self.write("C.cs", source.encode("utf-8"))
|
|
309
|
+
plan = self.run_fix([_finding("BOLA-1", "C.cs", 3)])[0]
|
|
310
|
+
self.assertEqual(plan.status, "applied", plan.reason)
|
|
311
|
+
patched = path.read_bytes()
|
|
312
|
+
self.assertEqual(patched.count(b"\n"), patched.count(b"\r\n"))
|
|
313
|
+
|
|
314
|
+
def test_import_goes_after_the_package_statement_not_into_a_license_comment(self) -> None:
|
|
315
|
+
self.write("pom.xml", BOOT3_POM.encode("utf-8"))
|
|
316
|
+
source = "/* Terms: see LICENSE;\n * package names are ours;\n */\n" + SPRING_CONTROLLER
|
|
317
|
+
self.write("src/OrderController.java", source.encode("utf-8"))
|
|
318
|
+
plan = self.run_fix([_finding("BOLA-1", "src/OrderController.java", 8)], dry_run=True)[0]
|
|
319
|
+
self.assertEqual(plan.status, "planned", plan.reason)
|
|
320
|
+
self.assertLess(plan.patched_content.index("package com.acme.web;"), plan.patched_content.index(GUARD_IMPORT))
|
|
321
|
+
self.assertLess(plan.patched_content.index("*/"), plan.patched_content.index(GUARD_IMPORT))
|
|
322
|
+
|
|
323
|
+
def test_direct_call_refuses_to_replace_undecodable_bytes(self) -> None:
|
|
324
|
+
path = self.write("Dao.java", self.sjis_controller())
|
|
325
|
+
with self.assertRaises(UnicodeDecodeError):
|
|
326
|
+
BOLAOwnershipPatcher().patch_file(path, 6, _finding("BOLA-1", "Dao.java", 6))
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
if __name__ == "__main__":
|
|
330
|
+
unittest.main()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hoanghainh/komagu",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "Security-First Legacy Modernization Platform CLI (LSA Kit, IAM Perimeter, Cross-User Testing)",
|
|
5
5
|
"main": "remediate/authz/nodejs/src/index.js",
|
|
6
6
|
"types": "remediate/authz/nodejs/src/index.d.ts",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"examples/",
|
|
18
18
|
"README.md",
|
|
19
19
|
"CHANGELOG.md",
|
|
20
|
+
"!examples/legacy-java-rehearsal/**",
|
|
20
21
|
"!**/__pycache__/**",
|
|
21
22
|
"!**/.gradle/**",
|
|
22
23
|
"!**/build/**",
|