@hoanghainh/komagu 2.0.0 → 2.1.0

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