@clear-capabilities/agentic-security-scanner 0.184.0 → 0.186.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/bin/agentic-security.js +111 -1
  3. package/dist/1128.index.js +912 -0
  4. package/dist/1665.index.js +828 -0
  5. package/dist/2584.index.js +1 -1
  6. package/dist/4171.index.js +2 -2
  7. package/dist/5752.index.js +466 -0
  8. package/dist/7405.index.js +1 -1
  9. package/dist/8158.index.js +1713 -0
  10. package/dist/8798.index.js +1 -1
  11. package/dist/9382.index.js +2 -2
  12. package/dist/9579.index.js +1 -1
  13. package/dist/agentic-security.mjs +29 -29
  14. package/dist/agentic-security.mjs.sha256 +1 -1
  15. package/dist/{dad58d1113a12c23a142.json → ca62ee93079be4c24eee.json} +29 -12
  16. package/dist/frontend/src/shell.js +12 -2
  17. package/package.json +29 -12
  18. package/src/dataflow/CLAUDE.md +46 -1
  19. package/src/dataflow/access-paths.js +12 -2
  20. package/src/dataflow/backward.js +42 -7
  21. package/src/dataflow/cross-service-taint.js +83 -1
  22. package/src/dataflow/demand-query.js +1168 -0
  23. package/src/dataflow/dep-fingerprint.js +718 -0
  24. package/src/dataflow/engine.js +48 -6
  25. package/src/dataflow/exploit-prover.js +6 -2
  26. package/src/dataflow/ifds-precise.js +22 -0
  27. package/src/dataflow/incremental-replay.js +118 -0
  28. package/src/dataflow/incremental-session.js +257 -0
  29. package/src/dataflow/incremental.js +237 -35
  30. package/src/dataflow/index.js +25 -94
  31. package/src/dataflow/ir-cache.js +268 -0
  32. package/src/dataflow/privacy-taint.js +7 -0
  33. package/src/dataflow/summaries.js +72 -6
  34. package/src/dataflow/tabulation.js +80 -24
  35. package/src/engine.js +113 -8
  36. package/src/graph/CLAUDE.md +31 -0
  37. package/src/graph/adapters/artifact.js +81 -0
  38. package/src/graph/adapters/crossrepo.js +75 -0
  39. package/src/graph/adapters/dataflow.js +76 -0
  40. package/src/graph/adapters/provenance.js +92 -0
  41. package/src/graph/adapters/semantic.js +89 -0
  42. package/src/graph/crosswalk-builders.js +152 -0
  43. package/src/graph/crosswalk.js +146 -0
  44. package/src/graph/index.js +15 -0
  45. package/src/graph/observation.js +57 -0
  46. package/src/graph/refs.js +105 -0
  47. package/src/graph/registry.js +129 -0
  48. package/src/graph/relations.js +81 -0
  49. package/src/graph/scc.js +241 -0
  50. package/src/graph/view.js +254 -0
  51. package/src/ir/CLAUDE.md +4 -0
  52. package/src/ir/callgraph.js +36 -8
  53. package/src/ir/index.js +18 -4
  54. package/src/ir/parser-js.js +10 -0
  55. package/src/language/CLAUDE.md +13 -0
  56. package/src/language/nix-option-types.js +170 -0
  57. package/src/language/nix-script-taint.js +46 -3
  58. package/src/language/nix-secrets.js +20 -2
  59. package/src/lineage/CLAUDE.md +72 -8
  60. package/src/lineage/DESIGN_GRAPH_BUILDER.md +1 -1
  61. package/src/lineage/DESIGN_PATH_PROVENANCE.md +6 -0
  62. package/src/lineage/boundary-contract.js +273 -0
  63. package/src/lineage/completion.js +205 -0
  64. package/src/lineage/continuation.js +140 -0
  65. package/src/lineage/control-credit.js +313 -0
  66. package/src/lineage/correlation.js +199 -0
  67. package/src/lineage/coverage.js +8 -1
  68. package/src/lineage/cross-repo-link.js +22 -0
  69. package/src/lineage/cut-planner.js +548 -0
  70. package/src/lineage/driver.js +219 -66
  71. package/src/lineage/engine.js +166 -21
  72. package/src/lineage/export-json.js +5 -0
  73. package/src/lineage/graph-builder.js +84 -7
  74. package/src/lineage/haskell-view.js +59 -0
  75. package/src/lineage/index.js +3 -0
  76. package/src/lineage/nix-view.js +35 -0
  77. package/src/lineage/owned-build.js +77 -0
  78. package/src/lineage/owned-worker-entry.js +22 -0
  79. package/src/lineage/owned-worker.js +95 -0
  80. package/src/lineage/path-evidence.js +123 -0
  81. package/src/lineage/path-predicates.js +529 -0
  82. package/src/lineage/path-query.js +189 -20
  83. package/src/lineage/path-store.js +191 -17
  84. package/src/lineage/scc-solver.js +186 -0
  85. package/src/lineage/smt-orchestrator.js +174 -0
  86. package/src/lineage/smt-z3-backend.js +294 -0
  87. package/src/lineage/source-seeding.js +36 -2
  88. package/src/lineage/summaries.js +66 -4
  89. package/src/lineage/value-versions.js +130 -0
  90. package/src/lineage/witness-selection.js +95 -0
  91. package/src/pipeline/scan-health.js +18 -0
  92. package/src/posture/CLAUDE.md +17 -0
  93. package/src/posture/business-logic.js +16 -7
  94. package/src/posture/dag-commands.js +442 -0
  95. package/src/posture/evidence-bundle.js +1 -1
  96. package/src/posture/finding-defaults.js +19 -0
  97. package/src/posture/git-history.js +91 -1
  98. package/src/posture/witness-bundle.js +573 -0
  99. package/src/posture/witness-runtime.js +276 -0
  100. package/src/report/index.js +2 -0
  101. package/src/sast/CLAUDE.md +2 -0
  102. package/src/sast/_call-args.js +70 -0
  103. package/src/sast/init-scope.js +87 -0
  104. package/src/sast/listen-bind-address.js +49 -0
  105. package/src/sast/redirect-target.js +106 -0
  106. package/src/sast/reflected-input.js +76 -0
  107. package/src/sast/send-file-root.js +27 -0
  108. package/src/sast/sensitive-data-logging.js +38 -1
  109. package/dist/119.index.js +0 -197
  110. package/dist/128.index.js +0 -129
  111. package/dist/1293.index.js +0 -5689
  112. package/dist/1336.index.js +0 -16182
  113. package/dist/1351.index.js +0 -133
  114. package/dist/1387.index.js +0 -271
  115. package/dist/1437.index.js +0 -23
  116. package/dist/1445.index.js +0 -110
  117. package/dist/1505.index.js +0 -151
  118. package/dist/1557.index.js +0 -293
  119. package/dist/1621.index.js +0 -311
  120. package/dist/1751.index.js +0 -160
  121. package/dist/1765.index.js +0 -727
  122. package/dist/1861.index.js +0 -690
  123. package/dist/1906.index.js +0 -433
  124. package/dist/208.index.js +0 -1631
  125. package/dist/2223.index.js +0 -159
  126. package/dist/2238.index.js +0 -295
  127. package/dist/2530.index.js +0 -152
  128. package/dist/2565.index.js +0 -116
  129. package/dist/2574.index.js +0 -127
  130. package/dist/2620.index.js +0 -345
  131. package/dist/2773.index.js +0 -98
  132. package/dist/2775.index.js +0 -32
  133. package/dist/2966.index.js +0 -171
  134. package/dist/2972.index.js +0 -87
  135. package/dist/2991.index.js +0 -131
  136. package/dist/3063.index.js +0 -833
  137. package/dist/3075.index.js +0 -227
  138. package/dist/3121.index.js +0 -297
  139. package/dist/3314.index.js +0 -263
  140. package/dist/3480.index.js +0 -1822
  141. package/dist/3481.index.js +0 -24
  142. package/dist/3506.index.js +0 -268
  143. package/dist/3533.index.js +0 -591
  144. package/dist/3653.index.js +0 -450
  145. package/dist/3740.index.js +0 -722
  146. package/dist/3822.index.js +0 -131
  147. package/dist/3868.index.js +0 -292
  148. package/dist/3918.index.js +0 -192
  149. package/dist/4090.index.js +0 -288
  150. package/dist/4113.index.js +0 -1286
  151. package/dist/4121.index.js +0 -1190
  152. package/dist/4142.index.js +0 -690
  153. package/dist/4168.index.js +0 -509
  154. package/dist/4281.index.js +0 -993
  155. package/dist/4336.index.js +0 -436
  156. package/dist/4387.index.js +0 -532
  157. package/dist/4413.index.js +0 -302
  158. package/dist/4476.index.js +0 -4510
  159. package/dist/4491.index.js +0 -133
  160. package/dist/4538.index.js +0 -185
  161. package/dist/4684.index.js +0 -319
  162. package/dist/4740.index.js +0 -165
  163. package/dist/4780.index.js +0 -209
  164. package/dist/4802.index.js +0 -225
  165. package/dist/4812.index.js +0 -379
  166. package/dist/4835.index.js +0 -126
  167. package/dist/494.index.js +0 -226
  168. package/dist/4991.index.js +0 -549
  169. package/dist/5199.index.js +0 -305
  170. package/dist/5216.index.js +0 -109
  171. package/dist/5439.index.js +0 -301
  172. package/dist/5443.index.js +0 -97
  173. package/dist/5496.index.js +0 -377
  174. package/dist/5738.index.js +0 -240
  175. package/dist/5801.index.js +0 -588
  176. package/dist/5817.index.js +0 -714
  177. package/dist/5858.index.js +0 -223
  178. package/dist/5933.index.js +0 -143
  179. package/dist/601.index.js +0 -23
  180. package/dist/6173.index.js +0 -273
  181. package/dist/6291.index.js +0 -1871
  182. package/dist/6312.index.js +0 -666
  183. package/dist/634.index.js +0 -16182
  184. package/dist/644.index.js +0 -87
  185. package/dist/6508.index.js +0 -283
  186. package/dist/6560.index.js +0 -645
  187. package/dist/6576.index.js +0 -422
  188. package/dist/6579.index.js +0 -271
  189. package/dist/6655.index.js +0 -3221
  190. package/dist/6745.index.js +0 -305
  191. package/dist/6818.index.js +0 -21642
  192. package/dist/6932.index.js +0 -1190
  193. package/dist/6972.index.js +0 -167
  194. package/dist/6995.index.js +0 -163
  195. package/dist/7249.index.js +0 -191
  196. package/dist/7372.index.js +0 -1197
  197. package/dist/7546.index.js +0 -541
  198. package/dist/76.index.js +0 -448
  199. package/dist/7879.index.js +0 -163
  200. package/dist/8025.index.js +0 -130
  201. package/dist/8054.index.js +0 -248
  202. package/dist/808.index.js +0 -1330
  203. package/dist/8097.index.js +0 -201
  204. package/dist/8126.index.js +0 -191
  205. package/dist/8178.index.js +0 -78
  206. package/dist/8382.index.js +0 -93
  207. package/dist/8408.index.js +0 -261
  208. package/dist/8540.index.js +0 -174
  209. package/dist/8635.index.js +0 -802
  210. package/dist/8688.index.js +0 -410
  211. package/dist/8699.index.js +0 -779
  212. package/dist/8891.index.js +0 -457
  213. package/dist/8916.index.js +0 -885
  214. package/dist/9146.index.js +0 -170
  215. package/dist/9168.index.js +0 -841
  216. package/dist/9292.index.js +0 -369
  217. package/dist/9422.index.js +0 -866
  218. package/dist/9440.index.js +0 -453
  219. package/dist/9479.index.js +0 -152
  220. package/dist/9481.index.js +0 -220
  221. package/dist/9539.index.js +0 -100
  222. package/dist/9547.index.js +0 -103
  223. package/dist/9692.index.js +0 -90
  224. package/dist/9800.index.js +0 -462
  225. package/dist/9899.index.js +0 -138
  226. package/dist/9981.index.js +0 -206
  227. package/dist/999.index.js +0 -117
  228. package/dist/9999.index.js +0 -1698
@@ -1,159 +0,0 @@
1
- export const id = 2223;
2
- export const ids = [2223];
3
- export const modules = {
4
-
5
- /***/ 82223:
6
- /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
-
8
- /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
- /* harmony export */ detectVendoredLibraries: () => (/* binding */ detectVendoredLibraries)
10
- /* harmony export */ });
11
- // Vendored / copied library detection via version-string fingerprinting.
12
- //
13
- // Detects library code copied into src/ that bypasses SCA. Uses version
14
- // string patterns (_.VERSION, jQuery.fn.jquery, etc.) and characteristic
15
- // function signatures to identify vendored libraries.
16
-
17
- const VERSION_FINGERPRINTS = [
18
- { pkg: 'lodash', ecosystem: 'npm', patterns: [
19
- { re: /\b(?:lodash|_)\.VERSION\s*=\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
20
- { re: /\b__lodash_hash_undefined__\b/, version: null },
21
- ]},
22
- { pkg: 'jquery', ecosystem: 'npm', patterns: [
23
- { re: /jQuery\.fn\.jquery\s*=\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
24
- { re: /\bjQuery\.fn\.init\b/, version: null },
25
- ]},
26
- { pkg: 'underscore', ecosystem: 'npm', patterns: [
27
- { re: /\b_\.VERSION\s*=\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
28
- ]},
29
- { pkg: 'moment', ecosystem: 'npm', patterns: [
30
- { re: /\bmoment\.version\s*=\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
31
- { re: /\bmoment\.(?:utc|parseZone|duration|locale)\b/, version: null },
32
- ]},
33
- { pkg: 'handlebars', ecosystem: 'npm', patterns: [
34
- { re: /\bHandlebars\.VERSION\s*=\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
35
- ]},
36
- { pkg: 'backbone', ecosystem: 'npm', patterns: [
37
- { re: /\bBackbone\.VERSION\s*=\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
38
- ]},
39
- { pkg: 'angular', ecosystem: 'npm', patterns: [
40
- { re: /\bangular\.version\s*=\s*\{[^}]*full\s*:\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
41
- ]},
42
- { pkg: 'vue', ecosystem: 'npm', patterns: [
43
- { re: /\bVue\.version\s*=\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
44
- ]},
45
- { pkg: 'react', ecosystem: 'npm', patterns: [
46
- { re: /\bReactVersion\s*=\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
47
- ]},
48
- { pkg: 'dompurify', ecosystem: 'npm', patterns: [
49
- { re: /\bDOMPurify\.version\s*=\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
50
- ]},
51
- { pkg: 'marked', ecosystem: 'npm', patterns: [
52
- { re: /\bmarked\.(?:defaults|setOptions|use|parse)\b[\s\S]{0,200}version\s*[:=]\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
53
- ]},
54
- { pkg: 'axios', ecosystem: 'npm', patterns: [
55
- { re: /\baxios\.VERSION\s*=\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
56
- ]},
57
- { pkg: 'socket.io-client', ecosystem: 'npm', patterns: [
58
- { re: /\bio\.protocol\s*=\s*(\d+)/, version: null },
59
- ]},
60
- { pkg: 'highlight.js', ecosystem: 'npm', patterns: [
61
- { re: /\bhljs\.versionString\s*=\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
62
- ]},
63
- { pkg: 'chart.js', ecosystem: 'npm', patterns: [
64
- { re: /\bChart\.version\s*=\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
65
- ]},
66
- ];
67
-
68
- const SKIP_DIRS = /(?:^|[/\\])(?:node_modules|vendor|dist|build|\.next|__pycache__|\.git)[/\\]/;
69
-
70
- function detectVendoredLibraries(fileContents) {
71
- if (!fileContents || typeof fileContents !== 'object') return [];
72
- const detected = [];
73
- const seen = new Set();
74
-
75
- for (const [fp, content] of Object.entries(fileContents)) {
76
- if (!content || typeof content !== 'string') continue;
77
- if (SKIP_DIRS.test(fp)) continue;
78
- if (content.length < 500) continue;
79
-
80
- for (const lib of VERSION_FINGERPRINTS) {
81
- for (const pat of lib.patterns) {
82
- const m = content.match(pat.re);
83
- if (!m) continue;
84
- const version = pat.versionGroup ? m[pat.versionGroup] : null;
85
- const key = `${lib.pkg}:${fp}`;
86
- if (seen.has(key)) continue;
87
- seen.add(key);
88
- detected.push({
89
- name: lib.pkg,
90
- version: version || 'unknown',
91
- ecosystem: lib.ecosystem,
92
- file: fp,
93
- scope: 'vendored',
94
- isVendored: true,
95
- });
96
- break;
97
- }
98
- }
99
- }
100
- // Pass 2: Function-body structural matching for minified/forked copies
101
- const FUNCTION_BODY_SIGS = [
102
- { pkg: 'lodash', ecosystem: 'npm', fn: 'merge', paramMin: 1,
103
- bodyContains: ['assignValue', 'baseFor', 'isObject', 'baseMerge'] },
104
- { pkg: 'lodash', ecosystem: 'npm', fn: 'template', paramMin: 1,
105
- bodyContains: ['sourceURL', 'interpolate', 'evaluate', 'escape'] },
106
- { pkg: 'lodash', ecosystem: 'npm', fn: 'defaultsDeep', paramMin: 1,
107
- bodyContains: ['baseMerge', 'isMergeableObject', 'customDefaultsMerge'] },
108
- { pkg: 'jquery', ecosystem: 'npm', fn: 'ajax', paramMin: 1,
109
- bodyContains: ['XMLHttpRequest', 'ajaxSettings', 'crossDomain', 'responseFields'] },
110
- { pkg: 'handlebars', ecosystem: 'npm', fn: 'compile', paramMin: 1,
111
- bodyContains: ['templateSpec', 'container', 'invokePartial', 'blockParams'] },
112
- { pkg: 'marked', ecosystem: 'npm', fn: 'parse', paramMin: 1,
113
- bodyContains: ['Lexer', 'Parser', 'blockTokens', 'walkTokens'] },
114
- { pkg: 'ejs', ecosystem: 'npm', fn: 'render', paramMin: 1,
115
- bodyContains: ['includeFile', 'resolveInclude', 'rethrow', 'escapeFn'] },
116
- { pkg: 'moment', ecosystem: 'npm', fn: 'format', paramMin: 0,
117
- bodyContains: ['formatMoment', 'expandFormat', 'makeFormatFunction', 'localFormattingTokens'] },
118
- { pkg: 'underscore', ecosystem: 'npm', fn: 'template', paramMin: 1,
119
- bodyContains: ['interpolate', 'evaluate', 'escape', 'templateSettings'] },
120
- { pkg: 'minimist', ecosystem: 'npm', fn: 'parse', paramMin: 1,
121
- bodyContains: ['boolean', 'alias', 'default', 'stopEarly', 'unknown'] },
122
- ];
123
-
124
- for (const [fp, content] of Object.entries(fileContents)) {
125
- if (!content || typeof content !== 'string') continue;
126
- if (SKIP_DIRS.test(fp)) continue;
127
- if (!/\.(?:js|mjs|cjs)$/i.test(fp)) continue;
128
- if (content.length < 200 || content.length > 500_000) continue;
129
-
130
- for (const sig of FUNCTION_BODY_SIGS) {
131
- const key = `${sig.pkg}:${fp}`;
132
- if (seen.has(key)) continue;
133
- const fnRe = new RegExp(`(?:function\\s+${sig.fn}|(?:const|let|var)\\s+${sig.fn}\\s*=|${sig.fn}\\s*[:=]\\s*function)\\s*\\(`, 'g');
134
- const m = fnRe.exec(content);
135
- if (!m) continue;
136
- const bodyWindow = content.slice(m.index, m.index + 2000);
137
- const matchCount = sig.bodyContains.filter(kw => bodyWindow.includes(kw)).length;
138
- if (matchCount < Math.ceil(sig.bodyContains.length * 0.6)) continue;
139
- seen.add(key);
140
- detected.push({
141
- name: sig.pkg,
142
- version: 'unknown',
143
- ecosystem: sig.ecosystem,
144
- file: fp,
145
- scope: 'vendored',
146
- isVendored: true,
147
- _detectionMethod: 'function-body-signature',
148
- _matchedKeywords: matchCount,
149
- });
150
- }
151
- }
152
-
153
- return detected;
154
- }
155
-
156
-
157
- /***/ })
158
-
159
- };
@@ -1,295 +0,0 @@
1
- export const id = 2238;
2
- export const ids = [2238];
3
- export const modules = {
4
-
5
- /***/ 32238:
6
- /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
-
8
- /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
- /* harmony export */ I3: () => (/* binding */ recordFixAttempt),
10
- /* harmony export */ fixDurationReport: () => (/* binding */ fixDurationReport),
11
- /* harmony export */ renderFixDurationSummary: () => (/* binding */ renderFixDurationSummary),
12
- /* harmony export */ sU: () => (/* binding */ loadFixAttempts)
13
- /* harmony export */ });
14
- /* unused harmony exports FIX_STAGES, bucketOf, summarizeFixDurations, _internals, summarizeFixAxes, renderFixAxes */
15
- /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(73024);
16
- /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(76760);
17
- /* harmony import */ var _state_dir_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(31174);
18
- // Time-to-validated-fix (R5, the reporting half).
19
- //
20
- // `verifyFix` already RUNS the stages and `test-runner.js` already times the
21
- // slowest one. What did not exist was anything durable to read afterwards, so
22
- // "how long does a fix actually take to validate" had no answer from real runs
23
- // — only an estimate (`time-to-fix.js` guesses engineering hours from family
24
- // and patch shape, before anything runs). This module is the opposite: it
25
- // records what the pipeline observed and reports the distribution.
26
- //
27
- // THE HONESTY RULES, which are most of why this file is longer than a mean:
28
- //
29
- // 1. A failed attempt is NOT a data point about how long a fix takes. Fixes
30
- // that fail verification fail fast (a re-scan that still sees the finding
31
- // never reaches the test suite), so blending them into one average makes
32
- // the pipeline look faster the worse it performs. Validated and failed
33
- // attempts are summarised separately and never merged.
34
- //
35
- // 2. "Tests skipped" is not "tests passed". A project with no detectable
36
- // suite can reach `ok:true` having run only the re-scan and the linter.
37
- // That is a weaker claim than a fix whose suite executed, and it is also
38
- // much faster, so counting the two together would quietly deflate the
39
- // headline. They get their own bucket: `validated` means the suite ran and
40
- // passed, `validatedWithoutTests` means there was no suite to run.
41
- //
42
- // 3. Every figure carries its `n`, and a percentile computed from too few
43
- // samples is labelled unreliable rather than omitted or silently reported.
44
- // Same precedent as the accuracy scorecard's `{n, d}` rates: a number
45
- // without its denominator is not a measurement.
46
- //
47
- // Storage is append-only JSONL at `<scanRoot>/.agentic-security/fix-metrics.jsonl`,
48
- // one record per verification attempt. Nothing here throws (posture
49
- // convention) — an unwritable or corrupt log degrades to "no metrics", never
50
- // to a failed verification.
51
-
52
-
53
-
54
-
55
-
56
- const LOG_FILE = 'fix-metrics.jsonl';
57
-
58
- // Below this many samples a percentile is an artifact of the sample, not a
59
- // property of the pipeline. Reported anyway (hiding it invites re-deriving it
60
- // wrong downstream) but flagged, so a caller cannot quote it as settled.
61
- const RELIABLE_N = 10;
62
-
63
- // The stages verifyFix runs, in execution order. Kept here so the recorder and
64
- // the summariser cannot drift apart on stage naming.
65
- const FIX_STAGES = Object.freeze(['rescan', 'lint', 'tests', 'honesty', 'poc']);
66
-
67
- function _logPath(scanRoot) {
68
- return (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.statePath)(scanRoot, LOG_FILE);
69
- }
70
-
71
- /**
72
- * Append one verification attempt. Best-effort and silent on failure: metrics
73
- * must never be able to fail a fix that otherwise verified.
74
- *
75
- * @returns {boolean} whether the record was written (for tests, not callers).
76
- */
77
- function recordFixAttempt(scanRoot, record) {
78
- if (!scanRoot || !record || typeof record !== 'object') return false;
79
- try {
80
- const dir = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .stateDir */ .Pn)(scanRoot);
81
- if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.isSafeStateDir)(dir)) return false;
82
- if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.stateWritesEnabled)()) return false;
83
- node_fs__WEBPACK_IMPORTED_MODULE_0__.mkdirSync(dir, { recursive: true });
84
- // One writeSync of one newline-terminated line: a concurrent reader sees
85
- // whole records or nothing, and a torn tail is dropped on read.
86
- node_fs__WEBPACK_IMPORTED_MODULE_0__.appendFileSync(_logPath(scanRoot), JSON.stringify(record) + '\n', 'utf8');
87
- return true;
88
- } catch { return false; }
89
- }
90
-
91
- /**
92
- * Read every well-formed attempt. A line that does not parse is skipped, not
93
- * fatal — the last line of an interrupted write is the expected case.
94
- */
95
- function loadFixAttempts(scanRoot) {
96
- try {
97
- const raw = node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(_logPath(scanRoot), 'utf8');
98
- const out = [];
99
- for (const line of raw.split('\n')) {
100
- if (!line.trim()) continue;
101
- try {
102
- const rec = JSON.parse(line);
103
- if (rec && typeof rec === 'object' && typeof rec.totalMs === 'number') out.push(rec);
104
- } catch { /* torn or hand-edited line — drop it, keep the rest */ }
105
- }
106
- return out;
107
- } catch { return []; }
108
- }
109
-
110
- // Nearest-rank percentile over an ascending array. Nearest-rank rather than
111
- // interpolated because these are observed durations, and an interpolated p50
112
- // reports a duration that no run actually took.
113
- function _pct(sorted, p) {
114
- if (!sorted.length) return null;
115
- const rank = Math.ceil((p / 100) * sorted.length);
116
- return sorted[Math.min(sorted.length - 1, Math.max(0, rank - 1))];
117
- }
118
-
119
- function _dist(values) {
120
- const v = values.filter(x => typeof x === 'number' && Number.isFinite(x) && x >= 0).sort((a, b) => a - b);
121
- if (!v.length) return { n: 0, minMs: null, p50Ms: null, p90Ms: null, maxMs: null, meanMs: null, reliable: false };
122
- const sum = v.reduce((a, b) => a + b, 0);
123
- return {
124
- n: v.length,
125
- minMs: v[0],
126
- p50Ms: _pct(v, 50),
127
- p90Ms: _pct(v, 90),
128
- maxMs: v[v.length - 1],
129
- meanMs: Math.round(sum / v.length),
130
- // Says whether the percentiles above may be quoted, not whether the count
131
- // is real. n and min/max/mean are exact at any sample size.
132
- reliable: v.length >= RELIABLE_N,
133
- };
134
- }
135
-
136
- // Which bucket an attempt belongs to. Deliberately total: every attempt lands
137
- // in exactly one, so the bucket counts always sum to the attempt count and a
138
- // mis-shaped record cannot silently vanish from the denominator.
139
- function bucketOf(a) {
140
- if (!a?.ok) return 'failed';
141
- return a.testsRan ? 'validated' : 'validatedWithoutTests';
142
- }
143
-
144
- /**
145
- * Summarise a set of attempts into the reported distribution.
146
- *
147
- * `validated` is the headline: attempts that verified AND whose test suite
148
- * actually ran and passed. The other two buckets exist so that headline cannot
149
- * be inflated by counting weaker or faster outcomes inside it.
150
- */
151
- function summarizeFixDurations(attempts) {
152
- const all = Array.isArray(attempts) ? attempts : [];
153
- const buckets = { validated: [], validatedWithoutTests: [], failed: [] };
154
- for (const a of all) buckets[bucketOf(a)].push(a);
155
-
156
- const byStage = {};
157
- for (const stage of FIX_STAGES) {
158
- // Per-stage timings come from validated runs only. A stage's duration in a
159
- // failed run is truncated by the failure (the pipeline stops), so mixing
160
- // them in would understate every stage after the first failure point.
161
- byStage[stage] = _dist(buckets.validated.map(a => a?.stages?.[stage]));
162
- }
163
-
164
- return {
165
- attempts: all.length,
166
- counts: {
167
- validated: buckets.validated.length,
168
- validatedWithoutTests: buckets.validatedWithoutTests.length,
169
- failed: buckets.failed.length,
170
- },
171
- timeToValidatedFix: _dist(buckets.validated.map(a => a.totalMs)),
172
- timeToValidatedFixWithoutTests: _dist(buckets.validatedWithoutTests.map(a => a.totalMs)),
173
- timeToFailure: _dist(buckets.failed.map(a => a.totalMs)),
174
- byStage,
175
- reliableAtOrAbove: RELIABLE_N,
176
- };
177
- }
178
-
179
- /** Read + summarise in one step. */
180
- function fixDurationReport(scanRoot) {
181
- return summarizeFixDurations(loadFixAttempts(scanRoot));
182
- }
183
-
184
- function _ms(v) {
185
- if (v == null) return '—';
186
- return v >= 1000 ? `${(v / 1000).toFixed(1)}s` : `${v}ms`;
187
- }
188
-
189
- /**
190
- * One-paragraph human summary. Returns null when there is nothing measured —
191
- * callers print nothing rather than printing an empty table.
192
- */
193
- function renderFixDurationSummary(sum) {
194
- if (!sum || !sum.attempts) return null;
195
- const d = sum.timeToValidatedFix;
196
- const parts = [];
197
- if (d.n) {
198
- parts.push(
199
- `time-to-validated-fix: median ${_ms(d.p50Ms)}, p90 ${_ms(d.p90Ms)} `
200
- + `(n=${d.n}${d.reliable ? '' : `, below ${sum.reliableAtOrAbove} — percentiles not yet reliable`})`,
201
- );
202
- } else {
203
- parts.push('time-to-validated-fix: no fix has both verified and had its test suite run yet');
204
- }
205
- if (sum.counts.validatedWithoutTests) {
206
- parts.push(`${sum.counts.validatedWithoutTests} verified with no detectable test suite (excluded from the median above)`);
207
- }
208
- if (sum.counts.failed) {
209
- parts.push(`${sum.counts.failed} failed verification, median ${_ms(sum.timeToFailure.p50Ms)} (counted separately)`);
210
- }
211
- return parts.join('; ') + '.';
212
- }
213
-
214
- const _internals = { _dist, _pct, RELIABLE_N };
215
-
216
-
217
- // ── PRD F6.1 — score fixes on THREE AXES, not one ──────────────────────────
218
- //
219
- // The three axes the PRD names:
220
- // (a) does the finding disappear — the rescan leg
221
- // (b) does the project's own suite pass — the tests leg
222
- // (c) does an independent verifier agree — the PoC re-check leg
223
- //
224
- // All three were already computed by verifyFixCore and then collapsed into one
225
- // boolean, which is the problem: **(a) alone is satisfiable by deleting code.**
226
- // A patch that removes the vulnerable function passes the rescan, has nothing
227
- // left to fail, and — on a project with no detectable test suite — reaches
228
- // ok:true having proven only that the detector went quiet.
229
- //
230
- // Reporting the axes separately makes that visible. `aOnly` is the number that
231
- // matters most and the one nobody was publishing: attempts that satisfied ONLY
232
- // the disappearance axis. A high aOnly with a high headline is the shape of a
233
- // remediation feature that is deleting code and calling it a fix.
234
- function summarizeFixAxes(attempts) {
235
- const list = Array.isArray(attempts) ? attempts.filter(Boolean) : [];
236
- const d = list.length;
237
-
238
- const rate = (pred) => ({ n: list.filter(pred).length, d });
239
-
240
- // Each axis is judged INDEPENDENTLY of the overall verdict, so a leg that
241
- // passed inside a failed attempt still counts for its own axis. Reading them
242
- // off `ok` would make the three axes three copies of the same number.
243
- const findingDisappeared = rate((a) => a.rescanOk === true || (a.ok === true && a.rescanOk !== false));
244
- const testsStillPass = rate((a) => a.testsRan === true && a.testsOk !== false);
245
- const verifierAgrees = rate((a) => a.pocOk === true);
246
-
247
- const satisfiesAll = rate((a) =>
248
- (a.rescanOk === true || (a.ok === true && a.rescanOk !== false))
249
- && a.testsRan === true && a.testsOk !== false
250
- && a.pocOk === true);
251
-
252
- // The honesty number: disappearance WITHOUT either corroborating axis.
253
- const aOnly = rate((a) => {
254
- const disappeared = a.rescanOk === true || (a.ok === true && a.rescanOk !== false);
255
- const corroborated = (a.testsRan === true && a.testsOk !== false) || a.pocOk === true;
256
- return disappeared && !corroborated;
257
- });
258
-
259
- return {
260
- total: d,
261
- findingDisappeared,
262
- testsStillPass,
263
- verifierAgrees,
264
- satisfiesAll,
265
- aOnly,
266
- meaning:
267
- 'findingDisappeared = the detector went quiet; testsStillPass = the project suite ran AND passed; '
268
- + 'verifierAgrees = an independent PoC re-check confirmed the hole is shut. '
269
- + 'aOnly counts attempts that satisfied ONLY disappearance — the shape a code-deleting "fix" produces.',
270
- caveat: d === 0
271
- ? 'no attempts recorded; every rate is 0/0 and means nothing'
272
- : 'rates carry {n,d}; a small d is indicative, not settled',
273
- };
274
- }
275
-
276
- /** Markdown for a report. Denominators always attached. */
277
- function renderFixAxes(sum) {
278
- if (!sum || !sum.total) return '_No fix attempts recorded._\n';
279
- const row = (label, r, note) => `| ${label} | ${r.n}/${r.d} | ${note} |`;
280
- return [
281
- '| Axis | Rate | Meaning |',
282
- '|---|---|---|',
283
- row('(a) finding disappeared', sum.findingDisappeared, 'the detector went quiet'),
284
- row('(b) project tests pass', sum.testsStillPass, 'the suite RAN and passed'),
285
- row('(c) verifier agrees', sum.verifierAgrees, 'an independent PoC re-check confirmed it'),
286
- row('all three', sum.satisfiesAll, 'the only row that means "fixed"'),
287
- row('(a) ALONE', sum.aOnly, 'satisfiable by deleting code — watch this number'),
288
- '',
289
- ].join('\n');
290
- }
291
-
292
-
293
- /***/ })
294
-
295
- };
@@ -1,152 +0,0 @@
1
- export const id = 2530;
2
- export const ids = [2530];
3
- export const modules = {
4
-
5
- /***/ 92530:
6
- /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
-
8
- /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
- /* harmony export */ verifyReleasePackage: () => (/* binding */ verifyReleasePackage)
10
- /* harmony export */ });
11
- /* unused harmony export MINIMUM_KINDS */
12
- /* harmony import */ var node_crypto__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(77598);
13
- // The verifier for a signed release assurance package (E7-03). Used by `scripts/verify-release-package.mjs` (a command-line wrapper) and by `release-view`.
14
- //
15
- // This file imports nothing but Node built-ins. It does not use the producer's code, so a defect in the producer cannot make it agree with itself. What it
16
- // checks, each reported under its own code:
17
- // FORMAT known schema, supported major version, no unknown top-level keys
18
- // SIGNATURE Ed25519 over the domain label plus the canonical JSON of everything but the signature, verified with the key the CALLER supplies; the key the
19
- // package names must be the same key (a package cannot vouch for itself, and a signature lifted from another package does not verify)
20
- // SNAPSHOT the snapshot digest matches its fields
21
- // EVIDENCE every record matches the digest in its reference; every reference matches its own digest; every reference is bound to this snapshot
22
- // LINEAGE every dependency is present, there is no cycle, and the stated order is the dependency order
23
- // MANDATORY every kind in the verifier's own minimum, in the caller's --require list, and in the package's own list is present
24
- // COMPLETE the package may not claim a complete scan over a scan health that is not complete
25
- // FINDINGS each finding's proof tier and exploitability flag equal what the finding itself says
26
- // ASSERTIONS every cited record is in the package, and a "supported" statement cites at least one
27
-
28
-
29
- const MINIMUM_KINDS = Object.freeze(['scan-health', 'finding']);
30
- const SCHEMA = 'agentic-security/release-package';
31
- const DOMAIN = `${SCHEMA}@1\n`;
32
- const TOP_KEYS = new Set(['schema', 'schemaVersion', 'snapshot', 'evidence', 'lineageOrder', 'requiredKinds', 'findings', 'completeness', 'policy', 'assertions', 'proves', 'doesNotProve', 'issuance', 'signature']);
33
- const ROLES = new Set(['finding', 'completeness', 'context', 'control', 'inventory']);
34
-
35
- const canon = (v) => {
36
- if (v === null || typeof v !== 'object') return JSON.stringify(v ?? null);
37
- if (Array.isArray(v)) return `[${v.map(canon).join(',')}]`;
38
- return `{${Object.keys(v).sort().map((k) => `${JSON.stringify(k)}:${canon(v[k])}`).join(',')}}`;
39
- };
40
- const digest = (v) => `sha256:${node_crypto__WEBPACK_IMPORTED_MODULE_0__.createHash('sha256').update(canon(v)).digest('hex')}`;
41
- const isObj = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
42
-
43
- function verifyReleasePackage(pkg, publicKeyPem, { require: extraRequired = [] } = {}) {
44
- const errors = [];
45
- const fail = (code, message) => errors.push({ code, message });
46
- if (!isObj(pkg)) return { ok: false, errors: [{ code: 'FORMAT', message: 'the package is not an object' }] };
47
- if (pkg.schema !== SCHEMA) fail('FORMAT', `unrecognised schema ${JSON.stringify(pkg.schema)}`);
48
- const major = Number(String(pkg.schemaVersion).split('.')[0]);
49
- if (major !== 1) fail('FORMAT', `unsupported version ${JSON.stringify(pkg.schemaVersion)}`);
50
- for (const k of Object.keys(pkg)) if (!TOP_KEYS.has(k)) fail('FORMAT', `unrecognised top-level key "${k}", which the signature does not vouch for`);
51
- if (errors.length) return { ok: false, errors };
52
-
53
- // ---- SIGNATURE
54
- const sig = pkg.signature;
55
- let publicKey = null;
56
- if (!publicKeyPem) fail('SIGNATURE', 'no public key was supplied');
57
- else { try { publicKey = node_crypto__WEBPACK_IMPORTED_MODULE_0__.createPublicKey(publicKeyPem); } catch (e) { fail('SIGNATURE', `the public key is unusable: ${e.message}`); } }
58
- if (!isObj(sig) || sig.algorithm !== 'ed25519' || typeof sig.value !== 'string') fail('SIGNATURE', 'the package is unsigned or uses an unsupported algorithm');
59
- else if (publicKey) {
60
- const { signature, ...rest } = pkg;
61
- let ok = false;
62
- try { ok = node_crypto__WEBPACK_IMPORTED_MODULE_0__.verify(null, Buffer.from(DOMAIN + canon(rest), 'utf8'), publicKey, Buffer.from(sig.value, 'base64')); } catch (e) { fail('SIGNATURE', `verification error: ${e.message}`); }
63
- if (!ok && !errors.some((e) => e.code === 'SIGNATURE')) fail('SIGNATURE', 'the signature does not match the package contents: something changed after signing, or it was signed by another key');
64
- const fp = node_crypto__WEBPACK_IMPORTED_MODULE_0__.createHash('sha256').update(publicKey.export({ type: 'spki', format: 'der' })).digest('hex');
65
- const named = pkg.issuance && pkg.issuance.issuer && pkg.issuance.issuer.keyFingerprint;
66
- if (named !== fp) fail('SIGNATURE', 'the key the package names is not the key supplied for verification');
67
- if (!pkg.issuance || pkg.issuance.independentlyCertified !== false) fail('SIGNATURE', 'a self-issued package may not claim independent certification');
68
- }
69
-
70
- // ---- SNAPSHOT
71
- const snap = pkg.snapshot;
72
- if (!isObj(snap)) fail('SNAPSHOT', 'there is no snapshot');
73
- else {
74
- const { digest: d, immutable, ...body } = snap;
75
- if (d !== digest(body)) fail('SNAPSHOT', 'the snapshot does not match its digest');
76
- if (immutable !== (!!body.commit && body.dirty === false)) fail('SNAPSHOT', 'the snapshot\'s immutability flag is not what its fields imply');
77
- }
78
-
79
- // ---- EVIDENCE
80
- const entries = Array.isArray(pkg.evidence) ? pkg.evidence : [];
81
- if (!Array.isArray(pkg.evidence)) fail('EVIDENCE', 'evidence is not a list');
82
- const byDigest = new Map();
83
- for (const [i, e] of entries.entries()) {
84
- if (!isObj(e) || !isObj(e.ref) || !('payload' in e)) { fail('EVIDENCE', `entry ${i} is malformed`); continue; }
85
- if (!ROLES.has(e.role)) fail('EVIDENCE', `entry ${i} has an unknown role ${JSON.stringify(e.role)}`);
86
- const { digest: rd, findingProvenanceDigest, otherProvenanceDigest, ...rb } = e.ref;
87
- if (rd !== digest(rb)) fail('EVIDENCE', `the reference of entry ${i} (${e.ref.kind}) does not match its own digest`);
88
- if (e.ref.payloadDigest !== digest(e.payload)) fail('EVIDENCE', `the ${e.ref.kind} record in entry ${i} does not match the digest in its reference`);
89
- if (isObj(snap) && e.ref.snapshotDigest !== snap.digest) fail('EVIDENCE', `the ${e.ref.kind} record in entry ${i} describes a different snapshot`);
90
- if (byDigest.has(e.ref.digest)) fail('EVIDENCE', `entry ${i} duplicates another reference`);
91
- byDigest.set(e.ref.digest, e);
92
- }
93
-
94
- // ---- LINEAGE
95
- for (const e of byDigest.values()) for (const dep of e.ref.restsOn || []) if (!byDigest.has(dep)) fail('LINEAGE', `${e.ref.kind} rests on a record that is not in the package (${String(dep).slice(0, 19)}...)`);
96
- const state = new Map(); const order = [];
97
- const visit = (d, trail) => {
98
- if (state.get(d) === 2) return; if (state.get(d) === 1) { fail('LINEAGE', `cycle through ${trail.concat(d).map((x) => x.slice(7, 15)).join(' -> ')}`); return; }
99
- state.set(d, 1);
100
- for (const dep of (byDigest.get(d) || { ref: {} }).ref.restsOn || []) if (byDigest.has(dep)) visit(dep, trail.concat(d));
101
- state.set(d, 2); order.push(d);
102
- };
103
- for (const d of [...byDigest.keys()].sort()) visit(d, []);
104
- if (JSON.stringify(order) !== JSON.stringify(pkg.lineageOrder)) fail('LINEAGE', 'the stated order is not the dependency order');
105
- if (JSON.stringify(entries.map((e) => e && e.ref && e.ref.digest)) !== JSON.stringify(pkg.lineageOrder)) fail('LINEAGE', 'the evidence list is not in the stated order');
106
-
107
- // ---- MANDATORY
108
- const kinds = new Set([...byDigest.values()].map((e) => e.ref.kind));
109
- const needed = new Set([...MINIMUM_KINDS, ...extraRequired, ...(Array.isArray(pkg.requiredKinds) ? pkg.requiredKinds : [])]);
110
- for (const k of MINIMUM_KINDS) if (!(Array.isArray(pkg.requiredKinds) && pkg.requiredKinds.includes(k))) fail('MANDATORY', `the package does not declare "${k}" as required`);
111
- for (const k of [...needed].sort()) if (!kinds.has(k)) fail('MANDATORY', `required evidence is missing: ${k}`);
112
-
113
- // ---- COMPLETE
114
- const healthEntry = [...byDigest.values()].find((e) => e.ref.kind === 'scan-health');
115
- const c = pkg.completeness;
116
- if (!isObj(c)) fail('COMPLETE', 'there is no completeness statement');
117
- else if (healthEntry) {
118
- const hs = healthEntry.payload && healthEntry.payload.status;
119
- if (c.scanStatus !== hs) fail('COMPLETE', `the package states scan status ${JSON.stringify(c.scanStatus)} but the scan health says ${JSON.stringify(hs)}`);
120
- if (c.verdict === 'complete' && hs !== 'complete') fail('COMPLETE', 'the package claims a complete scan over a scan health that is not complete');
121
- if (c.verdict !== 'complete' && hs === 'complete') fail('COMPLETE', 'the package calls an unfinished scan complete or the reverse: the verdict disagrees with the scan health');
122
- if (JSON.stringify(c.conditions) !== JSON.stringify(Array.isArray(healthEntry.payload.conditions) ? healthEntry.payload.conditions : [])) fail('COMPLETE', 'the stated conditions differ from the scan health\'s own');
123
- if (c.source !== healthEntry.ref.digest) fail('COMPLETE', 'the completeness statement cites a different scan health');
124
- }
125
-
126
- // ---- FINDINGS
127
- const findingEntries = [...byDigest.values()].filter((e) => e.ref.kind === 'finding');
128
- const listed = new Map((Array.isArray(pkg.findings) ? pkg.findings : []).map((f) => [f.findingRef, f]));
129
- if (listed.size !== findingEntries.length) fail('FINDINGS', `the package lists ${listed.size} findings but contains ${findingEntries.length}`);
130
- for (const e of findingEntries) {
131
- const f = listed.get(e.ref.digest);
132
- if (!f) { fail('FINDINGS', `finding ${e.ref.payloadId} is in the package but not in its findings summary`); continue; }
133
- const tier = e.payload && typeof e.payload.proofTier === 'string' ? e.payload.proofTier : 'unproven';
134
- if (f.proofTier !== tier) fail('FINDINGS', `finding ${e.ref.payloadId}: the summary says proof tier ${JSON.stringify(f.proofTier)} but the finding says ${JSON.stringify(tier)}`);
135
- if (f.exploitabilityProven !== (tier === 'execution-proven')) fail('FINDINGS', `finding ${e.ref.payloadId}: exploitability is stated as proven without an execution proof`);
136
- if (f.integrity !== 'unmodified-since-signing') fail('FINDINGS', `finding ${e.ref.payloadId}: unexpected integrity statement`);
137
- }
138
-
139
- // ---- ASSERTIONS
140
- for (const a of Array.isArray(pkg.assertions) ? pkg.assertions : []) {
141
- for (const d of a.evidence || []) if (!byDigest.has(d)) fail('ASSERTIONS', `assertion ${a.id} cites a record that is not in the package`);
142
- if (a.status === 'supported' && !(a.evidence || []).length) fail('ASSERTIONS', `assertion ${a.id} is "supported" but cites nothing`);
143
- if (!['supported', 'unsupported', 'unknown'].includes(a.status)) fail('ASSERTIONS', `assertion ${a.id} has an unknown status`);
144
- }
145
- return { ok: errors.length === 0, errors, issuer: pkg.issuance && pkg.issuance.issuer, completeness: isObj(c) ? c.verdict : null };
146
- }
147
-
148
-
149
-
150
- /***/ })
151
-
152
- };