@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,311 +0,0 @@
1
- export const id = 1621;
2
- export const ids = [1621,6995];
3
- export const modules = {
4
-
5
- /***/ 66995:
6
- /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
-
8
- /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
- /* harmony export */ addLegalHold: () => (/* binding */ addLegalHold),
10
- /* harmony export */ isUnderHold: () => (/* binding */ isUnderHold),
11
- /* harmony export */ listLegalHolds: () => (/* binding */ listLegalHolds),
12
- /* harmony export */ loadLegalHolds: () => (/* binding */ loadLegalHolds),
13
- /* harmony export */ removeLegalHold: () => (/* binding */ removeLegalHold)
14
- /* harmony export */ });
15
- /* unused harmony export LEGAL_HOLD_FILE */
16
- /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(73024);
17
- /* harmony import */ var _state_dir_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(69259);
18
- /* harmony import */ var _artifact_registry_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(81734);
19
- // FR-707 (assurance-hardening PRD): "Support legal hold and policy-
20
- // authorized retention exceptions | Legal hold is identity-bound, reasoned,
21
- // time-bounded where applicable, and auditable."
22
- //
23
- // A third instance of the recurring {owner, reason, expires_at} exception
24
- // shape this codebase already uses twice — `posture/suppressions.js`'s
25
- // pro-tier exception (scoped to a FINDING) and `posture/compliance-policy.js`'s
26
- // structured `not-applicable` (scoped to a COMPLIANCE CONTROL). Per D-0025,
27
- // these are deliberately distinct mechanisms serving different subjects, not
28
- // one shared module — this file is the third subject: a STATE ARTIFACT.
29
- //
30
- // Field naming matches the existing two schemas' snake_case convention
31
- // (`owner`, `reason`, `expires_at`) rather than inventing a fourth style.
32
- //
33
- // identity-bound -> `owner` (required, who placed the hold and is
34
- // accountable for lifting it)
35
- // reasoned -> `reason` (required — "we might need this later" is
36
- // not a reason; same discipline suppressions.js
37
- // already enforces for its own exceptions)
38
- // time-bounded
39
- // where applicable -> `expires_at` is OPTIONAL: a null/absent value is
40
- // an INDEFINITE hold, which the acceptance
41
- // criterion's own "where applicable" phrase
42
- // explicitly allows (a genuine legal matter may have
43
- // no known end date) — an ISO date value behaves
44
- // exactly like FR-506/FR-1004's own expiring
45
- // exceptions: once past, the hold is no longer
46
- // active and the artifact is exposed to its normal
47
- // retention TTL again.
48
- // auditable -> persisted as a single JSON array under
49
- // `.agentic-security/legal-holds.json` (itself
50
- // registered as operator-config — an operator/legal
51
- // team's own input, never scanner-written from scan
52
- // results), readable via `listLegalHolds`.
53
- //
54
- // Consulted from TWO places, not one: `retention-policy.js#findExpiredArtifacts`
55
- // (defense in depth for any caller reaching it directly) AND `cmdReset`
56
- // itself for its PLAIN (non-`--expired`) path, which deletes every
57
- // registered 'generated' artifact unconditionally and would otherwise blow
58
- // through a hold that only gated TTL expiry.
59
-
60
-
61
-
62
-
63
-
64
- const LEGAL_HOLD_FILE = 'legal-holds.json';
65
-
66
- function _loadRaw(scanRoot) {
67
- let fp;
68
- try { fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_1__.statePath)(scanRoot, LEGAL_HOLD_FILE); } catch { return []; }
69
- let raw;
70
- try { raw = node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(fp, 'utf8'); } catch { return []; }
71
- try {
72
- const doc = JSON.parse(raw);
73
- return Array.isArray(doc) ? doc : [];
74
- } catch { return []; }
75
- }
76
-
77
- /**
78
- * All legal holds ever recorded for this project, malformed entries
79
- * dropped rather than throwing. Never filters by expiry — see
80
- * `isUnderHold`/`listLegalHolds` for that.
81
- */
82
- function loadLegalHolds(scanRoot) {
83
- return _loadRaw(scanRoot).filter(h => h && typeof h === 'object' && typeof h.artifact === 'string' && typeof h.owner === 'string' && typeof h.reason === 'string');
84
- }
85
-
86
- /**
87
- * Is `artifactName` currently protected by an active (non-expired) hold?
88
- * Returns the matching hold record, or null. Multiple holds on the same
89
- * artifact are permitted (e.g. two independent legal matters); the first
90
- * still-active one found is returned.
91
- */
92
- function isUnderHold(artifactName, holds, now = Date.now()) {
93
- for (const h of holds) {
94
- if (h.artifact !== artifactName) continue;
95
- if (!h.expires_at) return h; // indefinite hold — always active
96
- const t = Date.parse(h.expires_at);
97
- if (!Number.isFinite(t) || t >= now) return h;
98
- }
99
- return null;
100
- }
101
-
102
- /**
103
- * Active (non-expired) holds only, unless `includeExpired`. For
104
- * `legal-hold list` / auditing.
105
- */
106
- function listLegalHolds(scanRoot, { includeExpired = false, now = Date.now() } = {}) {
107
- const holds = loadLegalHolds(scanRoot);
108
- if (includeExpired) return holds;
109
- return holds.filter(h => {
110
- if (!h.expires_at) return true;
111
- const t = Date.parse(h.expires_at);
112
- return !Number.isFinite(t) || t >= now;
113
- });
114
- }
115
-
116
- /**
117
- * Add a legal hold. Validates identity-bound + reasoned up front (both
118
- * required, non-empty) and that `expires_at`, if given, is a parseable
119
- * date in the future — an already-expired hold would be a hold that
120
- * protects nothing, which is never a legitimate request. `artifact` must
121
- * name a real registered artifact (artifact-registry.js) — a hold on an
122
- * unrecognised name can never protect anything and almost always means a
123
- * typo. Returns `{ok:true, hold}` or `{ok:false, reason}`; never throws.
124
- */
125
- function addLegalHold(scanRoot, { artifact, owner, reason, expires_at } = {}) {
126
- if (!artifact || typeof artifact !== 'string') return { ok: false, reason: '--artifact is required' };
127
- if (!(0,_artifact_registry_js__WEBPACK_IMPORTED_MODULE_2__/* .isRegisteredArtifact */ .Jl)(artifact)) return { ok: false, reason: `"${artifact}" is not a registered state artifact` };
128
- if (!owner || typeof owner !== 'string') return { ok: false, reason: '--owner is required (identity-bound)' };
129
- if (!reason || typeof reason !== 'string') return { ok: false, reason: '--reason is required (reasoned)' };
130
- if (expires_at) {
131
- const t = Date.parse(expires_at);
132
- if (!Number.isFinite(t)) return { ok: false, reason: 'expires_at must be a parseable date' };
133
- if (t < Date.now()) return { ok: false, reason: 'expires_at is in the past — a hold that already expired protects nothing' };
134
- }
135
- const hold = { artifact, owner, reason, expires_at: expires_at || null, created_at: new Date().toISOString() };
136
- const holds = _loadRaw(scanRoot);
137
- holds.push(hold);
138
- const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_1__.statePath)(scanRoot, LEGAL_HOLD_FILE);
139
- if (!(0,_state_dir_js__WEBPACK_IMPORTED_MODULE_1__.safeWriteState)(fp, JSON.stringify(holds, null, 2) + '\n')) {
140
- return { ok: false, reason: 'state writes are disabled (--no-state) or this is not a safe state directory' };
141
- }
142
- return { ok: true, hold };
143
- }
144
-
145
- /**
146
- * Remove every hold on `artifact` (lifting a hold, not letting it expire).
147
- * Returns the number removed. A no-op (0) if none existed — never an error.
148
- */
149
- function removeLegalHold(scanRoot, artifact) {
150
- const holds = _loadRaw(scanRoot);
151
- const remaining = holds.filter(h => !(h && h.artifact === artifact));
152
- const removedCount = holds.length - remaining.length;
153
- if (removedCount > 0) {
154
- const fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_1__.statePath)(scanRoot, LEGAL_HOLD_FILE);
155
- (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_1__.safeWriteState)(fp, JSON.stringify(remaining, null, 2) + '\n');
156
- }
157
- return removedCount;
158
- }
159
-
160
-
161
- /***/ }),
162
-
163
- /***/ 11621:
164
- /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
165
-
166
- /* harmony export */ __webpack_require__.d(__webpack_exports__, {
167
- /* harmony export */ findExpiredArtifacts: () => (/* binding */ findExpiredArtifacts)
168
- /* harmony export */ });
169
- /* unused harmony exports RETENTION_DEFAULTS, loadRetentionPolicy, effectiveTtlDays */
170
- /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(73024);
171
- /* harmony import */ var _util_yaml_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(32469);
172
- /* harmony import */ var _state_dir_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(69259);
173
- /* harmony import */ var _artifact_registry_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(81734);
174
- /* harmony import */ var _legal_hold_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(66995);
175
- // Retention policy: default and maximum TTL by artifact class (assurance-
176
- // hardening PRD FR-702).
177
- //
178
- // "Enforce default and maximum TTL by artifact class | Expired caches,
179
- // scans, evidence, tickets, and backups are purged or archived according
180
- // to policy."
181
- //
182
- // TWO NUMBERS PER CLASS, ON PURPOSE
183
- // --------------------------------------------------------------------------
184
- // `defaultDays` is what applies when nobody configures anything — a
185
- // reasonable, disclosed starting point, not a regulatory citation (an
186
- // operator with a real compliance regime should configure their own via
187
- // the policy file below). `maxDays` is a CEILING an operator's own
188
- // configuration cannot exceed: the literal "default AND maximum" wording
189
- // asks for both a floor-free default and a hard cap, not just a knob. An
190
- // operator who wants indefinite retention for `evidence` for their own
191
- // audit reasons should say so in their own retention program — this
192
- // module will not silently allow a TTL past its built-in ceiling for a
193
- // class, because "TTL" than can be configured to "never" is not a TTL.
194
- //
195
- // PURGE, NOT ARCHIVE
196
- // --------------------------------------------------------------------------
197
- // The acceptance criterion allows either. This implements PURGE (delete)
198
- // only — the simpler, safer, and more directly verifiable of the two.
199
- // "Archive" implies a defined export format and destination, which is
200
- // FR-706's own separate scope (manifest-based export); an operator who
201
- // wants to archive before purging already has the tool for it once FR-706
202
- // exists, or can back up `.agentic-security/` by their own means before
203
- // running the enforcement command.
204
- //
205
- // WHICH ARTIFACTS THIS APPLIES TO
206
- // --------------------------------------------------------------------------
207
- // Only 'generated' artifacts carrying a `retentionClass` in
208
- // artifact-registry.js — deliberately a SUBSET of all generated artifacts
209
- // (see that module's own header for which ones were left classless and
210
- // why). An 'operator-config' artifact is NEVER touched by this module,
211
- // regardless of age, matching FR-703's own "reset preserves operator-
212
- // authored configuration" precedent.
213
-
214
-
215
-
216
-
217
-
218
-
219
-
220
- const POLICY_FILE = 'retention-policy.yml';
221
- const MS_PER_DAY = 24 * 60 * 60 * 1000;
222
-
223
- // Engineering defaults, not a regulatory claim — see the header above.
224
- const RETENTION_DEFAULTS = {
225
- cache: { defaultDays: 7, maxDays: 30 },
226
- scan: { defaultDays: 90, maxDays: 365 },
227
- evidence: { defaultDays: 365, maxDays: 1095 },
228
- ticket: { defaultDays: 180, maxDays: 730 },
229
- backup: { defaultDays: 30, maxDays: 180 },
230
- };
231
-
232
- /**
233
- * Load an operator's `.agentic-security/retention-policy.yml`. Never
234
- * throws — a missing or malformed file degrades to "no overrides," the
235
- * same no-op-until-configured convention this repo uses for every other
236
- * policy surface. Shape:
237
- * cache: { defaultDays: 3 }
238
- * evidence: { defaultDays: 730 }
239
- * A class not mentioned, or a file not present at all, uses
240
- * RETENTION_DEFAULTS unmodified.
241
- */
242
- function loadRetentionPolicy(scanRoot) {
243
- if (!scanRoot) return null;
244
- let fp;
245
- try { fp = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__.statePath)(scanRoot, POLICY_FILE); } catch { return null; }
246
- let raw;
247
- try { raw = node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(fp, 'utf8'); } catch { return null; }
248
- try {
249
- const doc = _util_yaml_js__WEBPACK_IMPORTED_MODULE_1__/* .load */ .Hh(raw);
250
- if (!doc || typeof doc !== 'object') return null;
251
- return doc;
252
- } catch { return null; }
253
- }
254
-
255
- /**
256
- * The TTL (in days) actually in effect for a class, after applying any
257
- * operator override and clamping it to the class's own maxDays — an
258
- * override CANNOT raise retention past the ceiling, only lower it (or
259
- * leave it at the default). An unrecognised class (should not happen —
260
- * only artifact-registry.js's own 5 named classes are ever passed in)
261
- * degrades to null, meaning "not subject to a TTL."
262
- */
263
- function effectiveTtlDays(retentionClass, policy) {
264
- const bounds = RETENTION_DEFAULTS[retentionClass];
265
- if (!bounds) return null;
266
- const override = policy?.[retentionClass]?.defaultDays;
267
- if (typeof override === 'number' && Number.isFinite(override) && override >= 0) {
268
- return Math.min(override, bounds.maxDays);
269
- }
270
- return bounds.defaultDays;
271
- }
272
-
273
- /**
274
- * Which registered, retention-classed artifacts under `scanRoot`'s state
275
- * dir are currently past their effective TTL. Never throws — a missing
276
- * state dir or an artifact that does not exist on disk is simply absent
277
- * from the result, not an error.
278
- *
279
- * @returns {Array<{name: string, isDir: boolean, retentionClass: string,
280
- * ageDays: number, ttlDays: number}>}
281
- */
282
- function findExpiredArtifacts(scanRoot, { now = Date.now() } = {}) {
283
- const dir = (0,_state_dir_js__WEBPACK_IMPORTED_MODULE_2__/* .stateDir */ .Pn)(scanRoot);
284
- let dirExists = true;
285
- try { node_fs__WEBPACK_IMPORTED_MODULE_0__.accessSync(dir); } catch { dirExists = false; }
286
- if (!dirExists) return [];
287
- const policy = loadRetentionPolicy(scanRoot);
288
- // FR-707: an artifact under an active legal hold is NEVER reported as
289
- // expired, regardless of TTL — checked here (not just in cmdReset) so
290
- // any future caller of this function inherits the same guarantee.
291
- const holds = (0,_legal_hold_js__WEBPACK_IMPORTED_MODULE_4__.loadLegalHolds)(scanRoot);
292
- const expired = [];
293
- for (const artifact of (0,_artifact_registry_js__WEBPACK_IMPORTED_MODULE_3__/* .listArtifactsWithRetentionClass */ .U_)()) {
294
- const ttlDays = effectiveTtlDays(artifact.retentionClass, policy);
295
- if (ttlDays === null) continue;
296
- if ((0,_legal_hold_js__WEBPACK_IMPORTED_MODULE_4__.isUnderHold)(artifact.name, holds, now)) continue;
297
- const p = `${dir}/${artifact.name}`;
298
- let st;
299
- try { st = node_fs__WEBPACK_IMPORTED_MODULE_0__.statSync(p); } catch { continue; } // not present — nothing to expire
300
- const ageDays = (now - st.mtimeMs) / MS_PER_DAY;
301
- if (ageDays > ttlDays) {
302
- expired.push({ name: artifact.name, isDir: st.isDirectory(), retentionClass: artifact.retentionClass, ageDays, ttlDays });
303
- }
304
- }
305
- return expired;
306
- }
307
-
308
-
309
- /***/ })
310
-
311
- };
@@ -1,160 +0,0 @@
1
- export const id = 1751;
2
- export const ids = [1751];
3
- export const modules = {
4
-
5
- /***/ 71751:
6
- /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
-
8
- /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
- /* harmony export */ loadFreshLineageGraph: () => (/* binding */ loadFreshLineageGraph),
10
- /* harmony export */ loadSignedGraph: () => (/* binding */ loadSignedGraph)
11
- /* harmony export */ });
12
- /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(73024);
13
- /* harmony import */ var _posture_state_dir_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(69259);
14
- /* harmony import */ var _posture_integrity_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(37455);
15
- // graph-loader.js — Milestone 3, sub-project Server, increment 1.
16
- //
17
- // Reads and VERIFIES the `.agentic-security/lineage-graph.json` artifact
18
- // before `explore` is allowed to serve a single byte of it. Reuses
19
- // `posture/integrity.js`'s `verifyLastScan` DIRECTLY (per the plan and the
20
- // root CLAUDE.md's own instruction) — this module does not implement any
21
- // signature comparison of its own. `verifyLastScan` already uses
22
- // `crypto.timingSafeEqual` internally.
23
- //
24
- // Loaded ONCE at server startup (see bin/agentic-security.js's cmdExplore)
25
- // and held in memory for the life of the process — this is a read-only,
26
- // single-scan-snapshot server; a change to the graph on disk mid-session is
27
- // out of scope for this increment (threat-model doc's own "P0 is
28
- // read-only" framing).
29
-
30
-
31
-
32
-
33
-
34
- /**
35
- * @param {string} scanRoot
36
- * @returns {{ok:true, graph:object} | {ok:false, reason:'missing'|'unsigned'|'tampered'|'malformed', message:string}}
37
- *
38
- * Four, and only four, distinct failure reasons — each with its own clear
39
- * message so an operator knows exactly what to do next:
40
- * - 'missing' — no lineage-graph.json at all. Run a scan with
41
- * AGENTIC_SECURITY_LINEAGE_DEEP=1 first.
42
- * - 'unsigned' — the graph exists but its .sig sibling does not
43
- * (verifyLastScan returns null). Refuse to serve an
44
- * unverifiable graph.
45
- * - 'tampered' — the graph exists and has a .sig, but the signature does
46
- * not match the body (verifyLastScan returns false). The
47
- * file was modified after signing, or signed under a
48
- * different install key.
49
- * - 'malformed' — the body passed signature verification but is not
50
- * valid JSON. Should not happen from a normal scan; the
51
- * file may be corrupted on disk after signing.
52
- */
53
- function loadSignedGraph(scanRoot) {
54
- const graphPath = (0,_posture_state_dir_js__WEBPACK_IMPORTED_MODULE_1__.statePath)(scanRoot, 'lineage-graph.json');
55
- const sigPath = graphPath + '.sig';
56
-
57
- if (!node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(graphPath)) {
58
- return {
59
- ok: false,
60
- reason: 'missing',
61
- message: `No lineage graph found at ${graphPath}. Run a scan with AGENTIC_SECURITY_LINEAGE_DEEP=1 first (e.g. \`AGENTIC_SECURITY_LINEAGE_DEEP=1 agentic-security scan\`), then re-run \`agentic-security explore\`.`,
62
- };
63
- }
64
-
65
- let body;
66
- try {
67
- body = node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(graphPath, 'utf8');
68
- } catch (e) {
69
- return {
70
- ok: false,
71
- reason: 'missing',
72
- message: `Lineage graph found at ${graphPath} but could not be read: ${e && e.message ? e.message : e}.`,
73
- };
74
- }
75
-
76
- const verified = (0,_posture_integrity_js__WEBPACK_IMPORTED_MODULE_2__/* .verifyLastScan */ .Ef)(body, sigPath);
77
- if (verified === null) {
78
- return {
79
- ok: false,
80
- reason: 'unsigned',
81
- message: `Lineage graph at ${graphPath} has no signature file (${sigPath} is missing). Refusing to serve an unverifiable graph. Re-run the scan (AGENTIC_SECURITY_LINEAGE_DEEP=1) to regenerate both files together.`,
82
- };
83
- }
84
- if (verified === false) {
85
- return {
86
- ok: false,
87
- reason: 'tampered',
88
- message: `Lineage graph at ${graphPath} FAILED signature verification — its contents do not match ${sigPath}. The file may have been modified after the scan, or signed under a different install key. Refusing to serve a tampered graph. Re-run the scan to regenerate it.`,
89
- };
90
- }
91
-
92
- let graph;
93
- try {
94
- graph = JSON.parse(body);
95
- } catch (e) {
96
- return {
97
- ok: false,
98
- reason: 'malformed',
99
- message: `Lineage graph at ${graphPath} passed signature verification but is not valid JSON (${e && e.message ? e.message : e}). This should not happen from a normal scan — the file may be corrupted. Re-run the scan to regenerate it.`,
100
- };
101
- }
102
-
103
- return { ok: true, graph };
104
- }
105
-
106
- /**
107
- * Load .agentic-security/lineage-graph.json ONLY when it is genuinely
108
- * fresh for THIS scan — never merely because a file happens to exist on
109
- * disk. Shared by every caller that signs or narrates a graph:-derived
110
- * compliance claim (M4 sub-project 6c's final whole-branch review found
111
- * the identical staleness gap independently reachable from
112
- * `attest --obligations` AND `compliance --walkthrough`, and required
113
- * this predicate to live in exactly one place rather than being
114
- * copy-pasted per caller — a safety check that drifts between two
115
- * near-identical inline copies is worse than one shared bug).
116
- *
117
- * `.agentic-security/lineage-graph.json` is only rewritten when a scan
118
- * actually finishes building a graph (`if (scan.lineageGraph)` in
119
- * bin/agentic-security.js's persistence code) — an ordinary non-deep
120
- * rescan, or a deep scan whose lineage build fails, leaves whatever file
121
- * was there from an earlier successful deep scan untouched. Loading that
122
- * stale graph and joining it to the CURRENT scan's other data would let a
123
- * caller assert a graph-derived fact (e.g. "transit protected") about
124
- * code that has since changed.
125
- *
126
- * `enabled: true` in `scan.scanHealth.lineageAnalysis` does NOT by itself
127
- * mean the build succeeded — engine.js sets it the moment
128
- * AGENTIC_SECURITY_LINEAGE_DEEP=1 is read, before the build even starts,
129
- * and leaves it `true` even when the build later throws (only `failure`
130
- * gets set in that case). `requested && enabled` alone therefore still
131
- * accepts a stale graph after a failed rebuild — reproduced live via the
132
- * scan's own already-shipped fault-injection fixture
133
- * (test/lineage-fault-injection.test.js) before this `failure === null`
134
- * check was added.
135
- *
136
- * @param {string} scanRoot
137
- * @param {object} scan - the parsed last-scan.json for the CURRENT scan
138
- * @returns {{graph:object|null, fresh:boolean, loaded:ReturnType<typeof loadSignedGraph>}}
139
- * `fresh` is true only when a signed graph loaded successfully AND this
140
- * scan's own scanHealth confirms lineage analysis was requested,
141
- * enabled, and did not fail. `graph` is `loaded.graph` when fresh, else
142
- * `null` — never the stale file, even when one exists on disk.
143
- * `loaded` is the raw `loadSignedGraph` result, so a caller can still
144
- * distinguish "no file at all" from "a file exists but isn't fresh" for
145
- * its own disclosure message.
146
- */
147
- function loadFreshLineageGraph(scanRoot, scan) {
148
- const la = scan?.scanHealth?.lineageAnalysis;
149
- const requested = la?.requested === true;
150
- const enabled = la?.enabled === true;
151
- const failure = la?.failure ?? null;
152
- const loaded = loadSignedGraph(scanRoot);
153
- const fresh = loaded.ok && requested && enabled && failure === null;
154
- return { graph: fresh ? loaded.graph : null, fresh, loaded };
155
- }
156
-
157
-
158
- /***/ })
159
-
160
- };