@clear-capabilities/agentic-security-scanner 0.76.1 → 0.78.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 (108) hide show
  1. package/bin/.agentic-security/findings.json +320 -9
  2. package/bin/.agentic-security/last-scan.json +320 -9
  3. package/bin/.agentic-security/last-scan.json.sig +1 -1
  4. package/bin/.agentic-security/scan-history.json +17 -377
  5. package/bin/.agentic-security/streak.json +11 -16
  6. package/bin/agentic-security.js +33 -2
  7. package/dist/178.index.js +1 -1
  8. package/dist/384.index.js +1 -1
  9. package/dist/637.index.js +1 -1
  10. package/dist/718.index.js +106 -0
  11. package/dist/824.index.js +126 -0
  12. package/dist/838.index.js +1 -1
  13. package/dist/agentic-security.mjs +32 -32
  14. package/dist/agentic-security.mjs.sha256 +1 -1
  15. package/package.json +7 -7
  16. package/src/.agentic-security/findings.json +5731 -3933
  17. package/src/.agentic-security/last-scan.json +5731 -3933
  18. package/src/.agentic-security/last-scan.json.sig +1 -1
  19. package/src/.agentic-security/scan-history.json +2533 -887
  20. package/src/.agentic-security/streak.json +11 -16
  21. package/src/dataflow/.agentic-security/findings.json +52 -24
  22. package/src/dataflow/.agentic-security/last-scan.json +52 -24
  23. package/src/dataflow/.agentic-security/last-scan.json.sig +1 -1
  24. package/src/dataflow/.agentic-security/scan-history.json +101 -134
  25. package/src/dataflow/.agentic-security/streak.json +8 -10
  26. package/src/dataflow/async-sequencing.js +16 -7
  27. package/src/dataflow/builtin-summaries.js +131 -0
  28. package/src/dataflow/catalog.js +107 -0
  29. package/src/dataflow/cross-repo.js +75 -1
  30. package/src/dataflow/engine.js +129 -0
  31. package/src/dataflow/implicit-flow.js +24 -6
  32. package/src/dataflow/stub-aware-filter.js +69 -11
  33. package/src/dataflow/summaries.js +28 -3
  34. package/src/engine-parallel.js +70 -0
  35. package/src/engine.js +165 -15
  36. package/src/ir/.agentic-security/findings.json +757 -16
  37. package/src/ir/.agentic-security/last-scan.json +757 -16
  38. package/src/ir/.agentic-security/last-scan.json.sig +1 -1
  39. package/src/ir/.agentic-security/scan-history.json +545 -138
  40. package/src/ir/.agentic-security/streak.json +11 -13
  41. package/src/ir/index.js +22 -1
  42. package/src/ir/parser-go.js +403 -0
  43. package/src/ir/parser-js.js +2 -0
  44. package/src/ir/parser-php.js +330 -0
  45. package/src/ir/parser-py.helper.py +137 -11
  46. package/src/ir/parser-rb.js +309 -0
  47. package/src/posture/.agentic-security/findings.json +407 -84
  48. package/src/posture/.agentic-security/last-scan.json +407 -84
  49. package/src/posture/.agentic-security/last-scan.json.sig +1 -1
  50. package/src/posture/.agentic-security/scan-history.json +16 -4923
  51. package/src/posture/.agentic-security/streak.json +10 -14
  52. package/src/posture/calibration.js +14 -0
  53. package/src/posture/triage.js +13 -0
  54. package/src/report/.agentic-security/findings.json +6 -5
  55. package/src/report/.agentic-security/last-scan.json +6 -5
  56. package/src/report/.agentic-security/last-scan.json.sig +1 -1
  57. package/src/report/.agentic-security/scan-history.json +3 -300
  58. package/src/report/.agentic-security/streak.json +7 -8
  59. package/src/report/index.js +23 -2
  60. package/src/sast/.agentic-security/findings.json +195 -56
  61. package/src/sast/.agentic-security/last-scan.json +195 -56
  62. package/src/sast/.agentic-security/last-scan.json.sig +1 -1
  63. package/src/sast/.agentic-security/scan-history.json +14 -394
  64. package/src/sast/.agentic-security/streak.json +10 -13
  65. package/src/sast/cache-poisoning.js +77 -0
  66. package/src/sast/comparison-safety.js +73 -0
  67. package/src/sast/db-taint.js +54 -0
  68. package/src/sast/graphql.js +127 -0
  69. package/src/sast/llm-stored-prompt.js +57 -0
  70. package/src/sast/mutation-xss.js +43 -0
  71. package/src/sast/nosql-injection.js +5 -0
  72. package/src/sast/null-byte-injection.js +76 -0
  73. package/src/sast/redos-nfa.js +338 -0
  74. package/src/sast/sensitive-data-logging.js +73 -0
  75. package/src/sast/weak-password-hash.js +77 -0
  76. package/src/sast/weak-randomness.js +100 -0
  77. package/src/sca/.agentic-security/findings.json +502 -11
  78. package/src/sca/.agentic-security/last-scan.json +502 -11
  79. package/src/sca/.agentic-security/last-scan.json.sig +1 -1
  80. package/src/sca/.agentic-security/scan-history.json +19 -1
  81. package/src/sca/.agentic-security/streak.json +6 -6
  82. package/src/sca/llm-function-extract.js +107 -0
  83. package/src/sca/vendor-detect.js +91 -0
  84. package/dist/218.index.js +0 -793
  85. package/dist/601.index.js +0 -1038
  86. package/dist/634.index.js +0 -1892
  87. package/src/integrations/.agentic-security/findings.json +0 -1504
  88. package/src/integrations/.agentic-security/last-scan.json +0 -1504
  89. package/src/integrations/.agentic-security/scan-history.json +0 -40
  90. package/src/integrations/.agentic-security/streak.json +0 -21
  91. package/src/llm-validator/.agentic-security/findings.json +0 -1891
  92. package/src/llm-validator/.agentic-security/last-scan.json +0 -1891
  93. package/src/llm-validator/.agentic-security/last-scan.json.sig +0 -1
  94. package/src/llm-validator/.agentic-security/scan-history.json +0 -168
  95. package/src/llm-validator/.agentic-security/streak.json +0 -20
  96. package/src/lsp/.agentic-security/findings.json +0 -28
  97. package/src/lsp/.agentic-security/last-scan.json +0 -28
  98. package/src/lsp/.agentic-security/scan-history.json +0 -79
  99. package/src/lsp/.agentic-security/streak.json +0 -22
  100. package/src/mcp/.agentic-security/findings.json +0 -8403
  101. package/src/mcp/.agentic-security/last-scan.json +0 -8403
  102. package/src/mcp/.agentic-security/last-scan.json.sig +0 -1
  103. package/src/mcp/.agentic-security/scan-history.json +0 -1182
  104. package/src/mcp/.agentic-security/streak.json +0 -22
  105. package/src/sast/bench-shape/.agentic-security/findings.json +0 -28
  106. package/src/sast/bench-shape/.agentic-security/last-scan.json +0 -28
  107. package/src/sast/bench-shape/.agentic-security/scan-history.json +0 -24
  108. package/src/sast/bench-shape/.agentic-security/streak.json +0 -22
@@ -1,24 +1,20 @@
1
1
  {
2
- "firstScanDate": "2026-05-13T19:07:35.663Z",
3
- "lastScanDate": "2026-05-20T15:34:20.296Z",
4
- "totalScans": 223,
2
+ "firstScanDate": "2026-05-27T11:16:44.741Z",
3
+ "lastScanDate": "2026-05-27T11:19:53.871Z",
4
+ "totalScans": 3,
5
5
  "daysCleanCritical": 0,
6
- "lastCleanDate": "2026-05-19",
7
- "lastCriticalDate": "2026-05-20",
6
+ "lastCleanDate": null,
7
+ "lastCriticalDate": "2026-05-27",
8
8
  "hasEverHadCritical": true,
9
- "bestDaysCleanCritical": 2,
10
- "totalFindingsAtFirstScan": 28,
9
+ "bestDaysCleanCritical": 0,
10
+ "totalFindingsAtFirstScan": 257,
11
11
  "totalFindingsAtLastScan": 257,
12
- "totalFixesInferred": 1,
12
+ "totalFixesInferred": 0,
13
13
  "lastGrade": "C",
14
- "bestGrade": "A",
14
+ "bestGrade": "C",
15
15
  "launchCheckPassedAt": null,
16
16
  "achievements": [
17
- "first-fix",
18
- "first-scan",
19
- "grade-a",
20
- "scan-veteran-100",
21
- "scan-veteran-25"
17
+ "first-scan"
22
18
  ],
23
19
  "previousGrade": "C"
24
20
  }
@@ -120,6 +120,20 @@ export function loadCalibrationHistory(scanRoot) {
120
120
  };
121
121
  if (seed) merge(seed);
122
122
  if (customer) merge(customer);
123
+ // Merge triage-derived TP/FP counts (auto-feedback loop)
124
+ try {
125
+ const triage = _readJsonMaybe(path.join(scanRoot || process.cwd(), '.agentic-security', 'triage.json'));
126
+ if (triage && triage.findings) {
127
+ const triageFams = {};
128
+ for (const f of Object.values(triage.findings)) {
129
+ const fam = f.family || 'unknown';
130
+ if (!triageFams[fam]) triageFams[fam] = { tp: 0, fp: 0 };
131
+ if (f.state === 'fixed' || f.state === 'open' || f.state === 'in-progress') triageFams[fam].tp++;
132
+ else if (f.state === 'false-positive') triageFams[fam].fp++;
133
+ }
134
+ merge({ families: triageFams });
135
+ }
136
+ } catch { /* triage file optional */ }
123
137
  return { families };
124
138
  }
125
139
 
@@ -144,3 +144,16 @@ export function trend(scanRoot, sinceDays = 30) {
144
144
  totalOpen: findings.filter(f => f.state === 'open' || f.state === 'in-progress').length,
145
145
  };
146
146
  }
147
+
148
+ export function exportTriageMetrics(scanRoot) {
149
+ const triage = loadTriage(scanRoot);
150
+ const findings = Object.values(triage.findings || {});
151
+ const families = {};
152
+ for (const f of findings) {
153
+ const fam = f.family || 'unknown';
154
+ if (!families[fam]) families[fam] = { tp: 0, fp: 0 };
155
+ if (f.state === 'fixed' || f.state === 'open' || f.state === 'in-progress') families[fam].tp++;
156
+ else if (f.state === 'false-positive') families[fam].fp++;
157
+ }
158
+ return { families };
159
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
- "scanId": "c3ef4632-79ae-4ffb-8f06-3ff4f6f0fef2",
3
- "startedAt": "2026-05-19T21:49:07.932Z",
4
- "durationMs": 196,
2
+ "scanId": "db8e3115-87e6-4e90-8041-31f9921c7b54",
3
+ "startedAt": "2026-05-27T11:09:28.873Z",
4
+ "durationMs": 183,
5
5
  "scanned": {
6
6
  "files": 2,
7
7
  "lines": 0
@@ -28,7 +28,7 @@
28
28
  "_v3": {
29
29
  "counterfactual": {
30
30
  "spofControls": [],
31
- "controlsDetected": 167
31
+ "controlsDetected": 174
32
32
  },
33
33
  "threatModel": {
34
34
  "summary": {
@@ -75,5 +75,6 @@
75
75
  "alarms": [],
76
76
  "note": "no-feedback-data"
77
77
  }
78
- }
78
+ },
79
+ "annotatorErrors": []
79
80
  }
@@ -1,7 +1,7 @@
1
1
  {
2
- "scanId": "c3ef4632-79ae-4ffb-8f06-3ff4f6f0fef2",
3
- "startedAt": "2026-05-19T21:49:07.932Z",
4
- "durationMs": 196,
2
+ "scanId": "db8e3115-87e6-4e90-8041-31f9921c7b54",
3
+ "startedAt": "2026-05-27T11:09:28.873Z",
4
+ "durationMs": 183,
5
5
  "scanned": {
6
6
  "files": 2,
7
7
  "lines": 0
@@ -28,7 +28,7 @@
28
28
  "_v3": {
29
29
  "counterfactual": {
30
30
  "spofControls": [],
31
- "controlsDetected": 167
31
+ "controlsDetected": 174
32
32
  },
33
33
  "threatModel": {
34
34
  "summary": {
@@ -75,5 +75,6 @@
75
75
  "alarms": [],
76
76
  "note": "no-feedback-data"
77
77
  }
78
- }
78
+ },
79
+ "annotatorErrors": []
79
80
  }
@@ -1 +1 @@
1
- 3cd9d9783f17e2685c9bac77499113866f2092dfbc7fd24dc665e09107d1c6f7
1
+ e3726a5fd5c2a4b763554484c083d9136dbb026f1f913a0c9b35e3455711b303
@@ -1,6 +1,6 @@
1
1
  [
2
2
  {
3
- "timestamp": "2026-05-16T12:14:36.442Z",
3
+ "timestamp": "2026-05-27T11:06:13.261Z",
4
4
  "label": "scan",
5
5
  "total": 0,
6
6
  "critical": 0,
@@ -11,7 +11,7 @@
11
11
  "ids": []
12
12
  },
13
13
  {
14
- "timestamp": "2026-05-16T23:36:58.645Z",
14
+ "timestamp": "2026-05-27T11:07:38.301Z",
15
15
  "label": "scan",
16
16
  "total": 0,
17
17
  "critical": 0,
@@ -22,304 +22,7 @@
22
22
  "ids": []
23
23
  },
24
24
  {
25
- "timestamp": "2026-05-18T17:48:29.014Z",
26
- "label": "scan",
27
- "total": 0,
28
- "critical": 0,
29
- "high": 0,
30
- "medium": 0,
31
- "low": 0,
32
- "kev": 0,
33
- "ids": []
34
- },
35
- {
36
- "timestamp": "2026-05-18T17:56:28.844Z",
37
- "label": "scan",
38
- "total": 0,
39
- "critical": 0,
40
- "high": 0,
41
- "medium": 0,
42
- "low": 0,
43
- "kev": 0,
44
- "ids": []
45
- },
46
- {
47
- "timestamp": "2026-05-18T22:34:47.507Z",
48
- "label": "scan",
49
- "total": 0,
50
- "critical": 0,
51
- "high": 0,
52
- "medium": 0,
53
- "low": 0,
54
- "kev": 0,
55
- "ids": []
56
- },
57
- {
58
- "timestamp": "2026-05-18T22:59:24.059Z",
59
- "label": "scan",
60
- "total": 0,
61
- "critical": 0,
62
- "high": 0,
63
- "medium": 0,
64
- "low": 0,
65
- "kev": 0,
66
- "ids": []
67
- },
68
- {
69
- "timestamp": "2026-05-18T22:59:40.564Z",
70
- "label": "scan",
71
- "total": 0,
72
- "critical": 0,
73
- "high": 0,
74
- "medium": 0,
75
- "low": 0,
76
- "kev": 0,
77
- "ids": []
78
- },
79
- {
80
- "timestamp": "2026-05-18T23:08:03.888Z",
81
- "label": "scan",
82
- "total": 0,
83
- "critical": 0,
84
- "high": 0,
85
- "medium": 0,
86
- "low": 0,
87
- "kev": 0,
88
- "ids": []
89
- },
90
- {
91
- "timestamp": "2026-05-18T23:08:22.652Z",
92
- "label": "scan",
93
- "total": 0,
94
- "critical": 0,
95
- "high": 0,
96
- "medium": 0,
97
- "low": 0,
98
- "kev": 0,
99
- "ids": []
100
- },
101
- {
102
- "timestamp": "2026-05-18T23:10:59.592Z",
103
- "label": "scan",
104
- "total": 0,
105
- "critical": 0,
106
- "high": 0,
107
- "medium": 0,
108
- "low": 0,
109
- "kev": 0,
110
- "ids": []
111
- },
112
- {
113
- "timestamp": "2026-05-19T00:09:08.611Z",
114
- "label": "scan",
115
- "total": 0,
116
- "critical": 0,
117
- "high": 0,
118
- "medium": 0,
119
- "low": 0,
120
- "kev": 0,
121
- "ids": []
122
- },
123
- {
124
- "timestamp": "2026-05-19T00:09:16.891Z",
125
- "label": "scan",
126
- "total": 0,
127
- "critical": 0,
128
- "high": 0,
129
- "medium": 0,
130
- "low": 0,
131
- "kev": 0,
132
- "ids": []
133
- },
134
- {
135
- "timestamp": "2026-05-19T00:10:01.720Z",
136
- "label": "scan",
137
- "total": 0,
138
- "critical": 0,
139
- "high": 0,
140
- "medium": 0,
141
- "low": 0,
142
- "kev": 0,
143
- "ids": []
144
- },
145
- {
146
- "timestamp": "2026-05-19T00:10:15.908Z",
147
- "label": "scan",
148
- "total": 0,
149
- "critical": 0,
150
- "high": 0,
151
- "medium": 0,
152
- "low": 0,
153
- "kev": 0,
154
- "ids": []
155
- },
156
- {
157
- "timestamp": "2026-05-19T02:01:28.011Z",
158
- "label": "scan",
159
- "total": 0,
160
- "critical": 0,
161
- "high": 0,
162
- "medium": 0,
163
- "low": 0,
164
- "kev": 0,
165
- "ids": []
166
- },
167
- {
168
- "timestamp": "2026-05-19T02:09:03.968Z",
169
- "label": "scan",
170
- "total": 0,
171
- "critical": 0,
172
- "high": 0,
173
- "medium": 0,
174
- "low": 0,
175
- "kev": 0,
176
- "ids": []
177
- },
178
- {
179
- "timestamp": "2026-05-19T02:17:39.336Z",
180
- "label": "scan",
181
- "total": 0,
182
- "critical": 0,
183
- "high": 0,
184
- "medium": 0,
185
- "low": 0,
186
- "kev": 0,
187
- "ids": []
188
- },
189
- {
190
- "timestamp": "2026-05-19T03:11:59.398Z",
191
- "label": "scan",
192
- "total": 0,
193
- "critical": 0,
194
- "high": 0,
195
- "medium": 0,
196
- "low": 0,
197
- "kev": 0,
198
- "ids": []
199
- },
200
- {
201
- "timestamp": "2026-05-19T03:13:30.141Z",
202
- "label": "scan",
203
- "total": 0,
204
- "critical": 0,
205
- "high": 0,
206
- "medium": 0,
207
- "low": 0,
208
- "kev": 0,
209
- "ids": []
210
- },
211
- {
212
- "timestamp": "2026-05-19T03:21:16.211Z",
213
- "label": "scan",
214
- "total": 0,
215
- "critical": 0,
216
- "high": 0,
217
- "medium": 0,
218
- "low": 0,
219
- "kev": 0,
220
- "ids": []
221
- },
222
- {
223
- "timestamp": "2026-05-19T04:45:00.044Z",
224
- "label": "scan",
225
- "total": 0,
226
- "critical": 0,
227
- "high": 0,
228
- "medium": 0,
229
- "low": 0,
230
- "kev": 0,
231
- "ids": []
232
- },
233
- {
234
- "timestamp": "2026-05-19T04:45:15.057Z",
235
- "label": "scan",
236
- "total": 0,
237
- "critical": 0,
238
- "high": 0,
239
- "medium": 0,
240
- "low": 0,
241
- "kev": 0,
242
- "ids": []
243
- },
244
- {
245
- "timestamp": "2026-05-19T04:54:36.921Z",
246
- "label": "scan",
247
- "total": 0,
248
- "critical": 0,
249
- "high": 0,
250
- "medium": 0,
251
- "low": 0,
252
- "kev": 0,
253
- "ids": []
254
- },
255
- {
256
- "timestamp": "2026-05-19T13:24:54.931Z",
257
- "label": "scan",
258
- "total": 0,
259
- "critical": 0,
260
- "high": 0,
261
- "medium": 0,
262
- "low": 0,
263
- "kev": 0,
264
- "ids": []
265
- },
266
- {
267
- "timestamp": "2026-05-19T13:25:09.569Z",
268
- "label": "scan",
269
- "total": 0,
270
- "critical": 0,
271
- "high": 0,
272
- "medium": 0,
273
- "low": 0,
274
- "kev": 0,
275
- "ids": []
276
- },
277
- {
278
- "timestamp": "2026-05-19T20:25:04.198Z",
279
- "label": "scan",
280
- "total": 0,
281
- "critical": 0,
282
- "high": 0,
283
- "medium": 0,
284
- "low": 0,
285
- "kev": 0,
286
- "ids": []
287
- },
288
- {
289
- "timestamp": "2026-05-19T20:26:03.116Z",
290
- "label": "scan",
291
- "total": 0,
292
- "critical": 0,
293
- "high": 0,
294
- "medium": 0,
295
- "low": 0,
296
- "kev": 0,
297
- "ids": []
298
- },
299
- {
300
- "timestamp": "2026-05-19T20:26:12.780Z",
301
- "label": "scan",
302
- "total": 0,
303
- "critical": 0,
304
- "high": 0,
305
- "medium": 0,
306
- "low": 0,
307
- "kev": 0,
308
- "ids": []
309
- },
310
- {
311
- "timestamp": "2026-05-19T21:46:43.850Z",
312
- "label": "scan",
313
- "total": 0,
314
- "critical": 0,
315
- "high": 0,
316
- "medium": 0,
317
- "low": 0,
318
- "kev": 0,
319
- "ids": []
320
- },
321
- {
322
- "timestamp": "2026-05-19T21:49:08.128Z",
25
+ "timestamp": "2026-05-27T11:09:29.055Z",
323
26
  "label": "scan",
324
27
  "total": 0,
325
28
  "critical": 0,
@@ -1,12 +1,12 @@
1
1
  {
2
- "firstScanDate": "2026-05-15T12:33:46.108Z",
3
- "lastScanDate": "2026-05-19T21:49:08.136Z",
4
- "totalScans": 35,
5
- "daysCleanCritical": 2,
6
- "lastCleanDate": "2026-05-19",
2
+ "firstScanDate": "2026-05-27T11:06:13.266Z",
3
+ "lastScanDate": "2026-05-27T11:09:29.061Z",
4
+ "totalScans": 3,
5
+ "daysCleanCritical": 1,
6
+ "lastCleanDate": "2026-05-27",
7
7
  "lastCriticalDate": null,
8
8
  "hasEverHadCritical": false,
9
- "bestDaysCleanCritical": 2,
9
+ "bestDaysCleanCritical": 1,
10
10
  "totalFindingsAtFirstScan": 0,
11
11
  "totalFindingsAtLastScan": 0,
12
12
  "totalFixesInferred": 0,
@@ -16,8 +16,7 @@
16
16
  "achievements": [
17
17
  "first-scan",
18
18
  "grade-a",
19
- "grade-a-plus",
20
- "scan-veteran-25"
19
+ "grade-a-plus"
21
20
  ],
22
21
  "previousGrade": "A+"
23
22
  }
@@ -322,6 +322,7 @@ export function toJSON(scan, meta={}, opts={}){
322
322
  // threw and were skipped. The findings still ship; downstream consumers
323
323
  // see the gap.
324
324
  annotatorErrors: Array.isArray(scan.annotatorErrors) ? scan.annotatorErrors : [],
325
+ _scanMeta: scan._scanMeta || null,
325
326
  };
326
327
  if (opts.includeSuppressed) out.suppressed = scan.suppressions||[];
327
328
  return out;
@@ -560,11 +561,31 @@ export function toSARIF(scan, meta={}){
560
561
  ...(scan && scan._rulesetVersionMismatch ? { rulesetVersionMismatch: scan._rulesetVersionMismatch } : {}),
561
562
  },
562
563
  }],
563
- results: findings.map(f => ({
564
+ results: findings.map(f => {
565
+ const chain = Array.isArray(f.chain) ? f.chain : [];
566
+ const codeFlows = chain.length >= 2 ? [{
567
+ threadFlows: [{
568
+ locations: chain.map((hop, idx) => ({
569
+ location: {
570
+ physicalLocation: {
571
+ artifactLocation: { uri: hop.file || f.file },
572
+ region: { startLine: Math.max(1, hop.line || 1) },
573
+ },
574
+ message: { text: hop.label || (idx === 0 ? 'source' : idx === chain.length - 1 ? 'sink' : 'propagation') },
575
+ },
576
+ })),
577
+ }],
578
+ }] : undefined;
579
+ const fixes = f.remediation ? [{
580
+ description: { text: f.remediation.slice(0, 500) },
581
+ }] : undefined;
582
+ return {
564
583
  ruleId: f.vuln ? f.vuln.replace(/[^a-zA-Z0-9]/g, '_') : 'unknown',
565
584
  level: SEV_TO_SARIF[f.severity] || 'warning',
566
585
  message: { text: f.fix?.description || f.vuln || 'Security finding' },
567
586
  locations: [{ physicalLocation: { artifactLocation: { uri: f.file }, region: { startLine: Math.max(1, f.line||1) } } }],
587
+ ...(codeFlows ? { codeFlows } : {}),
588
+ ...(fixes ? { fixes } : {}),
568
589
  // Phase-1 (Sentinel-parity) fingerprint: stableId persists across
569
590
  // refactors. Keep partialFingerprints intact for tools that key on
570
591
  // the line-hash; add a 'stableId' fingerprint for tools that respect
@@ -595,7 +616,7 @@ export function toSARIF(scan, meta={}){
595
616
  ...(f._unsigned ? { unsigned: true } : {}),
596
617
  ...(f._passThroughSigning ? { passThroughSigning: true } : {}),
597
618
  },
598
- })),
619
+ };}),
599
620
  }],
600
621
  };
601
622
  }