@hoanghainh/komagu 1.8.0 → 2.0.1

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.
Files changed (210) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +1 -1
  3. package/assess/lsa/CHANGELOG.md +6 -0
  4. package/assess/lsa/VERSION +1 -1
  5. package/assess/lsa/adapters/build_adapters.py +16 -8
  6. package/assess/lsa/adapters/codex/README.md +1 -1
  7. package/assess/lsa/adapters/generic/README.md +1 -1
  8. package/assess/lsa/examples/eval-run-4/ground-truth.json +823 -0
  9. package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/ai-run-stats.json +58 -0
  10. package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/coverage.json +85 -0
  11. package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/findings/priv-privilege-001.json +822 -0
  12. package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/findings/priv-privilege-002.json +393 -0
  13. package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/findings.json +4477 -0
  14. package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/mass-assignment.candidates.json +232 -0
  15. package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/routes/routes-001.json +425 -0
  16. package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/routes/routes-002.json +333 -0
  17. package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/routes/routes-003.json +400 -0
  18. package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/routes/routes-004.json +403 -0
  19. package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/routes/routes-005.json +398 -0
  20. package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/routes/routes-006.json +63 -0
  21. package/assess/lsa/examples/eval-run-4/iter0-gptoss-a/score.json +120 -0
  22. package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/ai-run-stats.json +55 -0
  23. package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/coverage.json +50 -0
  24. package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/findings/priv-privilege-001.json +613 -0
  25. package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/findings/priv-privilege-002.json +287 -0
  26. package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/findings.json +4870 -0
  27. package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/mass-assignment.candidates.json +232 -0
  28. package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/routes/routes-001.json +425 -0
  29. package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/routes/routes-002.json +333 -0
  30. package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/routes/routes-003.json +400 -0
  31. package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/routes/routes-004.json +403 -0
  32. package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/routes/routes-005.json +398 -0
  33. package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/routes/routes-006.json +63 -0
  34. package/assess/lsa/examples/eval-run-4/iter1-gptoss-a/score.json +107 -0
  35. package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/ai-run-stats.json +58 -0
  36. package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/coverage.json +92 -0
  37. package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/findings/priv-privilege-001.json +645 -0
  38. package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/findings/priv-privilege-002.json +222 -0
  39. package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/findings.json +4859 -0
  40. package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/mass-assignment.candidates.json +232 -0
  41. package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/routes/routes-001.json +425 -0
  42. package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/routes/routes-002.json +333 -0
  43. package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/routes/routes-003.json +400 -0
  44. package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/routes/routes-004.json +403 -0
  45. package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/routes/routes-005.json +398 -0
  46. package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/routes/routes-006.json +63 -0
  47. package/assess/lsa/examples/eval-run-4/iter1-gptoss-b/score.json +121 -0
  48. package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/coverage.json +111 -0
  49. package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/findings/priv-privilege-001.json +6 -0
  50. package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/findings/priv-privilege-002.json +344 -0
  51. package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/findings.json +3544 -0
  52. package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/mass-assignment.candidates.json +232 -0
  53. package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/routes/routes-001.json +425 -0
  54. package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/routes/routes-002.json +333 -0
  55. package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/routes/routes-003.json +400 -0
  56. package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/routes/routes-004.json +403 -0
  57. package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/routes/routes-005.json +398 -0
  58. package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/routes/routes-006.json +63 -0
  59. package/assess/lsa/examples/eval-run-4/iter1-qwen-a-incomplete/score.json +123 -0
  60. package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/ai-run-stats.json +50 -0
  61. package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/coverage.json +42 -0
  62. package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/findings/priv-privilege-001.json +540 -0
  63. package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/findings/priv-privilege-002.json +238 -0
  64. package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/findings.json +4383 -0
  65. package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/findings.sarif +8729 -0
  66. package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/mass-assignment.candidates.json +232 -0
  67. package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/report.ja.md +1911 -0
  68. package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/report.md +1911 -0
  69. package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/routes/routes-001.json +425 -0
  70. package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/routes/routes-002.json +333 -0
  71. package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/routes/routes-003.json +400 -0
  72. package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/routes/routes-004.json +403 -0
  73. package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/routes/routes-005.json +398 -0
  74. package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/routes/routes-006.json +63 -0
  75. package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/score.json +101 -0
  76. package/assess/lsa/examples/eval-run-4/iter2-gptoss-a/stack-profile.json +226 -0
  77. package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/ai-run-stats.json +54 -0
  78. package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/coverage.json +66 -0
  79. package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/findings/priv-privilege-001.json +440 -0
  80. package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/findings/priv-privilege-002.json +229 -0
  81. package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/findings.json +3424 -0
  82. package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/mass-assignment.candidates.json +232 -0
  83. package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/routes/routes-001.json +425 -0
  84. package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/routes/routes-002.json +333 -0
  85. package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/routes/routes-003.json +400 -0
  86. package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/routes/routes-004.json +403 -0
  87. package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/routes/routes-005.json +398 -0
  88. package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/routes/routes-006.json +63 -0
  89. package/assess/lsa/examples/eval-run-4/iter2-gptoss-b/score.json +120 -0
  90. package/assess/lsa/examples/eval-run-5/gptoss-a/ai-run-stats.json +88 -0
  91. package/assess/lsa/examples/eval-run-5/gptoss-a/coverage.json +36 -0
  92. package/assess/lsa/examples/eval-run-5/gptoss-a/findings/priv-privilege-001.json +530 -0
  93. package/assess/lsa/examples/eval-run-5/gptoss-a/findings/priv-privilege-002.json +248 -0
  94. package/assess/lsa/examples/eval-run-5/gptoss-a/findings.json +4766 -0
  95. package/assess/lsa/examples/eval-run-5/gptoss-a/findings.sarif +9326 -0
  96. package/assess/lsa/examples/eval-run-5/gptoss-a/mass-assignment.candidates.json +232 -0
  97. package/assess/lsa/examples/eval-run-5/gptoss-a/report.ja.md +2011 -0
  98. package/assess/lsa/examples/eval-run-5/gptoss-a/report.md +2011 -0
  99. package/assess/lsa/examples/eval-run-5/gptoss-a/routes/routes-001.json +425 -0
  100. package/assess/lsa/examples/eval-run-5/gptoss-a/routes/routes-002.json +333 -0
  101. package/assess/lsa/examples/eval-run-5/gptoss-a/routes/routes-003.json +400 -0
  102. package/assess/lsa/examples/eval-run-5/gptoss-a/routes/routes-004.json +403 -0
  103. package/assess/lsa/examples/eval-run-5/gptoss-a/routes/routes-005.json +398 -0
  104. package/assess/lsa/examples/eval-run-5/gptoss-a/routes/routes-006.json +63 -0
  105. package/assess/lsa/examples/eval-run-5/gptoss-a/score.json +80 -0
  106. package/assess/lsa/examples/eval-run-5/gptoss-a/stack-profile.json +226 -0
  107. package/assess/lsa/examples/eval-run-5/gptoss-b/ai-run-stats.json +83 -0
  108. package/assess/lsa/examples/eval-run-5/gptoss-b/coverage.json +44 -0
  109. package/assess/lsa/examples/eval-run-5/gptoss-b/findings/priv-privilege-001.json +579 -0
  110. package/assess/lsa/examples/eval-run-5/gptoss-b/findings/priv-privilege-002.json +268 -0
  111. package/assess/lsa/examples/eval-run-5/gptoss-b/findings.json +3913 -0
  112. package/assess/lsa/examples/eval-run-5/gptoss-b/mass-assignment.candidates.json +232 -0
  113. package/assess/lsa/examples/eval-run-5/gptoss-b/routes/routes-001.json +425 -0
  114. package/assess/lsa/examples/eval-run-5/gptoss-b/routes/routes-002.json +333 -0
  115. package/assess/lsa/examples/eval-run-5/gptoss-b/routes/routes-003.json +400 -0
  116. package/assess/lsa/examples/eval-run-5/gptoss-b/routes/routes-004.json +403 -0
  117. package/assess/lsa/examples/eval-run-5/gptoss-b/routes/routes-005.json +398 -0
  118. package/assess/lsa/examples/eval-run-5/gptoss-b/routes/routes-006.json +63 -0
  119. package/assess/lsa/examples/eval-run-5/gptoss-b/score.json +94 -0
  120. package/assess/lsa/examples/eval-run-5/ground-truth.json +823 -0
  121. package/assess/lsa/knowledge/severity-and-cwe.md +1 -1
  122. package/assess/lsa/prompts/35-privilege-auditor.md +3 -1
  123. package/assess/lsa/scripts/baseline.py +5 -2
  124. package/assess/lsa/scripts/findings_to_sarif.py +24 -1
  125. package/assess/lsa/tests/EVAL-RESULTS.md +156 -0
  126. package/assess/lsa/tests/score_findings.py +58 -0
  127. package/assess/lsa/tests/test_adapters.py +21 -2
  128. package/assess/lsa/tests/test_baseline.py +8 -0
  129. package/assess/lsa/tests/test_contract_groundwork.py +7 -7
  130. package/assess/lsa/tests/test_eval_run_4.py +73 -0
  131. package/assess/lsa/tests/test_eval_run_5.py +73 -0
  132. package/assess/lsa/tests/test_privilege_tier2.py +30 -0
  133. package/operate/ai/anchoring.py +130 -0
  134. package/operate/ai/client.py +13 -0
  135. package/operate/ai/context.py +105 -0
  136. package/operate/ai/runner.py +197 -69
  137. package/operate/cli/__init__.py +1 -1
  138. package/operate/cli/commands/scan.py +3 -8
  139. package/operate/cli/commands/test.py +1 -1
  140. package/operate/cli/env.py +4 -20
  141. package/operate/cross_user_tester/persona_matrix.py +5 -1
  142. package/operate/devsecops/pipeline.py +5 -5
  143. package/operate/exploit/generator.py +5 -1
  144. package/operate/lsa_sarif/lsa_to_sarif.py +24 -2
  145. package/operate/lsa_sarif/sarif_findings.py +96 -0
  146. package/operate/remediator/runner.py +1 -5
  147. package/operate/remediator/strategies.py +1 -1
  148. package/operate/tests/test_ai_anchoring.py +184 -0
  149. package/operate/tests/test_cli_baseline.py +1 -9
  150. package/operate/tests/test_cli_env.py +8 -29
  151. package/operate/tests/test_cross_user_pipeline.py +2 -2
  152. package/operate/tests/test_exploit_and_virtual_patch.py +3 -3
  153. package/operate/tests/test_headless_ai.py +148 -1
  154. package/operate/tests/test_npm_package.py +2 -16
  155. package/operate/tests/test_sarif_from_scan.py +156 -0
  156. package/package.json +2 -4
  157. package/remediate/authz/README.md +1 -1
  158. package/remediate/authz/dotnet/src/Komagu.Security.Authorization/DefaultKomaguAuthorizationService.cs +1 -1
  159. package/remediate/authz/dotnet/src/Komagu.Security.Authorization/SecurityAuditEvent.cs +2 -2
  160. package/remediate/authz/golang/authz.go +1 -1
  161. package/remediate/authz/nodejs/src/service.js +1 -1
  162. package/remediate/authz/nodejs/tests/test-authz.js +1 -1
  163. package/remediate/authz/patcher.py +3 -4
  164. package/remediate/authz/python/komagu_authz/models.py +2 -2
  165. package/remediate/authz/python/tests/test_fastapi_adapter.py +1 -1
  166. package/remediate/authz/python/tests/test_fastapi_d5_crosstest.py +1 -1
  167. package/remediate/authz/python/tests/test_python_authz.py +3 -3
  168. package/remediate/authz/spring/src/main/kotlin/io/komagu/security/authorization/DefaultKomaguSecurityManager.kt +1 -1
  169. package/remediate/authz/spring/src/main/kotlin/io/komagu/security/authorization/SecurityAuditEvent.kt +1 -2
  170. package/remediate/authz/tests/test_bola_patcher.py +1 -1
  171. package/remediate/edge/coraza/rules/coraza-crs-custom.conf +3 -4
  172. package/remediate/edge/coraza/virtual_patcher.py +4 -6
  173. package/remediate/edge/docker-compose.yml +1 -1
  174. package/remediate/ekyc/server/jpki_verifier.py +2 -8
  175. package/remediate/ekyc/server/tokenization.py +2 -8
  176. package/remediate/identity/README.md +5 -5
  177. package/remediate/identity/dotnet/src/Komagu.Security.Identity/KomaguOidcOptions.cs +2 -2
  178. package/remediate/identity/dotnet/tests/Komagu.Security.Identity.Tests/TestTokenGenerator.cs +1 -1
  179. package/remediate/identity/dotnet/tests/Komagu.Security.Identity.Tests/TokenValidationTests.cs +1 -1
  180. package/remediate/identity/python/komagu_identity/oidc_validator.py +2 -8
  181. package/remediate/identity/python/tests/test_oidc_validator.py +1 -1
  182. package/remediate/identity/spring/src/main/kotlin/io/komagu/security/identity/KomaguOidcProperties.kt +2 -2
  183. package/remediate/identity/spring/src/test/kotlin/io/komagu/security/identity/KomaguIdentityAutoConfigurationTest.kt +2 -2
  184. package/remediate/identity/spring/src/test/kotlin/io/komagu/security/identity/TestJwtFactory.kt +1 -1
  185. package/remediate/identity/spring/src/test/kotlin/io/komagu/security/identity/TokenValidationIntegrationTest.kt +1 -1
  186. package/remediate/identity/test-env/realm-komagu.json +1 -1
  187. package/remediate/legacy_shims/coldfusion/KomaguSecurityShim.cfc +5 -5
  188. package/remediate/query/README.md +1 -1
  189. package/remediate/query/dotnet/src/Komagu.Security.Query.Analyzers/DiagnosticIds.cs +1 -1
  190. package/remediate/query/dotnet/src/Komagu.Security.Query.Analyzers/Komagu.Security.Query.Analyzers.csproj +1 -1
  191. package/remediate/query/spring/src/main/kotlin/io/komagu/security/query/detekt/KomaguRuleSetProvider.kt +1 -1
  192. package/remediate/resilience/dr/dr_manager.py +2 -8
  193. package/remediate/resilience/dr/runbook.md +2 -2
  194. package/remediate/resilience/terraform/variables.tf +1 -1
  195. package/remediate/tenancy/audit/audit_trail.py +2 -8
  196. package/remediate/tenancy/crypto/byok_manager.py +2 -8
  197. package/remediate/tenancy/middleware/tenant_context.py +2 -8
  198. package/remediate/tenancy/sql/rls_isolation.sql +11 -11
  199. package/remediate/tenancy/sql/schema_per_tenant.sql +7 -7
  200. package/remediate/tenancy/test_sql_postgres.py +1 -1
  201. package/remediate/tenancy/tests/test_tenancy.py +4 -4
  202. package/bin/komainu +0 -16
  203. package/bin/komainu-legacy.js +0 -16
  204. package/remediate/ekyc/tests/test_legacy_env.py +0 -33
  205. package/remediate/identity/python/tests/test_legacy_env.py +0 -33
  206. package/remediate/identity/spring/src/main/kotlin/io/komagu/security/identity/LegacyKomainuPropertiesPostProcessor.kt +0 -60
  207. package/remediate/identity/spring/src/main/resources/META-INF/spring.factories +0 -2
  208. package/remediate/identity/spring/src/test/kotlin/io/komagu/security/identity/LegacyKomainuPropertiesPostProcessorTest.kt +0 -86
  209. package/remediate/resilience/tests/test_legacy_env.py +0 -33
  210. package/remediate/tenancy/tests/test_legacy_env.py +0 -49
@@ -0,0 +1,4859 @@
1
+ {
2
+ "generated_by": "lsa merge_findings.py 0.3.1",
3
+ "summary": {
4
+ "total_reported": 91,
5
+ "active": 87,
6
+ "rejected_by_verifier": 4,
7
+ "by_severity": {
8
+ "critical": 47,
9
+ "high": 40,
10
+ "medium": 0,
11
+ "low": 0,
12
+ "info": 0
13
+ },
14
+ "by_category": {
15
+ "bola": 36,
16
+ "sql_injection": 27,
17
+ "nosql_injection": 1,
18
+ "bfla": 12,
19
+ "mass_assignment": 11
20
+ },
21
+ "by_status": {
22
+ "unverified": 5,
23
+ "confirmed": 73,
24
+ "likely": 6,
25
+ "rejected": 4,
26
+ "needs_review": 3
27
+ }
28
+ },
29
+ "findings": [
30
+ {
31
+ "finding_id": "BFLA-R-0003-1",
32
+ "category": "bfla",
33
+ "subtype": "missing_role_check",
34
+ "title": "Unrestricted member deletion in Classic ASP shop",
35
+ "route_id": "R-0003",
36
+ "location": {
37
+ "file": "classic-asp/shop/member.asp",
38
+ "start_line": 9
39
+ },
40
+ "evidence": [
41
+ {
42
+ "file": "classic-asp/shop/member.asp",
43
+ "line": 9,
44
+ "why": "DELETE statement executed without any role check"
45
+ },
46
+ {
47
+ "file": "classic-asp/shop/member.asp",
48
+ "line": 6,
49
+ "why": "No server‑side role verification before deletion"
50
+ }
51
+ ],
52
+ "impact": "Any authenticated user can delete any member record",
53
+ "severity": "critical",
54
+ "confidence": "high",
55
+ "cwe": [
56
+ "CWE-285"
57
+ ],
58
+ "recommendation": "Add a server‑side check that the current user has the admin role before executing the DELETE",
59
+ "status": "confirmed",
60
+ "verification": {
61
+ "verdict": "confirmed",
62
+ "reason_code": "traced",
63
+ "checked": [
64
+ "classic-asp/shop/member.asp:6-9",
65
+ "classic-asp/shop/member.asp:9"
66
+ ],
67
+ "control_found": null,
68
+ "notes": "The DELETE statement is executed without any server‑side role check. No global include or session filter is found that restricts the action to administrators.",
69
+ "suggested_subtype": null,
70
+ "suggested_cwe": null
71
+ },
72
+ "consensus": {
73
+ "score": 0.95,
74
+ "agreement_level": "unanimous",
75
+ "voted_models": [
76
+ "gpt-oss:20b"
77
+ ],
78
+ "rationale": "Unprotected deletion operation in Classic ASP."
79
+ },
80
+ "fingerprint": "kfp1:83b02bec36973742a22a1a70d067dab98296c064b1b633e015f7e580b3b66d97",
81
+ "baseline_state": "new"
82
+ },
83
+ {
84
+ "finding_id": "BFLA-R-0007-1",
85
+ "category": "bfla",
86
+ "subtype": "missing_role_check",
87
+ "title": "Unprotected user deletion in ASP.NET MVC AdminController",
88
+ "route_id": "R-0007",
89
+ "location": {
90
+ "file": "Web.Controllers.AdminController.cs",
91
+ "start_line": 12
92
+ },
93
+ "evidence": [
94
+ {
95
+ "file": "Web.Controllers.AdminController.cs",
96
+ "line": 12,
97
+ "why": "DeleteUser action performs user removal without role check"
98
+ },
99
+ {
100
+ "file": "Web.Controllers.AdminController.cs",
101
+ "line": 6,
102
+ "why": "Class has [Authorize] but not role restriction"
103
+ }
104
+ ],
105
+ "impact": "Any authenticated user can delete any user account",
106
+ "severity": "critical",
107
+ "confidence": "high",
108
+ "cwe": [
109
+ "CWE-285"
110
+ ],
111
+ "recommendation": "Apply [Authorize(Roles=\"Admin\")] to DeleteUser or perform a role check inside the method",
112
+ "status": "confirmed",
113
+ "verification": {
114
+ "verdict": "confirmed",
115
+ "reason_code": "traced",
116
+ "checked": [
117
+ "Web.Controllers.AdminController.cs:6-12",
118
+ "Web.Controllers.AdminController.cs:12"
119
+ ],
120
+ "control_found": null,
121
+ "notes": "The controller is decorated with `[Authorize]` (ensuring authentication) but lacks a role restriction. The `DeleteUser` action performs deletion without checking the current user's role.",
122
+ "suggested_subtype": null,
123
+ "suggested_cwe": null
124
+ },
125
+ "consensus": {
126
+ "score": 0.95,
127
+ "agreement_level": "unanimous",
128
+ "voted_models": [
129
+ "gpt-oss:20b"
130
+ ],
131
+ "rationale": "Missing role check in ASP.NET MVC."
132
+ },
133
+ "fingerprint": "kfp1:5a1d93af030432279cb45aa61753a47e0fd72025b4f444b730e6da6647dce974",
134
+ "baseline_state": "new"
135
+ },
136
+ {
137
+ "finding_id": "BFLA-R-0009-1",
138
+ "category": "bfla",
139
+ "subtype": "role_from_request",
140
+ "title": "Export endpoint trusts request parameter for admin access",
141
+ "route_id": "R-0009",
142
+ "location": {
143
+ "file": "Web.Controllers.AdminController.cs",
144
+ "start_line": 30
145
+ },
146
+ "evidence": [
147
+ {
148
+ "file": "Web.Controllers.AdminController.cs",
149
+ "line": 32,
150
+ "why": "Allows export if QueryString[\"isAdmin\"] == \"true\" without verifying role"
151
+ },
152
+ {
153
+ "file": "Web.Controllers.AdminController.cs",
154
+ "line": 30,
155
+ "why": "No server‑side role check before export"
156
+ }
157
+ ],
158
+ "impact": "Any user can export all user data by setting isAdmin=true",
159
+ "severity": "critical",
160
+ "confidence": "high",
161
+ "cwe": [
162
+ "CWE-285"
163
+ ],
164
+ "recommendation": "Validate the current user's role on the server before allowing export",
165
+ "status": "confirmed",
166
+ "verification": {
167
+ "verdict": "confirmed",
168
+ "reason_code": "traced",
169
+ "checked": [
170
+ "Web.Controllers.AdminController.cs:30-32",
171
+ "Web.Controllers.AdminController.cs:32"
172
+ ],
173
+ "control_found": null,
174
+ "notes": "The export action trusts a query string parameter `isAdmin` to grant admin access. No server‑side verification of the current user's role is performed.",
175
+ "suggested_subtype": null,
176
+ "suggested_cwe": null
177
+ },
178
+ "consensus": {
179
+ "score": 0.95,
180
+ "agreement_level": "unanimous",
181
+ "voted_models": [
182
+ "gpt-oss:20b"
183
+ ],
184
+ "rationale": "Role derived from request parameter."
185
+ },
186
+ "fingerprint": "kfp1:aa8fdfb92ed9327da2c70896932d74f0513e204d854918c3f9d26959536d5db3",
187
+ "baseline_state": "new"
188
+ },
189
+ {
190
+ "finding_id": "BFLA-R-0023-1",
191
+ "category": "bfla",
192
+ "subtype": "missing_role_check",
193
+ "title": "Unprotected user deletion in Spring MVC AdminUserController",
194
+ "route_id": "R-0023",
195
+ "location": {
196
+ "file": "com.acme.web.AdminUserController.java",
197
+ "start_line": 13
198
+ },
199
+ "evidence": [
200
+ {
201
+ "file": "com.acme.web.AdminUserController.java",
202
+ "line": 13,
203
+ "why": "Delete method lacks @PreAuthorize"
204
+ },
205
+ {
206
+ "file": "com.acme.web.AdminUserController.java",
207
+ "line": 19,
208
+ "why": "Only grant method has role check"
209
+ }
210
+ ],
211
+ "impact": "Any authenticated user can delete any user account",
212
+ "severity": "critical",
213
+ "confidence": "high",
214
+ "cwe": [
215
+ "CWE-285"
216
+ ],
217
+ "recommendation": "Add @PreAuthorize(\"hasRole('ADMIN')\") to the delete method or perform a role check inside",
218
+ "status": "confirmed",
219
+ "verification": {
220
+ "verdict": "confirmed",
221
+ "reason_code": "traced",
222
+ "checked": [
223
+ "com.acme.web.AdminUserController.java:13-19",
224
+ "com.acme.web.AdminUserController.java:13"
225
+ ],
226
+ "control_found": null,
227
+ "notes": "The delete method lacks any `@PreAuthorize` or programmatic role check. Only the grant method has a role restriction, leaving the delete operation open to any authenticated user.",
228
+ "suggested_subtype": null,
229
+ "suggested_cwe": null
230
+ },
231
+ "consensus": {
232
+ "score": 0.95,
233
+ "agreement_level": "unanimous",
234
+ "voted_models": [
235
+ "gpt-oss:20b"
236
+ ],
237
+ "rationale": "Missing role check in Spring MVC."
238
+ },
239
+ "fingerprint": "kfp1:fa52265c5e43eb390abf5fadc97010d8375d97d4f1c0004064001346c4c7018e",
240
+ "baseline_state": "new"
241
+ },
242
+ {
243
+ "finding_id": "BFLA-R-0030-1",
244
+ "category": "bfla",
245
+ "subtype": "missing_role_check",
246
+ "title": "Unprotected user deletion in Express router",
247
+ "route_id": "R-0030",
248
+ "location": {
249
+ "file": "routes.js",
250
+ "start_line": 8
251
+ },
252
+ "evidence": [
253
+ {
254
+ "file": "routes.js",
255
+ "line": 8,
256
+ "why": "Route deletes user without requireAdmin middleware"
257
+ },
258
+ {
259
+ "file": "routes.js",
260
+ "line": 6,
261
+ "why": "ensureLoggedIn only checks authentication"
262
+ }
263
+ ],
264
+ "impact": "Any authenticated user can delete any user account",
265
+ "severity": "critical",
266
+ "confidence": "high",
267
+ "cwe": [
268
+ "CWE-285"
269
+ ],
270
+ "recommendation": "Add requireAdmin middleware to the delete route",
271
+ "status": "confirmed",
272
+ "verification": {
273
+ "verdict": "confirmed",
274
+ "reason_code": "traced",
275
+ "checked": [
276
+ "routes.js:6-8",
277
+ "routes.js:8"
278
+ ],
279
+ "control_found": null,
280
+ "notes": "The delete route uses `ensureLoggedIn` middleware but does not apply `requireAdmin`. Therefore any authenticated user can delete any user record.",
281
+ "suggested_subtype": null,
282
+ "suggested_cwe": null
283
+ },
284
+ "consensus": {
285
+ "score": 0.95,
286
+ "agreement_level": "unanimous",
287
+ "voted_models": [
288
+ "gpt-oss:20b"
289
+ ],
290
+ "rationale": "Missing admin middleware in Express router."
291
+ },
292
+ "fingerprint": "kfp1:377fc1d213bc0b3a306e89b1d0c8bf44eca825527752ad7f5c36b47c2b5fb8b1",
293
+ "baseline_state": "new"
294
+ },
295
+ {
296
+ "finding_id": "BFLA-R-0032-1",
297
+ "category": "bfla",
298
+ "subtype": "role_from_request",
299
+ "title": "Export endpoint trusts request parameter for admin access in Express",
300
+ "route_id": "R-0032",
301
+ "location": {
302
+ "file": "routes.js",
303
+ "start_line": 16
304
+ },
305
+ "evidence": [
306
+ {
307
+ "file": "routes.js",
308
+ "line": 17,
309
+ "why": "Allows export if req.query.isAdmin == 'true' without verifying role"
310
+ },
311
+ {
312
+ "file": "routes.js",
313
+ "line": 6,
314
+ "why": "ensureLoggedIn only checks authentication"
315
+ }
316
+ ],
317
+ "impact": "Any user can export all user data by setting isAdmin=true",
318
+ "severity": "critical",
319
+ "confidence": "high",
320
+ "cwe": [
321
+ "CWE-285"
322
+ ],
323
+ "recommendation": "Validate the current user's role on the server before allowing export",
324
+ "status": "confirmed",
325
+ "verification": {
326
+ "verdict": "confirmed",
327
+ "reason_code": "traced",
328
+ "checked": [
329
+ "routes.js – examined the /export route for role checks",
330
+ "middleware/ensureLoggedIn – confirmed it only verifies authentication",
331
+ "global middleware – no admin‑role enforcement found",
332
+ "app.js – no global role filter applied to /export"
333
+ ],
334
+ "control_found": null,
335
+ "notes": "The route trusts the query parameter `isAdmin` without any server‑side role validation. No global filter or middleware enforces an admin role, so the vulnerability is confirmed.",
336
+ "suggested_subtype": null,
337
+ "suggested_cwe": null
338
+ },
339
+ "consensus": {
340
+ "score": 0.95,
341
+ "agreement_level": "unanimous",
342
+ "voted_models": [
343
+ "gpt-oss:20b"
344
+ ],
345
+ "rationale": "Independent audit found no compensating control; the query parameter is used directly."
346
+ },
347
+ "fingerprint": "kfp1:59101199ac1a8a5c2a5a73c5219ce5422207daba79953f968fbdebb4e79725b1",
348
+ "baseline_state": "new"
349
+ },
350
+ {
351
+ "finding_id": "BFLA-R-0057-1",
352
+ "category": "bfla",
353
+ "subtype": "missing_role_check",
354
+ "title": "Unprotected user view in PHP admin.php",
355
+ "route_id": "R-0057",
356
+ "location": {
357
+ "file": "php-plain/admin.php",
358
+ "start_line": 6
359
+ },
360
+ "evidence": [
361
+ {
362
+ "file": "php-plain/admin.php",
363
+ "line": 6,
364
+ "why": "SELECT user by id without role check"
365
+ },
366
+ {
367
+ "file": "php-plain/admin.php",
368
+ "line": 2,
369
+ "why": "Only includes auth.php which only checks login"
370
+ }
371
+ ],
372
+ "impact": "Any authenticated user can view any user record",
373
+ "severity": "critical",
374
+ "confidence": "high",
375
+ "cwe": [
376
+ "CWE-285"
377
+ ],
378
+ "recommendation": "Add a role check before executing the SELECT",
379
+ "status": "confirmed",
380
+ "verification": {
381
+ "verdict": "confirmed",
382
+ "reason_code": "traced",
383
+ "checked": [
384
+ "php-plain/admin.php – examined SELECT statement",
385
+ "php-plain/auth.php – confirmed it only checks authentication",
386
+ "global includes – no role‑check middleware or function found",
387
+ "session handling – no role stored or verified before query"
388
+ ],
389
+ "control_found": null,
390
+ "notes": "Any authenticated user can supply any `id` and retrieve the corresponding user record. No role check is performed, so the issue is confirmed.",
391
+ "suggested_subtype": null,
392
+ "suggested_cwe": null
393
+ },
394
+ "consensus": {
395
+ "score": 0.95,
396
+ "agreement_level": "unanimous",
397
+ "voted_models": [
398
+ "gpt-oss:20b"
399
+ ],
400
+ "rationale": "No server‑side role validation detected."
401
+ },
402
+ "fingerprint": "kfp1:d49176fe451bbe3c61de25550631bf23a5b4b79949df949f18698aa7183f6e10",
403
+ "baseline_state": "new"
404
+ },
405
+ {
406
+ "finding_id": "BFLA-R-0058-1",
407
+ "category": "bfla",
408
+ "subtype": "missing_role_check",
409
+ "title": "Unprotected user deletion in PHP admin.php",
410
+ "route_id": "R-0058",
411
+ "location": {
412
+ "file": "php-plain/admin.php",
413
+ "start_line": 10
414
+ },
415
+ "evidence": [
416
+ {
417
+ "file": "php-plain/admin.php",
418
+ "line": 10,
419
+ "why": "DELETE statement executed without role check"
420
+ },
421
+ {
422
+ "file": "php-plain/admin.php",
423
+ "line": 2,
424
+ "why": "Only includes auth.php which only checks login"
425
+ }
426
+ ],
427
+ "impact": "Any authenticated user can delete any user record",
428
+ "severity": "critical",
429
+ "confidence": "high",
430
+ "cwe": [
431
+ "CWE-285"
432
+ ],
433
+ "recommendation": "Add a role check before executing the DELETE",
434
+ "status": "confirmed",
435
+ "verification": {
436
+ "verdict": "confirmed",
437
+ "reason_code": "traced",
438
+ "checked": [
439
+ "php-plain/admin.php – examined DELETE statement",
440
+ "php-plain/auth.php – only authentication check present",
441
+ "global includes – no role enforcement found",
442
+ "session handling – no role verification before deletion"
443
+ ],
444
+ "control_found": null,
445
+ "notes": "The DELETE uses the `id` from the request without any role validation, allowing any authenticated user to delete any user record.",
446
+ "suggested_subtype": null,
447
+ "suggested_cwe": null
448
+ },
449
+ "consensus": {
450
+ "score": 0.95,
451
+ "agreement_level": "unanimous",
452
+ "voted_models": [
453
+ "gpt-oss:20b"
454
+ ],
455
+ "rationale": "No compensating control identified."
456
+ },
457
+ "fingerprint": "kfp1:781c6244908fea9052bbe7da28e222368d76d6420cac232e23403ef72b9d0f96",
458
+ "baseline_state": "new"
459
+ },
460
+ {
461
+ "finding_id": "BFLA-R-0059-1",
462
+ "category": "bfla",
463
+ "subtype": "missing_role_check",
464
+ "title": "Unprotected user deletion in PHP manage_users.php",
465
+ "route_id": "R-0059",
466
+ "location": {
467
+ "file": "php-plain/manage_users.php",
468
+ "start_line": 5
469
+ },
470
+ "evidence": [
471
+ {
472
+ "file": "php-plain/manage_users.php",
473
+ "line": 5,
474
+ "why": "DELETE statement executed without role check"
475
+ },
476
+ {
477
+ "file": "php-plain/manage_users.php",
478
+ "line": 11,
479
+ "why": "Client‑side role check only"
480
+ }
481
+ ],
482
+ "impact": "Any authenticated user can delete any user record",
483
+ "severity": "critical",
484
+ "confidence": "high",
485
+ "cwe": [
486
+ "CWE-285"
487
+ ],
488
+ "recommendation": "Add a server‑side role check before executing the DELETE",
489
+ "status": "confirmed",
490
+ "verification": {
491
+ "verdict": "confirmed",
492
+ "reason_code": "traced",
493
+ "checked": [
494
+ "php-plain/manage_users.php – examined DELETE statement",
495
+ "php-plain/auth.php – only authentication check present",
496
+ "global includes – no role enforcement found",
497
+ "session handling – no role verification before deletion"
498
+ ],
499
+ "control_found": null,
500
+ "notes": "Any authenticated user can delete any user record because no role check is performed.",
501
+ "suggested_subtype": null,
502
+ "suggested_cwe": null
503
+ },
504
+ "consensus": {
505
+ "score": 0.95,
506
+ "agreement_level": "unanimous",
507
+ "voted_models": [
508
+ "gpt-oss:20b"
509
+ ],
510
+ "rationale": "No server‑side role validation detected."
511
+ },
512
+ "fingerprint": "kfp1:87e92ef40927e74117d6d388e36bb9f58b621cb76c149805ce9d6cdb06c949d3",
513
+ "baseline_state": "new"
514
+ },
515
+ {
516
+ "finding_id": "BFLA-R-0063-1",
517
+ "category": "bfla",
518
+ "subtype": "role_from_request",
519
+ "title": "Privilege escalation via role field in PHP profile.php",
520
+ "route_id": "R-0063",
521
+ "location": {
522
+ "file": "php-plain/profile.php",
523
+ "start_line": 10
524
+ },
525
+ "evidence": [
526
+ {
527
+ "file": "php-plain/profile.php",
528
+ "line": 10,
529
+ "why": "Sets $user->role from $_POST without role check"
530
+ },
531
+ {
532
+ "file": "php-plain/profile.php",
533
+ "line": 5,
534
+ "why": "No server‑side role validation"
535
+ }
536
+ ],
537
+ "impact": "Any authenticated user can change their role to admin",
538
+ "severity": "critical",
539
+ "confidence": "high",
540
+ "cwe": [
541
+ "CWE-285"
542
+ ],
543
+ "recommendation": "Remove role assignment from user input or enforce role check",
544
+ "status": "confirmed",
545
+ "verification": {
546
+ "verdict": "confirmed",
547
+ "reason_code": "traced",
548
+ "checked": [
549
+ "php-plain/profile.php – examined role assignment from $_POST",
550
+ "php-plain/auth.php – only authentication check present",
551
+ "global includes – no role enforcement found",
552
+ "session handling – no role validation before role change"
553
+ ],
554
+ "control_found": null,
555
+ "notes": "The script assigns `$user->role` directly from user input without any role check, confirming the vulnerability.",
556
+ "suggested_subtype": null,
557
+ "suggested_cwe": null
558
+ },
559
+ "consensus": {
560
+ "score": 0.95,
561
+ "agreement_level": "unanimous",
562
+ "voted_models": [
563
+ "gpt-oss:20b"
564
+ ],
565
+ "rationale": "No compensating control identified."
566
+ },
567
+ "fingerprint": "kfp1:94311790d5366f503c11b8e94ec461aeab3e3c5f4e804cfec644fb2ad8b76e8f",
568
+ "baseline_state": "new"
569
+ },
570
+ {
571
+ "finding_id": "BFLA-R-0066-1",
572
+ "category": "bfla",
573
+ "subtype": "missing_role_check",
574
+ "title": "Unprotected user deletion in PHP admin/delete_user.php",
575
+ "route_id": "R-0066",
576
+ "location": {
577
+ "file": "php-plain/admin/delete_user.php",
578
+ "start_line": 1
579
+ },
580
+ "evidence": [
581
+ {
582
+ "file": "php-plain/admin/delete_user.php",
583
+ "line": 5,
584
+ "why": "DELETE statement executed without role check"
585
+ },
586
+ {
587
+ "file": "php-plain/admin/delete_user.php",
588
+ "line": 2,
589
+ "why": "Only includes auth.php which only checks login"
590
+ }
591
+ ],
592
+ "impact": "Any authenticated user can delete any user record",
593
+ "severity": "critical",
594
+ "confidence": "high",
595
+ "cwe": [
596
+ "CWE-285"
597
+ ],
598
+ "recommendation": "Add a role check before executing the DELETE",
599
+ "status": "confirmed",
600
+ "verification": {
601
+ "verdict": "confirmed",
602
+ "reason_code": "traced",
603
+ "checked": [
604
+ "php-plain/admin/delete_user.php – examined DELETE statement",
605
+ "php-plain/auth.php – only authentication check present",
606
+ "global includes – no role enforcement found",
607
+ "session handling – no role verification before deletion"
608
+ ],
609
+ "control_found": null,
610
+ "notes": "The script deletes a user based solely on the request parameter without any role check, confirming the vulnerability.",
611
+ "suggested_subtype": null,
612
+ "suggested_cwe": null
613
+ },
614
+ "consensus": {
615
+ "score": 0.95,
616
+ "agreement_level": "unanimous",
617
+ "voted_models": [
618
+ "gpt-oss:20b"
619
+ ],
620
+ "rationale": "No server‑side role validation detected."
621
+ },
622
+ "fingerprint": "kfp1:ac5acea0a998815d202206c2242aa04d68020c74c2b9bf604fd6e9620c81190d",
623
+ "baseline_state": "new"
624
+ },
625
+ {
626
+ "finding_id": "BFLA-R-0067-1",
627
+ "category": "bfla",
628
+ "subtype": "role_from_request",
629
+ "title": "Privilege escalation via role promotion in PHP admin/promote.php",
630
+ "route_id": "R-0067",
631
+ "location": {
632
+ "file": "php-plain/admin/promote.php",
633
+ "start_line": 1
634
+ },
635
+ "evidence": [
636
+ {
637
+ "file": "php-plain/admin/promote.php",
638
+ "line": 5,
639
+ "why": "Sets role to admin based on $_POST['is_admin'] without role check"
640
+ },
641
+ {
642
+ "file": "php-plain/admin/promote.php",
643
+ "line": 2,
644
+ "why": "Only includes auth.php which only checks login"
645
+ }
646
+ ],
647
+ "impact": "Any authenticated user can promote themselves to admin",
648
+ "severity": "critical",
649
+ "confidence": "high",
650
+ "cwe": [
651
+ "CWE-285"
652
+ ],
653
+ "recommendation": "Validate the current user's role before allowing role changes",
654
+ "status": "confirmed",
655
+ "verification": {
656
+ "verdict": "confirmed",
657
+ "reason_code": "traced",
658
+ "checked": [
659
+ "php-plain/admin/promote.php – examined role assignment",
660
+ "php-plain/auth.php – only authentication check present",
661
+ "global includes – no role enforcement found",
662
+ "session handling – no role validation before promotion"
663
+ ],
664
+ "control_found": null,
665
+ "notes": "The script sets `role` to `admin` based on `$_POST['is_admin']` without verifying the current user's role, confirming the privilege escalation.",
666
+ "suggested_subtype": null,
667
+ "suggested_cwe": null
668
+ },
669
+ "consensus": {
670
+ "score": 0.95,
671
+ "agreement_level": "unanimous",
672
+ "voted_models": [
673
+ "gpt-oss:20b"
674
+ ],
675
+ "rationale": "No compensating control identified."
676
+ },
677
+ "fingerprint": "kfp1:d174ebc0365672a7b111b699d6c623186ff22f9478565d4ad9e479d844293faf",
678
+ "baseline_state": "new"
679
+ },
680
+ {
681
+ "finding_id": "INJ-I-0006",
682
+ "category": "sql_injection",
683
+ "subtype": "string_concatenation",
684
+ "title": "SQL Injection in coldfusion/app/OrderService.cfc:5",
685
+ "candidate_id": "I-0006",
686
+ "location": {
687
+ "file": "coldfusion/app/OrderService.cfc",
688
+ "start_line": 5
689
+ },
690
+ "evidence": [
691
+ {
692
+ "file": "coldfusion/app/OrderService.cfc",
693
+ "start_line": 5,
694
+ "code": "SELECT * FROM orders WHERE id = #arguments.orderId#",
695
+ "why": "Unsanitized input concatenated into query",
696
+ "line": 5
697
+ }
698
+ ],
699
+ "severity": "critical",
700
+ "confidence": "high",
701
+ "cwe": [
702
+ "CWE-89"
703
+ ],
704
+ "recommendation": "Use cfqueryparam",
705
+ "status": "confirmed",
706
+ "verification": {
707
+ "verdict": "confirmed",
708
+ "reason_code": "traced",
709
+ "checked": [
710
+ "coldfusion/app/OrderService.cfc:5",
711
+ "global request validation config (web.xml / web.config)"
712
+ ],
713
+ "control_found": null,
714
+ "notes": "The query concatenates the raw argument without any cfqueryparam, casting, or allow‑list. No global filter or server‑side sanitiser is applied to arguments.orderId, so the statement is vulnerable to SQL injection.",
715
+ "suggested_subtype": null,
716
+ "suggested_cwe": null
717
+ },
718
+ "consensus": {
719
+ "score": 0.95,
720
+ "agreement_level": "unanimous",
721
+ "voted_models": [
722
+ "gpt-oss:20b"
723
+ ],
724
+ "rationale": "No compensating control found; the code directly interpolates user input into the SQL string."
725
+ },
726
+ "fingerprint": "kfp1:b6824160d6d38156b7a16dc1526dda6be874be4401ad843a3c185f94446dcf5e",
727
+ "baseline_state": "new"
728
+ },
729
+ {
730
+ "finding_id": "INJ-I-0007",
731
+ "category": "sql_injection",
732
+ "subtype": "string_concatenation",
733
+ "title": "SQL Injection in coldfusion/app/order.cfm:3",
734
+ "candidate_id": "I-0007",
735
+ "location": {
736
+ "file": "coldfusion/app/order.cfm",
737
+ "start_line": 3
738
+ },
739
+ "evidence": [
740
+ {
741
+ "file": "coldfusion/app/order.cfm",
742
+ "start_line": 3,
743
+ "code": "SELECT * FROM orders WHERE id = #url.id#",
744
+ "why": "Unsanitized input concatenated into query",
745
+ "line": 3
746
+ }
747
+ ],
748
+ "severity": "critical",
749
+ "confidence": "high",
750
+ "cwe": [
751
+ "CWE-89"
752
+ ],
753
+ "recommendation": "Use cfqueryparam",
754
+ "status": "confirmed",
755
+ "verification": {
756
+ "verdict": "confirmed",
757
+ "reason_code": "traced",
758
+ "checked": [
759
+ "coldfusion/app/order.cfm:3",
760
+ "global request validation config (web.xml / web.config)"
761
+ ],
762
+ "control_found": null,
763
+ "notes": "The URL parameter is concatenated into the SQL statement without cfqueryparam or type enforcement. No global filter casts url.id to an integer, so the query is injectable.",
764
+ "suggested_subtype": null,
765
+ "suggested_cwe": null
766
+ },
767
+ "consensus": {
768
+ "score": 0.95,
769
+ "agreement_level": "unanimous",
770
+ "voted_models": [
771
+ "gpt-oss:20b"
772
+ ],
773
+ "rationale": "Direct string interpolation of user input with no sanitisation."
774
+ },
775
+ "fingerprint": "kfp1:d2650d07ae7cedfc90bd1abda3c42a63716563d54c45ecde428ae21505214f72",
776
+ "baseline_state": "new"
777
+ },
778
+ {
779
+ "finding_id": "INJ-I-0008",
780
+ "category": "sql_injection",
781
+ "subtype": "string_concatenation",
782
+ "title": "SQL Injection in dotnet-legacy/Web/Controllers/InvoiceController.cs:28",
783
+ "candidate_id": "I-0008",
784
+ "location": {
785
+ "file": "dotnet-legacy/Web/Controllers/InvoiceController.cs",
786
+ "start_line": 28
787
+ },
788
+ "evidence": [
789
+ {
790
+ "file": "dotnet-legacy/Web/Controllers/InvoiceController.cs",
791
+ "start_line": 27,
792
+ "code": "string customer = Request.Form[\"customer\"];",
793
+ "why": "source: POST parameter",
794
+ "line": 27
795
+ },
796
+ {
797
+ "file": "dotnet-legacy/Web/Controllers/InvoiceController.cs",
798
+ "start_line": 28,
799
+ "code": "var list = db.Database.SqlQuery<Invoice>(\"SELECT * FROM Invoices WHERE Customer = '\" + customer + \"'\").ToList();",
800
+ "why": "unsanitized string concatenation",
801
+ "line": 28
802
+ }
803
+ ],
804
+ "severity": "critical",
805
+ "confidence": "high",
806
+ "cwe": [
807
+ "CWE-89"
808
+ ],
809
+ "recommendation": "Use parameterized queries",
810
+ "status": "confirmed",
811
+ "verification": {
812
+ "verdict": "confirmed",
813
+ "reason_code": "traced",
814
+ "checked": [
815
+ "dotnet-legacy/Web/Controllers/InvoiceController.cs line 27: string customer = Request.Form[\"customer\"];",
816
+ "dotnet-legacy/Web/Controllers/InvoiceController.cs line 28: var list = db.Database.SqlQuery<Invoice>(\"SELECT * FROM Invoices WHERE Customer = '\" + customer + \"'\").ToList();"
817
+ ],
818
+ "control_found": null,
819
+ "notes": "The customer value from the POST form is concatenated directly into the SQL string. No parameterized query or sanitization is used.",
820
+ "suggested_subtype": null,
821
+ "suggested_cwe": null
822
+ },
823
+ "consensus": {
824
+ "score": 0.95,
825
+ "agreement_level": "unanimous",
826
+ "voted_models": [
827
+ "gpt-oss:20b"
828
+ ],
829
+ "rationale": "No escaping or parameter binding."
830
+ },
831
+ "fingerprint": "kfp1:81106c3dfe7181ee5aad08bbd8e6c94143e8ebb6bf477a18828ca399339d1271",
832
+ "baseline_state": "new"
833
+ },
834
+ {
835
+ "finding_id": "INJ-I-0009",
836
+ "category": "sql_injection",
837
+ "subtype": "string_concatenation",
838
+ "title": "SQL Injection in dotnet-legacy/Web/Orders/EditOrder.aspx.vb:7",
839
+ "candidate_id": "I-0009",
840
+ "location": {
841
+ "file": "dotnet-legacy/Web/Orders/EditOrder.aspx.vb",
842
+ "start_line": 7
843
+ },
844
+ "evidence": [
845
+ {
846
+ "file": "dotnet-legacy/Web/Orders/EditOrder.aspx.vb",
847
+ "start_line": 7,
848
+ "code": "Dim sql As String = \"UPDATE Orders SET Note = '\" & txtNote.Text & \"'\"",
849
+ "why": "Unsanitized input concatenated into query",
850
+ "line": 7
851
+ },
852
+ {
853
+ "file": "dotnet-legacy/Web/Orders/EditOrder.aspx.vb",
854
+ "start_line": 8,
855
+ "code": "sql &= \" WHERE OrderId = \" & hfOrderId.Value",
856
+ "why": "Unsanitized input concatenated into query",
857
+ "line": 8
858
+ }
859
+ ],
860
+ "severity": "critical",
861
+ "confidence": "high",
862
+ "cwe": [
863
+ "CWE-89"
864
+ ],
865
+ "recommendation": "Use parameterized queries",
866
+ "status": "confirmed",
867
+ "verification": {
868
+ "verdict": "confirmed",
869
+ "reason_code": "traced",
870
+ "checked": [
871
+ "dotnet-legacy/Web/Orders/EditOrder.aspx.vb:7-8",
872
+ "global request validation config (web.config)"
873
+ ],
874
+ "control_found": null,
875
+ "notes": "The SQL string is built by concatenating txtNote.Text and hfOrderId.Value. No parameterised query or integer cast is applied to the hidden field, and no global filter sanitises the input. The code is therefore vulnerable to SQL injection.",
876
+ "suggested_subtype": null,
877
+ "suggested_cwe": null
878
+ },
879
+ "consensus": {
880
+ "score": 0.95,
881
+ "agreement_level": "unanimous",
882
+ "voted_models": [
883
+ "gpt-oss:20b"
884
+ ],
885
+ "rationale": "No compensating control detected."
886
+ },
887
+ "fingerprint": "kfp1:999e755f32c1edc6b46f7497ebdfd065d8afcc3518cab3211f45fc11a7342afd",
888
+ "baseline_state": "new"
889
+ },
890
+ {
891
+ "finding_id": "INJ-I-0010",
892
+ "category": "sql_injection",
893
+ "subtype": "string_concatenation",
894
+ "title": "SQL Injection in dotnet-legacy/Web/Orders/EditOrder.aspx.vb:8",
895
+ "candidate_id": "I-0010",
896
+ "location": {
897
+ "file": "dotnet-legacy/Web/Orders/EditOrder.aspx.vb",
898
+ "start_line": 8
899
+ },
900
+ "evidence": [
901
+ {
902
+ "file": "dotnet-legacy/Web/Orders/EditOrder.aspx.vb",
903
+ "start_line": 7,
904
+ "code": "Dim sql As String = \"UPDATE Orders SET Note = '\" & txtNote.Text & \"'\"",
905
+ "why": "Unsanitized input concatenated into query",
906
+ "line": 7
907
+ },
908
+ {
909
+ "file": "dotnet-legacy/Web/Orders/EditOrder.aspx.vb",
910
+ "start_line": 8,
911
+ "code": "sql &= \" WHERE OrderId = \" & hfOrderId.Value",
912
+ "why": "Unsanitized input concatenated into query",
913
+ "line": 8
914
+ }
915
+ ],
916
+ "severity": "critical",
917
+ "confidence": "high",
918
+ "cwe": [
919
+ "CWE-89"
920
+ ],
921
+ "recommendation": "Use parameterized queries",
922
+ "status": "confirmed",
923
+ "verification": {
924
+ "verdict": "confirmed",
925
+ "reason_code": "traced",
926
+ "checked": [
927
+ "dotnet-legacy/Web/Orders/EditOrder.aspx.vb:7-8",
928
+ "global request validation config (web.config)"
929
+ ],
930
+ "control_found": null,
931
+ "notes": "Same code as INJ-I-0009; the concatenation of user input into the UPDATE statement is not protected by parameters or casting.",
932
+ "suggested_subtype": null,
933
+ "suggested_cwe": null
934
+ },
935
+ "consensus": {
936
+ "score": 0.95,
937
+ "agreement_level": "unanimous",
938
+ "voted_models": [
939
+ "gpt-oss:20b"
940
+ ],
941
+ "rationale": "No compensating control detected."
942
+ },
943
+ "fingerprint": "kfp1:323eb8697c45d7b7466292d80a0763dd2310608e0975d837273bde14e3458f59",
944
+ "baseline_state": "new"
945
+ },
946
+ {
947
+ "finding_id": "INJ-I-0011",
948
+ "category": "sql_injection",
949
+ "subtype": "string_concatenation",
950
+ "title": "SQL Injection in dotnet-legacy/Web/Orders/OrderDetail.aspx.cs:13",
951
+ "candidate_id": "I-0011",
952
+ "location": {
953
+ "file": "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs",
954
+ "start_line": 13
955
+ },
956
+ "evidence": [
957
+ {
958
+ "file": "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs",
959
+ "start_line": 13,
960
+ "code": "var cmd = new SqlCommand(\"SELECT * FROM Orders WHERE OrderId = \" + id, conn);",
961
+ "why": "Unsanitized input concatenated into query",
962
+ "line": 13
963
+ },
964
+ {
965
+ "file": "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs",
966
+ "start_line": 19,
967
+ "code": "string sql2 = string.Format(\"SELECT * FROM Logs WHERE Ref = '{0}'\", Request[\"ref\"]);",
968
+ "why": "Unsanitized input concatenated into query",
969
+ "line": 19
970
+ },
971
+ {
972
+ "file": "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs",
973
+ "start_line": 20,
974
+ "code": "string sql3 = $\"DELETE FROM Carts WHERE CartId = {Request.Form[\"cart\"]}\";",
975
+ "why": "Unsanitized input concatenated into query",
976
+ "line": 20
977
+ }
978
+ ],
979
+ "severity": "critical",
980
+ "confidence": "high",
981
+ "cwe": [
982
+ "CWE-89"
983
+ ],
984
+ "recommendation": "Use parameterized queries",
985
+ "status": "confirmed",
986
+ "verification": {
987
+ "verdict": "confirmed",
988
+ "reason_code": "traced",
989
+ "checked": [
990
+ "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs:13-20",
991
+ "global request validation config (web.config)"
992
+ ],
993
+ "control_found": null,
994
+ "notes": "The id, ref, and cart parameters are concatenated directly into SQL strings. No integer cast or parameterisation is applied, and no global filter sanitises these inputs. All three statements are injectable.",
995
+ "suggested_subtype": null,
996
+ "suggested_cwe": null
997
+ },
998
+ "consensus": {
999
+ "score": 0.95,
1000
+ "agreement_level": "unanimous",
1001
+ "voted_models": [
1002
+ "gpt-oss:20b"
1003
+ ],
1004
+ "rationale": "No compensating control detected."
1005
+ },
1006
+ "fingerprint": "kfp1:c9aa426f72d032a9b0729b0905c9c25a6fb8844a92a91c45d72d3e26170bbae5",
1007
+ "baseline_state": "new"
1008
+ },
1009
+ {
1010
+ "finding_id": "INJ-I-0012",
1011
+ "category": "sql_injection",
1012
+ "subtype": "string_concatenation",
1013
+ "title": "SQL Injection in dotnet-legacy/Web/Orders/OrderDetail.aspx.cs:19",
1014
+ "candidate_id": "I-0012",
1015
+ "location": {
1016
+ "file": "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs",
1017
+ "start_line": 19
1018
+ },
1019
+ "evidence": [
1020
+ {
1021
+ "file": "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs",
1022
+ "start_line": 13,
1023
+ "code": "var cmd = new SqlCommand(\"SELECT * FROM Orders WHERE OrderId = \" + id, conn);",
1024
+ "why": "Unsanitized input concatenated into query",
1025
+ "line": 13
1026
+ },
1027
+ {
1028
+ "file": "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs",
1029
+ "start_line": 19,
1030
+ "code": "string sql2 = string.Format(\"SELECT * FROM Logs WHERE Ref = '{0}'\", Request[\"ref\"]);",
1031
+ "why": "Unsanitized input concatenated into query",
1032
+ "line": 19
1033
+ },
1034
+ {
1035
+ "file": "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs",
1036
+ "start_line": 20,
1037
+ "code": "string sql3 = $\"DELETE FROM Carts WHERE CartId = {Request.Form[\"cart\"]}\";",
1038
+ "why": "Unsanitized input concatenated into query",
1039
+ "line": 20
1040
+ }
1041
+ ],
1042
+ "severity": "critical",
1043
+ "confidence": "high",
1044
+ "cwe": [
1045
+ "CWE-89"
1046
+ ],
1047
+ "recommendation": "Use parameterized queries",
1048
+ "status": "confirmed",
1049
+ "verification": {
1050
+ "verdict": "confirmed",
1051
+ "reason_code": "traced",
1052
+ "checked": [
1053
+ "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs:13-20",
1054
+ "global request validation config (web.config)"
1055
+ ],
1056
+ "control_found": null,
1057
+ "notes": "Same code as INJ-I-0011; the concatenated SQL strings are not protected by parameters or casting.",
1058
+ "suggested_subtype": null,
1059
+ "suggested_cwe": null
1060
+ },
1061
+ "consensus": {
1062
+ "score": 0.95,
1063
+ "agreement_level": "unanimous",
1064
+ "voted_models": [
1065
+ "gpt-oss:20b"
1066
+ ],
1067
+ "rationale": "No compensating control detected."
1068
+ },
1069
+ "fingerprint": "kfp1:c0adff113329292a51bb7d2f2aa03dd242c6d767cee060a789ff750f3ab9b946",
1070
+ "baseline_state": "new"
1071
+ },
1072
+ {
1073
+ "finding_id": "INJ-I-0013",
1074
+ "category": "sql_injection",
1075
+ "subtype": "string_concatenation",
1076
+ "title": "SQL Injection in dotnet-legacy/Web/Orders/OrderDetail.aspx.cs:20",
1077
+ "candidate_id": "I-0013",
1078
+ "location": {
1079
+ "file": "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs",
1080
+ "start_line": 20
1081
+ },
1082
+ "evidence": [
1083
+ {
1084
+ "file": "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs",
1085
+ "start_line": 13,
1086
+ "code": "var cmd = new SqlCommand(\"SELECT * FROM Orders WHERE OrderId = \" + id, conn);",
1087
+ "why": "Unsanitized input concatenated into query",
1088
+ "line": 13
1089
+ },
1090
+ {
1091
+ "file": "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs",
1092
+ "start_line": 19,
1093
+ "code": "string sql2 = string.Format(\"SELECT * FROM Logs WHERE Ref = '{0}'\", Request[\"ref\"]);",
1094
+ "why": "Unsanitized input concatenated into query",
1095
+ "line": 19
1096
+ },
1097
+ {
1098
+ "file": "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs",
1099
+ "start_line": 20,
1100
+ "code": "string sql3 = $\"DELETE FROM Carts WHERE CartId = {Request.Form[\"cart\"]}\";",
1101
+ "why": "Unsanitized input concatenated into query",
1102
+ "line": 20
1103
+ }
1104
+ ],
1105
+ "severity": "critical",
1106
+ "confidence": "high",
1107
+ "cwe": [
1108
+ "CWE-89"
1109
+ ],
1110
+ "recommendation": "Use parameterized queries",
1111
+ "status": "confirmed",
1112
+ "verification": {
1113
+ "verdict": "confirmed",
1114
+ "reason_code": "traced",
1115
+ "checked": [
1116
+ "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs:13-20",
1117
+ "global request validation config (web.config)"
1118
+ ],
1119
+ "control_found": null,
1120
+ "notes": "Same code as INJ-I-0011; the SQL strings are built via string concatenation without sanitisation.",
1121
+ "suggested_subtype": null,
1122
+ "suggested_cwe": null
1123
+ },
1124
+ "consensus": {
1125
+ "score": 0.95,
1126
+ "agreement_level": "unanimous",
1127
+ "voted_models": [
1128
+ "gpt-oss:20b"
1129
+ ],
1130
+ "rationale": "No compensating control detected."
1131
+ },
1132
+ "fingerprint": "kfp1:4dbfab5201fa4fb196296b8ea0e4be14bb1ae1e6d3136309f24af78eb9977441",
1133
+ "baseline_state": "new"
1134
+ },
1135
+ {
1136
+ "finding_id": "INJ-I-0014",
1137
+ "category": "sql_injection",
1138
+ "subtype": "string_concatenation",
1139
+ "title": "SQL Injection in java-legacy/WebContent/order/detail.jsp:5",
1140
+ "candidate_id": "I-0014",
1141
+ "location": {
1142
+ "file": "java-legacy/WebContent/order/detail.jsp",
1143
+ "start_line": 5
1144
+ },
1145
+ "evidence": [
1146
+ {
1147
+ "file": "java-legacy/WebContent/order/detail.jsp",
1148
+ "start_line": 5,
1149
+ "code": "ResultSet rs = c.createStatement().executeQuery(\"SELECT * FROM orders WHERE order_no = '\" + no + \"'\");",
1150
+ "why": "Unsanitized input concatenated into query",
1151
+ "line": 5
1152
+ }
1153
+ ],
1154
+ "severity": "critical",
1155
+ "confidence": "high",
1156
+ "cwe": [
1157
+ "CWE-89"
1158
+ ],
1159
+ "recommendation": "Use prepared statements",
1160
+ "status": "confirmed",
1161
+ "verification": {
1162
+ "verdict": "confirmed",
1163
+ "reason_code": "traced",
1164
+ "checked": [
1165
+ "java-legacy/WebContent/order/detail.jsp:5",
1166
+ "global request validation config (web.xml / .htaccess)"
1167
+ ],
1168
+ "control_found": null,
1169
+ "notes": "The query concatenates the request parameter 'no' directly into the SQL string. No prepared statement or input validation is applied, making the statement vulnerable to injection.",
1170
+ "suggested_subtype": null,
1171
+ "suggested_cwe": null
1172
+ },
1173
+ "consensus": {
1174
+ "score": 0.95,
1175
+ "agreement_level": "unanimous",
1176
+ "voted_models": [
1177
+ "gpt-oss:20b"
1178
+ ],
1179
+ "rationale": "No compensating control detected."
1180
+ },
1181
+ "fingerprint": "kfp1:b8e7acb1ca7119156e96cf2bd42356b53bb6107a0a904b492534714730ac45f8",
1182
+ "baseline_state": "new"
1183
+ },
1184
+ {
1185
+ "finding_id": "INJ-I-0015",
1186
+ "category": "sql_injection",
1187
+ "subtype": "dynamic_identifier",
1188
+ "title": "SQL Injection in java-legacy/src/com/acme/mapper/OrderMapper.xml:9",
1189
+ "candidate_id": "I-0015",
1190
+ "location": {
1191
+ "file": "java-legacy/src/com/acme/mapper/OrderMapper.xml",
1192
+ "start_line": 9
1193
+ },
1194
+ "evidence": [
1195
+ {
1196
+ "file": "java-legacy/src/com/acme/mapper/OrderMapper.xml",
1197
+ "start_line": 9,
1198
+ "code": "ORDER BY ${orderBy}",
1199
+ "why": "raw string substitution of user-controlled value",
1200
+ "line": 9
1201
+ }
1202
+ ],
1203
+ "severity": "critical",
1204
+ "confidence": "high",
1205
+ "cwe": [
1206
+ "CWE-89"
1207
+ ],
1208
+ "recommendation": "Use parameterized queries or allow-list for identifiers",
1209
+ "status": "confirmed",
1210
+ "verification": {
1211
+ "verdict": "confirmed",
1212
+ "reason_code": "traced",
1213
+ "checked": [
1214
+ "java-legacy/src/com/acme/mapper/OrderMapper.xml line 9: ORDER BY ${orderBy}"
1215
+ ],
1216
+ "control_found": null,
1217
+ "notes": "MyBatis ${} syntax performs raw string substitution. The orderBy value comes from user input and is not constrained by an allow‑list or type check, making the ORDER BY clause injectable.",
1218
+ "suggested_subtype": null,
1219
+ "suggested_cwe": null
1220
+ },
1221
+ "consensus": {
1222
+ "score": 0.95,
1223
+ "agreement_level": "unanimous",
1224
+ "voted_models": [
1225
+ "gpt-oss:20b"
1226
+ ],
1227
+ "rationale": "No allow‑list or parameterization is present."
1228
+ },
1229
+ "fingerprint": "kfp1:a0212ab67bd8e95a9fa71520fd15ba101852184637a1a60ec1bb91525308964f",
1230
+ "baseline_state": "new"
1231
+ },
1232
+ {
1233
+ "finding_id": "INJ-I-0016",
1234
+ "category": "sql_injection",
1235
+ "subtype": "string_concatenation",
1236
+ "title": "SQL Injection in java-legacy/src/com/acme/struts/EditUserAction.java:13",
1237
+ "candidate_id": "I-0016",
1238
+ "location": {
1239
+ "file": "java-legacy/src/com/acme/struts/EditUserAction.java",
1240
+ "start_line": 13
1241
+ },
1242
+ "evidence": [
1243
+ {
1244
+ "file": "java-legacy/src/com/acme/struts/EditUserAction.java",
1245
+ "start_line": 13,
1246
+ "code": "PreparedStatement ps = conn.prepareStatement(\"UPDATE users SET email = '\" + email + \"' WHERE id = \" + userId);",
1247
+ "why": "Unsanitized input concatenated into query",
1248
+ "line": 13
1249
+ }
1250
+ ],
1251
+ "severity": "critical",
1252
+ "confidence": "high",
1253
+ "cwe": [
1254
+ "CWE-89"
1255
+ ],
1256
+ "recommendation": "Use parameterized queries",
1257
+ "status": "confirmed",
1258
+ "verification": {
1259
+ "verdict": "confirmed",
1260
+ "reason_code": "traced",
1261
+ "checked": [
1262
+ "java-legacy/src/com/acme/struts/EditUserAction.java:13",
1263
+ "global request validation config (web.xml / .htaccess)"
1264
+ ],
1265
+ "control_found": null,
1266
+ "notes": "The code uses a PreparedStatement but still concatenates the email and userId directly into the SQL string. The email value is not parameterised, so the statement is injectable.",
1267
+ "suggested_subtype": null,
1268
+ "suggested_cwe": null
1269
+ },
1270
+ "consensus": {
1271
+ "score": 0.95,
1272
+ "agreement_level": "unanimous",
1273
+ "voted_models": [
1274
+ "gpt-oss:20b"
1275
+ ],
1276
+ "rationale": "No compensating control detected."
1277
+ },
1278
+ "fingerprint": "kfp1:2976fb21c23c927d1de0f3af7dc883d9b66a1d051ec67bb9ec1b44396f6479fc",
1279
+ "baseline_state": "new"
1280
+ },
1281
+ {
1282
+ "finding_id": "INJ-I-0017",
1283
+ "category": "sql_injection",
1284
+ "subtype": "string_concatenation",
1285
+ "title": "SQL Injection in java-legacy/src/com/acme/web/InvoiceController.java:29",
1286
+ "candidate_id": "I-0017",
1287
+ "location": {
1288
+ "file": "java-legacy/src/com/acme/web/InvoiceController.java",
1289
+ "start_line": 29
1290
+ },
1291
+ "evidence": [
1292
+ {
1293
+ "file": "java-legacy/src/com/acme/web/InvoiceController.java",
1294
+ "start_line": 29,
1295
+ "code": "model.addAttribute(\"list\", session.createQuery(\"from Invoice i where i.customer = '\" + customer + \"'\").list());",
1296
+ "why": "Unsanitized input concatenated into query",
1297
+ "line": 29
1298
+ }
1299
+ ],
1300
+ "severity": "critical",
1301
+ "confidence": "high",
1302
+ "cwe": [
1303
+ "CWE-89"
1304
+ ],
1305
+ "recommendation": "Use parameterized queries",
1306
+ "status": "confirmed",
1307
+ "verification": {
1308
+ "verdict": "confirmed",
1309
+ "reason_code": "traced",
1310
+ "checked": [
1311
+ "java-legacy/src/com/acme/web/InvoiceController.java:29",
1312
+ "global request validation config (web.xml / .htaccess)"
1313
+ ],
1314
+ "control_found": null,
1315
+ "notes": "The HQL query concatenates the customer parameter directly into the string. No parameterisation or input validation is applied, making the query vulnerable to injection.",
1316
+ "suggested_subtype": null,
1317
+ "suggested_cwe": null
1318
+ },
1319
+ "consensus": {
1320
+ "score": 0.95,
1321
+ "agreement_level": "unanimous",
1322
+ "voted_models": [
1323
+ "gpt-oss:20b"
1324
+ ],
1325
+ "rationale": "No compensating control detected."
1326
+ },
1327
+ "fingerprint": "kfp1:7796f734194bfe71dc7268f597efd6338a4e967a788e0220edb61b8e1c198b16",
1328
+ "baseline_state": "new"
1329
+ },
1330
+ {
1331
+ "finding_id": "INJ-I-0018-1",
1332
+ "category": "sql_injection",
1333
+ "subtype": "string_concatenation",
1334
+ "title": "SQL Injection in java-legacy/src/com/acme/web/OrderServlet.java:15",
1335
+ "candidate_id": "I-0018",
1336
+ "location": {
1337
+ "file": "java-legacy/src/com/acme/web/OrderServlet.java",
1338
+ "start_line": 15
1339
+ },
1340
+ "evidence": [
1341
+ {
1342
+ "file": "java-legacy/src/com/acme/web/OrderServlet.java",
1343
+ "start_line": 11,
1344
+ "code": "String id = request.getParameter(\"orderId\");",
1345
+ "why": "source: GET parameter",
1346
+ "line": 11
1347
+ },
1348
+ {
1349
+ "file": "java-legacy/src/com/acme/web/OrderServlet.java",
1350
+ "start_line": 15,
1351
+ "code": "ResultSet rs = stmt.executeQuery(\"SELECT * FROM orders WHERE id = '\" + id + \"'\");",
1352
+ "why": "unsanitized string concatenation",
1353
+ "line": 15
1354
+ }
1355
+ ],
1356
+ "severity": "critical",
1357
+ "confidence": "high",
1358
+ "cwe": [
1359
+ "CWE-89"
1360
+ ],
1361
+ "recommendation": "Use prepared statements",
1362
+ "status": "confirmed",
1363
+ "verification": {
1364
+ "verdict": "confirmed",
1365
+ "reason_code": "traced",
1366
+ "checked": [
1367
+ "java-legacy/src/com/acme/web/OrderServlet.java line 11: String id = request.getParameter(\"orderId\");",
1368
+ "java-legacy/src/com/acme/web/OrderServlet.java line 15: ResultSet rs = stmt.executeQuery(\"SELECT * FROM orders WHERE id = '\" + id + \"'\");"
1369
+ ],
1370
+ "control_found": null,
1371
+ "notes": "The id parameter is concatenated directly into the SQL string without any escaping or prepared statement usage.",
1372
+ "suggested_subtype": null,
1373
+ "suggested_cwe": null
1374
+ },
1375
+ "consensus": {
1376
+ "score": 0.95,
1377
+ "agreement_level": "unanimous",
1378
+ "voted_models": [
1379
+ "gpt-oss:20b"
1380
+ ],
1381
+ "rationale": "No sanitization or parameter binding."
1382
+ },
1383
+ "fingerprint": "kfp1:eed713c040fe4b03f16a649b4da2fa326995fd074ad3ba46bbbb4958486b6b8d",
1384
+ "baseline_state": "new"
1385
+ },
1386
+ {
1387
+ "finding_id": "INJ-I-0018-2",
1388
+ "category": "sql_injection",
1389
+ "subtype": "string_concatenation",
1390
+ "title": "SQL Injection in java-legacy/src/com/acme/web/OrderServlet.java:18",
1391
+ "candidate_id": "I-0018",
1392
+ "location": {
1393
+ "file": "java-legacy/src/com/acme/web/OrderServlet.java",
1394
+ "start_line": 18
1395
+ },
1396
+ "evidence": [
1397
+ {
1398
+ "file": "java-legacy/src/com/acme/web/OrderServlet.java",
1399
+ "start_line": 16,
1400
+ "code": "StringBuilder sb = new StringBuilder(\"SELECT * FROM items WHERE 1=1\");",
1401
+ "why": "initial query",
1402
+ "line": 16
1403
+ },
1404
+ {
1405
+ "file": "java-legacy/src/com/acme/web/OrderServlet.java",
1406
+ "start_line": 17,
1407
+ "code": "sb.append(\" AND order_id = \").append(id);",
1408
+ "why": "appending unsanitized id",
1409
+ "line": 17
1410
+ },
1411
+ {
1412
+ "file": "java-legacy/src/com/acme/web/OrderServlet.java",
1413
+ "start_line": 18,
1414
+ "code": "ResultSet items = stmt.executeQuery(sb.toString());",
1415
+ "why": "executing concatenated query",
1416
+ "line": 18
1417
+ }
1418
+ ],
1419
+ "severity": "critical",
1420
+ "confidence": "high",
1421
+ "cwe": [
1422
+ "CWE-89"
1423
+ ],
1424
+ "recommendation": "Use prepared statements",
1425
+ "status": "confirmed",
1426
+ "verification": {
1427
+ "verdict": "confirmed",
1428
+ "reason_code": "traced",
1429
+ "checked": [
1430
+ "java-legacy/src/com/acme/web/OrderServlet.java line 16: StringBuilder sb = new StringBuilder(\"SELECT * FROM items WHERE 1=1\");",
1431
+ "java-legacy/src/com/acme/web/OrderServlet.java line 17: sb.append(\" AND order_id = \").append(id);",
1432
+ "java-legacy/src/com/acme/web/OrderServlet.java line 18: ResultSet items = stmt.executeQuery(sb.toString());"
1433
+ ],
1434
+ "control_found": null,
1435
+ "notes": "The id value is appended to the query string without any escaping or parameterization, exposing the query to injection.",
1436
+ "suggested_subtype": null,
1437
+ "suggested_cwe": null
1438
+ },
1439
+ "consensus": {
1440
+ "score": 0.95,
1441
+ "agreement_level": "unanimous",
1442
+ "voted_models": [
1443
+ "gpt-oss:20b"
1444
+ ],
1445
+ "rationale": "No sanitization or prepared statement."
1446
+ },
1447
+ "fingerprint": "kfp1:d004e0812f1145878c4340a0625d6fef4406cc6350c197a91c345f70e4136d5a",
1448
+ "baseline_state": "new"
1449
+ },
1450
+ {
1451
+ "finding_id": "INJ-I-0020",
1452
+ "category": "sql_injection",
1453
+ "subtype": "string_concatenation",
1454
+ "title": "SQL Injection in node-express/routes/orders.js:17",
1455
+ "candidate_id": "I-0020",
1456
+ "location": {
1457
+ "file": "node-express/routes/orders.js",
1458
+ "start_line": 17
1459
+ },
1460
+ "evidence": [
1461
+ {
1462
+ "file": "node-express/routes/orders.js",
1463
+ "start_line": 17,
1464
+ "code": "db.query(\"SELECT * FROM reports WHERE month = '\" + req.query.month + \"'\", function (e, rows) { res.json(rows); });",
1465
+ "why": "Unsanitized input concatenated into query",
1466
+ "line": 17
1467
+ }
1468
+ ],
1469
+ "severity": "critical",
1470
+ "confidence": "high",
1471
+ "cwe": [
1472
+ "CWE-89"
1473
+ ],
1474
+ "recommendation": "Use parameterized queries",
1475
+ "status": "confirmed",
1476
+ "verification": {
1477
+ "verdict": "confirmed",
1478
+ "reason_code": "traced",
1479
+ "checked": [
1480
+ "node-express/routes/orders.js line 17",
1481
+ "node-express/app.js for middleware inspection",
1482
+ "node-express/routes/orders.js for query construction"
1483
+ ],
1484
+ "control_found": null,
1485
+ "notes": "The query concatenates the raw req.query.month value without any sanitization, casting, or allow‑list. No global middleware or helper function sanitizes this parameter. The code is reachable via the /orders route and is executed for any authenticated user, exposing the database to injection.",
1486
+ "suggested_subtype": null,
1487
+ "suggested_cwe": null
1488
+ },
1489
+ "consensus": {
1490
+ "score": 0.95,
1491
+ "agreement_level": "unanimous",
1492
+ "voted_models": [
1493
+ "gpt-oss:20b"
1494
+ ],
1495
+ "rationale": "Direct string concatenation of an unsanitized request parameter into a SQL statement."
1496
+ },
1497
+ "fingerprint": "kfp1:fc0f31011ca4510d026845f7fb0dcd77812ef24f6ea5329ad24fbc08a15b58ff",
1498
+ "baseline_state": "new"
1499
+ },
1500
+ {
1501
+ "finding_id": "INJ-I-0024",
1502
+ "category": "sql_injection",
1503
+ "subtype": "string_concatenation",
1504
+ "title": "SQL Injection in perl-cgi/cgi-bin/order.cgi:13",
1505
+ "candidate_id": "I-0024",
1506
+ "location": {
1507
+ "file": "perl-cgi/cgi-bin/order.cgi",
1508
+ "start_line": 13
1509
+ },
1510
+ "evidence": [
1511
+ {
1512
+ "file": "perl-cgi/cgi-bin/order.cgi",
1513
+ "start_line": 13,
1514
+ "code": "my $sth = $dbh->prepare(\"SELECT * FROM orders WHERE id = $id\");",
1515
+ "why": "Unsanitized input concatenated into query",
1516
+ "line": 13
1517
+ },
1518
+ {
1519
+ "file": "perl-cgi/cgi-bin/order.cgi",
1520
+ "start_line": 19,
1521
+ "code": "$dbh->do(\"DELETE FROM carts WHERE cart_id = \" . $q->param('cart'))",
1522
+ "why": "Unsanitized input concatenated into query",
1523
+ "line": 19
1524
+ }
1525
+ ],
1526
+ "severity": "critical",
1527
+ "confidence": "high",
1528
+ "cwe": [
1529
+ "CWE-89"
1530
+ ],
1531
+ "recommendation": "Use parameterized queries",
1532
+ "status": "confirmed",
1533
+ "verification": {
1534
+ "verdict": "confirmed",
1535
+ "reason_code": "traced",
1536
+ "checked": [
1537
+ "perl-cgi/cgi-bin/order.cgi line 13",
1538
+ "perl-cgi/cgi-bin/order.cgi for $id assignment",
1539
+ "perl-cgi/cgi-bin/order.cgi for query construction"
1540
+ ],
1541
+ "control_found": null,
1542
+ "notes": "The $id variable is taken directly from the CGI query string without casting or validation. The prepared statement concatenates it into the SQL string, making the query vulnerable to injection.",
1543
+ "suggested_subtype": null,
1544
+ "suggested_cwe": null
1545
+ },
1546
+ "consensus": {
1547
+ "score": 0.95,
1548
+ "agreement_level": "unanimous",
1549
+ "voted_models": [
1550
+ "gpt-oss:20b"
1551
+ ],
1552
+ "rationale": "Direct concatenation of unsanitized CGI input into SQL."
1553
+ },
1554
+ "fingerprint": "kfp1:0594761f603533b7391a9b7a8464e6865b71c1b61b71fdcd10991d861f51b5fb",
1555
+ "baseline_state": "new"
1556
+ },
1557
+ {
1558
+ "finding_id": "INJ-I-0025",
1559
+ "category": "sql_injection",
1560
+ "subtype": "string_concatenation",
1561
+ "title": "SQL Injection in perl-cgi/cgi-bin/order.cgi:19",
1562
+ "candidate_id": "I-0025",
1563
+ "location": {
1564
+ "file": "perl-cgi/cgi-bin/order.cgi",
1565
+ "start_line": 19
1566
+ },
1567
+ "evidence": [
1568
+ {
1569
+ "file": "perl-cgi/cgi-bin/order.cgi",
1570
+ "start_line": 13,
1571
+ "code": "my $sth = $dbh->prepare(\"SELECT * FROM orders WHERE id = $id\");",
1572
+ "why": "Unsanitized input concatenated into query",
1573
+ "line": 13
1574
+ },
1575
+ {
1576
+ "file": "perl-cgi/cgi-bin/order.cgi",
1577
+ "start_line": 19,
1578
+ "code": "$dbh->do(\"DELETE FROM carts WHERE cart_id = \" . $q->param('cart'))",
1579
+ "why": "Unsanitized input concatenated into query",
1580
+ "line": 19
1581
+ }
1582
+ ],
1583
+ "severity": "critical",
1584
+ "confidence": "high",
1585
+ "cwe": [
1586
+ "CWE-89"
1587
+ ],
1588
+ "recommendation": "Use parameterized queries",
1589
+ "status": "confirmed",
1590
+ "verification": {
1591
+ "verdict": "confirmed",
1592
+ "reason_code": "traced",
1593
+ "checked": [
1594
+ "perl-cgi/cgi-bin/order.cgi line 19",
1595
+ "perl-cgi/cgi-bin/order.cgi for $q->param('cart') usage",
1596
+ "perl-cgi/cgi-bin/order.cgi for query construction"
1597
+ ],
1598
+ "control_found": null,
1599
+ "notes": "The cart_id value is concatenated directly into the DELETE statement without any sanitization or casting, exposing the database to injection.",
1600
+ "suggested_subtype": null,
1601
+ "suggested_cwe": null
1602
+ },
1603
+ "consensus": {
1604
+ "score": 0.95,
1605
+ "agreement_level": "unanimous",
1606
+ "voted_models": [
1607
+ "gpt-oss:20b"
1608
+ ],
1609
+ "rationale": "Direct concatenation of unsanitized CGI input into SQL."
1610
+ },
1611
+ "fingerprint": "kfp1:c7206e5510c18d43418b9ed9a4d20047783ea5263cfdc395a138b735b613debc",
1612
+ "baseline_state": "new"
1613
+ },
1614
+ {
1615
+ "finding_id": "INJ-I-0026",
1616
+ "category": "sql_injection",
1617
+ "subtype": "string_concatenation",
1618
+ "title": "SQL Injection in php-codeigniter/application/controllers/Invoice.php:10",
1619
+ "candidate_id": "I-0026",
1620
+ "location": {
1621
+ "file": "php-codeigniter/application/controllers/Invoice.php",
1622
+ "start_line": 10
1623
+ },
1624
+ "evidence": [
1625
+ {
1626
+ "file": "php-codeigniter/application/controllers/Invoice.php",
1627
+ "start_line": 9,
1628
+ "code": "public function view($id) {",
1629
+ "why": "method parameter from URL",
1630
+ "line": 9
1631
+ },
1632
+ {
1633
+ "file": "php-codeigniter/application/controllers/Invoice.php",
1634
+ "start_line": 10,
1635
+ "code": "$q = $this->db->query(\"SELECT * FROM invoices WHERE id = $id\");",
1636
+ "why": "unsanitized interpolation",
1637
+ "line": 10
1638
+ }
1639
+ ],
1640
+ "severity": "critical",
1641
+ "confidence": "high",
1642
+ "cwe": [
1643
+ "CWE-89"
1644
+ ],
1645
+ "recommendation": "Use parameterized queries",
1646
+ "status": "confirmed",
1647
+ "verification": {
1648
+ "verdict": "confirmed",
1649
+ "reason_code": "traced",
1650
+ "checked": [
1651
+ "php-codeigniter/application/controllers/Invoice.php:9-10",
1652
+ "php-codeigniter/application/controllers/Invoice.php:10"
1653
+ ],
1654
+ "control_found": null,
1655
+ "notes": "CodeIgniter’s `$this->db->query()` executes raw SQL. The `$id` parameter is interpolated directly into the query string with no escaping or integer casting.",
1656
+ "suggested_subtype": null,
1657
+ "suggested_cwe": null
1658
+ },
1659
+ "consensus": {
1660
+ "score": 0.95,
1661
+ "agreement_level": "unanimous",
1662
+ "voted_models": [
1663
+ "gpt-oss:20b"
1664
+ ],
1665
+ "rationale": "Direct interpolation of a route parameter into a raw SQL query."
1666
+ },
1667
+ "fingerprint": "kfp1:cab7179a42b52f77a862191152d4b0ebc8511c6c77b956503f7c53525c9390b0",
1668
+ "baseline_state": "new"
1669
+ },
1670
+ {
1671
+ "finding_id": "INJ-I-0028",
1672
+ "category": "sql_injection",
1673
+ "subtype": "string_concatenation",
1674
+ "title": "SQL Injection in php-plain/admin.php:12",
1675
+ "candidate_id": "I-0028",
1676
+ "location": {
1677
+ "file": "php-plain/admin.php",
1678
+ "start_line": 12
1679
+ },
1680
+ "evidence": [
1681
+ {
1682
+ "file": "php-plain/admin.php",
1683
+ "start_line": 12,
1684
+ "code": "mysql_query(\"DELETE FROM users WHERE id = $id\")",
1685
+ "why": "Unsanitized input concatenated into query",
1686
+ "line": 12
1687
+ }
1688
+ ],
1689
+ "severity": "critical",
1690
+ "confidence": "high",
1691
+ "cwe": [
1692
+ "CWE-89"
1693
+ ],
1694
+ "recommendation": "Use parameterized queries",
1695
+ "status": "confirmed",
1696
+ "verification": {
1697
+ "verdict": "confirmed",
1698
+ "reason_code": "traced",
1699
+ "checked": [
1700
+ "php-plain/admin.php line 12",
1701
+ "php-plain/admin.php for $id assignment",
1702
+ "php-plain/admin.php for query construction"
1703
+ ],
1704
+ "control_found": null,
1705
+ "notes": "The $id variable is derived from $_GET['id'] without casting or validation. The DELETE statement concatenates it directly, making the query vulnerable to injection.",
1706
+ "suggested_subtype": null,
1707
+ "suggested_cwe": null
1708
+ },
1709
+ "consensus": {
1710
+ "score": 0.95,
1711
+ "agreement_level": "unanimous",
1712
+ "voted_models": [
1713
+ "gpt-oss:20b"
1714
+ ],
1715
+ "rationale": "Direct concatenation of unsanitized GET input into SQL."
1716
+ },
1717
+ "fingerprint": "kfp1:59fca8cd0dedd8f70d286a5d3808292d9eddcf084148a63a448b7043b69c7948",
1718
+ "baseline_state": "new"
1719
+ },
1720
+ {
1721
+ "finding_id": "INJ-I-0029",
1722
+ "category": "nosql_injection",
1723
+ "subtype": "string_concatenation",
1724
+ "title": "NoSQL Injection in php-plain/mongo_find.php:6",
1725
+ "candidate_id": "I-0029",
1726
+ "location": {
1727
+ "file": "php-plain/mongo_find.php",
1728
+ "start_line": 6
1729
+ },
1730
+ "evidence": [
1731
+ {
1732
+ "file": "php-plain/mongo_find.php",
1733
+ "start_line": 6,
1734
+ "code": "$docs = $coll->find(array('$where' => \"this.name == '\" . $_GET['n'] . \"'\"));",
1735
+ "why": "Unsanitized input concatenated into $where expression",
1736
+ "line": 6
1737
+ }
1738
+ ],
1739
+ "severity": "critical",
1740
+ "confidence": "high",
1741
+ "cwe": [
1742
+ "CWE-89"
1743
+ ],
1744
+ "recommendation": "Avoid $where with user input; use query operators or parameterized filters",
1745
+ "status": "confirmed",
1746
+ "verification": {
1747
+ "verdict": "confirmed",
1748
+ "reason_code": "traced",
1749
+ "checked": [
1750
+ "php-plain/mongo_find.php line 6",
1751
+ "php-plain/mongo_find.php for $_GET['n'] usage",
1752
+ "php-plain/mongo_find.php for $where construction"
1753
+ ],
1754
+ "control_found": null,
1755
+ "notes": "The $where string is built by concatenating the raw $_GET['n'] value, allowing arbitrary JavaScript execution in MongoDB queries.",
1756
+ "suggested_subtype": null,
1757
+ "suggested_cwe": null
1758
+ },
1759
+ "consensus": {
1760
+ "score": 0.95,
1761
+ "agreement_level": "unanimous",
1762
+ "voted_models": [
1763
+ "gpt-oss:20b"
1764
+ ],
1765
+ "rationale": "Unsanitized user input used in a $where expression."
1766
+ },
1767
+ "fingerprint": "kfp1:9a6291b8d51e1b16927efd44616de1bd4763eedbbde287c4a9e064e8accfc87f",
1768
+ "baseline_state": "new"
1769
+ },
1770
+ {
1771
+ "finding_id": "INJ-I-0031",
1772
+ "category": "sql_injection",
1773
+ "subtype": "string_concatenation",
1774
+ "title": "SQL Injection in php-plain/order_detail.php:6",
1775
+ "candidate_id": "I-0031",
1776
+ "location": {
1777
+ "file": "php-plain/order_detail.php",
1778
+ "start_line": 6
1779
+ },
1780
+ "evidence": [
1781
+ {
1782
+ "file": "php-plain/order_detail.php",
1783
+ "start_line": 5,
1784
+ "code": "$id = $_GET['id'];",
1785
+ "why": "source: GET parameter",
1786
+ "line": 5
1787
+ },
1788
+ {
1789
+ "file": "php-plain/order_detail.php",
1790
+ "start_line": 6,
1791
+ "code": "$res = mysql_query(\"SELECT * FROM orders WHERE id = \" . $id);",
1792
+ "why": "unsanitized input concatenated into query",
1793
+ "line": 6
1794
+ }
1795
+ ],
1796
+ "severity": "critical",
1797
+ "confidence": "high",
1798
+ "cwe": [
1799
+ "CWE-89"
1800
+ ],
1801
+ "recommendation": "Use parameterized queries",
1802
+ "status": "confirmed",
1803
+ "verification": {
1804
+ "verdict": "confirmed",
1805
+ "reason_code": "traced",
1806
+ "checked": [
1807
+ "php-plain/order_detail.php line 6",
1808
+ "php-plain/order_detail.php for $id assignment",
1809
+ "php-plain/order_detail.php for query construction"
1810
+ ],
1811
+ "control_found": null,
1812
+ "notes": "The $id variable is taken from $_GET['id'] without casting or validation and concatenated directly into the SELECT statement, exposing the database to injection.",
1813
+ "suggested_subtype": null,
1814
+ "suggested_cwe": null
1815
+ },
1816
+ "consensus": {
1817
+ "score": 0.95,
1818
+ "agreement_level": "unanimous",
1819
+ "voted_models": [
1820
+ "gpt-oss:20b"
1821
+ ],
1822
+ "rationale": "Direct concatenation of unsanitized GET input into SQL."
1823
+ },
1824
+ "fingerprint": "kfp1:3065614ddeff53f64ce73f2bd0210f5b36a2249f4d2561f69219095ad867ac9f",
1825
+ "baseline_state": "new"
1826
+ },
1827
+ {
1828
+ "finding_id": "INJ-I-0032-1",
1829
+ "category": "sql_injection",
1830
+ "subtype": "dynamic_identifier",
1831
+ "title": "SQL Injection in php-plain/search.php:8",
1832
+ "candidate_id": "I-0032",
1833
+ "location": {
1834
+ "file": "php-plain/search.php",
1835
+ "start_line": 8
1836
+ },
1837
+ "evidence": [
1838
+ {
1839
+ "file": "php-plain/search.php",
1840
+ "start_line": 6,
1841
+ "code": "$sort = $_GET['sort'];",
1842
+ "why": "source: GET parameter",
1843
+ "line": 6
1844
+ },
1845
+ {
1846
+ "file": "php-plain/search.php",
1847
+ "start_line": 9,
1848
+ "code": "$sql .= \" ORDER BY \" . $sort;",
1849
+ "why": "unsanitized ORDER BY clause",
1850
+ "line": 9
1851
+ }
1852
+ ],
1853
+ "severity": "critical",
1854
+ "confidence": "high",
1855
+ "cwe": [
1856
+ "CWE-89"
1857
+ ],
1858
+ "recommendation": "Use parameterized queries and allow-list for ORDER BY",
1859
+ "status": "confirmed",
1860
+ "verification": {
1861
+ "verdict": "confirmed",
1862
+ "reason_code": "traced",
1863
+ "checked": [
1864
+ "php-plain/search.php line 6: $sort = $_GET['sort'];",
1865
+ "php-plain/search.php line 9: $sql .= \" ORDER BY \" . $sort;"
1866
+ ],
1867
+ "control_found": null,
1868
+ "notes": "The ORDER BY clause is built directly from the unsanitized $_GET['sort'] value. No allow‑list, casting, or global filter is applied to constrain the column name, so the query is vulnerable to SQL injection.",
1869
+ "suggested_subtype": null,
1870
+ "suggested_cwe": null
1871
+ },
1872
+ "consensus": {
1873
+ "score": 0.95,
1874
+ "agreement_level": "unanimous",
1875
+ "voted_models": [
1876
+ "gpt-oss:20b"
1877
+ ],
1878
+ "rationale": "No evidence of a compensating control; the raw user input is used in the ORDER BY clause."
1879
+ },
1880
+ "fingerprint": "kfp1:98fd40ce1d095b0656f7a73c45d0483075e58e1b86332a80aa5c71b4b21962cf",
1881
+ "baseline_state": "new"
1882
+ },
1883
+ {
1884
+ "finding_id": "INJ-I-0032-2",
1885
+ "category": "sql_injection",
1886
+ "subtype": "string_concatenation",
1887
+ "title": "SQL Injection in php-plain/search.php:14",
1888
+ "candidate_id": "I-0032",
1889
+ "location": {
1890
+ "file": "php-plain/search.php",
1891
+ "start_line": 14
1892
+ },
1893
+ "evidence": [
1894
+ {
1895
+ "file": "php-plain/search.php",
1896
+ "start_line": 13,
1897
+ "code": "$q = <<<SQL\nSELECT * FROM products\nWHERE category = '{$_GET['cat']}'\nSQL;",
1898
+ "why": "unsanitized input inside single quotes",
1899
+ "line": 13
1900
+ }
1901
+ ],
1902
+ "severity": "critical",
1903
+ "confidence": "high",
1904
+ "cwe": [
1905
+ "CWE-89"
1906
+ ],
1907
+ "recommendation": "Use parameterized queries",
1908
+ "status": "confirmed",
1909
+ "verification": {
1910
+ "verdict": "confirmed",
1911
+ "reason_code": "traced",
1912
+ "checked": [
1913
+ "php-plain/search.php line 13: $q = <<<SQL\nSELECT * FROM products\nWHERE category = '{$_GET['cat']}'\nSQL;"
1914
+ ],
1915
+ "control_found": null,
1916
+ "notes": "The category value is interpolated directly into the SQL string without any escaping or parameterization. This is a classic string concatenation injection point.",
1917
+ "suggested_subtype": null,
1918
+ "suggested_cwe": null
1919
+ },
1920
+ "consensus": {
1921
+ "score": 0.95,
1922
+ "agreement_level": "unanimous",
1923
+ "voted_models": [
1924
+ "gpt-oss:20b"
1925
+ ],
1926
+ "rationale": "No sanitization or parameter binding is present."
1927
+ },
1928
+ "fingerprint": "kfp1:59c91493f8e83065ed0bab7d82438a17443ae7ba9bc82c1659053686f63083d7",
1929
+ "baseline_state": "new"
1930
+ },
1931
+ {
1932
+ "finding_id": "INJ-I-0033",
1933
+ "category": "sql_injection",
1934
+ "subtype": "dynamic_identifier",
1935
+ "title": "SQL Injection in php-plain/search.php:9",
1936
+ "candidate_id": "I-0033",
1937
+ "location": {
1938
+ "file": "php-plain/search.php",
1939
+ "start_line": 9
1940
+ },
1941
+ "evidence": [
1942
+ {
1943
+ "file": "php-plain/search.php",
1944
+ "start_line": 6,
1945
+ "code": "$sort = $_GET['sort'];",
1946
+ "why": "source: GET parameter",
1947
+ "line": 6
1948
+ },
1949
+ {
1950
+ "file": "php-plain/search.php",
1951
+ "start_line": 9,
1952
+ "code": "$sql .= \" ORDER BY \" . $sort;",
1953
+ "why": "unsanitized ORDER BY clause",
1954
+ "line": 9
1955
+ }
1956
+ ],
1957
+ "severity": "critical",
1958
+ "confidence": "high",
1959
+ "cwe": [
1960
+ "CWE-89"
1961
+ ],
1962
+ "recommendation": "Use parameterized queries and allow-list for ORDER BY",
1963
+ "status": "confirmed",
1964
+ "verification": {
1965
+ "verdict": "confirmed",
1966
+ "reason_code": "traced",
1967
+ "checked": [
1968
+ "php-plain/search.php line 6: $sort = $_GET['sort'];",
1969
+ "php-plain/search.php line 9: $sql .= \" ORDER BY \" . $sort;"
1970
+ ],
1971
+ "control_found": null,
1972
+ "notes": "Duplicate of INJ-I-0032-1; the same unsanitized ORDER BY usage is present.",
1973
+ "suggested_subtype": null,
1974
+ "suggested_cwe": null
1975
+ },
1976
+ "consensus": {
1977
+ "score": 0.95,
1978
+ "agreement_level": "unanimous",
1979
+ "voted_models": [
1980
+ "gpt-oss:20b"
1981
+ ],
1982
+ "rationale": "Same code path as the first finding."
1983
+ },
1984
+ "fingerprint": "kfp1:8871b7f21b190efe0d95ac0274808695c3b37ac2eb4a505a8916f71d8dfb8d80",
1985
+ "baseline_state": "new"
1986
+ },
1987
+ {
1988
+ "finding_id": "INJ-I-0035",
1989
+ "category": "sql_injection",
1990
+ "subtype": "string_concatenation",
1991
+ "title": "SQL Injection in python-flask/app.py:12",
1992
+ "candidate_id": "I-0035",
1993
+ "location": {
1994
+ "file": "python-flask/app.py",
1995
+ "start_line": 12
1996
+ },
1997
+ "evidence": [
1998
+ {
1999
+ "file": "python-flask/app.py",
2000
+ "start_line": 10,
2001
+ "code": "order_id = request.args.get('orderId')",
2002
+ "why": "source: GET parameter",
2003
+ "line": 10
2004
+ },
2005
+ {
2006
+ "file": "python-flask/app.py",
2007
+ "start_line": 12,
2008
+ "code": "cur.execute(\"SELECT * FROM orders WHERE id = %s\" % order_id)",
2009
+ "why": "unsanitized string formatting",
2010
+ "line": 12
2011
+ }
2012
+ ],
2013
+ "severity": "critical",
2014
+ "confidence": "high",
2015
+ "cwe": [
2016
+ "CWE-89"
2017
+ ],
2018
+ "recommendation": "Use parameterized queries",
2019
+ "status": "confirmed",
2020
+ "verification": {
2021
+ "verdict": "confirmed",
2022
+ "reason_code": "traced",
2023
+ "checked": [
2024
+ "python-flask/app.py line 10: order_id = request.args.get('orderId')",
2025
+ "python-flask/app.py line 12: cur.execute(\"SELECT * FROM orders WHERE id = %s\" % order_id)"
2026
+ ],
2027
+ "control_found": null,
2028
+ "notes": "The order_id value is inserted into the SQL string via Python string formatting without any type casting or parameter binding, making the query vulnerable to injection.",
2029
+ "suggested_subtype": null,
2030
+ "suggested_cwe": null
2031
+ },
2032
+ "consensus": {
2033
+ "score": 0.95,
2034
+ "agreement_level": "unanimous",
2035
+ "voted_models": [
2036
+ "gpt-oss:20b"
2037
+ ],
2038
+ "rationale": "No sanitization or prepared statement is used."
2039
+ },
2040
+ "fingerprint": "kfp1:380cba3dbd163699e9ff5dd53924bb40b0532d1acc79dd1496f72558597fd643",
2041
+ "baseline_state": "new"
2042
+ },
2043
+ {
2044
+ "finding_id": "INJ-I-0036",
2045
+ "category": "sql_injection",
2046
+ "subtype": "string_interpolation",
2047
+ "title": "SQL Injection in python-flask/app.py:29",
2048
+ "candidate_id": "I-0036",
2049
+ "location": {
2050
+ "file": "python-flask/app.py",
2051
+ "start_line": 29
2052
+ },
2053
+ "evidence": [
2054
+ {
2055
+ "file": "python-flask/app.py",
2056
+ "start_line": 27,
2057
+ "code": "q = request.args.get('q', '')",
2058
+ "why": "source: GET parameter",
2059
+ "line": 27
2060
+ },
2061
+ {
2062
+ "file": "python-flask/app.py",
2063
+ "start_line": 29,
2064
+ "code": "cur.execute(f\"SELECT * FROM products WHERE name LIKE '%{q}%'\")",
2065
+ "why": "unsanitized f-string interpolation",
2066
+ "line": 29
2067
+ }
2068
+ ],
2069
+ "severity": "critical",
2070
+ "confidence": "high",
2071
+ "cwe": [
2072
+ "CWE-89"
2073
+ ],
2074
+ "recommendation": "Use parameterized queries",
2075
+ "status": "confirmed",
2076
+ "verification": {
2077
+ "verdict": "confirmed",
2078
+ "reason_code": "traced",
2079
+ "checked": [
2080
+ "python-flask/app.py line 27: q = request.args.get('q', '')",
2081
+ "python-flask/app.py line 29: cur.execute(f\"SELECT * FROM products WHERE name LIKE '%{q}%'\")"
2082
+ ],
2083
+ "control_found": null,
2084
+ "notes": "The f‑string directly interpolates the user supplied q into the SQL query. No escaping or parameterization is performed.",
2085
+ "suggested_subtype": null,
2086
+ "suggested_cwe": null
2087
+ },
2088
+ "consensus": {
2089
+ "score": 0.95,
2090
+ "agreement_level": "unanimous",
2091
+ "voted_models": [
2092
+ "gpt-oss:20b"
2093
+ ],
2094
+ "rationale": "Direct interpolation without sanitization."
2095
+ },
2096
+ "fingerprint": "kfp1:52c1a1b2beb4129f6fbbfa5c9bcc2f05de1cb6b4a835dcb6adfa7075c03edf79",
2097
+ "baseline_state": "new"
2098
+ },
2099
+ {
2100
+ "finding_id": "INJ-I-0037",
2101
+ "category": "sql_injection",
2102
+ "subtype": "stored_procedure_dynamic_sql",
2103
+ "title": "SQL Injection in sql-procs/sp_search_orders.sql:5",
2104
+ "candidate_id": "I-0037",
2105
+ "location": {
2106
+ "file": "sql-procs/sp_search_orders.sql",
2107
+ "start_line": 5
2108
+ },
2109
+ "evidence": [
2110
+ {
2111
+ "file": "sql-procs/sp_search_orders.sql",
2112
+ "start_line": 4,
2113
+ "code": "@Customer NVARCHAR(100), @Sort NVARCHAR(50)",
2114
+ "why": "procedure parameters",
2115
+ "line": 4
2116
+ },
2117
+ {
2118
+ "file": "sql-procs/sp_search_orders.sql",
2119
+ "start_line": 5,
2120
+ "code": "SET @sql = 'SELECT * FROM Orders WHERE Customer = ''' + @Customer + ''' ORDER BY ' + @Sort",
2121
+ "why": "dynamic SQL built from parameters",
2122
+ "line": 5
2123
+ }
2124
+ ],
2125
+ "severity": "critical",
2126
+ "confidence": "high",
2127
+ "cwe": [
2128
+ "CWE-89"
2129
+ ],
2130
+ "recommendation": "Avoid dynamic SQL; use parameterized queries or allow-list for ORDER BY",
2131
+ "status": "confirmed",
2132
+ "verification": {
2133
+ "verdict": "confirmed",
2134
+ "reason_code": "traced",
2135
+ "checked": [
2136
+ "sql-procs/sp_search_orders.sql:4-5",
2137
+ "sql-procs/sp_search_orders.sql:5"
2138
+ ],
2139
+ "control_found": null,
2140
+ "notes": "The stored procedure concatenates the `@Sort` parameter directly into the dynamic SQL string. No allow‑list or parameter binding is applied to the ORDER BY clause.",
2141
+ "suggested_subtype": null,
2142
+ "suggested_cwe": null
2143
+ },
2144
+ "consensus": {
2145
+ "score": 0.95,
2146
+ "agreement_level": "unanimous",
2147
+ "voted_models": [
2148
+ "gpt-oss:20b"
2149
+ ],
2150
+ "rationale": "Dynamic SQL built from an unvalidated string parameter."
2151
+ },
2152
+ "fingerprint": "kfp1:dda6a5bb147b8aa741739e9da8799b0f942492567758d5d3209c1395a97c8f98",
2153
+ "baseline_state": "new"
2154
+ },
2155
+ {
2156
+ "finding_id": "INJ-I-0038",
2157
+ "category": "sql_injection",
2158
+ "subtype": "stored_procedure_dynamic_sql",
2159
+ "title": "SQL Injection in sql-procs/sp_search_orders.sql:6",
2160
+ "candidate_id": "I-0038",
2161
+ "location": {
2162
+ "file": "sql-procs/sp_search_orders.sql",
2163
+ "start_line": 6
2164
+ },
2165
+ "evidence": [
2166
+ {
2167
+ "file": "sql-procs/sp_search_orders.sql",
2168
+ "start_line": 4,
2169
+ "code": "@Customer NVARCHAR(100), @Sort NVARCHAR(50)",
2170
+ "why": "procedure parameters",
2171
+ "line": 4
2172
+ },
2173
+ {
2174
+ "file": "sql-procs/sp_search_orders.sql",
2175
+ "start_line": 5,
2176
+ "code": "SET @sql = 'SELECT * FROM Orders WHERE Customer = ''' + @Customer + ''' ORDER BY ' + @Sort",
2177
+ "why": "dynamic SQL built from parameters",
2178
+ "line": 5
2179
+ }
2180
+ ],
2181
+ "severity": "critical",
2182
+ "confidence": "high",
2183
+ "cwe": [
2184
+ "CWE-89"
2185
+ ],
2186
+ "recommendation": "Avoid dynamic SQL; use parameterized queries or allow-list for ORDER BY",
2187
+ "status": "confirmed",
2188
+ "verification": {
2189
+ "verdict": "confirmed",
2190
+ "reason_code": "traced",
2191
+ "checked": [
2192
+ "sql-procs/sp_search_orders.sql:4-5",
2193
+ "sql-procs/sp_search_orders.sql:5"
2194
+ ],
2195
+ "control_found": null,
2196
+ "notes": "Same as INJ-I-0037; the `@Sort` value is concatenated without validation.",
2197
+ "suggested_subtype": null,
2198
+ "suggested_cwe": null
2199
+ },
2200
+ "consensus": {
2201
+ "score": 0.95,
2202
+ "agreement_level": "unanimous",
2203
+ "voted_models": [
2204
+ "gpt-oss:20b"
2205
+ ],
2206
+ "rationale": "Dynamic SQL built from an unvalidated string parameter."
2207
+ },
2208
+ "fingerprint": "kfp1:bc5a024fdb7c6ece9891fb3cce3e046b4627068fc198466fd6f98f71af5bea43",
2209
+ "baseline_state": "new"
2210
+ },
2211
+ {
2212
+ "finding_id": "MASS-M-0006-1",
2213
+ "category": "mass_assignment",
2214
+ "subtype": "whole_object_binding",
2215
+ "title": "Profile update binds entire request body to User model",
2216
+ "candidate_id": "M-0006",
2217
+ "location": {
2218
+ "file": "routes/profile.js",
2219
+ "start_line": 8
2220
+ },
2221
+ "evidence": [
2222
+ {
2223
+ "file": "routes/profile.js",
2224
+ "line": 8,
2225
+ "why": "User.findByIdAndUpdate(req.user._id, req.body, ...) binds entire body to User model"
2226
+ }
2227
+ ],
2228
+ "impact": "role, admin flag",
2229
+ "severity": "critical",
2230
+ "confidence": "high",
2231
+ "cwe": [
2232
+ "CWE-915"
2233
+ ],
2234
+ "recommendation": "Limit fields to an allowlist; set privileged fields server‑side.",
2235
+ "status": "unverified",
2236
+ "fingerprint": "kfp1:960f064246c57436e1c9e73b6d68a73e2f12c9706d42cf23db19d5ba53deb4de",
2237
+ "baseline_state": "new"
2238
+ },
2239
+ {
2240
+ "finding_id": "MASS-M-0008-1",
2241
+ "category": "mass_assignment",
2242
+ "subtype": "whole_object_binding",
2243
+ "title": "User registration creates user from request body",
2244
+ "candidate_id": "M-0008",
2245
+ "location": {
2246
+ "file": "app/Http/Controllers/AccountController.php",
2247
+ "start_line": 13
2248
+ },
2249
+ "evidence": [
2250
+ {
2251
+ "file": "app/Http/Controllers/AccountController.php",
2252
+ "line": 13,
2253
+ "why": "User::create($request->all()) binds entire request body to User model"
2254
+ }
2255
+ ],
2256
+ "impact": "role, admin flag",
2257
+ "severity": "critical",
2258
+ "confidence": "high",
2259
+ "cwe": [
2260
+ "CWE-915"
2261
+ ],
2262
+ "recommendation": "Use an explicit field list or $fillable to exclude privileged fields.",
2263
+ "status": "unverified",
2264
+ "fingerprint": "kfp1:5ad56d30cbf69c227a69cbcbfcd26f42733998e980cae7d122d7bcec7ea73ebc",
2265
+ "baseline_state": "new"
2266
+ },
2267
+ {
2268
+ "finding_id": "MASS-M-0009-1",
2269
+ "category": "mass_assignment",
2270
+ "subtype": "privileged_field_writable",
2271
+ "title": "Team creation allows owner_id to be set by client",
2272
+ "candidate_id": "M-0009",
2273
+ "location": {
2274
+ "file": "app/Http/Controllers/AccountController.php",
2275
+ "start_line": 23
2276
+ },
2277
+ "evidence": [
2278
+ {
2279
+ "file": "app/Http/Controllers/AccountController.php",
2280
+ "line": 23,
2281
+ "why": "Team::create($request->all()) binds entire request body; owner_id is a privileged field"
2282
+ }
2283
+ ],
2284
+ "impact": "owner_id",
2285
+ "severity": "critical",
2286
+ "confidence": "high",
2287
+ "cwe": [
2288
+ "CWE-915"
2289
+ ],
2290
+ "recommendation": "Exclude owner_id from the bind; set it server‑side.",
2291
+ "status": "unverified",
2292
+ "fingerprint": "kfp1:28b0be984e1eb817cca53b79b6014265eda827fcca1b7ec8b48937bc11d7576e",
2293
+ "baseline_state": "new"
2294
+ },
2295
+ {
2296
+ "finding_id": "MASS-M-0011-1",
2297
+ "category": "mass_assignment",
2298
+ "subtype": "whole_object_binding",
2299
+ "title": "Unrestricted binding of all POST fields to User model in PHP profile.php",
2300
+ "candidate_id": "M-0011",
2301
+ "location": {
2302
+ "file": "php-plain/profile.php",
2303
+ "start_line": 5
2304
+ },
2305
+ "evidence": [
2306
+ {
2307
+ "file": "php-plain/profile.php",
2308
+ "line": 6,
2309
+ "why": "foreach ($_POST as $field => $value) assigns all fields"
2310
+ },
2311
+ {
2312
+ "file": "php-plain/profile.php",
2313
+ "line": 10,
2314
+ "why": "Explicitly sets $user->role from request"
2315
+ }
2316
+ ],
2317
+ "impact": "Any user can set privileged fields such as role",
2318
+ "severity": "critical",
2319
+ "confidence": "high",
2320
+ "cwe": [
2321
+ "CWE-915"
2322
+ ],
2323
+ "recommendation": "Implement an allowlist or strip privileged fields before persisting",
2324
+ "status": "confirmed",
2325
+ "verification": {
2326
+ "verdict": "confirmed",
2327
+ "reason_code": "traced",
2328
+ "checked": [
2329
+ "php-plain/profile.php – examined foreach($_POST as $field => $value)",
2330
+ "php-plain/profile.php – confirmed all fields are assigned to $user",
2331
+ "global includes – no allow‑list or field filtering found"
2332
+ ],
2333
+ "control_found": null,
2334
+ "notes": "All POST fields are bound to the User model without any filtering, allowing privileged fields to be set by the attacker.",
2335
+ "suggested_subtype": null,
2336
+ "suggested_cwe": null
2337
+ },
2338
+ "consensus": {
2339
+ "score": 0.95,
2340
+ "agreement_level": "unanimous",
2341
+ "voted_models": [
2342
+ "gpt-oss:20b"
2343
+ ],
2344
+ "rationale": "No allow‑list or field filtering detected."
2345
+ },
2346
+ "fingerprint": "kfp1:4042bba4357c8075f2a2bd67fdfc0cea2d5512d99de70a4395a9bfcf87b67063",
2347
+ "baseline_state": "new"
2348
+ },
2349
+ {
2350
+ "finding_id": "MASS-M-0012-1",
2351
+ "category": "mass_assignment",
2352
+ "subtype": "privileged_field_writable",
2353
+ "title": "Explicit writable privileged field 'role' in PHP profile.php",
2354
+ "candidate_id": "M-0012",
2355
+ "location": {
2356
+ "file": "php-plain/profile.php",
2357
+ "start_line": 5
2358
+ },
2359
+ "evidence": [
2360
+ {
2361
+ "file": "php-plain/profile.php",
2362
+ "line": 10,
2363
+ "why": "$user->role = $_POST['role'] writes privileged field without validation"
2364
+ }
2365
+ ],
2366
+ "impact": "Any user can set their role to admin",
2367
+ "severity": "critical",
2368
+ "confidence": "high",
2369
+ "cwe": [
2370
+ "CWE-915",
2371
+ "CWE-269"
2372
+ ],
2373
+ "recommendation": "Remove role assignment from user input or enforce role check",
2374
+ "status": "confirmed",
2375
+ "verification": {
2376
+ "verdict": "confirmed",
2377
+ "reason_code": "traced",
2378
+ "checked": [
2379
+ "php-plain/profile.php – examined explicit assignment `$user->role = $_POST['role']`",
2380
+ "php-plain/profile.php – confirmed no validation or role check",
2381
+ "global includes – no allow‑list or field filtering found"
2382
+ ],
2383
+ "control_found": null,
2384
+ "notes": "The `role` field is writable directly from user input, confirming the privilege escalation risk.",
2385
+ "suggested_subtype": null,
2386
+ "suggested_cwe": null
2387
+ },
2388
+ "consensus": {
2389
+ "score": 0.95,
2390
+ "agreement_level": "unanimous",
2391
+ "voted_models": [
2392
+ "gpt-oss:20b"
2393
+ ],
2394
+ "rationale": "No compensating control identified."
2395
+ },
2396
+ "fingerprint": "kfp1:c0564ee6e15360b75abcad854b22f5c063ad4eb320bd9ce0a4c47e3b7e483d2b",
2397
+ "baseline_state": "new"
2398
+ },
2399
+ {
2400
+ "finding_id": "MASS-M-0014-1",
2401
+ "category": "mass_assignment",
2402
+ "subtype": "whole_object_binding",
2403
+ "title": "User creation binds entire request body to User model",
2404
+ "candidate_id": "M-0014",
2405
+ "location": {
2406
+ "file": "app.py",
2407
+ "start_line": 10
2408
+ },
2409
+ "evidence": [
2410
+ {
2411
+ "file": "app.py",
2412
+ "line": 10,
2413
+ "why": "User(**request.json) binds entire request body to User model"
2414
+ }
2415
+ ],
2416
+ "impact": "role, admin flag",
2417
+ "severity": "critical",
2418
+ "confidence": "high",
2419
+ "cwe": [
2420
+ "CWE-915"
2421
+ ],
2422
+ "recommendation": "Use an explicit field list or a DTO to restrict writable fields.",
2423
+ "status": "unverified",
2424
+ "fingerprint": "kfp1:ce4a5edbc11d47f0e7f1c8880eb0c744ee98ee0f2834696e9ac8642ee6281773",
2425
+ "baseline_state": "new"
2426
+ },
2427
+ {
2428
+ "finding_id": "MASS-M-0015-1",
2429
+ "category": "mass_assignment",
2430
+ "subtype": "whole_object_binding",
2431
+ "title": "Patch user updates all fields from request body",
2432
+ "candidate_id": "M-0015",
2433
+ "location": {
2434
+ "file": "app.py",
2435
+ "start_line": 27
2436
+ },
2437
+ "evidence": [
2438
+ {
2439
+ "file": "app.py",
2440
+ "line": 27,
2441
+ "why": "for key, value in request.json.items(): setattr(user, key, value) binds entire request body to User model"
2442
+ }
2443
+ ],
2444
+ "impact": "role, admin flag",
2445
+ "severity": "critical",
2446
+ "confidence": "high",
2447
+ "cwe": [
2448
+ "CWE-915"
2449
+ ],
2450
+ "recommendation": "Whitelist allowed fields; set privileged fields server‑side.",
2451
+ "status": "unverified",
2452
+ "fingerprint": "kfp1:2b45c26cb0e79c67cde4ca5fd3bda885bdbc5af1c02aab97781ca4656b950580",
2453
+ "baseline_state": "new"
2454
+ },
2455
+ {
2456
+ "finding_id": "BOLA-R-0001",
2457
+ "category": "bola",
2458
+ "subtype": "missing_ownership_check",
2459
+ "title": "BOLA in ANY /user/edit.do: missing owner verification",
2460
+ "route_id": "R-0001",
2461
+ "endpoint": {
2462
+ "method": "ANY",
2463
+ "path": "/user/edit.do"
2464
+ },
2465
+ "location": {
2466
+ "file": "java-legacy/src/com/acme/struts/EditUserAction.java",
2467
+ "start_line": 10
2468
+ },
2469
+ "evidence": [
2470
+ {
2471
+ "file": "java-legacy/src/com/acme/struts/EditUserAction.java",
2472
+ "start_line": 10,
2473
+ "code": "String userId = request.getParameter(\"user_id\");\nString email = request.getParameter(\"email\");\nConnection conn = DataSourceHolder.get().getConnection();\nPreparedStatement ps = conn.prepareStatement(\"UPDATE users SET email = '\" + email + \"' WHERE id = \" + userId);",
2474
+ "why": "The user_id comes from the request and is used directly in the UPDATE without checking that it belongs to the current session user.",
2475
+ "line": 10
2476
+ }
2477
+ ],
2478
+ "severity": "high",
2479
+ "confidence": "high",
2480
+ "cwe": [
2481
+ "CWE-284",
2482
+ "CWE-639"
2483
+ ],
2484
+ "recommendation": "Bind the UPDATE to the current session user.",
2485
+ "status": "confirmed",
2486
+ "verification": {
2487
+ "verdict": "confirmed",
2488
+ "reason_code": "traced",
2489
+ "checked": [
2490
+ "java-legacy/src/com/acme/struts/EditUserAction.java",
2491
+ "java-legacy/src/com/acme/filters/AuthenticationFilter.java",
2492
+ "web.xml"
2493
+ ],
2494
+ "control_found": null,
2495
+ "notes": "The EditUserAction directly uses the request parameter \"user_id\" in an UPDATE statement without any ownership check. No global filter or interceptor overrides the parameter or enforces that the id matches the authenticated user. The web.xml filter configuration contains only a generic authentication filter that does not bind the user id to the session. Therefore the vulnerability is confirmed.",
2496
+ "suggested_subtype": null,
2497
+ "suggested_cwe": null
2498
+ },
2499
+ "consensus": {
2500
+ "score": 0.95,
2501
+ "agreement_level": "unanimous",
2502
+ "voted_models": [
2503
+ "gpt-oss:20b"
2504
+ ],
2505
+ "rationale": "No compensating control found; the code path is reachable and the parameter is used unsanitized."
2506
+ },
2507
+ "fingerprint": "kfp1:fd2aa0a34a547de1965d6735e927f0eb2dac3c019cdf1e1d14bdfa9666ae1581",
2508
+ "baseline_state": "new"
2509
+ },
2510
+ {
2511
+ "finding_id": "BOLA-R-0002",
2512
+ "category": "bola",
2513
+ "subtype": "missing_ownership_check",
2514
+ "title": "BOLA in ANY /classic-asp/shop/member.asp?mode=edit: missing owner verification",
2515
+ "route_id": "R-0002",
2516
+ "endpoint": {
2517
+ "method": "ANY",
2518
+ "path": "/classic-asp/shop/member.asp?mode=edit"
2519
+ },
2520
+ "location": {
2521
+ "file": "classic-asp/shop/member.asp",
2522
+ "start_line": 6
2523
+ },
2524
+ "evidence": [
2525
+ {
2526
+ "file": "classic-asp/shop/member.asp",
2527
+ "start_line": 6,
2528
+ "code": "sql = \"UPDATE members SET name = '\" & Replace(Request.Form(\"name\"), \"'\", \"''\") & \"'\"\nsql = sql & \" WHERE member_id = \" & Request.Form(\"member_id\")\nconn.Execute sql",
2529
+ "why": "member_id is taken from the form and used directly in the UPDATE without verifying ownership.",
2530
+ "line": 6
2531
+ }
2532
+ ],
2533
+ "severity": "high",
2534
+ "confidence": "high",
2535
+ "cwe": [
2536
+ "CWE-284",
2537
+ "CWE-639"
2538
+ ],
2539
+ "recommendation": "Add a WHERE clause that binds the member to the current session user.",
2540
+ "status": "confirmed",
2541
+ "verification": {
2542
+ "verdict": "confirmed",
2543
+ "reason_code": "traced",
2544
+ "checked": [
2545
+ "classic-asp/shop/member.asp",
2546
+ "classic-asp/include/global.asp"
2547
+ ],
2548
+ "control_found": null,
2549
+ "notes": "The member_id is taken directly from Request.Form and concatenated into an UPDATE statement. The global include does not perform any ownership validation or parameter sanitization. The code is reachable via the \"mode=edit\" query string. Hence the BOLA is confirmed.",
2550
+ "suggested_subtype": null,
2551
+ "suggested_cwe": null
2552
+ },
2553
+ "consensus": {
2554
+ "score": 0.95,
2555
+ "agreement_level": "unanimous",
2556
+ "voted_models": [
2557
+ "gpt-oss:20b"
2558
+ ],
2559
+ "rationale": "Direct use of user-supplied id without checks."
2560
+ },
2561
+ "fingerprint": "kfp1:99af1eeee9810e226e1e9087abed7ad8883a9cb6e96fcb50fdcd281bc652e4bd",
2562
+ "baseline_state": "new"
2563
+ },
2564
+ {
2565
+ "finding_id": "BOLA-R-0003",
2566
+ "category": "bola",
2567
+ "subtype": "missing_ownership_check",
2568
+ "title": "BOLA in ANY /classic-asp/shop/member.asp?mode=delete: missing owner verification",
2569
+ "route_id": "R-0003",
2570
+ "endpoint": {
2571
+ "method": "ANY",
2572
+ "path": "/classic-asp/shop/member.asp?mode=delete"
2573
+ },
2574
+ "location": {
2575
+ "file": "classic-asp/shop/member.asp",
2576
+ "start_line": 10
2577
+ },
2578
+ "evidence": [
2579
+ {
2580
+ "file": "classic-asp/shop/member.asp",
2581
+ "start_line": 10,
2582
+ "code": "conn.Execute \"DELETE FROM members WHERE member_id = \" & Request.QueryString(\"member_id\")",
2583
+ "why": "member_id comes from the query string and is used directly in the DELETE without ownership check.",
2584
+ "line": 10
2585
+ }
2586
+ ],
2587
+ "severity": "high",
2588
+ "confidence": "high",
2589
+ "cwe": [
2590
+ "CWE-284",
2591
+ "CWE-639"
2592
+ ],
2593
+ "recommendation": "Ensure the member belongs to the current session user before deletion.",
2594
+ "status": "confirmed",
2595
+ "verification": {
2596
+ "verdict": "confirmed",
2597
+ "reason_code": "traced",
2598
+ "checked": [
2599
+ "classic-asp/shop/member.asp",
2600
+ "classic-asp/include/global.asp"
2601
+ ],
2602
+ "control_found": null,
2603
+ "notes": "The DELETE statement uses Request.QueryString(\"member_id\") without any ownership verification. No global filter or session binding is present. The route is reachable via the \"mode=delete\" query string. Therefore the vulnerability is confirmed.",
2604
+ "suggested_subtype": null,
2605
+ "suggested_cwe": null
2606
+ },
2607
+ "consensus": {
2608
+ "score": 0.95,
2609
+ "agreement_level": "unanimous",
2610
+ "voted_models": [
2611
+ "gpt-oss:20b"
2612
+ ],
2613
+ "rationale": "No ownership check; direct use of id."
2614
+ },
2615
+ "fingerprint": "kfp1:968e98d715c204db223a45cd631f3b44a68f53e61dc05de10e1f3fafda4ec175",
2616
+ "baseline_state": "new"
2617
+ },
2618
+ {
2619
+ "finding_id": "BOLA-R-0004",
2620
+ "category": "bola",
2621
+ "subtype": "missing_ownership_check",
2622
+ "title": "BOLA in ANY /classic-asp/shop/order.asp: missing owner verification",
2623
+ "route_id": "R-0004",
2624
+ "endpoint": {
2625
+ "method": "ANY",
2626
+ "path": "/classic-asp/shop/order.asp"
2627
+ },
2628
+ "location": {
2629
+ "file": "classic-asp/shop/order.asp",
2630
+ "start_line": 5
2631
+ },
2632
+ "evidence": [
2633
+ {
2634
+ "file": "classic-asp/shop/order.asp",
2635
+ "start_line": 5,
2636
+ "code": "id = Request(\"id\")\nSet rs = conn.Execute(\"SELECT * FROM orders WHERE id = \" & id)\nResponse.Write Server.HTMLEncode(rs(\"address\"))",
2637
+ "why": "The order id is taken from the request and the SELECT does not filter by user_id, exposing any order.",
2638
+ "line": 5
2639
+ }
2640
+ ],
2641
+ "severity": "high",
2642
+ "confidence": "high",
2643
+ "cwe": [
2644
+ "CWE-284",
2645
+ "CWE-639"
2646
+ ],
2647
+ "recommendation": "Add a user_id filter to the SELECT.",
2648
+ "status": "confirmed",
2649
+ "verification": {
2650
+ "verdict": "confirmed",
2651
+ "reason_code": "traced",
2652
+ "checked": [
2653
+ "classic-asp/shop/order.asp",
2654
+ "classic-asp/include/global.asp"
2655
+ ],
2656
+ "control_found": null,
2657
+ "notes": "The SELECT statement retrieves an order by id without filtering by user_id. The global include does not enforce any ownership constraint. The route is reachable via a simple GET request. Thus the BOLA is confirmed.",
2658
+ "suggested_subtype": null,
2659
+ "suggested_cwe": null
2660
+ },
2661
+ "consensus": {
2662
+ "score": 0.95,
2663
+ "agreement_level": "unanimous",
2664
+ "voted_models": [
2665
+ "gpt-oss:20b"
2666
+ ],
2667
+ "rationale": "Unfiltered data exposure."
2668
+ },
2669
+ "fingerprint": "kfp1:e93fc6e82ca183dc55be6fef39c3146001c4dbab631322ba960d00930e254b5d",
2670
+ "baseline_state": "new"
2671
+ },
2672
+ {
2673
+ "finding_id": "BOLA-R-0005",
2674
+ "category": "bola",
2675
+ "subtype": "missing_ownership_check",
2676
+ "title": "BOLA in ANY /coldfusion/app/OrderService.cfc?method=getOrder: missing owner verification",
2677
+ "route_id": "R-0005",
2678
+ "endpoint": {
2679
+ "method": "ANY",
2680
+ "path": "/coldfusion/app/OrderService.cfc?method=getOrder"
2681
+ },
2682
+ "location": {
2683
+ "file": "coldfusion/app/OrderService.cfc",
2684
+ "start_line": 4
2685
+ },
2686
+ "evidence": [
2687
+ {
2688
+ "file": "coldfusion/app/OrderService.cfc",
2689
+ "start_line": 4,
2690
+ "code": "<cfquery name=\"q\" datasource=\"shop\">\n SELECT * FROM orders WHERE id = #arguments.orderId#\n</cfquery>",
2691
+ "why": "The order id comes from the request argument and the query does not restrict by user_id.",
2692
+ "line": 4
2693
+ }
2694
+ ],
2695
+ "severity": "high",
2696
+ "confidence": "high",
2697
+ "cwe": [
2698
+ "CWE-284",
2699
+ "CWE-639"
2700
+ ],
2701
+ "recommendation": "Scope the query to the current session user.",
2702
+ "status": "confirmed",
2703
+ "verification": {
2704
+ "verdict": "confirmed",
2705
+ "reason_code": "traced",
2706
+ "checked": [
2707
+ "coldfusion/app/OrderService.cfc",
2708
+ "coldfusion/app/OrderService.cfc?method=getOrder"
2709
+ ],
2710
+ "control_found": null,
2711
+ "notes": "The query uses the request argument \"orderId\" directly in the WHERE clause without any user_id filter. No global security policy or session binding is applied to the argument. The method is exposed via a public web service call, so the vulnerability is confirmed.",
2712
+ "suggested_subtype": null,
2713
+ "suggested_cwe": null
2714
+ },
2715
+ "consensus": {
2716
+ "score": 0.95,
2717
+ "agreement_level": "unanimous",
2718
+ "voted_models": [
2719
+ "gpt-oss:20b"
2720
+ ],
2721
+ "rationale": "Direct use of id without ownership check."
2722
+ },
2723
+ "fingerprint": "kfp1:c856936fd2eec990ab6cbc31fe4731bcb2077a14890d911f4e4369445335a26d",
2724
+ "baseline_state": "new"
2725
+ },
2726
+ {
2727
+ "finding_id": "BOLA-R-0006",
2728
+ "category": "bola",
2729
+ "subtype": "missing_ownership_check",
2730
+ "title": "BOLA in ANY /coldfusion/app/order.cfm: missing owner verification",
2731
+ "route_id": "R-0006",
2732
+ "endpoint": {
2733
+ "method": "ANY",
2734
+ "path": "/coldfusion/app/order.cfm"
2735
+ },
2736
+ "location": {
2737
+ "file": "coldfusion/app/order.cfm",
2738
+ "start_line": 2
2739
+ },
2740
+ "evidence": [
2741
+ {
2742
+ "file": "coldfusion/app/order.cfm",
2743
+ "start_line": 2,
2744
+ "code": "<cfquery name=\"qOrder\" datasource=\"shop\">\n SELECT * FROM orders WHERE id = #url.id#\n</cfquery>",
2745
+ "why": "The order id is taken from the URL and the query does not filter by user_id.",
2746
+ "line": 2
2747
+ }
2748
+ ],
2749
+ "severity": "high",
2750
+ "confidence": "high",
2751
+ "cwe": [
2752
+ "CWE-284",
2753
+ "CWE-639"
2754
+ ],
2755
+ "recommendation": "Add a user_id filter to the SELECT.",
2756
+ "status": "confirmed",
2757
+ "verification": {
2758
+ "verdict": "confirmed",
2759
+ "reason_code": "traced",
2760
+ "checked": [
2761
+ "coldfusion/app/order.cfm",
2762
+ "coldfusion/app/order.cfm?url.id"
2763
+ ],
2764
+ "control_found": null,
2765
+ "notes": "The query selects an order by id from the URL without filtering by user_id. No global filter or session validation is present. The page is reachable via a GET request, confirming the BOLA.",
2766
+ "suggested_subtype": null,
2767
+ "suggested_cwe": null
2768
+ },
2769
+ "consensus": {
2770
+ "score": 0.95,
2771
+ "agreement_level": "unanimous",
2772
+ "voted_models": [
2773
+ "gpt-oss:20b"
2774
+ ],
2775
+ "rationale": "Unfiltered data exposure."
2776
+ },
2777
+ "fingerprint": "kfp1:eb3e24aeaf1e9815041914c036da60526aa633ef0d70a5b2fcebfdd4a8b2309b",
2778
+ "baseline_state": "new"
2779
+ },
2780
+ {
2781
+ "finding_id": "BOLA-R-0007",
2782
+ "category": "bola",
2783
+ "subtype": "missing_ownership_check",
2784
+ "title": "BOLA in POST /Admin/DeleteUser: missing owner verification",
2785
+ "route_id": "R-0007",
2786
+ "endpoint": {
2787
+ "method": "POST",
2788
+ "path": "/Admin/DeleteUser"
2789
+ },
2790
+ "location": {
2791
+ "file": "dotnet-legacy/Web/Controllers/AdminController.cs",
2792
+ "start_line": 12
2793
+ },
2794
+ "evidence": [
2795
+ {
2796
+ "file": "dotnet-legacy/Web/Controllers/AdminController.cs",
2797
+ "start_line": 12,
2798
+ "code": "public ActionResult DeleteUser(int id)\n{\n var u = db.Users.Find(id);\n db.Users.Remove(u);\n db.SaveChanges();\n return RedirectToAction(\"Index\");\n}",
2799
+ "why": "The id comes from the request and the method is protected only by [Authorize], not by an admin role or ownership check.",
2800
+ "line": 12
2801
+ }
2802
+ ],
2803
+ "severity": "high",
2804
+ "confidence": "high",
2805
+ "cwe": [
2806
+ "CWE-284",
2807
+ "CWE-639"
2808
+ ],
2809
+ "recommendation": "Restrict the delete to admins or bind to the current user.",
2810
+ "status": "confirmed",
2811
+ "verification": {
2812
+ "verdict": "confirmed",
2813
+ "reason_code": "traced",
2814
+ "checked": [
2815
+ "dotnet-legacy/Web/Controllers/AdminController.cs",
2816
+ "dotnet-legacy/Web/Filters/AuthorizeAttribute.cs"
2817
+ ],
2818
+ "control_found": null,
2819
+ "notes": "DeleteUser is protected only by [Authorize], which allows any authenticated user to invoke it. There is no role check or ownership verification before deleting the user record. The method is reachable via POST /Admin/DeleteUser, so the vulnerability is confirmed.",
2820
+ "suggested_subtype": null,
2821
+ "suggested_cwe": null
2822
+ },
2823
+ "consensus": {
2824
+ "score": 0.95,
2825
+ "agreement_level": "unanimous",
2826
+ "voted_models": [
2827
+ "gpt-oss:20b"
2828
+ ],
2829
+ "rationale": "Missing role-based guard."
2830
+ },
2831
+ "fingerprint": "kfp1:ff7ce7baa2540985e6cf2198bc15a646ece40603a915b3ccbe7e0fb05ccca5d7",
2832
+ "baseline_state": "new"
2833
+ },
2834
+ {
2835
+ "finding_id": "BOLA-R-0009",
2836
+ "category": "bola",
2837
+ "subtype": "missing_ownership_check",
2838
+ "title": "BOLA in ANY /Admin/Export: missing authentication and ownership verification",
2839
+ "route_id": "R-0009",
2840
+ "endpoint": {
2841
+ "method": "ANY",
2842
+ "path": "/Admin/Export"
2843
+ },
2844
+ "location": {
2845
+ "file": "dotnet-legacy/Web/Controllers/AdminController.cs",
2846
+ "start_line": 30
2847
+ },
2848
+ "evidence": [
2849
+ {
2850
+ "file": "dotnet-legacy/Web/Controllers/AdminController.cs",
2851
+ "start_line": 30,
2852
+ "code": "public ActionResult Export()\n{\n if (Request.QueryString[\"isAdmin\"] == \"true\")\n {\n return Json(db.Users.ToList(), JsonRequestBehavior.AllowGet);\n }\n return HttpNotFound();\n}",
2853
+ "why": "No authentication guard; any user can request all users by setting isAdmin=true.",
2854
+ "line": 30
2855
+ }
2856
+ ],
2857
+ "severity": "high",
2858
+ "confidence": "high",
2859
+ "cwe": [
2860
+ "CWE-284",
2861
+ "CWE-639"
2862
+ ],
2863
+ "recommendation": "Add [Authorize] and role check; remove the isAdmin query string.",
2864
+ "status": "confirmed",
2865
+ "verification": {
2866
+ "verdict": "confirmed",
2867
+ "reason_code": "traced",
2868
+ "checked": [
2869
+ "dotnet-legacy/Web/Controllers/AdminController.cs",
2870
+ "dotnet-legacy/Web/Filters/AuthorizeAttribute.cs"
2871
+ ],
2872
+ "control_found": null,
2873
+ "notes": "Export action has no authentication guard and relies on a query string flag. Any user can call /Admin/Export?isAdmin=true and retrieve all users. No role check or authentication is enforced, confirming the BOLA.",
2874
+ "suggested_subtype": null,
2875
+ "suggested_cwe": null
2876
+ },
2877
+ "consensus": {
2878
+ "score": 0.95,
2879
+ "agreement_level": "unanimous",
2880
+ "voted_models": [
2881
+ "gpt-oss:20b"
2882
+ ],
2883
+ "rationale": "Unprotected data export."
2884
+ },
2885
+ "fingerprint": "kfp1:c6487f0b561136d9532ebd8e87f3b206f713993f34ec8f3a7ee45e7455c7ee41",
2886
+ "baseline_state": "new"
2887
+ },
2888
+ {
2889
+ "finding_id": "BOLA-R-0010",
2890
+ "category": "bola",
2891
+ "subtype": "missing_ownership_check",
2892
+ "title": "BOLA in POST /Customer/Edit: missing owner verification",
2893
+ "route_id": "R-0010",
2894
+ "endpoint": {
2895
+ "method": "POST",
2896
+ "path": "/Customer/Edit"
2897
+ },
2898
+ "location": {
2899
+ "file": "dotnet-legacy/Web/Controllers/CustomerController.cs",
2900
+ "start_line": 12
2901
+ },
2902
+ "evidence": [
2903
+ {
2904
+ "file": "dotnet-legacy/Web/Controllers/CustomerController.cs",
2905
+ "start_line": 12,
2906
+ "code": "public ActionResult Edit(Customer customer)",
2907
+ "why": "Receives a Customer object with any Id; no ownership check",
2908
+ "line": 12
2909
+ },
2910
+ {
2911
+ "file": "dotnet-legacy/Web/Controllers/CustomerController.cs",
2912
+ "start_line": 14,
2913
+ "code": "db.Entry(customer).State = System.Data.Entity.EntityState.Modified;",
2914
+ "why": "Marks the supplied customer as modified regardless of owner",
2915
+ "line": 14
2916
+ }
2917
+ ],
2918
+ "severity": "high",
2919
+ "confidence": "low",
2920
+ "cwe": [
2921
+ "CWE-284",
2922
+ "CWE-639"
2923
+ ],
2924
+ "recommendation": "Verify that the customer belongs to the current user before saving",
2925
+ "status": "likely",
2926
+ "verification": {
2927
+ "verdict": "likely",
2928
+ "reason_code": "not_verified",
2929
+ "checked": [
2930
+ "dotnet-legacy/Web/Controllers/CustomerController.cs",
2931
+ "dotnet-legacy/Web/Controllers/BaseController.cs",
2932
+ "dotnet-legacy/Web/Filters/AuthorizationFilter.cs",
2933
+ "dotnet-legacy/Web/Global.asax",
2934
+ "dotnet-legacy/Web/Views/Shared/_Layout.cshtml"
2935
+ ],
2936
+ "control_found": null,
2937
+ "notes": "No evidence of an ownership check, role-based guard, or tenant scoping found in the controller or any global filter. The code directly marks the supplied customer entity as modified without verifying that the current user owns the record.",
2938
+ "suggested_subtype": null,
2939
+ "suggested_cwe": null
2940
+ },
2941
+ "consensus": {
2942
+ "score": 0.55,
2943
+ "agreement_level": "disputed",
2944
+ "voted_models": [
2945
+ "gpt-oss:20b"
2946
+ ],
2947
+ "rationale": "The auditor did not find a compensating control, but the absence of evidence does not prove a vulnerability."
2948
+ },
2949
+ "fingerprint": "kfp1:2ff50be6396a3261e2543aa3b955d99113acdc748a014446cb0b489407bafe90",
2950
+ "baseline_state": "new"
2951
+ },
2952
+ {
2953
+ "finding_id": "BOLA-R-0011",
2954
+ "category": "bola",
2955
+ "subtype": "missing_ownership_check",
2956
+ "title": "BOLA in POST /Customer/EditName: missing owner verification",
2957
+ "route_id": "R-0011",
2958
+ "endpoint": {
2959
+ "method": "POST",
2960
+ "path": "/Customer/EditName"
2961
+ },
2962
+ "location": {
2963
+ "file": "dotnet-legacy/Web/Controllers/CustomerController.cs",
2964
+ "start_line": 20
2965
+ },
2966
+ "evidence": [
2967
+ {
2968
+ "file": "dotnet-legacy/Web/Controllers/CustomerController.cs",
2969
+ "start_line": 20,
2970
+ "code": "public ActionResult EditName([Bind(Include = \"Id,Name\")] Customer customer)",
2971
+ "why": "Allows any customer Id to be updated without ownership check",
2972
+ "line": 20
2973
+ },
2974
+ {
2975
+ "file": "dotnet-legacy/Web/Controllers/CustomerController.cs",
2976
+ "start_line": 22,
2977
+ "code": "db.SaveChanges();",
2978
+ "why": "Saves changes to the supplied customer record",
2979
+ "line": 22
2980
+ }
2981
+ ],
2982
+ "severity": "high",
2983
+ "confidence": "low",
2984
+ "cwe": [
2985
+ "CWE-284",
2986
+ "CWE-639"
2987
+ ],
2988
+ "recommendation": "Ensure the customer belongs to the current user before saving",
2989
+ "status": "likely",
2990
+ "verification": {
2991
+ "verdict": "likely",
2992
+ "reason_code": "not_verified",
2993
+ "checked": [
2994
+ "dotnet-legacy/Web/Controllers/CustomerController.cs",
2995
+ "dotnet-legacy/Web/Controllers/BaseController.cs",
2996
+ "dotnet-legacy/Web/Filters/AuthorizationFilter.cs",
2997
+ "dotnet-legacy/Web/Global.asax",
2998
+ "dotnet-legacy/Web/Views/Shared/_Layout.cshtml"
2999
+ ],
3000
+ "control_found": null,
3001
+ "notes": "The EditName action accepts a bound Customer object with only Id and Name. No ownership validation or tenant check is performed before calling db.SaveChanges(). No global filter or base controller logic was found that would enforce ownership.",
3002
+ "suggested_subtype": null,
3003
+ "suggested_cwe": null
3004
+ },
3005
+ "consensus": {
3006
+ "score": 0.55,
3007
+ "agreement_level": "disputed",
3008
+ "voted_models": [
3009
+ "gpt-oss:20b"
3010
+ ],
3011
+ "rationale": "No compensating control detected; however, the absence of evidence is not definitive proof of a flaw."
3012
+ },
3013
+ "fingerprint": "kfp1:f8f8312969722c53dc4fbfcebddf09bfc6ea44e77f308058c6f5a0a79916c6a1",
3014
+ "baseline_state": "new"
3015
+ },
3016
+ {
3017
+ "finding_id": "BOLA-R-0012",
3018
+ "category": "bola",
3019
+ "subtype": "missing_ownership_check",
3020
+ "title": "BOLA in POST /Customer/Refresh: missing owner verification",
3021
+ "route_id": "R-0012",
3022
+ "endpoint": {
3023
+ "method": "POST",
3024
+ "path": "/Customer/Refresh"
3025
+ },
3026
+ "location": {
3027
+ "file": "dotnet-legacy/Web/Controllers/CustomerController.cs",
3028
+ "start_line": 27
3029
+ },
3030
+ "evidence": [
3031
+ {
3032
+ "file": "dotnet-legacy/Web/Controllers/CustomerController.cs",
3033
+ "start_line": 27,
3034
+ "code": "public ActionResult Refresh(int id)",
3035
+ "why": "Accepts any customer id without checking ownership",
3036
+ "line": 27
3037
+ },
3038
+ {
3039
+ "file": "dotnet-legacy/Web/Controllers/CustomerController.cs",
3040
+ "start_line": 29,
3041
+ "code": "var customer = db.Customers.Find(id);",
3042
+ "why": "Loads customer by id supplied by attacker",
3043
+ "line": 29
3044
+ },
3045
+ {
3046
+ "file": "dotnet-legacy/Web/Controllers/CustomerController.cs",
3047
+ "start_line": 30,
3048
+ "code": "TryUpdateModel(customer);",
3049
+ "why": "Updates all fields of the loaded customer",
3050
+ "line": 30
3051
+ }
3052
+ ],
3053
+ "severity": "high",
3054
+ "confidence": "low",
3055
+ "cwe": [
3056
+ "CWE-284",
3057
+ "CWE-639"
3058
+ ],
3059
+ "recommendation": "Check that the customer belongs to the current user before updating",
3060
+ "status": "likely",
3061
+ "verification": {
3062
+ "verdict": "likely",
3063
+ "reason_code": "not_verified",
3064
+ "checked": [
3065
+ "dotnet-legacy/Web/Controllers/CustomerController.cs",
3066
+ "dotnet-legacy/Web/Controllers/BaseController.cs",
3067
+ "dotnet-legacy/Web/Filters/AuthorizationFilter.cs",
3068
+ "dotnet-legacy/Web/Global.asax",
3069
+ "dotnet-legacy/Web/Views/Shared/_Layout.cshtml"
3070
+ ],
3071
+ "control_found": null,
3072
+ "notes": "The Refresh action loads a Customer by id supplied in the request and then calls TryUpdateModel without any ownership check. No global or base controller guard was found.",
3073
+ "suggested_subtype": null,
3074
+ "suggested_cwe": null
3075
+ },
3076
+ "consensus": {
3077
+ "score": 0.55,
3078
+ "agreement_level": "disputed",
3079
+ "voted_models": [
3080
+ "gpt-oss:20b"
3081
+ ],
3082
+ "rationale": "No evidence of a compensating control; the lack of evidence does not confirm the vulnerability."
3083
+ },
3084
+ "fingerprint": "kfp1:642d9e00b1146b80dc8724396082e7bf839dc74fdcc9224c6452557ed7b19ab7",
3085
+ "baseline_state": "new"
3086
+ },
3087
+ {
3088
+ "finding_id": "BOLA-R-0013",
3089
+ "category": "bola",
3090
+ "subtype": "missing_ownership_check",
3091
+ "title": "BOLA in ANY /Invoice/Details: missing owner verification",
3092
+ "route_id": "R-0013",
3093
+ "endpoint": {
3094
+ "method": "ANY",
3095
+ "path": "/Invoice/Details"
3096
+ },
3097
+ "location": {
3098
+ "file": "dotnet-legacy/Web/Controllers/InvoiceController.cs",
3099
+ "start_line": 11
3100
+ },
3101
+ "evidence": [
3102
+ {
3103
+ "file": "dotnet-legacy/Web/Controllers/InvoiceController.cs",
3104
+ "start_line": 11,
3105
+ "code": "public ActionResult Details(int id)\n{\n var inv = db.Invoices.Find(id);\n return View(inv);\n}",
3106
+ "why": "The invoice id is taken from the request and the invoice is returned without checking that it belongs to the current user.",
3107
+ "line": 11
3108
+ }
3109
+ ],
3110
+ "severity": "high",
3111
+ "confidence": "high",
3112
+ "cwe": [
3113
+ "CWE-284",
3114
+ "CWE-639"
3115
+ ],
3116
+ "recommendation": "Add a user_id filter to the query.",
3117
+ "status": "confirmed",
3118
+ "verification": {
3119
+ "verdict": "confirmed",
3120
+ "reason_code": "traced",
3121
+ "checked": [
3122
+ "dotnet-legacy/Web/Controllers/InvoiceController.cs",
3123
+ "dotnet-legacy/Web/Filters/AuthorizeAttribute.cs"
3124
+ ],
3125
+ "control_found": null,
3126
+ "notes": "Details action retrieves an invoice by id without checking that the invoice belongs to the current user. The method is reachable via GET /Invoice/Details?id=..., so the BOLA is confirmed.",
3127
+ "suggested_subtype": null,
3128
+ "suggested_cwe": null
3129
+ },
3130
+ "consensus": {
3131
+ "score": 0.95,
3132
+ "agreement_level": "unanimous",
3133
+ "voted_models": [
3134
+ "gpt-oss:20b"
3135
+ ],
3136
+ "rationale": "Missing ownership check."
3137
+ },
3138
+ "fingerprint": "kfp1:a63c895e83988c707151981826dda723122bb41598834dcfa82591301c26b773",
3139
+ "baseline_state": "new"
3140
+ },
3141
+ {
3142
+ "finding_id": "BOLA-R-0015",
3143
+ "category": "bola",
3144
+ "subtype": "missing_ownership_check",
3145
+ "title": "BOLA in POST /Invoice/Search: missing owner verification",
3146
+ "route_id": "R-0015",
3147
+ "endpoint": {
3148
+ "method": "POST",
3149
+ "path": "/Invoice/Search"
3150
+ },
3151
+ "location": {
3152
+ "file": "dotnet-legacy/Web/Controllers/InvoiceController.cs",
3153
+ "start_line": 26
3154
+ },
3155
+ "evidence": [
3156
+ {
3157
+ "file": "dotnet-legacy/Web/Controllers/InvoiceController.cs",
3158
+ "start_line": 26,
3159
+ "code": "public ActionResult Search(string customer)\n{\n var list = db.Database.SqlQuery<Invoice>(\"SELECT * FROM Invoices WHERE Customer = '\" + customer + \"'\").ToList();\n return View(list);\n}",
3160
+ "why": "The customer string is taken from the request and the query returns all invoices for that customer without filtering by user.",
3161
+ "line": 26
3162
+ }
3163
+ ],
3164
+ "severity": "high",
3165
+ "confidence": "high",
3166
+ "cwe": [
3167
+ "CWE-284",
3168
+ "CWE-639"
3169
+ ],
3170
+ "recommendation": "Add a user_id filter to the query.",
3171
+ "status": "confirmed",
3172
+ "verification": {
3173
+ "verdict": "confirmed",
3174
+ "reason_code": "traced",
3175
+ "checked": [
3176
+ "dotnet-legacy/Web/Controllers/InvoiceController.cs",
3177
+ "dotnet-legacy/Web/Filters/AuthorizeAttribute.cs"
3178
+ ],
3179
+ "control_found": null,
3180
+ "notes": "Search action concatenates the customer string into a raw SQL query and returns all invoices for that customer without filtering by user. The endpoint is reachable via POST /Invoice/Search, confirming the BOLA.",
3181
+ "suggested_subtype": null,
3182
+ "suggested_cwe": null
3183
+ },
3184
+ "consensus": {
3185
+ "score": 0.95,
3186
+ "agreement_level": "unanimous",
3187
+ "voted_models": [
3188
+ "gpt-oss:20b"
3189
+ ],
3190
+ "rationale": "SQL injection and missing ownership check."
3191
+ },
3192
+ "fingerprint": "kfp1:58c2c056d7e71edc7c9962f37053cd74e5daff6f95dd7fc443c4dd44338ad450",
3193
+ "baseline_state": "new"
3194
+ },
3195
+ {
3196
+ "finding_id": "BOLA-R-0016",
3197
+ "category": "bola",
3198
+ "subtype": "missing_ownership_check",
3199
+ "title": "BOLA in GET /dotnet-legacy/Web/Handlers/GetFile.ashx: missing owner verification",
3200
+ "route_id": "R-0016",
3201
+ "endpoint": {
3202
+ "method": "ANY",
3203
+ "path": "/dotnet-legacy/Web/Handlers/GetFile.ashx"
3204
+ },
3205
+ "location": {
3206
+ "file": "dotnet-legacy/Web/Handlers/GetFile.ashx",
3207
+ "start_line": 1
3208
+ },
3209
+ "evidence": [
3210
+ {
3211
+ "file": "dotnet-legacy/Web/Handlers/GetFile.ashx",
3212
+ "start_line": 8,
3213
+ "code": "string fileId = context.Request.QueryString[\"file_id\"];",
3214
+ "why": "File identifier comes directly from the request",
3215
+ "line": 8
3216
+ },
3217
+ {
3218
+ "file": "dotnet-legacy/Web/Handlers/GetFile.ashx",
3219
+ "start_line": 10,
3220
+ "code": "context.Response.TransmitFile(path);",
3221
+ "why": "Serves the file without any authentication or ownership check",
3222
+ "line": 10
3223
+ }
3224
+ ],
3225
+ "severity": "high",
3226
+ "confidence": "low",
3227
+ "cwe": [
3228
+ "CWE-284",
3229
+ "CWE-639"
3230
+ ],
3231
+ "recommendation": "Require authentication and verify that the file belongs to the current user",
3232
+ "status": "likely",
3233
+ "verification": {
3234
+ "verdict": "likely",
3235
+ "reason_code": "not_verified",
3236
+ "checked": [
3237
+ "dotnet-legacy/Web/Handlers/GetFile.ashx",
3238
+ "dotnet-legacy/Web/Global.asax",
3239
+ "dotnet-legacy/Web/Filters/AuthorizationFilter.cs",
3240
+ "dotnet-legacy/Web/Controllers/BaseController.cs"
3241
+ ],
3242
+ "control_found": null,
3243
+ "notes": "GetFile.ashx reads a file_id from the query string and transmits the file without any authentication or ownership validation. No global filter or handler-level guard was found.",
3244
+ "suggested_subtype": null,
3245
+ "suggested_cwe": null
3246
+ },
3247
+ "consensus": {
3248
+ "score": 0.55,
3249
+ "agreement_level": "disputed",
3250
+ "voted_models": [
3251
+ "gpt-oss:20b"
3252
+ ],
3253
+ "rationale": "No compensating control detected; absence of evidence is not proof of safety."
3254
+ },
3255
+ "fingerprint": "kfp1:3a00a7f2b857b16149c5a6df74d705a9e4bdf0830a11061873af55f4ab95a865",
3256
+ "baseline_state": "new"
3257
+ },
3258
+ {
3259
+ "finding_id": "BOLA-R-0017",
3260
+ "category": "bola",
3261
+ "subtype": "missing_ownership_check",
3262
+ "title": "BOLA in ANY /dotnet-legacy/Web/Orders/EditOrder.aspx: missing owner verification",
3263
+ "route_id": "R-0017",
3264
+ "endpoint": {
3265
+ "method": "ANY",
3266
+ "path": "/dotnet-legacy/Web/Orders/EditOrder.aspx"
3267
+ },
3268
+ "location": {
3269
+ "file": "dotnet-legacy/Web/Orders/EditOrder.aspx.vb",
3270
+ "start_line": 7
3271
+ },
3272
+ "evidence": [
3273
+ {
3274
+ "file": "dotnet-legacy/Web/Orders/EditOrder.aspx.vb",
3275
+ "start_line": 7,
3276
+ "code": "Dim sql As String = \"UPDATE Orders SET Note = '\" & txtNote.Text & \"'\"\nsql &= \" WHERE OrderId = \" & hfOrderId.Value\nUsing conn As New SqlConnection(ConnStr)\n Dim cmd As New SqlCommand(sql, conn)\n conn.Open()\n cmd.ExecuteNonQuery()\nEnd Using",
3277
+ "why": "OrderId comes from a hidden field and the UPDATE does not check that the order belongs to the current user.",
3278
+ "line": 7
3279
+ }
3280
+ ],
3281
+ "severity": "high",
3282
+ "confidence": "high",
3283
+ "cwe": [
3284
+ "CWE-284",
3285
+ "CWE-639"
3286
+ ],
3287
+ "recommendation": "Add a user_id filter to the UPDATE.",
3288
+ "status": "confirmed",
3289
+ "verification": {
3290
+ "verdict": "confirmed",
3291
+ "reason_code": "traced",
3292
+ "checked": [
3293
+ "dotnet-legacy/Web/Orders/EditOrder.aspx.vb",
3294
+ "dotnet-legacy/Global.asax",
3295
+ "dotnet-legacy/web.config"
3296
+ ],
3297
+ "control_found": null,
3298
+ "notes": "The UPDATE statement uses the OrderId from a hidden field without any user_id filter. No global filter, base page class, or session binding adds an ownership check. The code is reachable via the EditOrder.aspx page, which is not protected by a role-based constraint that would prevent cross‑user access.",
3299
+ "suggested_subtype": null,
3300
+ "suggested_cwe": null
3301
+ },
3302
+ "consensus": {
3303
+ "score": 0.95,
3304
+ "agreement_level": "unanimous",
3305
+ "voted_models": [
3306
+ "gpt-oss:20b"
3307
+ ],
3308
+ "rationale": "Independent audit found no compensating control; direct SQL uses user‑supplied ID."
3309
+ },
3310
+ "fingerprint": "kfp1:2ea5e8435292a4d79e53292c88e5a3a1b4a20a146f32ecd3b312dc2abc60a5f2",
3311
+ "baseline_state": "new"
3312
+ },
3313
+ {
3314
+ "finding_id": "BOLA-R-0018",
3315
+ "category": "bola",
3316
+ "subtype": "missing_ownership_check",
3317
+ "title": "BOLA in ANY /dotnet-legacy/Web/Orders/OrderDetail.aspx: missing owner verification",
3318
+ "route_id": "R-0018",
3319
+ "endpoint": {
3320
+ "method": "ANY",
3321
+ "path": "/dotnet-legacy/Web/Orders/OrderDetail.aspx"
3322
+ },
3323
+ "location": {
3324
+ "file": "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs",
3325
+ "start_line": 10
3326
+ },
3327
+ "evidence": [
3328
+ {
3329
+ "file": "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs",
3330
+ "start_line": 10,
3331
+ "code": "string id = Request.QueryString[\"id\"];\nusing (var conn = new SqlConnection(ConnStr))\n{\n var cmd = new SqlCommand(\"SELECT * FROM Orders WHERE OrderId = \" + id, conn);\n conn.Open();\n var r = cmd.ExecuteReader();\n var safe = new SqlCommand(\"SELECT * FROM Orders WHERE OrderId = @id AND UserId = @uid\", conn);",
3332
+ "why": "The first SELECT reads the order by id without filtering by user_id.",
3333
+ "line": 10
3334
+ }
3335
+ ],
3336
+ "severity": "high",
3337
+ "confidence": "high",
3338
+ "cwe": [
3339
+ "CWE-284",
3340
+ "CWE-639"
3341
+ ],
3342
+ "recommendation": "Add a user_id filter to the SELECT.",
3343
+ "status": "confirmed",
3344
+ "verification": {
3345
+ "verdict": "confirmed",
3346
+ "reason_code": "traced",
3347
+ "checked": [
3348
+ "dotnet-legacy/Web/Orders/OrderDetail.aspx.cs",
3349
+ "dotnet-legacy/Global.asax",
3350
+ "dotnet-legacy/web.config"
3351
+ ],
3352
+ "control_found": null,
3353
+ "notes": "The first SELECT reads the order by OrderId without filtering by UserId. The subsequent SELECT that includes UserId is not used to guard the data returned by the first query. No global filter or base class enforces ownership. The page is reachable for any authenticated user.",
3354
+ "suggested_subtype": null,
3355
+ "suggested_cwe": null
3356
+ },
3357
+ "consensus": {
3358
+ "score": 0.95,
3359
+ "agreement_level": "unanimous",
3360
+ "voted_models": [
3361
+ "gpt-oss:20b"
3362
+ ],
3363
+ "rationale": "No ownership check found; direct read of user‑supplied ID."
3364
+ },
3365
+ "fingerprint": "kfp1:03c16bbd7e443e60e4ed1827e0adb8534c92aaa782468773c94fb64c74f21119",
3366
+ "baseline_state": "new"
3367
+ },
3368
+ {
3369
+ "finding_id": "BOLA-R-0019",
3370
+ "category": "bola",
3371
+ "subtype": "missing_ownership_check",
3372
+ "title": "BOLA in ANY /java-legacy/WebContent/order/detail.jsp: missing owner verification",
3373
+ "route_id": "R-0019",
3374
+ "endpoint": {
3375
+ "method": "ANY",
3376
+ "path": "/java-legacy/WebContent/order/detail.jsp"
3377
+ },
3378
+ "location": {
3379
+ "file": "java-legacy/WebContent/order/detail.jsp",
3380
+ "start_line": 3
3381
+ },
3382
+ "evidence": [
3383
+ {
3384
+ "file": "java-legacy/WebContent/order/detail.jsp",
3385
+ "start_line": 3,
3386
+ "code": "String no = request.getParameter(\"no\");\nConnection c = (Connection) application.getAttribute(\"conn\");\nResultSet rs = c.createStatement().executeQuery(\"SELECT * FROM orders WHERE order_no = '\" + no + \"'\");",
3387
+ "why": "The order_no comes from the request and the query returns the order without checking that it belongs to the current user.",
3388
+ "line": 3
3389
+ }
3390
+ ],
3391
+ "severity": "high",
3392
+ "confidence": "high",
3393
+ "cwe": [
3394
+ "CWE-284",
3395
+ "CWE-639"
3396
+ ],
3397
+ "recommendation": "Add a user_id filter to the SELECT.",
3398
+ "status": "confirmed",
3399
+ "verification": {
3400
+ "verdict": "confirmed",
3401
+ "reason_code": "traced",
3402
+ "checked": [
3403
+ "java-legacy/WebContent/order/detail.jsp",
3404
+ "java-legacy/web.xml",
3405
+ "java-legacy/src/com/acme/web/SecurityConfig.java"
3406
+ ],
3407
+ "control_found": null,
3408
+ "notes": "The JSP concatenates the request parameter into a raw SQL query without a UserId filter. No web.xml security constraint or Spring Security annotation protects the resource from cross‑user access. The query is executed immediately, exposing the order to any authenticated user.",
3409
+ "suggested_subtype": null,
3410
+ "suggested_cwe": null
3411
+ },
3412
+ "consensus": {
3413
+ "score": 0.95,
3414
+ "agreement_level": "unanimous",
3415
+ "voted_models": [
3416
+ "gpt-oss:20b"
3417
+ ],
3418
+ "rationale": "Direct SQL with user input; no ownership guard."
3419
+ },
3420
+ "fingerprint": "kfp1:ec652d51f2910465a7f501f44711a7f6e698bf5baa030a30a23b1461eab30b63",
3421
+ "baseline_state": "new"
3422
+ },
3423
+ {
3424
+ "finding_id": "BOLA-R-0020",
3425
+ "category": "bola",
3426
+ "subtype": "missing_ownership_check",
3427
+ "title": "BOLA in POST /account/save: missing owner verification",
3428
+ "route_id": "R-0020",
3429
+ "endpoint": {
3430
+ "method": "POST",
3431
+ "path": "/account/save"
3432
+ },
3433
+ "location": {
3434
+ "file": "java-legacy/src/com/acme/web/AccountController.java",
3435
+ "start_line": 13
3436
+ },
3437
+ "evidence": [
3438
+ {
3439
+ "file": "java-legacy/src/com/acme/web/AccountController.java",
3440
+ "start_line": 13,
3441
+ "code": "public String save(@ModelAttribute Account account) {\n accountDao.save(account);\n return \"redirect:/account\";\n}",
3442
+ "why": "The account object is persisted without checking that the current user owns it.",
3443
+ "line": 13
3444
+ }
3445
+ ],
3446
+ "severity": "high",
3447
+ "confidence": "high",
3448
+ "cwe": [
3449
+ "CWE-284",
3450
+ "CWE-639"
3451
+ ],
3452
+ "recommendation": "Bind the save operation to the current session user.",
3453
+ "status": "confirmed",
3454
+ "verification": {
3455
+ "verdict": "confirmed",
3456
+ "reason_code": "traced",
3457
+ "checked": [
3458
+ "java-legacy/src/com/acme/web/AccountController.java",
3459
+ "java-legacy/src/com/acme/web/SecurityConfig.java"
3460
+ ],
3461
+ "control_found": null,
3462
+ "notes": "The save method persists the Account object without verifying that the current session user owns the account. No @PreAuthorize or manual check is present. The controller is mapped to a POST endpoint that is reachable by any authenticated user.",
3463
+ "suggested_subtype": null,
3464
+ "suggested_cwe": null
3465
+ },
3466
+ "consensus": {
3467
+ "score": 0.95,
3468
+ "agreement_level": "unanimous",
3469
+ "voted_models": [
3470
+ "gpt-oss:20b"
3471
+ ],
3472
+ "rationale": "No ownership validation; direct persistence."
3473
+ },
3474
+ "fingerprint": "kfp1:da6ba81e707bc9691e776f217be38c128a81b5a660f94e3a2778b25f874e1957",
3475
+ "baseline_state": "new"
3476
+ },
3477
+ {
3478
+ "finding_id": "BOLA-R-0021",
3479
+ "category": "bola",
3480
+ "subtype": "missing_ownership_check",
3481
+ "title": "BOLA in POST /account/rename: missing owner verification",
3482
+ "route_id": "R-0021",
3483
+ "endpoint": {
3484
+ "method": "POST",
3485
+ "path": "/account/rename"
3486
+ },
3487
+ "location": {
3488
+ "file": "java-legacy/src/com/acme/web/AccountController.java",
3489
+ "start_line": 19
3490
+ },
3491
+ "evidence": [
3492
+ {
3493
+ "file": "java-legacy/src/com/acme/web/AccountController.java",
3494
+ "start_line": 19,
3495
+ "code": "public String rename(@ModelAttribute RenameForm form) {\n accountDao.rename(form.getId(), form.getName());\n return \"redirect:/account\";\n}",
3496
+ "why": "The rename operation uses the supplied ID without verifying ownership.",
3497
+ "line": 19
3498
+ }
3499
+ ],
3500
+ "severity": "high",
3501
+ "confidence": "high",
3502
+ "cwe": [
3503
+ "CWE-284",
3504
+ "CWE-639"
3505
+ ],
3506
+ "recommendation": "Verify that the account belongs to the current user before renaming.",
3507
+ "status": "confirmed",
3508
+ "verification": {
3509
+ "verdict": "confirmed",
3510
+ "reason_code": "traced",
3511
+ "checked": [
3512
+ "java-legacy/src/com/acme/web/AccountController.java",
3513
+ "java-legacy/src/com/acme/web/SecurityConfig.java"
3514
+ ],
3515
+ "control_found": null,
3516
+ "notes": "The rename method calls accountDao.rename with the supplied ID and name, without checking that the ID belongs to the current user. No security annotation or manual check is present.",
3517
+ "suggested_subtype": null,
3518
+ "suggested_cwe": null
3519
+ },
3520
+ "consensus": {
3521
+ "score": 0.95,
3522
+ "agreement_level": "unanimous",
3523
+ "voted_models": [
3524
+ "gpt-oss:20b"
3525
+ ],
3526
+ "rationale": "Direct use of user‑supplied ID; no ownership guard."
3527
+ },
3528
+ "fingerprint": "kfp1:436b1118118c6a11d9a08401ceb7e845c737fc510a1527be37b102622c97cf52",
3529
+ "baseline_state": "new"
3530
+ },
3531
+ {
3532
+ "finding_id": "BOLA-R-0022",
3533
+ "category": "bola",
3534
+ "subtype": "missing_ownership_check",
3535
+ "title": "BOLA in PUT /account/api: missing owner verification",
3536
+ "route_id": "R-0022",
3537
+ "endpoint": {
3538
+ "method": "PUT",
3539
+ "path": "/account/api"
3540
+ },
3541
+ "location": {
3542
+ "file": "java-legacy/src/com/acme/web/AccountController.java",
3543
+ "start_line": 25
3544
+ },
3545
+ "evidence": [
3546
+ {
3547
+ "file": "java-legacy/src/com/acme/web/AccountController.java",
3548
+ "start_line": 25,
3549
+ "code": "public Account update(@RequestBody Account account) {\n return accountDao.save(account);\n}",
3550
+ "why": "The account is updated based solely on the request body, with no ownership check.",
3551
+ "line": 25
3552
+ }
3553
+ ],
3554
+ "severity": "high",
3555
+ "confidence": "high",
3556
+ "cwe": [
3557
+ "CWE-284",
3558
+ "CWE-639"
3559
+ ],
3560
+ "recommendation": "Bind the update to the current user or validate ownership before saving.",
3561
+ "status": "confirmed",
3562
+ "verification": {
3563
+ "verdict": "confirmed",
3564
+ "reason_code": "traced",
3565
+ "checked": [
3566
+ "java-legacy/src/com/acme/web/AccountController.java",
3567
+ "java-legacy/src/com/acme/web/SecurityConfig.java"
3568
+ ],
3569
+ "control_found": null,
3570
+ "notes": "The update method returns accountDao.save(account) with no check that the account belongs to the session user. No @PreAuthorize or manual ownership validation is present.",
3571
+ "suggested_subtype": null,
3572
+ "suggested_cwe": null
3573
+ },
3574
+ "consensus": {
3575
+ "score": 0.95,
3576
+ "agreement_level": "unanimous",
3577
+ "voted_models": [
3578
+ "gpt-oss:20b"
3579
+ ],
3580
+ "rationale": "No ownership validation; direct persistence."
3581
+ },
3582
+ "fingerprint": "kfp1:da398947b87b88d6858e1e8fbf762cd899ea7d68e9159b815743d29deef2a666",
3583
+ "baseline_state": "new"
3584
+ },
3585
+ {
3586
+ "finding_id": "BOLA-R-0023",
3587
+ "category": "bola",
3588
+ "subtype": "missing_ownership_check",
3589
+ "title": "BOLA in POST /admin/users/{id}/delete: missing ownership and admin check",
3590
+ "route_id": "R-0023",
3591
+ "endpoint": {
3592
+ "method": "POST",
3593
+ "path": "/admin/users/{id}/delete"
3594
+ },
3595
+ "location": {
3596
+ "file": "java-legacy/src/com/acme/web/AdminUserController.java",
3597
+ "start_line": 13
3598
+ },
3599
+ "evidence": [
3600
+ {
3601
+ "file": "java-legacy/src/com/acme/web/AdminUserController.java",
3602
+ "start_line": 13,
3603
+ "code": "public String delete(@PathVariable(\"id\") Long id) {\n userDao.deleteById(id);\n return \"redirect:/admin/users\";\n}",
3604
+ "why": "The endpoint deletes any user by ID without checking that the caller is an admin or that the user is not the caller.",
3605
+ "line": 13
3606
+ }
3607
+ ],
3608
+ "severity": "high",
3609
+ "confidence": "high",
3610
+ "cwe": [
3611
+ "CWE-284",
3612
+ "CWE-639"
3613
+ ],
3614
+ "recommendation": "Require admin role and verify that the target user is not the current user.",
3615
+ "status": "confirmed",
3616
+ "verification": {
3617
+ "verdict": "confirmed",
3618
+ "reason_code": "traced",
3619
+ "checked": [
3620
+ "java-legacy/src/com/acme/web/AdminUserController.java",
3621
+ "java-legacy/src/com/acme/web/SecurityConfig.java"
3622
+ ],
3623
+ "control_found": null,
3624
+ "notes": "The delete method calls userDao.deleteById(id) without any role or ownership check. The controller is mapped under /admin/users, but there is no @PreAuthorize or security constraint that verifies the caller is an admin or that the target user is not the caller. The endpoint is reachable by any authenticated user with access to the admin path.",
3625
+ "suggested_subtype": null,
3626
+ "suggested_cwe": null
3627
+ },
3628
+ "consensus": {
3629
+ "score": 0.95,
3630
+ "agreement_level": "unanimous",
3631
+ "voted_models": [
3632
+ "gpt-oss:20b"
3633
+ ],
3634
+ "rationale": "No admin or ownership guard found."
3635
+ },
3636
+ "fingerprint": "kfp1:a4f576c3ecb678956e186f0ee7498af55e42a7b8632ed464b8eada05c59007b4",
3637
+ "baseline_state": "new"
3638
+ },
3639
+ {
3640
+ "finding_id": "BOLA-R-0025",
3641
+ "category": "bola",
3642
+ "subtype": "missing_ownership_check",
3643
+ "title": "BOLA in GET /invoices/{id}: missing owner verification",
3644
+ "route_id": "R-0025",
3645
+ "endpoint": {
3646
+ "method": "GET",
3647
+ "path": "/invoices/{id}"
3648
+ },
3649
+ "location": {
3650
+ "file": "java-legacy/src/com/acme/web/InvoiceController.java",
3651
+ "start_line": 15
3652
+ },
3653
+ "evidence": [
3654
+ {
3655
+ "file": "java-legacy/src/com/acme/web/InvoiceController.java",
3656
+ "start_line": 15,
3657
+ "code": "public String show(@PathVariable(\"id\") Long id, Model model) {\n model.addAttribute(\"invoice\", invoiceDao.findById(id));\n return \"invoice/show\";\n}",
3658
+ "why": "The invoice is retrieved by ID without ensuring it belongs to the current user.",
3659
+ "line": 15
3660
+ }
3661
+ ],
3662
+ "severity": "high",
3663
+ "confidence": "high",
3664
+ "cwe": [
3665
+ "CWE-284",
3666
+ "CWE-639"
3667
+ ],
3668
+ "recommendation": "Scope the query to the current user or validate ownership after retrieval.",
3669
+ "status": "confirmed",
3670
+ "verification": {
3671
+ "verdict": "confirmed",
3672
+ "reason_code": "traced",
3673
+ "checked": [
3674
+ "java-legacy/src/com/acme/web/InvoiceController.java",
3675
+ "java-legacy/src/com/acme/web/SecurityConfig.java"
3676
+ ],
3677
+ "control_found": null,
3678
+ "notes": "The show method retrieves an invoice by ID without filtering by the current user. No @PreAuthorize or manual check is present. The endpoint is reachable by any authenticated user.",
3679
+ "suggested_subtype": null,
3680
+ "suggested_cwe": null
3681
+ },
3682
+ "consensus": {
3683
+ "score": 0.95,
3684
+ "agreement_level": "unanimous",
3685
+ "voted_models": [
3686
+ "gpt-oss:20b"
3687
+ ],
3688
+ "rationale": "Direct ID lookup; no ownership guard."
3689
+ },
3690
+ "fingerprint": "kfp1:4213dd0e852a3473b5f77a0dd8c52fbbb9776802a6d4475b6edad70be330df73",
3691
+ "baseline_state": "new"
3692
+ },
3693
+ {
3694
+ "finding_id": "BOLA-R-0027",
3695
+ "category": "bola",
3696
+ "subtype": "missing_ownership_check",
3697
+ "title": "BOLA in GET /invoices/search: missing owner verification",
3698
+ "route_id": "R-0027",
3699
+ "endpoint": {
3700
+ "method": "GET",
3701
+ "path": "/invoices/search"
3702
+ },
3703
+ "location": {
3704
+ "file": "java-legacy/src/com/acme/web/InvoiceController.java",
3705
+ "start_line": 27
3706
+ },
3707
+ "evidence": [
3708
+ {
3709
+ "file": "java-legacy/src/com/acme/web/InvoiceController.java",
3710
+ "start_line": 27,
3711
+ "code": "public String search(@RequestParam(\"customer\") String customer, Model model) {\n model.addAttribute(\"list\", session.createQuery(\"from Invoice i where i.customer = '\" + customer + \"'\").list());\n return \"invoice/list\";\n}",
3712
+ "why": "The search returns all invoices for a customer without restricting to the current user.",
3713
+ "line": 27
3714
+ }
3715
+ ],
3716
+ "severity": "high",
3717
+ "confidence": "high",
3718
+ "cwe": [
3719
+ "CWE-284",
3720
+ "CWE-639"
3721
+ ],
3722
+ "recommendation": "Add a filter for the current user or validate each returned invoice.",
3723
+ "status": "confirmed",
3724
+ "verification": {
3725
+ "verdict": "confirmed",
3726
+ "reason_code": "traced",
3727
+ "checked": [
3728
+ "java-legacy/src/com/acme/web/InvoiceController.java",
3729
+ "java-legacy/src/com/acme/web/SecurityConfig.java"
3730
+ ],
3731
+ "control_found": null,
3732
+ "notes": "The search method builds a query using the customer parameter without scoping to the current user. No @PreAuthorize or manual ownership check is present. The endpoint is reachable by any authenticated user.",
3733
+ "suggested_subtype": null,
3734
+ "suggested_cwe": null
3735
+ },
3736
+ "consensus": {
3737
+ "score": 0.95,
3738
+ "agreement_level": "unanimous",
3739
+ "voted_models": [
3740
+ "gpt-oss:20b"
3741
+ ],
3742
+ "rationale": "Unscoped query; no ownership guard."
3743
+ },
3744
+ "fingerprint": "kfp1:b576c39e6469454d66f748f828a753ff3f9f250c5538a9c88b4a57760334d368",
3745
+ "baseline_state": "new"
3746
+ },
3747
+ {
3748
+ "finding_id": "BOLA-R-0028",
3749
+ "category": "bola",
3750
+ "subtype": "missing_ownership_check",
3751
+ "title": "BOLA in GET /order/view: missing owner verification",
3752
+ "route_id": "R-0028",
3753
+ "endpoint": {
3754
+ "method": "GET",
3755
+ "path": "/order/view"
3756
+ },
3757
+ "location": {
3758
+ "file": "java-legacy/src/com/acme/web/OrderServlet.java",
3759
+ "start_line": 8
3760
+ },
3761
+ "evidence": [
3762
+ {
3763
+ "file": "java-legacy/src/com/acme/web/OrderServlet.java",
3764
+ "start_line": 8,
3765
+ "code": "protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {\n HttpSession session = request.getSession();\n Object loginUser = session.getAttribute(\"loginUser\");\n String id = request.getParameter(\"orderId\");\n Connection conn = DataSourceHolder.get().getConnection();\n Statement stmt = conn.createStatement();\n ResultSet rs = stmt.executeQuery(\"SELECT * FROM orders WHERE id = '\" + id + \"'\");\n // ...",
3766
+ "why": "The order is fetched by ID without ensuring it belongs to the current user.",
3767
+ "line": 8
3768
+ }
3769
+ ],
3770
+ "severity": "high",
3771
+ "confidence": "high",
3772
+ "cwe": [
3773
+ "CWE-284",
3774
+ "CWE-639"
3775
+ ],
3776
+ "recommendation": "Add an ownership check to the query or validate after retrieval.",
3777
+ "status": "confirmed",
3778
+ "verification": {
3779
+ "verdict": "confirmed",
3780
+ "reason_code": "traced",
3781
+ "checked": [
3782
+ "java-legacy/src/com/acme/web/OrderServlet.java",
3783
+ "java-legacy/src/com/acme/web/SecurityConfig.java"
3784
+ ],
3785
+ "control_found": null,
3786
+ "notes": "The servlet reads the orderId from the request and executes a raw SQL query without filtering by the session user. No ownership check or role guard is present. The endpoint is reachable by any authenticated user.",
3787
+ "suggested_subtype": null,
3788
+ "suggested_cwe": null
3789
+ },
3790
+ "consensus": {
3791
+ "score": 0.95,
3792
+ "agreement_level": "unanimous",
3793
+ "voted_models": [
3794
+ "gpt-oss:20b"
3795
+ ],
3796
+ "rationale": "Direct ID lookup; no ownership guard."
3797
+ },
3798
+ "fingerprint": "kfp1:099a9a98e36374b37df8f56b6de8534fbd7feb621c3632bac20d1272028a36c7",
3799
+ "baseline_state": "new"
3800
+ },
3801
+ {
3802
+ "finding_id": "BOLA-R-0029",
3803
+ "category": "bola",
3804
+ "subtype": "missing_ownership_check",
3805
+ "title": "BOLA in POST /ProfileServlet: missing owner verification",
3806
+ "route_id": "R-0029",
3807
+ "endpoint": {
3808
+ "method": "POST",
3809
+ "path": "<see web.xml servlet-mapping>"
3810
+ },
3811
+ "location": {
3812
+ "file": "java-legacy/src/com/acme/web/ProfileServlet.java",
3813
+ "start_line": 9
3814
+ },
3815
+ "evidence": [
3816
+ {
3817
+ "file": "java-legacy/src/com/acme/web/ProfileServlet.java",
3818
+ "start_line": 10,
3819
+ "code": "Account account = new Account();",
3820
+ "why": "Creates a new account object",
3821
+ "line": 10
3822
+ },
3823
+ {
3824
+ "file": "java-legacy/src/com/acme/web/ProfileServlet.java",
3825
+ "start_line": 12,
3826
+ "code": "BeanUtils.populate(account, request.getParameterMap());",
3827
+ "why": "Populates the account with any parameters supplied by the attacker",
3828
+ "line": 12
3829
+ }
3830
+ ],
3831
+ "severity": "high",
3832
+ "confidence": "low",
3833
+ "cwe": [
3834
+ "CWE-284",
3835
+ "CWE-639"
3836
+ ],
3837
+ "recommendation": "Bind the populated account to the current user or validate ownership",
3838
+ "status": "likely",
3839
+ "verification": {
3840
+ "verdict": "likely",
3841
+ "reason_code": "not_verified",
3842
+ "checked": [
3843
+ "java-legacy/src/com/acme/web/ProfileServlet.java",
3844
+ "java-legacy/src/com/acme/web/ServletBase.java",
3845
+ "java-legacy/src/com/acme/web/filters/AuthorizationFilter.java",
3846
+ "java-legacy/src/com/acme/web/GlobalConfig.xml"
3847
+ ],
3848
+ "control_found": null,
3849
+ "notes": "ProfileServlet populates an Account object from request parameters without any ownership check or role guard. No global filter or base servlet logic was found that would enforce ownership.",
3850
+ "suggested_subtype": null,
3851
+ "suggested_cwe": null
3852
+ },
3853
+ "consensus": {
3854
+ "score": 0.55,
3855
+ "agreement_level": "disputed",
3856
+ "voted_models": [
3857
+ "gpt-oss:20b"
3858
+ ],
3859
+ "rationale": "No evidence of a compensating control; the absence of evidence does not confirm the vulnerability."
3860
+ },
3861
+ "fingerprint": "kfp1:f8c53a1c11f4e356cd58bf22809c720589dfa46c38243f129f05d96341d8a333",
3862
+ "baseline_state": "new"
3863
+ },
3864
+ {
3865
+ "finding_id": "BOLA-R-0030",
3866
+ "category": "bola",
3867
+ "subtype": "missing_ownership_check",
3868
+ "title": "BOLA in POST /admin/users/:id/delete: missing admin check",
3869
+ "route_id": "R-0030",
3870
+ "endpoint": {
3871
+ "method": "POST",
3872
+ "path": "/admin/users/:id/delete"
3873
+ },
3874
+ "location": {
3875
+ "file": "node-express/routes/admin.js",
3876
+ "start_line": 8
3877
+ },
3878
+ "evidence": [
3879
+ {
3880
+ "file": "node-express/routes/admin.js",
3881
+ "start_line": 8,
3882
+ "code": "router.post('/admin/users/:id/delete', ensureLoggedIn, function (req, res) {\n User.deleteOne({ _id: req.params.id }, function () { res.sendStatus(204); });\n});",
3883
+ "why": "The route deletes any user by ID without checking that the caller is an admin.",
3884
+ "line": 8
3885
+ }
3886
+ ],
3887
+ "severity": "high",
3888
+ "confidence": "high",
3889
+ "cwe": [
3890
+ "CWE-284",
3891
+ "CWE-639"
3892
+ ],
3893
+ "recommendation": "Require admin role before allowing deletion.",
3894
+ "status": "confirmed",
3895
+ "verification": {
3896
+ "verdict": "confirmed",
3897
+ "reason_code": "traced",
3898
+ "checked": [
3899
+ "node-express/routes/admin.js",
3900
+ "node-express/middleware/ensureLoggedIn.js",
3901
+ "node-express/app.js"
3902
+ ],
3903
+ "control_found": null,
3904
+ "notes": "The route uses only ensureLoggedIn, which only verifies authentication. No admin role check or ownership filter is applied before the delete operation. No integer casting or allow‑list is present.",
3905
+ "suggested_subtype": null,
3906
+ "suggested_cwe": null
3907
+ },
3908
+ "consensus": {
3909
+ "score": 0.95,
3910
+ "agreement_level": "unanimous",
3911
+ "voted_models": [
3912
+ "gpt-oss:20b"
3913
+ ],
3914
+ "rationale": "No admin guard found; the delete is performed directly with the supplied id."
3915
+ },
3916
+ "fingerprint": "kfp1:3dd2e6ecf2f0730978e0c2a05f31e2af3920a107cd2af0b5ea81c9ded7f6a100",
3917
+ "baseline_state": "new"
3918
+ },
3919
+ {
3920
+ "finding_id": "BOLA-R-0033",
3921
+ "category": "bola",
3922
+ "subtype": "missing_ownership_check",
3923
+ "title": "BOLA in GET /orders/:id: missing owner verification",
3924
+ "route_id": "R-0033",
3925
+ "endpoint": {
3926
+ "method": "GET",
3927
+ "path": "/orders/:id"
3928
+ },
3929
+ "location": {
3930
+ "file": "node-express/routes/orders.js",
3931
+ "start_line": 8
3932
+ },
3933
+ "evidence": [
3934
+ {
3935
+ "file": "node-express/routes/orders.js",
3936
+ "start_line": 8,
3937
+ "code": "Order.findById(req.params.id, function (err, order) { res.json(order); });",
3938
+ "why": "ID comes from the request and the query does not filter by the authenticated user.",
3939
+ "line": 8
3940
+ }
3941
+ ],
3942
+ "severity": "high",
3943
+ "confidence": "high",
3944
+ "cwe": [
3945
+ "CWE-284",
3946
+ "CWE-639"
3947
+ ],
3948
+ "recommendation": "Bind the query to the current session user.",
3949
+ "status": "confirmed",
3950
+ "verification": {
3951
+ "verdict": "confirmed",
3952
+ "reason_code": "traced",
3953
+ "checked": [
3954
+ "node-express/routes/orders.js",
3955
+ "node-express/models/Order.js"
3956
+ ],
3957
+ "control_found": null,
3958
+ "notes": "Order.findById is called with the raw request id. The Order model does not automatically scope by user, and no middleware or allow‑list restricts access. The query returns any order by id.",
3959
+ "suggested_subtype": null,
3960
+ "suggested_cwe": null
3961
+ },
3962
+ "consensus": {
3963
+ "score": 0.95,
3964
+ "agreement_level": "unanimous",
3965
+ "voted_models": [
3966
+ "gpt-oss:20b"
3967
+ ],
3968
+ "rationale": "No ownership check or user filter is present."
3969
+ },
3970
+ "fingerprint": "kfp1:3efb41972fd6c818041a922563d6a1e65eb9cdcd63b764b52ff4ffce238a6c8d",
3971
+ "baseline_state": "new"
3972
+ },
3973
+ {
3974
+ "finding_id": "BOLA-R-0035",
3975
+ "category": "bola",
3976
+ "subtype": "missing_ownership_check",
3977
+ "title": "BOLA in GET /reports: missing owner verification",
3978
+ "route_id": "R-0035",
3979
+ "endpoint": {
3980
+ "method": "GET",
3981
+ "path": "/reports"
3982
+ },
3983
+ "location": {
3984
+ "file": "node-express/routes/orders.js",
3985
+ "start_line": 16
3986
+ },
3987
+ "evidence": [
3988
+ {
3989
+ "file": "node-express/routes/orders.js",
3990
+ "start_line": 16,
3991
+ "code": "db.query(\"SELECT * FROM reports WHERE month = '\" + req.query.month + \"'\", function (e, rows) { res.json(rows); });",
3992
+ "why": "The query returns all reports for the given month without scoping to the authenticated user.",
3993
+ "line": 16
3994
+ }
3995
+ ],
3996
+ "severity": "high",
3997
+ "confidence": "high",
3998
+ "cwe": [
3999
+ "CWE-284",
4000
+ "CWE-639"
4001
+ ],
4002
+ "recommendation": "Add a user filter to the reports query.",
4003
+ "status": "confirmed",
4004
+ "verification": {
4005
+ "verdict": "confirmed",
4006
+ "reason_code": "traced",
4007
+ "checked": [
4008
+ "node-express/routes/orders.js",
4009
+ "node-express/models/Report.js"
4010
+ ],
4011
+ "control_found": null,
4012
+ "notes": "The SQL string concatenates the month directly from req.query without any user filter or allow‑list. The query can return all reports for the month.",
4013
+ "suggested_subtype": null,
4014
+ "suggested_cwe": null
4015
+ },
4016
+ "consensus": {
4017
+ "score": 0.95,
4018
+ "agreement_level": "unanimous",
4019
+ "voted_models": [
4020
+ "gpt-oss:20b"
4021
+ ],
4022
+ "rationale": "No scoping to the authenticated user."
4023
+ },
4024
+ "fingerprint": "kfp1:9be9be3e74c3014c61d218020cd85c64557f3e1c49d462ce43942dd410bfa0a6",
4025
+ "baseline_state": "new"
4026
+ },
4027
+ {
4028
+ "finding_id": "BOLA-R-0037",
4029
+ "category": "bola",
4030
+ "subtype": "missing_ownership_check",
4031
+ "title": "BOLA in GET /search: missing owner verification",
4032
+ "route_id": "R-0037",
4033
+ "endpoint": {
4034
+ "method": "GET",
4035
+ "path": "/search"
4036
+ },
4037
+ "location": {
4038
+ "file": "node-express/routes/orders.js",
4039
+ "start_line": 24
4040
+ },
4041
+ "evidence": [
4042
+ {
4043
+ "file": "node-express/routes/orders.js",
4044
+ "start_line": 24,
4045
+ "code": "db.collection('items').find({ $where: \"this.name == '\" + req.query.n + \"'\" }).toArray(function (e, r) { res.json(r); });",
4046
+ "why": "The query returns items matching the name without scoping to the authenticated user.",
4047
+ "line": 24
4048
+ }
4049
+ ],
4050
+ "severity": "high",
4051
+ "confidence": "high",
4052
+ "cwe": [
4053
+ "CWE-284",
4054
+ "CWE-639"
4055
+ ],
4056
+ "recommendation": "Add a user filter to the items query.",
4057
+ "status": "confirmed",
4058
+ "verification": {
4059
+ "verdict": "confirmed",
4060
+ "reason_code": "traced",
4061
+ "checked": [
4062
+ "node-express/routes/orders.js",
4063
+ "node-express/models/Item.js"
4064
+ ],
4065
+ "control_found": null,
4066
+ "notes": "The $where clause is built from req.query.n without any user context or allow‑list. Any item matching the name can be returned.",
4067
+ "suggested_subtype": null,
4068
+ "suggested_cwe": null
4069
+ },
4070
+ "consensus": {
4071
+ "score": 0.95,
4072
+ "agreement_level": "unanimous",
4073
+ "voted_models": [
4074
+ "gpt-oss:20b"
4075
+ ],
4076
+ "rationale": "No ownership guard or sanitization."
4077
+ },
4078
+ "fingerprint": "kfp1:89a1069104f2e244e26853b7bc117cd96e8315252b38628037302493f8147b9a",
4079
+ "baseline_state": "new"
4080
+ },
4081
+ {
4082
+ "finding_id": "BOLA-R-0038",
4083
+ "category": "bola",
4084
+ "subtype": "missing_ownership_check",
4085
+ "title": "BOLA in GET /stats: missing owner verification",
4086
+ "route_id": "R-0038",
4087
+ "endpoint": {
4088
+ "method": "GET",
4089
+ "path": "/stats"
4090
+ },
4091
+ "location": {
4092
+ "file": "node-express/routes/orders.js",
4093
+ "start_line": 28
4094
+ },
4095
+ "evidence": [
4096
+ {
4097
+ "file": "node-express/routes/orders.js",
4098
+ "start_line": 28,
4099
+ "code": "db.query(`SELECT COUNT(*) AS n FROM orders WHERE shop_id = ${req.query.shop}`)",
4100
+ "why": "The query counts orders for a shop without ensuring the shop belongs to the authenticated user.",
4101
+ "line": 28
4102
+ }
4103
+ ],
4104
+ "severity": "high",
4105
+ "confidence": "high",
4106
+ "cwe": [
4107
+ "CWE-284",
4108
+ "CWE-639"
4109
+ ],
4110
+ "recommendation": "Add a user filter to the stats query.",
4111
+ "status": "confirmed",
4112
+ "verification": {
4113
+ "verdict": "confirmed",
4114
+ "reason_code": "traced",
4115
+ "checked": [
4116
+ "node-express/routes/orders.js",
4117
+ "node-express/models/Stats.js"
4118
+ ],
4119
+ "control_found": null,
4120
+ "notes": "The shop_id is taken directly from req.query.shop and interpolated into the SQL. No check that the shop belongs to the authenticated user.",
4121
+ "suggested_subtype": null,
4122
+ "suggested_cwe": null
4123
+ },
4124
+ "consensus": {
4125
+ "score": 0.95,
4126
+ "agreement_level": "unanimous",
4127
+ "voted_models": [
4128
+ "gpt-oss:20b"
4129
+ ],
4130
+ "rationale": "No user‑scoped filter."
4131
+ },
4132
+ "fingerprint": "kfp1:e0d4076276caf1e9557699dcd9494f106ac443c30bfdb55c71fb7aaedda29b14",
4133
+ "baseline_state": "new"
4134
+ },
4135
+ {
4136
+ "finding_id": "BOLA-R-0043",
4137
+ "category": "bola",
4138
+ "subtype": "missing_ownership_check",
4139
+ "title": "BOLA in Perl CGI order.cgi: missing owner verification",
4140
+ "route_id": "R-0043",
4141
+ "endpoint": {
4142
+ "method": "ANY",
4143
+ "path": "/perl-cgi/cgi-bin/order.cgi"
4144
+ },
4145
+ "location": {
4146
+ "file": "perl-cgi/cgi-bin/order.cgi",
4147
+ "start_line": 13
4148
+ },
4149
+ "evidence": [
4150
+ {
4151
+ "file": "perl-cgi/cgi-bin/order.cgi",
4152
+ "start_line": 13,
4153
+ "code": "$sth = $dbh->prepare(\"SELECT * FROM orders WHERE id = $id\");",
4154
+ "why": "The query returns an order by id without filtering by the authenticated user.",
4155
+ "line": 13
4156
+ }
4157
+ ],
4158
+ "severity": "high",
4159
+ "confidence": "high",
4160
+ "cwe": [
4161
+ "CWE-284",
4162
+ "CWE-639"
4163
+ ],
4164
+ "recommendation": "Add a user filter to the order query.",
4165
+ "status": "confirmed",
4166
+ "verification": {
4167
+ "verdict": "confirmed",
4168
+ "reason_code": "traced",
4169
+ "checked": [
4170
+ "perl-cgi/cgi-bin/order.cgi"
4171
+ ],
4172
+ "control_found": null,
4173
+ "notes": "The query selects by id from the CGI parameter without any session or ownership check. No integer casting or allow‑list is present.",
4174
+ "suggested_subtype": null,
4175
+ "suggested_cwe": null
4176
+ },
4177
+ "consensus": {
4178
+ "score": 0.95,
4179
+ "agreement_level": "unanimous",
4180
+ "voted_models": [
4181
+ "gpt-oss:20b"
4182
+ ],
4183
+ "rationale": "Direct use of id from request."
4184
+ },
4185
+ "fingerprint": "kfp1:0823c809d4892eb324f1b12ff01c568fed5e7b10e716e2a0064b9ed53c6f0380",
4186
+ "baseline_state": "new"
4187
+ },
4188
+ {
4189
+ "finding_id": "BOLA-R-0044",
4190
+ "category": "bola",
4191
+ "subtype": "missing_ownership_check",
4192
+ "title": "BOLA in Invoice view: missing owner verification",
4193
+ "route_id": "R-0044",
4194
+ "endpoint": {
4195
+ "method": "ANY",
4196
+ "path": "/invoice/view/{id}"
4197
+ },
4198
+ "location": {
4199
+ "file": "php-codeigniter/application/controllers/Invoice.php",
4200
+ "start_line": 10
4201
+ },
4202
+ "evidence": [
4203
+ {
4204
+ "file": "php-codeigniter/application/controllers/Invoice.php",
4205
+ "start_line": 10,
4206
+ "code": "$q = $this->db->query(\"SELECT * FROM invoices WHERE id = $id\");",
4207
+ "why": "The query returns an invoice by id without filtering by the authenticated user.",
4208
+ "line": 10
4209
+ }
4210
+ ],
4211
+ "severity": "high",
4212
+ "confidence": "high",
4213
+ "cwe": [
4214
+ "CWE-284",
4215
+ "CWE-639"
4216
+ ],
4217
+ "recommendation": "Add a user filter to the invoice query.",
4218
+ "status": "confirmed",
4219
+ "verification": {
4220
+ "verdict": "confirmed",
4221
+ "reason_code": "traced",
4222
+ "checked": [
4223
+ "php-codeigniter/application/controllers/Invoice.php"
4224
+ ],
4225
+ "control_found": null,
4226
+ "notes": "The SELECT statement uses the id from the request without filtering by the logged‑in user. No ownership guard or allow‑list is applied.",
4227
+ "suggested_subtype": null,
4228
+ "suggested_cwe": null
4229
+ },
4230
+ "consensus": {
4231
+ "score": 0.95,
4232
+ "agreement_level": "unanimous",
4233
+ "voted_models": [
4234
+ "gpt-oss:20b"
4235
+ ],
4236
+ "rationale": "No user scoping."
4237
+ },
4238
+ "fingerprint": "kfp1:9e7c8370d77a2f7c2d568a97ad128c427ef21f3b9775cfaa95eb72b2c5da6115",
4239
+ "baseline_state": "new"
4240
+ },
4241
+ {
4242
+ "finding_id": "BOLA-R-0046",
4243
+ "category": "bola",
4244
+ "subtype": "missing_ownership_check",
4245
+ "title": "BOLA in Invoice update: missing owner verification",
4246
+ "route_id": "R-0046",
4247
+ "endpoint": {
4248
+ "method": "ANY",
4249
+ "path": "/invoice/update"
4250
+ },
4251
+ "location": {
4252
+ "file": "php-codeigniter/application/controllers/Invoice.php",
4253
+ "start_line": 23
4254
+ },
4255
+ "evidence": [
4256
+ {
4257
+ "file": "php-codeigniter/application/controllers/Invoice.php",
4258
+ "start_line": 23,
4259
+ "code": "$this->db->query(\"UPDATE invoices SET note = ? WHERE id = ?\", array($note, $id));",
4260
+ "why": "The update statement modifies an invoice by id without ensuring the invoice belongs to the authenticated user.",
4261
+ "line": 23
4262
+ }
4263
+ ],
4264
+ "severity": "high",
4265
+ "confidence": "high",
4266
+ "cwe": [
4267
+ "CWE-284",
4268
+ "CWE-639"
4269
+ ],
4270
+ "recommendation": "Add a user filter to the update statement.",
4271
+ "status": "confirmed",
4272
+ "verification": {
4273
+ "verdict": "confirmed",
4274
+ "reason_code": "traced",
4275
+ "checked": [
4276
+ "php-codeigniter/application/controllers/Invoice.php"
4277
+ ],
4278
+ "control_found": null,
4279
+ "notes": "The UPDATE statement modifies an invoice by id with no check that the invoice belongs to the current user. No ownership guard or allow‑list is present.",
4280
+ "suggested_subtype": null,
4281
+ "suggested_cwe": null
4282
+ },
4283
+ "consensus": {
4284
+ "score": 0.95,
4285
+ "agreement_level": "unanimous",
4286
+ "voted_models": [
4287
+ "gpt-oss:20b"
4288
+ ],
4289
+ "rationale": "Direct use of id."
4290
+ },
4291
+ "fingerprint": "kfp1:4f200be5155b2854ef32764906134098549058ea95b61c478a18aa59988ef8cb",
4292
+ "baseline_state": "new"
4293
+ },
4294
+ {
4295
+ "finding_id": "BOLA-R-0060",
4296
+ "category": "bola",
4297
+ "subtype": "missing_ownership_check",
4298
+ "title": "BOLA in GET /php-plain/mongo_find.php: missing owner verification",
4299
+ "route_id": "R-0060",
4300
+ "endpoint": {
4301
+ "method": "GET",
4302
+ "path": "/php-plain/mongo_find.php"
4303
+ },
4304
+ "location": {
4305
+ "file": "php-plain/mongo_find.php",
4306
+ "start_line": 1
4307
+ },
4308
+ "evidence": [
4309
+ {
4310
+ "file": "php-plain/mongo_find.php",
4311
+ "start_line": 5,
4312
+ "code": "$doc = $coll->findOne(array('login' => $_GET['login'], 'pass' => $_GET['pass']));",
4313
+ "why": "Allows lookup of any member by login/pass supplied by attacker",
4314
+ "line": 5
4315
+ },
4316
+ {
4317
+ "file": "php-plain/mongo_find.php",
4318
+ "start_line": 6,
4319
+ "code": "$docs = $coll->find(array('$where' => \"this.name == '\" . $_GET['n'] . \"'\"));",
4320
+ "why": "Allows lookup of any member by name supplied by attacker",
4321
+ "line": 6
4322
+ }
4323
+ ],
4324
+ "severity": "high",
4325
+ "confidence": "low",
4326
+ "cwe": [
4327
+ "CWE-284",
4328
+ "CWE-639"
4329
+ ],
4330
+ "recommendation": "Scope queries to the current user or validate ownership",
4331
+ "status": "likely",
4332
+ "verification": {
4333
+ "verdict": "likely",
4334
+ "reason_code": "not_verified",
4335
+ "checked": [
4336
+ "php-plain/mongo_find.php",
4337
+ "php-plain/config.php",
4338
+ "php-plain/filters.php",
4339
+ "php-plain/helpers.php"
4340
+ ],
4341
+ "control_found": null,
4342
+ "notes": "mongo_find.php performs unfiltered queries based on user-supplied GET parameters. No server-side filtering or ownership check is present, and no global filter or helper function is applied to enforce scoping.",
4343
+ "suggested_subtype": null,
4344
+ "suggested_cwe": null
4345
+ },
4346
+ "consensus": {
4347
+ "score": 0.55,
4348
+ "agreement_level": "disputed",
4349
+ "voted_models": [
4350
+ "gpt-oss:20b"
4351
+ ],
4352
+ "rationale": "No compensating control detected; absence of evidence is not proof of safety."
4353
+ },
4354
+ "fingerprint": "kfp1:62e5b04f2bdb1026b6c3751806a6ab2b51efcab5004f5ee616af5066e9cb309b",
4355
+ "baseline_state": "new"
4356
+ },
4357
+ {
4358
+ "finding_id": "BOLA-R-0073",
4359
+ "category": "bola",
4360
+ "subtype": "missing_ownership_check",
4361
+ "title": "BOLA in POST /users/<uid>/name: missing owner verification",
4362
+ "route_id": "R-0073",
4363
+ "endpoint": {
4364
+ "method": "POST",
4365
+ "path": "/users/<int:uid>/name"
4366
+ },
4367
+ "location": {
4368
+ "file": "python-flask/profile.py",
4369
+ "start_line": 16
4370
+ },
4371
+ "evidence": [
4372
+ {
4373
+ "file": "python-flask/profile.py",
4374
+ "start_line": 18,
4375
+ "code": "user = User.query.get(uid)",
4376
+ "why": "Loads user by supplied uid without checking current session user",
4377
+ "line": 18
4378
+ },
4379
+ {
4380
+ "file": "python-flask/profile.py",
4381
+ "start_line": 19,
4382
+ "code": "user.name = request.json[\"name\"]",
4383
+ "why": "Updates name of any user identified by uid",
4384
+ "line": 19
4385
+ }
4386
+ ],
4387
+ "severity": "high",
4388
+ "confidence": "high",
4389
+ "cwe": [
4390
+ "CWE-284",
4391
+ "CWE-639"
4392
+ ],
4393
+ "recommendation": "Bind the update to the current session user",
4394
+ "status": "confirmed",
4395
+ "verification": {
4396
+ "verdict": "confirmed",
4397
+ "reason_code": "traced",
4398
+ "checked": [
4399
+ "python-flask/profile.py"
4400
+ ],
4401
+ "control_found": null,
4402
+ "notes": "The user is loaded by uid from the request without verifying that uid matches the session user. No ownership check or integer casting is performed.",
4403
+ "suggested_subtype": null,
4404
+ "suggested_cwe": null
4405
+ },
4406
+ "consensus": {
4407
+ "score": 0.95,
4408
+ "agreement_level": "unanimous",
4409
+ "voted_models": [
4410
+ "gpt-oss:20b"
4411
+ ],
4412
+ "rationale": "No ownership guard."
4413
+ },
4414
+ "fingerprint": "kfp1:bfc179df51db588076982a9b160cccb6aa5f7c21b296231a88eb6cbdfe2c3842",
4415
+ "baseline_state": "new"
4416
+ },
4417
+ {
4418
+ "finding_id": "BOLA-R-0074",
4419
+ "category": "bola",
4420
+ "subtype": "missing_ownership_check",
4421
+ "title": "BOLA in PATCH /users/<uid>: missing owner verification",
4422
+ "route_id": "R-0074",
4423
+ "endpoint": {
4424
+ "method": "PATCH",
4425
+ "path": "/users/<int:uid>"
4426
+ },
4427
+ "location": {
4428
+ "file": "python-flask/profile.py",
4429
+ "start_line": 24
4430
+ },
4431
+ "evidence": [
4432
+ {
4433
+ "file": "python-flask/profile.py",
4434
+ "start_line": 26,
4435
+ "code": "user = User.query.get(uid)",
4436
+ "why": "Loads user by supplied uid without checking current session user",
4437
+ "line": 26
4438
+ },
4439
+ {
4440
+ "file": "python-flask/profile.py",
4441
+ "start_line": 27,
4442
+ "code": "for key, value in request.json.items():\n setattr(user, key, value)",
4443
+ "why": "Allows arbitrary field updates on any user identified by uid",
4444
+ "line": 27
4445
+ }
4446
+ ],
4447
+ "severity": "high",
4448
+ "confidence": "high",
4449
+ "cwe": [
4450
+ "CWE-284",
4451
+ "CWE-639"
4452
+ ],
4453
+ "recommendation": "Enforce ownership before applying updates",
4454
+ "status": "confirmed",
4455
+ "verification": {
4456
+ "verdict": "confirmed",
4457
+ "reason_code": "traced",
4458
+ "checked": [
4459
+ "python-flask/profile.py"
4460
+ ],
4461
+ "control_found": null,
4462
+ "notes": "The user is loaded by uid from the request and arbitrary fields are set without checking that uid matches the session user. No ownership guard is present.",
4463
+ "suggested_subtype": null,
4464
+ "suggested_cwe": null
4465
+ },
4466
+ "consensus": {
4467
+ "score": 0.95,
4468
+ "agreement_level": "unanimous",
4469
+ "voted_models": [
4470
+ "gpt-oss:20b"
4471
+ ],
4472
+ "rationale": "No ownership guard."
4473
+ },
4474
+ "fingerprint": "kfp1:53120a82329f249cb5d921adbb2d6965297c52b37440e5f47d8a2525bbe46fde",
4475
+ "baseline_state": "new"
4476
+ },
4477
+ {
4478
+ "finding_id": "MASS-M-0001-1",
4479
+ "category": "mass_assignment",
4480
+ "subtype": "whole_object_binding",
4481
+ "title": "Unrestricted binding of Customer model in C# controller",
4482
+ "candidate_id": "M-0001",
4483
+ "location": {
4484
+ "file": "Web.Controllers.CustomerController.cs",
4485
+ "start_line": 12
4486
+ },
4487
+ "evidence": [
4488
+ {
4489
+ "file": "Web.Controllers.CustomerController.cs",
4490
+ "line": 12,
4491
+ "why": "Edit action accepts Customer object without allowlist"
4492
+ }
4493
+ ],
4494
+ "impact": "Potentially writable privileged fields if Customer contains them",
4495
+ "severity": "high",
4496
+ "confidence": "high",
4497
+ "cwe": [
4498
+ "CWE-915"
4499
+ ],
4500
+ "recommendation": "Use a view model or [Bind(Include=...)] to restrict bound properties",
4501
+ "status": "confirmed",
4502
+ "verification": {
4503
+ "verdict": "confirmed",
4504
+ "reason_code": "traced",
4505
+ "checked": [
4506
+ "Web.Controllers.CustomerController.cs – examined Edit POST action",
4507
+ "Web.Controllers.CustomerController.cs – confirmed no [Bind] attribute or view model used",
4508
+ "Global filters – no custom model binder restricting properties found",
4509
+ "ASP.NET MVC configuration – no global binding restrictions set"
4510
+ ],
4511
+ "control_found": null,
4512
+ "notes": "The Edit action accepts a Customer object directly from the request without an allow‑list, exposing any writable property to the attacker.",
4513
+ "suggested_subtype": null,
4514
+ "suggested_cwe": null
4515
+ },
4516
+ "consensus": {
4517
+ "score": 0.95,
4518
+ "agreement_level": "unanimous",
4519
+ "voted_models": [
4520
+ "gpt-oss:20b"
4521
+ ],
4522
+ "rationale": "No compensating control detected."
4523
+ },
4524
+ "fingerprint": "kfp1:cdde712f81e275289517ff0aa14f88d1de6b979f932c47c5acdfbd28bd1d608a",
4525
+ "baseline_state": "new"
4526
+ },
4527
+ {
4528
+ "finding_id": "MASS-M-0002-1",
4529
+ "category": "mass_assignment",
4530
+ "subtype": "whole_object_binding",
4531
+ "title": "TryUpdateModel usage in Customer controller may bind privileged fields",
4532
+ "candidate_id": "M-0002",
4533
+ "location": {
4534
+ "file": "Web.Controllers.CustomerController.cs",
4535
+ "start_line": 27
4536
+ },
4537
+ "evidence": [
4538
+ {
4539
+ "file": "Web.Controllers.CustomerController.cs",
4540
+ "line": 27,
4541
+ "why": "TryUpdateModel(customer) binds all request fields"
4542
+ }
4543
+ ],
4544
+ "impact": "Potentially writable privileged fields if Customer contains them",
4545
+ "severity": "high",
4546
+ "confidence": "medium",
4547
+ "cwe": [
4548
+ "CWE-915"
4549
+ ],
4550
+ "recommendation": "Specify bind include or use a view model",
4551
+ "status": "needs_review",
4552
+ "fingerprint": "kfp1:f09b3b5b8e12b3b11918cf4af38c6c36169267885c7b40e9032db79048e3161e",
4553
+ "baseline_state": "new"
4554
+ },
4555
+ {
4556
+ "finding_id": "MASS-M-0003-1",
4557
+ "category": "mass_assignment",
4558
+ "subtype": "whole_object_binding",
4559
+ "title": "Unrestricted binding of Account model in Java controller",
4560
+ "candidate_id": "M-0003",
4561
+ "location": {
4562
+ "file": "com.acme.web.AccountController.java",
4563
+ "start_line": 14
4564
+ },
4565
+ "evidence": [
4566
+ {
4567
+ "file": "com.acme.web.AccountController.java",
4568
+ "line": 14,
4569
+ "why": "save(@ModelAttribute Account account) binds all fields"
4570
+ }
4571
+ ],
4572
+ "impact": "Potentially writable privileged fields if Account contains them",
4573
+ "severity": "high",
4574
+ "confidence": "medium",
4575
+ "cwe": [
4576
+ "CWE-915"
4577
+ ],
4578
+ "recommendation": "Use a DTO or @InitBinder to restrict fields",
4579
+ "status": "needs_review",
4580
+ "fingerprint": "kfp1:6211f5ee56bc795fa9aca6d4b901ca5cbf8c103886ae81ad58e2718814cf5ea4",
4581
+ "baseline_state": "new"
4582
+ },
4583
+ {
4584
+ "finding_id": "MASS-M-0005-1",
4585
+ "category": "mass_assignment",
4586
+ "subtype": "whole_object_binding",
4587
+ "title": "BeanUtils.populate binds all request parameters to Account in Java servlet",
4588
+ "candidate_id": "M-0005",
4589
+ "location": {
4590
+ "file": "com.acme.web.ProfileServlet.java",
4591
+ "start_line": 12
4592
+ },
4593
+ "evidence": [
4594
+ {
4595
+ "file": "com.acme.web.ProfileServlet.java",
4596
+ "line": 12,
4597
+ "why": "BeanUtils.populate(account, request.getParameterMap()) binds all fields"
4598
+ }
4599
+ ],
4600
+ "impact": "Potentially writable privileged fields if Account contains them",
4601
+ "severity": "high",
4602
+ "confidence": "medium",
4603
+ "cwe": [
4604
+ "CWE-915"
4605
+ ],
4606
+ "recommendation": "Whitelist allowed properties or use a DTO",
4607
+ "status": "needs_review",
4608
+ "fingerprint": "kfp1:280fbe9fc6354a3b39f8b80322c3d64e8f8f4b3c7602365f28d40520fc4d27d1",
4609
+ "baseline_state": "new"
4610
+ },
4611
+ {
4612
+ "finding_id": "INJ-I-0001",
4613
+ "category": "sql_injection",
4614
+ "subtype": "string_concatenation",
4615
+ "title": "SQL Injection in classic-asp/shop/member.asp:6",
4616
+ "candidate_id": "I-0001",
4617
+ "location": {
4618
+ "file": "classic-asp/shop/member.asp",
4619
+ "start_line": 6
4620
+ },
4621
+ "evidence": [
4622
+ {
4623
+ "file": "classic-asp/shop/member.asp",
4624
+ "start_line": 6,
4625
+ "code": "sql = \"UPDATE members SET name = '\" & Replace(Request.Form(\"name\"), \"'\", \"''\") & \"'\"",
4626
+ "why": "Unsanitized input concatenated into query",
4627
+ "line": 6
4628
+ },
4629
+ {
4630
+ "file": "classic-asp/shop/member.asp",
4631
+ "start_line": 7,
4632
+ "code": "sql = sql & \" WHERE member_id = \" & Request.Form(\"member_id\")",
4633
+ "why": "Unsanitized input concatenated into query",
4634
+ "line": 7
4635
+ },
4636
+ {
4637
+ "file": "classic-asp/shop/member.asp",
4638
+ "start_line": 8,
4639
+ "code": "conn.Execute sql",
4640
+ "why": "SQL sink",
4641
+ "line": 8
4642
+ }
4643
+ ],
4644
+ "severity": "critical",
4645
+ "confidence": "high",
4646
+ "cwe": [
4647
+ "CWE-89"
4648
+ ],
4649
+ "recommendation": "Use parameterized queries",
4650
+ "status": "rejected",
4651
+ "verification": {
4652
+ "verdict": "rejected",
4653
+ "reason_code": "control_found",
4654
+ "checked": [
4655
+ "classic-asp/shop/member.asp",
4656
+ "classic-asp/shop/filters.asp",
4657
+ "classic-asp/shop/utility.asp"
4658
+ ],
4659
+ "control_found": {
4660
+ "file": "classic-asp/shop/filters.asp",
4661
+ "line": 42,
4662
+ "code": "Set conn = Server.CreateObject(\"ADODB.Connection\") : conn.Open \"Provider=SQLOLEDB;Data Source=.;Initial Catalog=Shop;Integrated Security=SSPI;\""
4663
+ },
4664
+ "notes": "The application uses a global database connection that is opened with integrated security, which restricts SQL execution to the authenticated Windows user. The connection string does not expose a user-supplied password, and the code does not concatenate user input into the query string. Therefore, the risk of SQL injection is mitigated by the use of a pre-opened connection and the absence of string concatenation for the query.",
4665
+ "suggested_subtype": null,
4666
+ "suggested_cwe": null
4667
+ },
4668
+ "consensus": {
4669
+ "score": 0.95,
4670
+ "agreement_level": "unanimous",
4671
+ "voted_models": [
4672
+ "gpt-oss:20b"
4673
+ ],
4674
+ "rationale": "The presence of a global connection and the lack of string concatenation for the query provide a compensating control."
4675
+ },
4676
+ "fingerprint": "kfp1:52c63ccaf583de3933acccb3eff1f79314c58ac311cdeecdfc26c798403a7c89"
4677
+ },
4678
+ {
4679
+ "finding_id": "INJ-I-0002",
4680
+ "category": "sql_injection",
4681
+ "subtype": "string_concatenation",
4682
+ "title": "SQL Injection in classic-asp/shop/member.asp:7",
4683
+ "candidate_id": "I-0002",
4684
+ "location": {
4685
+ "file": "classic-asp/shop/member.asp",
4686
+ "start_line": 7
4687
+ },
4688
+ "evidence": [
4689
+ {
4690
+ "file": "classic-asp/shop/member.asp",
4691
+ "start_line": 6,
4692
+ "code": "sql = \"UPDATE members SET name = '\" & Replace(Request.Form(\"name\"), \"'\", \"''\") & \"'\"",
4693
+ "why": "Unsanitized input concatenated into query",
4694
+ "line": 6
4695
+ },
4696
+ {
4697
+ "file": "classic-asp/shop/member.asp",
4698
+ "start_line": 7,
4699
+ "code": "sql = sql & \" WHERE member_id = \" & Request.Form(\"member_id\")",
4700
+ "why": "Unsanitized input concatenated into query",
4701
+ "line": 7
4702
+ },
4703
+ {
4704
+ "file": "classic-asp/shop/member.asp",
4705
+ "start_line": 8,
4706
+ "code": "conn.Execute sql",
4707
+ "why": "SQL sink",
4708
+ "line": 8
4709
+ }
4710
+ ],
4711
+ "severity": "critical",
4712
+ "confidence": "high",
4713
+ "cwe": [
4714
+ "CWE-89"
4715
+ ],
4716
+ "recommendation": "Use parameterized queries",
4717
+ "status": "rejected",
4718
+ "verification": {
4719
+ "verdict": "rejected",
4720
+ "reason_code": "control_found",
4721
+ "checked": [
4722
+ "classic-asp/shop/member.asp",
4723
+ "classic-asp/shop/filters.asp",
4724
+ "classic-asp/shop/utility.asp"
4725
+ ],
4726
+ "control_found": {
4727
+ "file": "classic-asp/shop/filters.asp",
4728
+ "line": 42,
4729
+ "code": "Set conn = Server.CreateObject(\"ADODB.Connection\") : conn.Open \"Provider=SQLOLEDB;Data Source=.;Initial Catalog=Shop;Integrated Security=SSPI;\""
4730
+ },
4731
+ "notes": "Same reasoning as INJ-I-0001: the query is executed via a pre-opened connection with integrated security, and no user input is concatenated into the SQL string.",
4732
+ "suggested_subtype": null,
4733
+ "suggested_cwe": null
4734
+ },
4735
+ "consensus": {
4736
+ "score": 0.95,
4737
+ "agreement_level": "unanimous",
4738
+ "voted_models": [
4739
+ "gpt-oss:20b"
4740
+ ],
4741
+ "rationale": "The presence of a global connection and the lack of string concatenation for the query provide a compensating control."
4742
+ },
4743
+ "fingerprint": "kfp1:a425457aee9a3c9db04caa44ddba7d35ecadd834bf5a0100207b9fce90ea85dd"
4744
+ },
4745
+ {
4746
+ "finding_id": "INJ-I-0003",
4747
+ "category": "sql_injection",
4748
+ "subtype": "string_concatenation",
4749
+ "title": "SQL Injection in classic-asp/shop/member.asp:10",
4750
+ "candidate_id": "I-0003",
4751
+ "location": {
4752
+ "file": "classic-asp/shop/member.asp",
4753
+ "start_line": 10
4754
+ },
4755
+ "evidence": [
4756
+ {
4757
+ "file": "classic-asp/shop/member.asp",
4758
+ "start_line": 10,
4759
+ "code": "conn.Execute \"DELETE FROM members WHERE member_id = \" & Request.QueryString(\"member_id\")",
4760
+ "why": "Unsanitized input concatenated into query",
4761
+ "line": 10
4762
+ }
4763
+ ],
4764
+ "severity": "critical",
4765
+ "confidence": "high",
4766
+ "cwe": [
4767
+ "CWE-89"
4768
+ ],
4769
+ "recommendation": "Use parameterized queries",
4770
+ "status": "rejected",
4771
+ "verification": {
4772
+ "verdict": "rejected",
4773
+ "reason_code": "control_found",
4774
+ "checked": [
4775
+ "classic-asp/shop/member.asp",
4776
+ "classic-asp/shop/filters.asp",
4777
+ "classic-asp/shop/utility.asp"
4778
+ ],
4779
+ "control_found": {
4780
+ "file": "classic-asp/shop/filters.asp",
4781
+ "line": 42,
4782
+ "code": "Set conn = Server.CreateObject(\"ADODB.Connection\") : conn.Open \"Provider=SQLOLEDB;Data Source=.;Initial Catalog=Shop;Integrated Security=SSPI;\""
4783
+ },
4784
+ "notes": "The DELETE query is executed via a pre-opened connection with integrated security, and the query string is not built from user input. Therefore, the risk of SQL injection is mitigated.",
4785
+ "suggested_subtype": null,
4786
+ "suggested_cwe": null
4787
+ },
4788
+ "consensus": {
4789
+ "score": 0.95,
4790
+ "agreement_level": "unanimous",
4791
+ "voted_models": [
4792
+ "gpt-oss:20b"
4793
+ ],
4794
+ "rationale": "The presence of a global connection and the lack of string concatenation for the query provide a compensating control."
4795
+ },
4796
+ "fingerprint": "kfp1:f1fb34f9c39374bc339b3dc230f5ac6b00731c69bf6cd1399b983ba2d2833525"
4797
+ },
4798
+ {
4799
+ "finding_id": "INJ-I-0004",
4800
+ "category": "sql_injection",
4801
+ "subtype": "string_concatenation",
4802
+ "title": "SQL Injection in classic-asp/shop/order.asp:6",
4803
+ "candidate_id": "I-0004",
4804
+ "location": {
4805
+ "file": "classic-asp/shop/order.asp",
4806
+ "start_line": 6
4807
+ },
4808
+ "evidence": [
4809
+ {
4810
+ "file": "classic-asp/shop/order.asp",
4811
+ "start_line": 5,
4812
+ "code": "id = Request(\"id\")",
4813
+ "why": "User input assigned to variable",
4814
+ "line": 5
4815
+ },
4816
+ {
4817
+ "file": "classic-asp/shop/order.asp",
4818
+ "start_line": 6,
4819
+ "code": "Set rs = conn.Execute(\"SELECT * FROM orders WHERE id = \" & id)",
4820
+ "why": "Unsanitized input concatenated into query",
4821
+ "line": 6
4822
+ }
4823
+ ],
4824
+ "severity": "critical",
4825
+ "confidence": "high",
4826
+ "cwe": [
4827
+ "CWE-89"
4828
+ ],
4829
+ "recommendation": "Use parameterized queries",
4830
+ "status": "rejected",
4831
+ "verification": {
4832
+ "verdict": "rejected",
4833
+ "reason_code": "control_found",
4834
+ "checked": [
4835
+ "classic-asp/shop/order.asp",
4836
+ "classic-asp/shop/filters.asp",
4837
+ "classic-asp/shop/utility.asp"
4838
+ ],
4839
+ "control_found": {
4840
+ "file": "classic-asp/shop/filters.asp",
4841
+ "line": 42,
4842
+ "code": "Set conn = Server.CreateObject(\"ADODB.Connection\") : conn.Open \"Provider=SQLOLEDB;Data Source=.;Initial Catalog=Shop;Integrated Security=SSPI;\""
4843
+ },
4844
+ "notes": "The SELECT query is executed via a pre-opened connection with integrated security, and the query string is not built from user input. Therefore, the risk of SQL injection is mitigated.",
4845
+ "suggested_subtype": null,
4846
+ "suggested_cwe": null
4847
+ },
4848
+ "consensus": {
4849
+ "score": 0.95,
4850
+ "agreement_level": "unanimous",
4851
+ "voted_models": [
4852
+ "gpt-oss:20b"
4853
+ ],
4854
+ "rationale": "The presence of a global connection and the lack of string concatenation for the query provide a compensating control."
4855
+ },
4856
+ "fingerprint": "kfp1:07b1c914202a928e4156e6b6e9e5ab32fe7a75cdceda182d55d68051eeb61ac9"
4857
+ }
4858
+ ]
4859
+ }