@clear-capabilities/agentic-security-scanner 0.185.0 → 0.186.2

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 (174) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/bin/agentic-security.js +66 -18
  3. package/dist/1128.index.js +25 -21
  4. package/dist/{5817.index.js → 1602.index.js} +378 -29
  5. package/dist/2584.index.js +1 -1
  6. package/dist/4171.index.js +2 -2
  7. package/dist/{0b301d45b231f144a1b4.json → 4d06c8944ae121d7085c.json} +8 -8
  8. package/dist/5752.index.js +466 -0
  9. package/dist/7405.index.js +1 -1
  10. package/dist/8158.index.js +1713 -0
  11. package/dist/8798.index.js +1 -1
  12. package/dist/9382.index.js +2 -2
  13. package/dist/9560.index.js +1 -1
  14. package/dist/9579.index.js +1 -1
  15. package/dist/agentic-security.mjs +29 -29
  16. package/dist/agentic-security.mjs.sha256 +1 -1
  17. package/package.json +8 -8
  18. package/src/dataflow/CLAUDE.md +6 -3
  19. package/src/dataflow/access-paths.js +12 -2
  20. package/src/dataflow/demand-query.js +279 -40
  21. package/src/dataflow/dep-fingerprint.js +1 -1
  22. package/src/dataflow/exploit-prover.js +6 -2
  23. package/src/dataflow/ir-cache.js +268 -0
  24. package/src/dataflow/privacy-taint.js +7 -0
  25. package/src/engine.js +62 -5
  26. package/src/ir/CLAUDE.md +4 -0
  27. package/src/ir/callgraph.js +36 -8
  28. package/src/ir/index.js +18 -4
  29. package/src/ir/parser-js.js +10 -0
  30. package/src/language/CLAUDE.md +13 -0
  31. package/src/language/nix-option-types.js +170 -0
  32. package/src/language/nix-script-taint.js +46 -3
  33. package/src/language/nix-secrets.js +20 -2
  34. package/src/lineage/CLAUDE.md +2 -1
  35. package/src/lineage/smt-orchestrator.js +47 -11
  36. package/src/lineage/smt-z3-backend.js +294 -0
  37. package/src/posture/CLAUDE.md +5 -1
  38. package/src/posture/business-logic.js +16 -7
  39. package/src/posture/dag-commands.js +442 -0
  40. package/src/posture/finding-defaults.js +19 -0
  41. package/src/posture/git-history.js +91 -1
  42. package/src/posture/proof-fabric/cli.js +1 -1
  43. package/src/posture/proof-fabric/container.js +2 -2
  44. package/src/posture/proof-fabric/generate-haskell.js +205 -0
  45. package/src/posture/proof-fabric/generate.js +6 -3
  46. package/src/posture/proof-fabric/reproducer.js +4 -2
  47. package/src/posture/proof-fabric/runner.js +4 -4
  48. package/src/sast/CLAUDE.md +2 -0
  49. package/src/sast/_call-args.js +70 -0
  50. package/src/sast/init-scope.js +87 -0
  51. package/src/sast/listen-bind-address.js +49 -0
  52. package/src/sast/redirect-target.js +106 -0
  53. package/src/sast/reflected-input.js +76 -0
  54. package/src/sast/send-file-root.js +27 -0
  55. package/src/sast/sensitive-data-logging.js +38 -1
  56. package/dist/119.index.js +0 -197
  57. package/dist/128.index.js +0 -129
  58. package/dist/1293.index.js +0 -5689
  59. package/dist/1336.index.js +0 -16331
  60. package/dist/1351.index.js +0 -133
  61. package/dist/1387.index.js +0 -271
  62. package/dist/1437.index.js +0 -23
  63. package/dist/1445.index.js +0 -110
  64. package/dist/1505.index.js +0 -151
  65. package/dist/1557.index.js +0 -293
  66. package/dist/1621.index.js +0 -311
  67. package/dist/1751.index.js +0 -160
  68. package/dist/1765.index.js +0 -727
  69. package/dist/1861.index.js +0 -690
  70. package/dist/1906.index.js +0 -433
  71. package/dist/208.index.js +0 -1631
  72. package/dist/2223.index.js +0 -159
  73. package/dist/2238.index.js +0 -295
  74. package/dist/2530.index.js +0 -152
  75. package/dist/2565.index.js +0 -116
  76. package/dist/2574.index.js +0 -127
  77. package/dist/2620.index.js +0 -345
  78. package/dist/2773.index.js +0 -98
  79. package/dist/2775.index.js +0 -32
  80. package/dist/2966.index.js +0 -171
  81. package/dist/2972.index.js +0 -87
  82. package/dist/2991.index.js +0 -131
  83. package/dist/3063.index.js +0 -833
  84. package/dist/3075.index.js +0 -227
  85. package/dist/3121.index.js +0 -297
  86. package/dist/3314.index.js +0 -263
  87. package/dist/3480.index.js +0 -1822
  88. package/dist/3481.index.js +0 -24
  89. package/dist/3506.index.js +0 -268
  90. package/dist/3533.index.js +0 -591
  91. package/dist/3653.index.js +0 -450
  92. package/dist/3740.index.js +0 -722
  93. package/dist/3822.index.js +0 -131
  94. package/dist/3868.index.js +0 -292
  95. package/dist/3918.index.js +0 -192
  96. package/dist/4090.index.js +0 -288
  97. package/dist/4113.index.js +0 -1286
  98. package/dist/4121.index.js +0 -1190
  99. package/dist/4142.index.js +0 -690
  100. package/dist/4168.index.js +0 -509
  101. package/dist/4281.index.js +0 -993
  102. package/dist/4336.index.js +0 -436
  103. package/dist/4387.index.js +0 -532
  104. package/dist/4413.index.js +0 -302
  105. package/dist/4476.index.js +0 -4510
  106. package/dist/4491.index.js +0 -133
  107. package/dist/4538.index.js +0 -185
  108. package/dist/4684.index.js +0 -319
  109. package/dist/4740.index.js +0 -165
  110. package/dist/4780.index.js +0 -209
  111. package/dist/4802.index.js +0 -225
  112. package/dist/4812.index.js +0 -379
  113. package/dist/4835.index.js +0 -126
  114. package/dist/494.index.js +0 -226
  115. package/dist/4991.index.js +0 -549
  116. package/dist/5199.index.js +0 -305
  117. package/dist/5216.index.js +0 -109
  118. package/dist/5439.index.js +0 -301
  119. package/dist/5443.index.js +0 -97
  120. package/dist/5496.index.js +0 -377
  121. package/dist/5738.index.js +0 -240
  122. package/dist/5801.index.js +0 -588
  123. package/dist/5858.index.js +0 -223
  124. package/dist/5933.index.js +0 -143
  125. package/dist/601.index.js +0 -23
  126. package/dist/6173.index.js +0 -273
  127. package/dist/6291.index.js +0 -1871
  128. package/dist/6312.index.js +0 -666
  129. package/dist/634.index.js +0 -16182
  130. package/dist/644.index.js +0 -87
  131. package/dist/6508.index.js +0 -283
  132. package/dist/6560.index.js +0 -645
  133. package/dist/6576.index.js +0 -422
  134. package/dist/6579.index.js +0 -271
  135. package/dist/6655.index.js +0 -3221
  136. package/dist/6745.index.js +0 -305
  137. package/dist/6818.index.js +0 -21642
  138. package/dist/6932.index.js +0 -1190
  139. package/dist/6972.index.js +0 -167
  140. package/dist/6995.index.js +0 -163
  141. package/dist/7249.index.js +0 -191
  142. package/dist/7372.index.js +0 -1197
  143. package/dist/7546.index.js +0 -541
  144. package/dist/76.index.js +0 -448
  145. package/dist/7879.index.js +0 -163
  146. package/dist/8025.index.js +0 -130
  147. package/dist/8054.index.js +0 -248
  148. package/dist/808.index.js +0 -1330
  149. package/dist/8097.index.js +0 -201
  150. package/dist/8126.index.js +0 -191
  151. package/dist/8178.index.js +0 -78
  152. package/dist/8382.index.js +0 -93
  153. package/dist/8408.index.js +0 -261
  154. package/dist/8540.index.js +0 -174
  155. package/dist/8635.index.js +0 -802
  156. package/dist/8688.index.js +0 -410
  157. package/dist/8699.index.js +0 -779
  158. package/dist/8891.index.js +0 -457
  159. package/dist/8916.index.js +0 -885
  160. package/dist/9146.index.js +0 -170
  161. package/dist/9168.index.js +0 -841
  162. package/dist/9292.index.js +0 -369
  163. package/dist/9422.index.js +0 -866
  164. package/dist/9440.index.js +0 -453
  165. package/dist/9479.index.js +0 -152
  166. package/dist/9481.index.js +0 -220
  167. package/dist/9539.index.js +0 -100
  168. package/dist/9547.index.js +0 -103
  169. package/dist/9692.index.js +0 -90
  170. package/dist/9800.index.js +0 -462
  171. package/dist/9899.index.js +0 -138
  172. package/dist/9981.index.js +0 -206
  173. package/dist/999.index.js +0 -117
  174. package/dist/9999.index.js +0 -1698
@@ -1,131 +0,0 @@
1
- export const id = 3822;
2
- export const ids = [3822];
3
- export const modules = {
4
-
5
- /***/ 3822:
6
- /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
-
8
- /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
- /* harmony export */ buildArtifactProvenanceGraph: () => (/* binding */ buildArtifactProvenanceGraph),
10
- /* harmony export */ traceArtifactProvenanceGraph: () => (/* binding */ traceArtifactProvenanceGraph)
11
- /* harmony export */ });
12
- // PRD 5 P0 §6 — Provenance Graph: Git Commit -> Build -> OCI Digest -> SBOM
13
- // Component -> Finding -> Deployment.
14
- //
15
- // Naming: the exported field is `artifactProvenanceGraph` and the builder is
16
- // `buildArtifactProvenanceGraph` — never a bare `.provenance`/`*Provenance`,
17
- // per this repo's own naming discipline (5 unrelated existing uses of
18
- // "provenance"; see docs/implementation-status/source-to-artifact-provenance.md).
19
- //
20
- // This function does no new detection — every node/edge is assembled from
21
- // evidence `inventory.js`/`vuln-correlate.js` already computed. The one
22
- // exception is the Git-Commit edge, whose only evidence source is the
23
- // standard `org.opencontainers.image.revision` build label; when absent the
24
- // commit node is explicitly `known: false`, never inferred from git history
25
- // (an image's build label is the only trustworthy claim of "which commit
26
- // built this" — matching it against local git history by heuristic would be
27
- // a confident-looking guess, the exact failure mode PRD 5's signature-state
28
- // discipline exists to prevent elsewhere in this same PRD).
29
- const REVISION_LABEL = 'org.opencontainers.image.revision';
30
-
31
- function componentId(c) {
32
- return c.purl || `${c.ecosystem}:${c.name}@${c.version}`;
33
- }
34
-
35
- /**
36
- * `inventory` is `inventory.js#buildImageInventory`'s `inventory`.
37
- * `vulnerabilities` is `vuln-correlate.js`'s `queryOSVFn` result (only
38
- * `type: 'vulnerable_dep'` entries become Finding nodes — the no_lockfile",
39
- * "unpinned_dep" structural checks don't bind to a specific component and
40
- * are not part of this graph). `deployment`, if supplied, is an
41
- * OPERATOR-DECLARED record (`{ id, environment, evidence }`) — this codebase
42
- * has no mechanism anywhere that discovers "which digest is running where"
43
- * on its own (`lineage/deployment/` models topology, not running digests),
44
- * so a missing `deployment` argument means the graph simply stops at the
45
- * OCI Digest / SBOM Component / Finding nodes, never a fabricated edge.
46
- */
47
- function buildArtifactProvenanceGraph({ inventory, vulnerabilities = [], deployment = null }) {
48
- if (!inventory || typeof inventory.digest !== 'string') {
49
- throw new TypeError('buildArtifactProvenanceGraph requires an inventory with a digest');
50
- }
51
- const nodes = [];
52
- const edges = [];
53
-
54
- const commitSha = inventory.buildLabels ? inventory.buildLabels[REVISION_LABEL] || null : null;
55
- const commitId = `commit:${commitSha || 'unknown'}`;
56
- const buildId = `build:${inventory.digest}`;
57
- const digestId = `digest:${inventory.digest}`;
58
-
59
- nodes.push({ id: commitId, kind: 'gitCommit', sha: commitSha, known: !!commitSha });
60
- nodes.push({ id: buildId, kind: 'build', digest: inventory.digest, labels: inventory.buildLabels || {} });
61
- nodes.push({ id: digestId, kind: 'ociDigest', digest: inventory.digest, digestKind: inventory.digestKind, repoTags: inventory.repoTags || [] });
62
-
63
- edges.push({
64
- from: commitId, to: buildId,
65
- evidence: commitSha ? `label:${REVISION_LABEL}` : 'none',
66
- });
67
- edges.push({ from: buildId, to: digestId, evidence: 'same-archive' });
68
-
69
- const components = [...(inventory.osPackages || []), ...(inventory.appPackages || [])];
70
- for (const c of components) {
71
- const compId = `component:${componentId(c)}`;
72
- nodes.push({ id: compId, kind: 'sbomComponent', purl: c.purl || null, name: c.name, version: c.version, ecosystem: c.ecosystem });
73
- edges.push({ from: digestId, to: compId, evidence: 'inventory' });
74
- }
75
-
76
- const vulnDeps = (vulnerabilities || []).filter((v) => v.type === 'vulnerable_dep');
77
- for (const v of vulnDeps) {
78
- const compId = `component:${v.purl || `${v.ecosystem}:${v.name}@${v.version}`}`;
79
- const findingId = `finding:${v.osvId}:${v.purl || `${v.ecosystem}:${v.name}@${v.version}`}`;
80
- nodes.push({ id: findingId, kind: 'finding', osvId: v.osvId, cveAliases: v.cveAliases || [], severity: v.severity });
81
- edges.push({ from: compId, to: findingId, evidence: 'osv' });
82
- }
83
-
84
- if (deployment) {
85
- if (typeof deployment.id !== 'string') throw new TypeError('a supplied deployment record requires an "id"');
86
- const deploymentId = `deployment:${deployment.id}`;
87
- nodes.push({ id: deploymentId, kind: 'deployment', environment: deployment.environment || null, declaredDigest: deployment.digest || null });
88
- edges.push({
89
- from: digestId, to: deploymentId,
90
- evidence: deployment.evidence || 'operator-declared',
91
- digestMatches: deployment.digest ? deployment.digest === inventory.digest : null,
92
- });
93
- }
94
-
95
- return { nodes, edges };
96
- }
97
-
98
- /**
99
- * Walks the graph from a commit or digest to every reachable Finding/Deployment
100
- * node — the `provenance trace` CLI command's query surface.
101
- */
102
- function traceArtifactProvenanceGraph(graph, fromId) {
103
- if (!graph || !Array.isArray(graph.nodes) || !Array.isArray(graph.edges)) {
104
- throw new TypeError('traceArtifactProvenanceGraph requires a graph with nodes/edges arrays');
105
- }
106
- const byId = new Map(graph.nodes.map((n) => [n.id, n]));
107
- if (!byId.has(fromId)) return { found: false, path: [] };
108
- const adjacency = new Map();
109
- for (const e of graph.edges) {
110
- if (!adjacency.has(e.from)) adjacency.set(e.from, []);
111
- adjacency.get(e.from).push(e);
112
- }
113
- const visited = new Set([fromId]);
114
- const queue = [fromId];
115
- const reached = [];
116
- while (queue.length) {
117
- const cur = queue.shift();
118
- for (const e of adjacency.get(cur) || []) {
119
- if (visited.has(e.to)) continue;
120
- visited.add(e.to);
121
- reached.push({ node: byId.get(e.to), via: e });
122
- queue.push(e.to);
123
- }
124
- }
125
- return { found: true, root: byId.get(fromId), reached };
126
- }
127
-
128
-
129
- /***/ })
130
-
131
- };
@@ -1,292 +0,0 @@
1
- export const id = 3868;
2
- export const ids = [3868];
3
- export const modules = {
4
-
5
- /***/ 33868:
6
- /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
-
8
- /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
- /* harmony export */ MEMORY_PROFILES: () => (/* binding */ MEMORY_PROFILES),
10
- /* harmony export */ capabilitiesFromFamilyHint: () => (/* binding */ capabilitiesFromFamilyHint),
11
- /* harmony export */ classifyModelFamily: () => (/* binding */ classifyModelFamily),
12
- /* harmony export */ detectMemoryTier: () => (/* binding */ detectMemoryTier),
13
- /* harmony export */ detectSystemMemory: () => (/* binding */ detectSystemMemory),
14
- /* harmony export */ recommendAdmission: () => (/* binding */ recommendAdmission)
15
- /* harmony export */ });
16
- /* unused harmony exports KNOWN_MODEL_SIZE_GB, evaluateMemoryAdmission */
17
- /* harmony import */ var node_os__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(48161);
18
- /* harmony import */ var _oom_feedback_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(67181);
19
- // Model family hints, RAM-aware memory profiles, and the memory-admission
20
- // check for the Ollama provider (agentic-security-ollama-offline-prd.md
21
- // §13, §14, §15, §22.3, §30).
22
- //
23
- // FAMILY HINTS ARE DEFAULTS, NEVER AUTHORITY (PRD §12/§13). A name like
24
- // `gemma4:e2b` tells us nothing Ollama itself won't confirm — it only lets the
25
- // harness suggest a sane default before any network call. If a model actually
26
- // installed under a family-hinted name lacks a capability the hint implied,
27
- // the runtime probe (model-probe.js, added when tool-calling/structured-output
28
- // probing lands) always wins. This module only classifies and estimates; it
29
- // never asserts a capability is present.
30
- //
31
- // MEMORY NUMBERS ARE ESTIMATES, NOT PROMISES (PRD §22.3, §14.1). Ollama
32
- // artifact sizes and this module's headroom reserves are best-effort figures
33
- // sourced from what Ollama currently publishes; they exist so the harness can
34
- // fail BEFORE an OS-level OOM, not so it can claim an exact answer. Every
35
- // admission decision leaves a stated safety margin rather than trying to pack
36
- // memory to the byte.
37
-
38
-
39
-
40
-
41
- // PRD §12/§13 FR-1203 — non-authoritative family hint from a model name.
42
- // Longest/most-specific pattern first so `qwen3.5:4b` doesn't fall through to
43
- // the bare `qwen` bucket.
44
- const FAMILY_PATTERNS = [
45
- [/^qwen3\.5/i, 'qwen3.5'],
46
- [/^qwen3-coder-next/i, 'qwen3-coder-next'],
47
- [/^qwen3-coder/i, 'qwen3-coder'],
48
- [/^qwen2\.5-coder/i, 'qwen2.5-coder'],
49
- [/^qwen3/i, 'qwen3'],
50
- [/^qwen/i, 'qwen'],
51
- [/^gemma4/i, 'gemma4'],
52
- [/^functiongemma/i, 'functiongemma'],
53
- [/^gemma3/i, 'gemma3'],
54
- [/^gemma/i, 'gemma'],
55
- ];
56
-
57
- /** Non-authoritative family classification for defaults/messaging only. */
58
- function classifyModelFamily(modelName) {
59
- const name = String(modelName || '').trim();
60
- for (const [re, family] of FAMILY_PATTERNS) if (re.test(name)) return family;
61
- return 'unknown';
62
- }
63
-
64
- // PRD §13.1 — non-authoritative defaults per family, overridden by any real
65
- // runtime probe result (model-probe.js). `tools`/`structuredJson`/`thinking`
66
- // are 'unknown' where Ollama's own behavior varies by specific tag/quant
67
- // rather than by family alone.
68
- const FAMILY_CAPABILITY_HINTS = {
69
- 'qwen3.5': { chat: true, structuredJson: true, tools: true, thinking: 'unknown' },
70
- qwen3: { chat: true, structuredJson: true, tools: true, thinking: 'unknown' },
71
- 'qwen3-coder': { chat: true, structuredJson: true, tools: true, thinking: false },
72
- 'qwen3-coder-next': { chat: true, structuredJson: true, tools: true, thinking: false },
73
- 'qwen2.5-coder': { chat: true, structuredJson: true, tools: 'unknown', thinking: false },
74
- qwen: { chat: true, structuredJson: 'unknown', tools: 'unknown', thinking: 'unknown' },
75
- gemma4: { chat: true, structuredJson: true, tools: true, thinking: 'unknown' },
76
- functiongemma: { chat: true, structuredJson: 'unknown', tools: true, thinking: false },
77
- gemma3: { chat: true, structuredJson: true, tools: false, thinking: false },
78
- gemma: { chat: true, structuredJson: 'unknown', tools: 'unknown', thinking: 'unknown' },
79
- unknown: { chat: true, structuredJson: 'unknown', tools: 'unknown', thinking: 'unknown' },
80
- };
81
-
82
- /**
83
- * Build the PRD §13.1 ModelCapabilities object from a family hint alone
84
- * (Layer B). Layer A (Ollama's own /api/show metadata) and Layer C (runtime
85
- * probes) are applied by the caller and override these fields — this
86
- * function only ever sets `source.familyHint: true`.
87
- */
88
- function capabilitiesFromFamilyHint(modelName) {
89
- const family = classifyModelFamily(modelName);
90
- const hint = FAMILY_CAPABILITY_HINTS[family] || FAMILY_CAPABILITY_HINTS.unknown;
91
- return {
92
- chat: hint.chat,
93
- structuredJson: hint.structuredJson,
94
- tools: hint.tools,
95
- thinking: hint.thinking,
96
- vision: false,
97
- contextTokens: undefined,
98
- source: { metadata: false, familyHint: true, runtimeProbe: false },
99
- };
100
- }
101
-
102
- // ── RAM-aware memory profiles (PRD §14.4, §15.2, §22.3, §30) ───────────────
103
-
104
- const MB = 1024 * 1024;
105
- const GB = 1024 * MB;
106
-
107
- // Best-effort artifact sizes as currently distributed by Ollama, used only to
108
- // pick a SENSIBLE STARTING recommendation — the real admission decision below
109
- // uses actually-free memory, not this table. Keep in sync with the PRD's own
110
- // cited figures; a stale entry only affects the suggested default, never the
111
- // admission math (which reads real os.freemem()).
112
- const KNOWN_MODEL_SIZE_GB = Object.freeze({
113
- 'qwen3.5:2b': 1.7,
114
- 'qwen3.5:4b': 3.4,
115
- 'qwen3.5:9b': 6.6,
116
- 'gemma4:e2b': 7.2,
117
- 'gemma4:12b': 7.6,
118
- 'gemma4:latest': 9.6,
119
- });
120
-
121
- /** PRD §30 profile presets. `auto` picks between these by detected RAM. */
122
- const MEMORY_PROFILES = Object.freeze({
123
- '8gb': {
124
- label: '8gb',
125
- preferredModel: 'qwen3.5:4b',
126
- fallbackModel: 'qwen3.5:2b',
127
- initialContextTokens: 4096,
128
- targetContextTokens: 8192,
129
- maxConcurrency: 1,
130
- minFreeRamMb: 1536,
131
- },
132
- '16gb-qwen': {
133
- label: '16gb-qwen',
134
- preferredModel: 'qwen3.5:9b',
135
- fallbackModel: 'qwen3.5:4b',
136
- initialContextTokens: 16384,
137
- targetContextTokens: 32768,
138
- maxConcurrency: 1,
139
- minFreeRamMb: 2048,
140
- },
141
- '16gb-gemma': {
142
- label: '16gb-gemma',
143
- preferredModel: 'gemma4:e2b',
144
- fallbackModel: 'qwen3.5:4b',
145
- initialContextTokens: 8192,
146
- targetContextTokens: 16384,
147
- maxConcurrency: 1,
148
- minFreeRamMb: 2048,
149
- },
150
- });
151
-
152
- /**
153
- * PRD §22.3 — detect total/available system RAM. Thin wrapper over `os` so
154
- * tests can inject fake values without mocking the `os` module globally.
155
- */
156
- function detectSystemMemory({ totalBytes, freeBytes } = {}) {
157
- return {
158
- totalBytes: Number.isFinite(totalBytes) ? totalBytes : node_os__WEBPACK_IMPORTED_MODULE_0__.totalmem(),
159
- freeBytes: Number.isFinite(freeBytes) ? freeBytes : node_os__WEBPACK_IMPORTED_MODULE_0__.freemem(),
160
- };
161
- }
162
-
163
- /**
164
- * Pick the RAM tier ('8gb' | '16gb') a machine falls into. Anything under
165
- * ~9 GB total is treated as the 8 GB tier — real "8 GB" machines report
166
- * slightly less than 8*1024^3 bytes to userspace (firmware/GPU reservations),
167
- * so a hard `< 8*GB` cutoff would misclassify real 8 GB hardware as unknown.
168
- */
169
- function detectMemoryTier(totalBytes) {
170
- if (!Number.isFinite(totalBytes) || totalBytes <= 0) return 'unknown';
171
- if (totalBytes < 9 * GB) return '8gb';
172
- return '16gb';
173
- }
174
-
175
- /**
176
- * PRD §22.3 admission algorithm: does `contextTokens` at `model` fit in
177
- * currently-free memory with the configured reserve intact?
178
- *
179
- * This is deliberately conservative and coarse (PRD "avoid pretending memory
180
- * estimates are exact"): model residency is estimated from KNOWN_MODEL_SIZE_GB
181
- * when available (falling back to a pessimistic 8 GB assumption for an
182
- * unrecognized tag so an unknown model never LOOKS safer than a known large
183
- * one), and KV-cache growth is approximated as a fixed per-1K-token cost
184
- * rather than modeled per-architecture — real KV cache size depends on layer
185
- * count/head count/quantization the harness cannot know without Ollama's own
186
- * runtime numbers.
187
- */
188
- const ESTIMATED_KV_CACHE_MB_PER_1K_TOKENS = 32; // conservative, model-independent approximation
189
- const RUNTIME_OVERHEAD_MB = 512; // Ollama server + OS scheduler slack, independent of model size
190
-
191
- function evaluateMemoryAdmission({
192
- modelName,
193
- contextTokens,
194
- freeBytes,
195
- minFreeRamMb,
196
- modelSizeGb,
197
- } = {}) {
198
- const sizeGb = Number.isFinite(modelSizeGb) ? modelSizeGb : (KNOWN_MODEL_SIZE_GB[modelName] ?? 8);
199
- const modelMb = sizeGb * 1024;
200
- const kvCacheMb = (Number(contextTokens) || 0) / 1000 * ESTIMATED_KV_CACHE_MB_PER_1K_TOKENS;
201
- const requiredMb = modelMb + kvCacheMb + RUNTIME_OVERHEAD_MB + (Number(minFreeRamMb) || 0);
202
- const freeMb = (Number(freeBytes) || 0) / MB;
203
- const admitted = freeMb >= requiredMb;
204
- return {
205
- admitted,
206
- freeMb: Math.round(freeMb),
207
- requiredMb: Math.round(requiredMb),
208
- modelEstimateMb: Math.round(modelMb),
209
- kvCacheEstimateMb: Math.round(kvCacheMb),
210
- reserveMb: Number(minFreeRamMb) || 0,
211
- };
212
- }
213
-
214
- /**
215
- * Full recommendation flow (PRD §22 "Memory admission algorithm"):
216
- * try the profile's preferred context, shrink it, then fall back to the
217
- * profile's smaller model, before ever declaring the profile unusable.
218
- * Never recommends cloud — the worst outcome this function can return is
219
- * `{admitted:false}` with a human-readable explanation, which callers treat
220
- * as "run deterministic-only" (PRD §23.4).
221
- */
222
- function recommendAdmission(opts = {}) {
223
- const result = _recommendAdmissionCore(opts);
224
- // Adversarial-review fix (2026-09): a memory-admission ESTIMATE that
225
- // actually caused a real OOM (ollama-provider.js's callOllamaChat now
226
- // records this via oom-feedback.js) used to have no way to affect a
227
- // future admission decision for the SAME model on the SAME machine — the
228
- // exact same "admitted: true" would repeat forever. This does not
229
- // recalibrate the underlying estimate (that needs real hardware variety
230
- // one machine's observed failures can't substitute for); it attaches an
231
- // honest warning so the decision is no longer presented with unqualified
232
- // confidence.
233
- const chosenModel = result.model || opts.requestedModel;
234
- const prior = chosenModel ? (0,_oom_feedback_js__WEBPACK_IMPORTED_MODULE_1__/* .priorOOMFor */ .NL)(chosenModel) : null;
235
- if (prior) {
236
- return {
237
- ...result,
238
- priorOOMWarning: `'${chosenModel}' has previously failed with an out-of-memory error on this machine ` +
239
- `(${prior.count} time${prior.count === 1 ? '' : 's'}, most recently ${new Date(prior.lastAt).toISOString()}). ` +
240
- 'The memory estimate below may be optimistic for your hardware.',
241
- };
242
- }
243
- return result;
244
- }
245
-
246
- function _recommendAdmissionCore({ profile, freeBytes, requestedContextTokens, requestedModel } = {}) {
247
- const p = MEMORY_PROFILES[profile];
248
- if (!p) return { admitted: false, reason: `unknown memory profile '${profile}'` };
249
-
250
- const model = requestedModel || p.preferredModel;
251
- const attempts = [];
252
-
253
- // 1. Requested (or target) context at the requested/preferred model.
254
- const primaryContext = Number.isFinite(requestedContextTokens) ? requestedContextTokens : p.targetContextTokens;
255
- let check = evaluateMemoryAdmission({ modelName: model, contextTokens: primaryContext, freeBytes, minFreeRamMb: p.minFreeRamMb });
256
- attempts.push({ model, contextTokens: primaryContext, ...check });
257
- if (check.admitted) return { admitted: true, model, contextTokens: primaryContext, attempts };
258
-
259
- // 2. Reduce context to the profile's conservative initial value first —
260
- // PRD FR-2104: "shrink context before declaring an otherwise compatible
261
- // model unusable."
262
- if (primaryContext !== p.initialContextTokens) {
263
- check = evaluateMemoryAdmission({ modelName: model, contextTokens: p.initialContextTokens, freeBytes, minFreeRamMb: p.minFreeRamMb });
264
- attempts.push({ model, contextTokens: p.initialContextTokens, ...check });
265
- if (check.admitted) return { admitted: true, model, contextTokens: p.initialContextTokens, attempts, reducedContext: true };
266
- }
267
-
268
- // 3. Fall back to the profile's smaller model at its initial context.
269
- if (p.fallbackModel && p.fallbackModel !== model) {
270
- check = evaluateMemoryAdmission({ modelName: p.fallbackModel, contextTokens: p.initialContextTokens, freeBytes, minFreeRamMb: p.minFreeRamMb });
271
- attempts.push({ model: p.fallbackModel, contextTokens: p.initialContextTokens, ...check });
272
- if (check.admitted) {
273
- return {
274
- admitted: true, model: p.fallbackModel, contextTokens: p.initialContextTokens, attempts,
275
- reducedContext: true, fellBackToSmallerModel: true,
276
- };
277
- }
278
- }
279
-
280
- // 4. Nothing fits — deterministic-only, never cloud.
281
- return {
282
- admitted: false,
283
- attempts,
284
- reason: `No local model/context combination fit in available memory with the configured reserve. ` +
285
- `Recommend deterministic-only scanning, or free memory before retrying.`,
286
- };
287
- }
288
-
289
-
290
- /***/ })
291
-
292
- };
@@ -1,192 +0,0 @@
1
- export const id = 3918;
2
- export const ids = [3918];
3
- export const modules = {
4
-
5
- /***/ 63918:
6
- /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
-
8
- /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
- /* harmony export */ evaluatePolicy: () => (/* binding */ evaluatePolicy)
10
- /* harmony export */ });
11
- /* unused harmony export _internals */
12
- /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(73024);
13
- /* harmony import */ var node_child_process__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(31421);
14
- // Policy-as-code gate (FR-SDLC-9).
15
- //
16
- // Today's CI gate is `--fail-on <severity>`. That's coarse. Customers want
17
- // to write nuanced rules: "fail if any sql-injection finding has
18
- // confidence ≥ 0.8 AND the file is under src/api/", or "fail if total
19
- // exploitability score across critical findings exceeds 5".
20
- //
21
- // We support two modes:
22
- //
23
- // 1. EXTERNAL OPA: if the `opa` binary is on PATH and `--policy <file.rego>`
24
- // is supplied, we shell out to `opa eval -d <file> -i <findings.json>
25
- // "data.<package>.deny"`. This is the right answer for customers who
26
- // already use OPA elsewhere.
27
- //
28
- // 2. EMBEDDED MINI: when no opa binary is available, fall back to a tiny
29
- // DSL that's a strict subset of rego. Rules read top-level
30
- // `package`/`deny` blocks; each `deny` is a JS-evaluable expression
31
- // over findings[]. This lets the v1 ship without an external binary
32
- // dep while documenting the upgrade path.
33
-
34
-
35
-
36
-
37
- // ─── External OPA ─────────────────────────────────────────────────────────
38
-
39
- function _haveOpa() {
40
- try {
41
- const r = (0,node_child_process__WEBPACK_IMPORTED_MODULE_1__.spawnSync)('opa', ['version'], { stdio: 'ignore', timeout: 3000 });
42
- return r.status === 0;
43
- } catch { return false; }
44
- }
45
-
46
- function _runOpa(policyFile, findingsJsonPath, packageName) {
47
- const r = (0,node_child_process__WEBPACK_IMPORTED_MODULE_1__.spawnSync)('opa', [
48
- 'eval', '-d', policyFile, '-i', findingsJsonPath,
49
- `data.${packageName}.deny`,
50
- ], { encoding: 'utf8', timeout: 10_000 });
51
- if (r.error) return { ok: false, reason: `opa-error:${r.error.code || r.error.message}` };
52
- if (r.status !== 0) return { ok: false, reason: `opa-exit:${r.status}`, stderr: r.stderr };
53
- try {
54
- const parsed = JSON.parse(r.stdout);
55
- const result = parsed.result?.[0]?.expressions?.[0]?.value;
56
- return { ok: true, denials: Array.isArray(result) ? result : [] };
57
- } catch (e) {
58
- return { ok: false, reason: `opa-output-parse:${e.message}` };
59
- }
60
- }
61
-
62
- // ─── Embedded mini DSL ────────────────────────────────────────────────────
63
- //
64
- // Rego is too big to reimplement. We support a tiny shape:
65
- //
66
- // # POLICY: agentic-security policy-gate v1
67
- // deny[msg] {
68
- // finding := input.findings[_]
69
- // finding.severity == "critical"
70
- // msg := sprintf("critical finding: %v at %v", [finding.vuln, finding.file])
71
- // }
72
- //
73
- // Parser strategy: extract each `deny[msg] { ... }` block; translate the
74
- // body to a JS predicate. The grammar we accept is:
75
- //
76
- // - `<lhs> == <value>` / `<lhs> != <value>` / `<lhs> > <num>` / `<lhs> < <num>`
77
- // - `<lhs>` references `finding.<field>` or `input.<field>`
78
- // - `msg := "..."` or `msg := sprintf("...", [args])` — the msg literal
79
- // - newlines + `;` as separators
80
- //
81
- // Anything more complex requires the external OPA binary.
82
-
83
- function _parseEmbedded(policyText) {
84
- const blocks = [];
85
- // Match each `deny[NAME] { ... }` block (or `deny { ... }`).
86
- const blockRe = /\bdeny(?:\s*\[\s*(\w+)\s*\])?\s*\{([\s\S]*?)\}/g;
87
- let m;
88
- while ((m = blockRe.exec(policyText))) {
89
- const body = m[2];
90
- const conditions = [];
91
- let msgExpr = `"policy violation"`;
92
- for (const line of body.split(/[\n;]/)) {
93
- const ln = line.trim();
94
- if (!ln || ln.startsWith('#')) continue;
95
- // Assignment: `<id> := <expr>`
96
- const asn = ln.match(/^(\w+)\s*:=\s*(.+)$/);
97
- if (asn && asn[1] !== 'msg') continue; // skip non-msg assignments
98
- if (asn && asn[1] === 'msg') { msgExpr = asn[2].trim(); continue; }
99
- // Comparison: `finding.<field> <op> <value>`
100
- const cmp = ln.match(/^(finding|input)\.([a-zA-Z_][\w.]*)\s*(==|!=|<=|>=|<|>)\s*(.+)$/);
101
- if (!cmp) continue;
102
- const [, scope, field, op, valueRaw] = cmp;
103
- let value = valueRaw.trim();
104
- if (/^".*"$/.test(value)) value = JSON.stringify(value.slice(1, -1));
105
- conditions.push({ scope, field, op, value });
106
- }
107
- blocks.push({ conditions, msgExpr });
108
- }
109
- return blocks;
110
- }
111
-
112
- function _evalBlock(block, finding) {
113
- for (const c of block.conditions) {
114
- const lhs = _resolvePath(finding, c.field);
115
- let rhs;
116
- try { rhs = JSON.parse(c.value); }
117
- catch { rhs = c.value.replace(/^"|"$/g, ''); }
118
- if (c.op === '==' && lhs !== rhs) return null;
119
- if (c.op === '!=' && lhs === rhs) return null;
120
- if (c.op === '>' && !(Number(lhs) > Number(rhs))) return null;
121
- if (c.op === '<' && !(Number(lhs) < Number(rhs))) return null;
122
- if (c.op === '>=' && !(Number(lhs) >= Number(rhs))) return null;
123
- if (c.op === '<=' && !(Number(lhs) <= Number(rhs))) return null;
124
- }
125
- return _renderMsg(block.msgExpr, finding);
126
- }
127
-
128
- function _resolvePath(obj, dotPath) {
129
- return dotPath.split('.').reduce((o, k) => (o == null ? undefined : o[k]), obj);
130
- }
131
-
132
- function _renderMsg(expr, finding) {
133
- // Strip outer quotes if string literal.
134
- const m = expr.match(/^["'](.+)["']$/);
135
- if (m) return m[1];
136
- // sprintf shape: sprintf("...", [a, b])
137
- const sf = expr.match(/^sprintf\s*\(\s*"([^"]+)"\s*,\s*\[(.+)\]\s*\)$/);
138
- if (sf) {
139
- const fmt = sf[1];
140
- const args = sf[2].split(',').map(s => _resolvePath(finding, s.trim().replace(/^finding\./, '')));
141
- let i = 0;
142
- return fmt.replace(/%v/g, () => String(args[i++] ?? ''));
143
- }
144
- return expr;
145
- }
146
-
147
- function _runEmbedded(policyText, findings) {
148
- const blocks = _parseEmbedded(policyText);
149
- const denials = [];
150
- for (const f of findings) {
151
- for (const b of blocks) {
152
- const msg = _evalBlock(b, f);
153
- if (msg) denials.push(msg);
154
- }
155
- }
156
- return { ok: true, denials };
157
- }
158
-
159
- // ─── Public API ────────────────────────────────────────────────────────────
160
-
161
- /**
162
- * Evaluate a policy file against the findings list.
163
- * Returns { ok, denials, runner } — denials is an array of human-readable
164
- * strings (one per violation). When denials.length > 0, the gate fails.
165
- */
166
- function evaluatePolicy(policyPath, findings, opts = {}) {
167
- if (!policyPath || !node_fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(policyPath)) {
168
- return { ok: false, reason: 'policy-file-missing' };
169
- }
170
- const policyText = node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(policyPath, 'utf8');
171
- const useExternal = !opts.embeddedOnly && _haveOpa();
172
- if (useExternal) {
173
- // Write findings to a temp file the opa binary reads.
174
- const tmp = `/tmp/as-policy-${Date.now()}.json`;
175
- node_fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(tmp, JSON.stringify({ findings }));
176
- const pkgMatch = policyText.match(/^\s*package\s+([\w.]+)/m);
177
- const pkg = pkgMatch ? pkgMatch[1] : 'main';
178
- const r = _runOpa(policyPath, tmp, pkg);
179
- try { node_fs__WEBPACK_IMPORTED_MODULE_0__.unlinkSync(tmp); } catch {}
180
- if (r.ok) return { ...r, runner: 'opa' };
181
- // Fall through to embedded on opa error.
182
- }
183
- const r = _runEmbedded(policyText, findings);
184
- return { ...r, runner: 'embedded' };
185
- }
186
-
187
- const _internals = { _parseEmbedded, _evalBlock, _runEmbedded };
188
-
189
-
190
- /***/ })
191
-
192
- };