0sec-cli 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/0sec.js +322 -0
  2. package/README.md +63 -52
  3. package/attacks/data-exfiltration/pii-leakage.yaml +27 -0
  4. package/attacks/encoding-bypass/base64-encoding.yaml +24 -0
  5. package/attacks/jailbreak/dan-roleplay.yaml +27 -0
  6. package/attacks/jailbreak/hypothetical-scenario.yaml +25 -0
  7. package/attacks/jailbreak/multilingual-bypass.yaml +22 -0
  8. package/attacks/output-manipulation/harmful-content.yaml +25 -0
  9. package/attacks/prompt-injection/context-manipulation.yaml +32 -0
  10. package/attacks/prompt-injection/direct-injection.yaml +28 -0
  11. package/attacks/prompt-injection/indirect-injection.yaml +33 -0
  12. package/attacks/system-prompt-extraction/direct-ask.yaml +30 -0
  13. package/attacks/system-prompt-extraction/markdown-exfil.yaml +26 -0
  14. package/attacks/tool-misuse/ssrf-via-tools.yaml +27 -0
  15. package/chunks/adapt-loop-ECQG7454.js +18 -0
  16. package/chunks/adgraph-JLGA6RYI.js +54 -0
  17. package/chunks/agent/skills/frameworks/entra-id.yaml +63 -0
  18. package/chunks/agent/skills/frameworks/graphql-introspection.yaml +116 -0
  19. package/chunks/agent/skills/frameworks/nextjs.yaml +82 -0
  20. package/chunks/agent/skills/frameworks/python-web.yaml +85 -0
  21. package/chunks/agent/skills/frameworks/supabase.yaml +83 -0
  22. package/chunks/agent/skills/frameworks/wordpress-deep.yaml +122 -0
  23. package/chunks/agent/skills/techniques/ad-attack-paths.yaml +57 -0
  24. package/chunks/agent/skills/techniques/advisory-disclosure.yaml +59 -0
  25. package/chunks/agent/skills/techniques/assumption-mining.yaml +53 -0
  26. package/chunks/agent/skills/techniques/blind-exploitation.yaml +143 -0
  27. package/chunks/agent/skills/techniques/crypto-misuse.yaml +88 -0
  28. package/chunks/agent/skills/techniques/cve-poc-adaptation.yaml +82 -0
  29. package/chunks/agent/skills/techniques/entra-attack-paths.yaml +55 -0
  30. package/chunks/agent/skills/techniques/http-conformance-diff.yaml +48 -0
  31. package/chunks/agent/skills/techniques/jwt-attacks.yaml +137 -0
  32. package/chunks/agent/skills/techniques/kernel-weaponization.yaml +53 -0
  33. package/chunks/agent/skills/techniques/llm-excessive-agency.yaml +55 -0
  34. package/chunks/agent/skills/techniques/llm-insecure-output-handling.yaml +56 -0
  35. package/chunks/agent/skills/techniques/llm-prompt-injection.yaml +54 -0
  36. package/chunks/agent/skills/techniques/llm-prompt-layer-write.yaml +68 -0
  37. package/chunks/agent/skills/techniques/llm-rag-poisoning.yaml +57 -0
  38. package/chunks/agent/skills/techniques/llm-safety-eval.yaml +51 -0
  39. package/chunks/agent/skills/techniques/npm-ecosystem.yaml +51 -0
  40. package/chunks/agent/skills/techniques/poc-verification.yaml +55 -0
  41. package/chunks/agent/skills/techniques/race-condition.yaml +122 -0
  42. package/chunks/agent/skills/techniques/scoped-fix.yaml +49 -0
  43. package/chunks/agent/skills/techniques/seedless-depth-review.yaml +59 -0
  44. package/chunks/agent/skills/techniques/spec-differential.yaml +48 -0
  45. package/chunks/agent/skills/techniques/variant-hunting.yaml +55 -0
  46. package/chunks/agent/skills/vulnerabilities/cardano-eutxo-validators.yaml +72 -0
  47. package/chunks/agent/skills/vulnerabilities/command-injection.yaml +99 -0
  48. package/chunks/agent/skills/vulnerabilities/deserialization-chains.yaml +106 -0
  49. package/chunks/agent/skills/vulnerabilities/native-memory-safety.yaml +93 -0
  50. package/chunks/agent/skills/vulnerabilities/path-traversal.yaml +78 -0
  51. package/chunks/agent/skills/vulnerabilities/prototype-pollution.yaml +132 -0
  52. package/chunks/agent/skills/vulnerabilities/request-smuggling.yaml +129 -0
  53. package/chunks/agent/skills/vulnerabilities/sqli-advanced.yaml +102 -0
  54. package/chunks/agent/skills/vulnerabilities/ssrf-bypass.yaml +84 -0
  55. package/chunks/agent/skills/vulnerabilities/ssti-exploitation.yaml +112 -0
  56. package/chunks/agent/skills/vulnerabilities/structural-sqli.yaml +91 -0
  57. package/chunks/appsec-catalog-CBWHGTEL.js +24 -0
  58. package/chunks/artifact-scraper-GJEZMOHF.js +34 -0
  59. package/chunks/assumption-mining-BVMMUZHB.js +79 -0
  60. package/chunks/chunk-2CJ776PV.js +2430 -0
  61. package/chunks/chunk-2RMLOJVB.js +47 -0
  62. package/chunks/chunk-2SANI5RH.js +4728 -0
  63. package/chunks/chunk-3MOLBTLS.js +953 -0
  64. package/chunks/chunk-3QFDYBZQ.js +5108 -0
  65. package/chunks/chunk-47TQJWDH.js +3729 -0
  66. package/chunks/chunk-4Y4KQJXE.js +600 -0
  67. package/chunks/chunk-53G27VPS.js +136 -0
  68. package/chunks/chunk-57ZENEX2.js +169 -0
  69. package/chunks/chunk-5G3ZXFBW.js +182 -0
  70. package/chunks/chunk-6LKRLK2R.js +542 -0
  71. package/chunks/chunk-7DQEV5QI.js +287 -0
  72. package/chunks/chunk-A6CLR72I.js +235 -0
  73. package/chunks/chunk-BKZFDZ23.js +361 -0
  74. package/chunks/chunk-CLHCDHP4.js +471 -0
  75. package/chunks/chunk-D6S3IIPG.js +518 -0
  76. package/chunks/chunk-DN25OQQA.js +1070 -0
  77. package/chunks/chunk-DW5UWPFY.js +275 -0
  78. package/chunks/chunk-F3WBKITT.js +687 -0
  79. package/chunks/chunk-H2FFLZNK.js +3 -0
  80. package/chunks/chunk-H44E2CRN.js +193 -0
  81. package/chunks/chunk-HDWV7PGI.js +596 -0
  82. package/chunks/chunk-HE7LCA7G.js +381 -0
  83. package/chunks/chunk-HT6P7RY3.js +2597 -0
  84. package/chunks/chunk-IOL7D5YV.js +515 -0
  85. package/chunks/chunk-IORC6MQX.js +72828 -0
  86. package/chunks/chunk-IR537GON.js +49 -0
  87. package/chunks/chunk-K26SZ37E.js +5735 -0
  88. package/chunks/chunk-KKGE5RQE.js +1094 -0
  89. package/chunks/chunk-KLTNTE2Z.js +121 -0
  90. package/chunks/chunk-KTQDLNSR.js +1732 -0
  91. package/chunks/chunk-LOHTE223.js +2572 -0
  92. package/chunks/chunk-LP3HHYQU.js +245 -0
  93. package/chunks/chunk-MMLDQR4H.js +852 -0
  94. package/chunks/chunk-MYVT64FN.js +16 -0
  95. package/chunks/chunk-O462Y7P2.js +33019 -0
  96. package/chunks/chunk-OEFNRYI2.js +150 -0
  97. package/chunks/chunk-P6WKNFWX.js +1968 -0
  98. package/chunks/chunk-QI233I24.js +333 -0
  99. package/chunks/chunk-QJQKHO7G.js +917 -0
  100. package/chunks/chunk-QKICO43A.js +808 -0
  101. package/chunks/chunk-QOKTUOU2.js +102 -0
  102. package/chunks/chunk-RDZYQQLW.js +596 -0
  103. package/chunks/chunk-RJWOYEOG.js +271 -0
  104. package/chunks/chunk-RRMJC3ZE.js +105 -0
  105. package/chunks/chunk-RWONANDA.js +973 -0
  106. package/chunks/chunk-SAFFWQW4.js +5921 -0
  107. package/chunks/chunk-SF4KZ4O3.js +502 -0
  108. package/chunks/chunk-SNKTC4BP.js +1422 -0
  109. package/chunks/chunk-SOG2U7B3.js +662 -0
  110. package/chunks/chunk-SZJCPG2I.js +110 -0
  111. package/chunks/chunk-UJ4IK5PO.js +2010 -0
  112. package/chunks/chunk-UM3ZNQIM.js +9329 -0
  113. package/chunks/chunk-VGRDNSHA.js +3249 -0
  114. package/chunks/chunk-VQG4FT5D.js +685 -0
  115. package/chunks/chunk-WKNNZVJS.js +2798 -0
  116. package/chunks/chunk-WU6AFRAZ.js +1167 -0
  117. package/chunks/chunk-WVTBZEQO.js +1531 -0
  118. package/chunks/chunk-YLMN3N25.js +4054 -0
  119. package/chunks/commands-U3AMV5ZU.js +16795 -0
  120. package/chunks/corpus-v1.json +403 -0
  121. package/chunks/cost-ledger-ZCKMBJEN.js +13 -0
  122. package/chunks/data/appsec-archetypes.json +102 -0
  123. package/chunks/data/chromium-archetypes.json +204 -0
  124. package/chunks/data/freebsd-archetypes.json +171 -0
  125. package/chunks/data/kernel-archetypes.json +611 -0
  126. package/chunks/db-26NGQFKO.js +16 -0
  127. package/chunks/disclose-A6AEIEDA.js +132 -0
  128. package/chunks/dist-FDKALV4R.js +2772 -0
  129. package/chunks/dist-GL66KMUX.js +74 -0
  130. package/chunks/dist-JR67XKYO.js +159 -0
  131. package/chunks/eval-runner-DDLE5RQ3.js +27 -0
  132. package/chunks/example-manifest.json +95 -0
  133. package/chunks/exploit-agent-XP23ISN7.js +98 -0
  134. package/chunks/exploit-autoclimb-E6T7RRSC.js +81 -0
  135. package/chunks/exploit-climb-2FAUP6XU.js +358 -0
  136. package/chunks/fix-IG7LYXH3.js +12 -0
  137. package/chunks/github-issues-MJ6OYOOU.js +157 -0
  138. package/chunks/harness-HWOYFKFY.js +22 -0
  139. package/chunks/http-conformance-DU66MZIU.js +11 -0
  140. package/chunks/http-sender-GWH2IYEA.js +10 -0
  141. package/chunks/hunt-scan-XPELSPQQ.js +38 -0
  142. package/chunks/identity-6ZAIIWOR.js +191 -0
  143. package/chunks/kernel-primitive-TENE3R7T.js +44 -0
  144. package/chunks/kernel-vm-runner-4F6QSNFY.js +62 -0
  145. package/chunks/memsafety-scan-J7SECV5W.js +16 -0
  146. package/chunks/native-loop-NTONUDP7.js +54 -0
  147. package/chunks/npm-detectors-KB5Y5ZFX.js +69 -0
  148. package/chunks/npm-dynamic-discovery-AHROOZDB.js +13 -0
  149. package/chunks/orchestrate-ZUWAUWBC.js +62 -0
  150. package/chunks/pipeline-FCARFZU3.js +14 -0
  151. package/chunks/pre-recon-cve-66EB6G4M.js +351 -0
  152. package/chunks/prepare-MYY743TK.js +13 -0
  153. package/chunks/process-3Q7QJIOZ.js +13 -0
  154. package/chunks/replay-runner-RRR4E2A7.js +39 -0
  155. package/chunks/run-ESNN4V5W.js +32632 -0
  156. package/chunks/runtime-62ZQAH7H.js +43 -0
  157. package/chunks/runtime-J7PLXZNM.js +12 -0
  158. package/chunks/scan-stream-FHI2FYZE.js +96 -0
  159. package/chunks/scope-BI7BF4ZY.js +18 -0
  160. package/chunks/session-store-BCFQYDCE.js +28 -0
  161. package/chunks/source-files-PWRQR6LY.js +12 -0
  162. package/chunks/specdrift-WCLH6TTQ.js +19 -0
  163. package/chunks/variant-candidates-6KM6F6MD.js +13 -0
  164. package/chunks/web-recon-prepass-DPVCBRZA.js +1148 -0
  165. package/dashboard/assets/0sec-icon-66SreztZ.gif +0 -0
  166. package/dashboard/assets/bot-yILPnRgD.js +1 -0
  167. package/dashboard/assets/chevron-down-BeDJ-Vka.js +1 -0
  168. package/dashboard/assets/circle-alert-BEnMBMT-.js +1 -0
  169. package/dashboard/assets/client-DRYsuOGl.js +9 -0
  170. package/dashboard/assets/copy-Bi5vzoF9.js +1 -0
  171. package/dashboard/assets/desktop-CXTQonNm.js +32 -0
  172. package/dashboard/assets/desktop-NOekk4QS.css +1 -0
  173. package/dashboard/assets/dist-CmX-h7a4.js +1 -0
  174. package/dashboard/assets/dist-DAwYQmf_.js +45 -0
  175. package/dashboard/assets/findings-page-CACW9nw2.js +5 -0
  176. package/dashboard/assets/format-PoISqsES.js +1 -0
  177. package/dashboard/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  178. package/dashboard/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  179. package/dashboard/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  180. package/dashboard/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  181. package/dashboard/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  182. package/dashboard/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  183. package/dashboard/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  184. package/dashboard/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  185. package/dashboard/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  186. package/dashboard/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  187. package/dashboard/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  188. package/dashboard/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  189. package/dashboard/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  190. package/dashboard/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  191. package/dashboard/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  192. package/dashboard/assets/jsx-runtime-C7oxC63R.js +1 -0
  193. package/dashboard/assets/live-page-DrgQqQK5.js +1 -0
  194. package/dashboard/assets/meta-tile-CcskIV_o.js +1 -0
  195. package/dashboard/assets/operations-BKE8T-vY.css +2 -0
  196. package/dashboard/assets/operations-D8nUP5_m.js +4 -0
  197. package/dashboard/assets/operations-app-CQQhG54n.js +2 -0
  198. package/dashboard/assets/overview-page-DTGS8dPo.js +1 -0
  199. package/dashboard/assets/page-header-MFVvEtZW.js +1 -0
  200. package/dashboard/assets/play-Dwo-9xt6.js +1 -0
  201. package/dashboard/assets/scans-page-ChVtq8Us.js +1 -0
  202. package/dashboard/assets/search-CiooTLck.js +1 -0
  203. package/dashboard/assets/siren-CCBwTSuk.js +1 -0
  204. package/dashboard/assets/table-BYS-nIVA.js +1 -0
  205. package/dashboard/assets/tabs-DivxbhQy.js +1 -0
  206. package/dashboard/desktop.html +21 -0
  207. package/dashboard/index.html +15 -0
  208. package/package.json +30 -18
  209. package/bin/0sec.cjs +0 -361
@@ -0,0 +1,287 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __0secCreateRequire } from "node:module";
3
+ const require = __0secCreateRequire(import.meta.url);
4
+ import {
5
+ allowlistedChildEnv
6
+ } from "./chunk-KLTNTE2Z.js";
7
+
8
+ // packages/core/dist/stages/npm-detectors/osv-lookup.js
9
+ var DEFAULT_ENDPOINT = "https://api.osv.dev/v1/querybatch";
10
+ var DEFAULT_TIMEOUT_MS = 8e3;
11
+ var DEFAULT_RETRIES = 1;
12
+ var RETRY_BACKOFF_MS = 400;
13
+ var OsvLookupError = class extends Error {
14
+ cause;
15
+ constructor(message, cause) {
16
+ super(message);
17
+ this.cause = cause;
18
+ this.name = "OsvLookupError";
19
+ }
20
+ };
21
+ function deriveForkSiblings(name) {
22
+ const out = /* @__PURE__ */ new Set();
23
+ const dotted = /^([a-z0-9][a-z0-9-]*)\.[a-z0-9.-]+$/.exec(name);
24
+ if (dotted)
25
+ out.add(dotted[1]);
26
+ const scoped = /^@([a-z0-9][a-z0-9-]*)\/([a-z0-9][a-z0-9._-]*)$/.exec(name);
27
+ if (scoped && scoped[1] !== "types")
28
+ out.add(scoped[2]);
29
+ out.delete(name);
30
+ return [...out];
31
+ }
32
+ function createOsvAdvisoryLookup(options = {}) {
33
+ const endpoint = options.endpoint ?? DEFAULT_ENDPOINT;
34
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
35
+ const retries = options.retries ?? DEFAULT_RETRIES;
36
+ const deriveSiblings = options.deriveSiblings ?? deriveForkSiblings;
37
+ return async (name, version, _cwe) => {
38
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
39
+ if (typeof fetchImpl !== "function") {
40
+ throw new OsvLookupError("no fetch implementation available for OSV lookup");
41
+ }
42
+ const siblings = deriveSiblings(name).filter((s) => s && s !== name);
43
+ const queries = [
44
+ { package: { ecosystem: "npm", name }, ...version ? { version } : {} },
45
+ ...siblings.map((s) => ({ package: { ecosystem: "npm", name: s } }))
46
+ ];
47
+ const data = await postBatch(fetchImpl, endpoint, { queries }, timeoutMs, retries);
48
+ const results = data.results ?? [];
49
+ const advisories = [];
50
+ for (const v of results[0]?.vulns ?? []) {
51
+ if (v?.id)
52
+ advisories.push(v.id);
53
+ }
54
+ siblings.forEach((sib, i) => {
55
+ for (const v of results[i + 1]?.vulns ?? []) {
56
+ if (v?.id)
57
+ advisories.push(`sibling:${sib}:${v.id}`);
58
+ }
59
+ });
60
+ return [...new Set(advisories)];
61
+ };
62
+ }
63
+ async function postBatch(fetchImpl, endpoint, body, timeoutMs, retries) {
64
+ let lastErr;
65
+ for (let attempt = 0; attempt <= retries; attempt++) {
66
+ const controller = new AbortController();
67
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
68
+ try {
69
+ const res = await fetchImpl(endpoint, {
70
+ method: "POST",
71
+ headers: { "content-type": "application/json", accept: "application/json" },
72
+ body: JSON.stringify(body),
73
+ signal: controller.signal
74
+ });
75
+ if (res.status === 429 || res.status >= 500) {
76
+ lastErr = new OsvLookupError(`OSV transient status ${res.status}`);
77
+ if (attempt < retries) {
78
+ await delay(RETRY_BACKOFF_MS * (attempt + 1));
79
+ continue;
80
+ }
81
+ throw lastErr;
82
+ }
83
+ if (!res.ok) {
84
+ throw new OsvLookupError(`OSV lookup failed: HTTP ${res.status}`);
85
+ }
86
+ const json = await res.json();
87
+ if (!json || typeof json !== "object" || !("results" in json)) {
88
+ throw new OsvLookupError("OSV lookup returned a malformed response (no `results`)");
89
+ }
90
+ return json;
91
+ } catch (err) {
92
+ lastErr = err;
93
+ const transient = isAbortOrNetwork(err);
94
+ if (transient && attempt < retries) {
95
+ await delay(RETRY_BACKOFF_MS * (attempt + 1));
96
+ continue;
97
+ }
98
+ if (err instanceof OsvLookupError)
99
+ throw err;
100
+ throw new OsvLookupError(`OSV lookup errored: ${errMsg(err)}`, err);
101
+ } finally {
102
+ clearTimeout(timer);
103
+ }
104
+ }
105
+ throw new OsvLookupError(`OSV lookup exhausted retries: ${errMsg(lastErr)}`, lastErr);
106
+ }
107
+ function isAbortOrNetwork(err) {
108
+ if (err instanceof OsvLookupError)
109
+ return false;
110
+ const name = err?.name;
111
+ return name === "AbortError" || name === "TypeError" || err instanceof Error;
112
+ }
113
+ function delay(ms) {
114
+ return new Promise((resolve) => setTimeout(resolve, ms));
115
+ }
116
+ function errMsg(e) {
117
+ return e instanceof Error ? e.message.split("\n")[0].slice(0, 160) : String(e);
118
+ }
119
+
120
+ // packages/core/dist/stages/npm-detectors/probe.js
121
+ import { createRequire } from "node:module";
122
+ import { pathToFileURL } from "node:url";
123
+ function inProcessProbe(pkg, baseDir, note) {
124
+ const req = createRequire(pathToFileURL(baseDir.endsWith("/") ? baseDir : `${baseDir}/`).href);
125
+ return {
126
+ pkg,
127
+ baseDir,
128
+ load(moduleId) {
129
+ try {
130
+ return req(moduleId);
131
+ } catch {
132
+ return void 0;
133
+ }
134
+ },
135
+ note
136
+ };
137
+ }
138
+ function staticProbe(pkg, modules, note) {
139
+ return {
140
+ pkg,
141
+ baseDir: "/dev/null",
142
+ load(moduleId) {
143
+ return Object.prototype.hasOwnProperty.call(modules, moduleId) ? modules[moduleId] : void 0;
144
+ },
145
+ note
146
+ };
147
+ }
148
+
149
+ // packages/core/dist/stages/npm-detectors/sandbox-probe.js
150
+ import { execFile } from "node:child_process";
151
+ import { mkdtemp, rm } from "node:fs/promises";
152
+ import { tmpdir } from "node:os";
153
+ import { join } from "node:path";
154
+ import { fileURLToPath } from "node:url";
155
+ var DEFAULT_INSTALL_TIMEOUT_MS = 18e4;
156
+ var DEFAULT_RUN_TIMEOUT_MS = 12e4;
157
+ function defaultHarnessPath() {
158
+ return fileURLToPath(new URL("./sandbox-harness.js", import.meta.url));
159
+ }
160
+ function localSandboxProvider() {
161
+ return {
162
+ async create() {
163
+ const workdir = await mkdtemp(join(tmpdir(), "0sec-npm-dyn-"));
164
+ return {
165
+ workdir,
166
+ run(cmd, args, runOpts) {
167
+ return new Promise((resolve, reject) => {
168
+ execFile(cmd, args, {
169
+ cwd: runOpts?.cwd ?? workdir,
170
+ timeout: runOpts?.timeoutMs ?? DEFAULT_RUN_TIMEOUT_MS,
171
+ maxBuffer: 64 * 1024 * 1024,
172
+ // This child runs UNTRUSTED npm package code (install + harness
173
+ // exec). Inheriting process.env would hand every provider /
174
+ // cloud credential to attacker-authored package code via one
175
+ // `process.env` read. Build from the allowlist instead; a fresh
176
+ // npm install needs only PATH/HOME/TMPDIR, all carried there.
177
+ env: allowlistedChildEnv(runOpts?.env ?? {})
178
+ }, (err, stdout, stderr) => {
179
+ const anyErr = err;
180
+ if (anyErr && typeof anyErr.code !== "number") {
181
+ reject(anyErr);
182
+ return;
183
+ }
184
+ resolve({
185
+ stdout: stdout?.toString() ?? "",
186
+ stderr: stderr?.toString() ?? "",
187
+ exitCode: typeof anyErr?.code === "number" ? anyErr.code : 0
188
+ });
189
+ });
190
+ });
191
+ },
192
+ async dispose() {
193
+ await rm(workdir, { recursive: true, force: true });
194
+ }
195
+ };
196
+ }
197
+ };
198
+ }
199
+ function parseHarnessResult(stdout) {
200
+ const lines = stdout.split("\n").map((l) => l.trim()).filter(Boolean);
201
+ for (let i = lines.length - 1; i >= 0; i--) {
202
+ const line = lines[i];
203
+ if (!line.startsWith("{"))
204
+ continue;
205
+ try {
206
+ const parsed = JSON.parse(line);
207
+ if (parsed && Array.isArray(parsed.outcomes)) {
208
+ return { outcomes: parsed.outcomes, warnings: Array.isArray(parsed.warnings) ? parsed.warnings : [] };
209
+ }
210
+ } catch {
211
+ }
212
+ }
213
+ return void 0;
214
+ }
215
+ function createSandboxPackageRunner(options = {}) {
216
+ const provider = options.provider ?? localSandboxProvider();
217
+ const harnessPath = options.harnessPath ?? defaultHarnessPath();
218
+ const nodeBin = options.nodeBin ?? process.execPath;
219
+ const installTimeoutMs = options.installTimeoutMs ?? DEFAULT_INSTALL_TIMEOUT_MS;
220
+ const runTimeoutMs = options.runTimeoutMs ?? DEFAULT_RUN_TIMEOUT_MS;
221
+ const log = options.log ?? (() => {
222
+ });
223
+ return async function sandboxRunner(pkg, detectorIds) {
224
+ let session;
225
+ try {
226
+ session = await provider.create();
227
+ } catch (e) {
228
+ log(`SKIP ${pkg.name}: sandbox create failed: ${errMsg2(e)}`);
229
+ return void 0;
230
+ }
231
+ let result;
232
+ try {
233
+ const spec = pkg.version ? `${pkg.name}@${pkg.version}` : pkg.name;
234
+ const install = await session.run("npm", ["install", "--ignore-scripts", "--no-audit", "--no-fund", "--no-package-lock", "--prefix", session.workdir, spec], { cwd: session.workdir, timeoutMs: installTimeoutMs });
235
+ if (install.exitCode !== 0) {
236
+ log(`SKIP ${pkg.name}: npm install exited ${install.exitCode}: ${firstLine(install.stderr)}`);
237
+ return void 0;
238
+ }
239
+ const config = JSON.stringify({
240
+ installDir: session.workdir,
241
+ pkg,
242
+ detectorIds,
243
+ offlineDedup: options.offlineDedup ?? false
244
+ });
245
+ const harness = await session.run(nodeBin, [harnessPath, config], {
246
+ cwd: session.workdir,
247
+ timeoutMs: runTimeoutMs
248
+ });
249
+ if (harness.exitCode !== 0) {
250
+ log(`SKIP ${pkg.name}: harness exited ${harness.exitCode}: ${firstLine(harness.stderr)}`);
251
+ return void 0;
252
+ }
253
+ const parsed = parseHarnessResult(harness.stdout);
254
+ if (!parsed) {
255
+ log(`SKIP ${pkg.name}: harness produced no parseable result`);
256
+ return void 0;
257
+ }
258
+ result = parsed;
259
+ } catch (e) {
260
+ log(`SKIP ${pkg.name}: sandbox run threw: ${errMsg2(e)}`);
261
+ result = void 0;
262
+ } finally {
263
+ try {
264
+ await session.dispose();
265
+ } catch (e) {
266
+ log(`WARN ${pkg.name}: sandbox dispose failed: ${errMsg2(e)}`);
267
+ }
268
+ }
269
+ return result;
270
+ };
271
+ }
272
+ function errMsg2(e) {
273
+ return e instanceof Error ? e.message.split("\n")[0].slice(0, 200) : String(e);
274
+ }
275
+ function firstLine(s) {
276
+ return (s.split("\n").find(Boolean) ?? "").slice(0, 200);
277
+ }
278
+
279
+ export {
280
+ OsvLookupError,
281
+ deriveForkSiblings,
282
+ createOsvAdvisoryLookup,
283
+ inProcessProbe,
284
+ staticProbe,
285
+ localSandboxProvider,
286
+ createSandboxPackageRunner
287
+ };
@@ -0,0 +1,235 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __0secCreateRequire } from "node:module";
3
+ const require = __0secCreateRequire(import.meta.url);
4
+ import {
5
+ LlmApiRuntime
6
+ } from "./chunk-2SANI5RH.js";
7
+
8
+ // packages/core/dist/stages/variant-candidates.js
9
+ import { execFileSync } from "node:child_process";
10
+
11
+ // packages/core/dist/stages/hunt-reachability.js
12
+ var UNREACHABLE_PATH_PREFIXES = [
13
+ // ── Not built on COS (kernelctf-config.ts COS_CONFIG_STATE) ──
14
+ "net/kcm/",
15
+ // CONFIG_AF_KCM not-built — the original "kcm lesson"
16
+ "net/bluetooth/",
17
+ // CONFIG_BT not-built
18
+ "net/can/",
19
+ // CONFIG_CAN not-built
20
+ "fs/f2fs/",
21
+ // CONFIG_F2FS_FS not-built
22
+ // ── Exotic/unbuilt drivers (observed this session; same story, not yet
23
+ // in kernelctf-config.ts's token map) ──
24
+ "drivers/infiniband/",
25
+ // RXE/infiniband — not built on COS
26
+ "drivers/net/wireless/",
27
+ // mwifiex etc. — not built on COS
28
+ "drivers/media/",
29
+ // exotic media drivers — not built on COS
30
+ "drivers/hid/",
31
+ // hidraw — not built on COS
32
+ "drivers/usb/gadget/",
33
+ // USB gadget — not built / not a zero-cap network path
34
+ "drivers/usb/atm/",
35
+ // uea/ueagle-atm — not built on COS
36
+ "drivers/xen/",
37
+ // xen paravirt — COS is not a Xen guest
38
+ "drivers/staging/",
39
+ // staging tree — not built on COS, churny/unstable
40
+ // ── Exotic filesystems: the trigger is mount(2), which needs
41
+ // CAP_SYS_ADMIN a zero-cap kernelCTF attacker doesn't have (mirrors
42
+ // kernelctf-config.ts's ext4/xfs/btrfs/ntfs3/fuse `needsCap:
43
+ // CAP_SYS_ADMIN` entries); these ones aren't built on COS at all either ──
44
+ "fs/reiserfs/",
45
+ "fs/hpfs/",
46
+ "fs/sysv/",
47
+ "fs/ntfs/",
48
+ // legacy ntfs (distinct from the ntfs3 module)
49
+ "fs/jfs/"
50
+ ];
51
+ var REACHABLE_PATH_PREFIXES = [
52
+ "crypto/",
53
+ // CONFIG_CRYPTO_USER_API_{AEAD,SKCIPHER,HASH} — AF_ALG, zero-cap
54
+ "net/unix/",
55
+ // CONFIG_UNIX — AF_UNIX, zero-cap
56
+ "net/core/",
57
+ // core socket/skb plumbing, zero-cap from any AF_* socket
58
+ "net/ipv4/",
59
+ // core IPv4, zero-cap
60
+ "net/ipv6/",
61
+ // core IPv6, zero-cap
62
+ // CONFIG_PACKET — kernelctf-config.ts marks needsCap CAP_NET_RAW for
63
+ // AF_PACKET itself; still listed reachable here (worth the finder budget).
64
+ "net/packet/",
65
+ "net/vmw_vsock/",
66
+ // CONFIG_VSOCKETS(+_LOOPBACK) — AF_VSOCK loopback, zero-cap
67
+ "net/tls/",
68
+ // CONFIG_TLS — kTLS, zero-cap
69
+ "net/xfrm/",
70
+ // IPsec policy/state — reachable via standard AF_INET{,6} sockets
71
+ "net/netlink/",
72
+ // AF_NETLINK core — zero-cap socket family
73
+ "ipc/",
74
+ // CONFIG_SYSVIPC — System-V IPC (msg/sem/shm), zero-cap
75
+ // CONFIG_EXT4_FS / CONFIG_XFS_FS — kernelctf-config.ts marks needsCap
76
+ // CAP_SYS_ADMIN for the *mount* trigger; still listed reachable here (core
77
+ // FS code is real audit surface, worth the finder budget).
78
+ "fs/ext4/",
79
+ "fs/xfs/",
80
+ // unprivileged_bpf_disabled is commonly runtime-off; listed reachable here
81
+ // rather than dropped outright (flag the caveat in the finder hint instead).
82
+ "kernel/bpf/",
83
+ "security/keys/"
84
+ // CONFIG_KEYS — keyrings / add_key(2), zero-cap
85
+ ];
86
+ function hasPathSegmentPrefix(path, prefix) {
87
+ const idx = path.indexOf(prefix);
88
+ if (idx === -1)
89
+ return false;
90
+ return idx === 0 || path[idx - 1] === "/";
91
+ }
92
+ function classifyPathReachability(path) {
93
+ const norm = path.replace(/\\/g, "/");
94
+ for (const prefix of UNREACHABLE_PATH_PREFIXES)
95
+ if (hasPathSegmentPrefix(norm, prefix))
96
+ return "unreachable";
97
+ for (const prefix of REACHABLE_PATH_PREFIXES)
98
+ if (hasPathSegmentPrefix(norm, prefix))
99
+ return "reachable";
100
+ return "unknown";
101
+ }
102
+ function applyReachabilityGate(paths, opts) {
103
+ if (!opts.reachableOnly && !opts.reachablePrefer)
104
+ return { paths, unreachableCount: 0 };
105
+ const classified = paths.map((path) => ({ path, cls: classifyPathReachability(path) }));
106
+ const unreachableCount = classified.filter((c) => c.cls === "unreachable").length;
107
+ if (opts.reachableOnly) {
108
+ return { paths: classified.filter((c) => c.cls === "reachable").map((c) => c.path), unreachableCount };
109
+ }
110
+ const rank = { reachable: 0, unknown: 1, unreachable: 2 };
111
+ const sorted = [...classified].sort((a, b) => rank[a.cls] - rank[b.cls]).map((c) => c.path);
112
+ return { paths: sorted, unreachableCount };
113
+ }
114
+
115
+ // packages/core/dist/stages/variant-candidates.js
116
+ var clip = (s, n) => s.length > n ? s.slice(0, n) + `
117
+ ...[truncated ${s.length - n} chars]` : s;
118
+ function fixedPathsFromDiff(diff) {
119
+ const out = [];
120
+ for (const line of diff.split("\n")) {
121
+ const m = /^\+\+\+ b\/(.+)$/.exec(line);
122
+ if (m && m[1] !== "/dev/null")
123
+ out.push(m[1].trim());
124
+ }
125
+ return [...new Set(out)];
126
+ }
127
+ function grepFiles(pattern, sourceRoot, includeGlobs) {
128
+ const args = ["-rlE", ...includeGlobs.map((g) => `--include=${g}`), "--", pattern, "."];
129
+ try {
130
+ const out = execFileSync("grep", args, { cwd: sourceRoot, encoding: "utf8", maxBuffer: 32 * 1024 * 1024, timeout: 6e4 });
131
+ return out.split("\n").map((s) => s.replace(/^\.\//, "").trim()).filter(Boolean);
132
+ } catch {
133
+ return [];
134
+ }
135
+ }
136
+ async function generateVariantCandidates(input) {
137
+ const log = input.log ?? (() => {
138
+ });
139
+ const warnings = [];
140
+ const maxCandidates = input.maxCandidates ?? 40;
141
+ const includeGlobs = input.includeGlobs ?? ["*.c", "*.h", "*.cc", "*.cpp"];
142
+ let diff = input.fix.diff ?? "";
143
+ if (!diff && input.fix.commit) {
144
+ try {
145
+ diff = execFileSync("git", ["-C", input.sourceRoot, "show", "--no-color", input.fix.commit], {
146
+ encoding: "utf8",
147
+ maxBuffer: 16 * 1024 * 1024,
148
+ timeout: 3e4
149
+ });
150
+ } catch (e) {
151
+ throw new Error(`could not read fix commit ${input.fix.commit}: ${String(e).slice(0, 160)}`);
152
+ }
153
+ }
154
+ if (!diff.trim())
155
+ throw new Error("variant hunt needs fix.diff or fix.commit");
156
+ const fixedPaths = fixedPathsFromDiff(diff);
157
+ const tool = {
158
+ name: "emit_variant_plan",
159
+ description: "Emit the variant-hunt plan derived from the fix diff.",
160
+ input_schema: {
161
+ type: "object",
162
+ properties: {
163
+ bugClass: { type: "string", description: "The bug class the fix closed, e.g. 'missing bounds check before a TLV copy'." },
164
+ pattern: { type: "string", description: "The exact unguarded code shape (the sink) and how the fix guarded it." },
165
+ grepPatterns: {
166
+ type: "array",
167
+ items: { type: "string" },
168
+ description: "2-6 grep ERE patterns that locate the SAME sink shape at OTHER call-sites/files (the variant sites where the fix was NOT applied). Match the sink/call, not the added guard."
169
+ }
170
+ },
171
+ required: ["bugClass", "pattern", "grepPatterns"]
172
+ }
173
+ };
174
+ const system = "You are a kernel/security VARIANT-HUNT analyst. Given a security fix diff, identify the bug class and the unguarded code SHAPE (the sink) the fix added a guard around. Then write grep EXTENDED-REGEX patterns that find the SAME bug CLASS at OTHER call-sites across the tree \u2014 where an INCOMPLETE fix or an independent instance lives.\nCRITICAL \u2014 match the CLASS, not the original site: the patched file is EXCLUDED from results, so a pattern that hardcodes the original's specific identifiers (field names like `sensf_res`, struct names like `digital_*`, the variable `resp`) will match ONLY the original and yield ZERO variants. WILDCARD the identifiers. Match the dangerous SHAPE \u2014 e.g. a length-controlled copy into a fixed buffer regardless of names: `memcpy\\([^,]+,[^,]+,[^)]*->len\\)`, or an unchecked `->len`/`->size` used as a copy/index bound. Match the sink, NOT the guard that was added.\nEmit 3-6 patterns ranging from moderately specific (the same struct-field-copy shape with wildcarded names) to general (any length-bounded memcpy/copy_from of that family). Aim for tens of hits to triage, not zero and not thousands. Call emit_variant_plan.";
175
+ const messages = [{ role: "user", content: [{ type: "text", text: `## Fix diff
176
+ ${clip(diff, 24e3)}` }] }];
177
+ const rt = new LlmApiRuntime({ type: "api", ...input.model ? { model: input.model } : {}, timeout: 24e4 });
178
+ let plan = null;
179
+ try {
180
+ const res = await rt.executeNative(system, messages, [tool], { onThinking() {
181
+ }, onDelta() {
182
+ }, onText() {
183
+ }, onUsage() {
184
+ } });
185
+ const call = (res.content ?? []).find((b) => b.type === "tool_use" && b.name === "emit_variant_plan");
186
+ if (call?.input)
187
+ plan = call.input;
188
+ } catch (e) {
189
+ throw new Error(`variant-plan LLM call failed: ${String(e).slice(0, 200)}`);
190
+ }
191
+ if (!plan || !Array.isArray(plan.grepPatterns) || plan.grepPatterns.length === 0) {
192
+ throw new Error("model did not emit a usable variant plan");
193
+ }
194
+ log(`[variant] bug class: ${plan.bugClass}`);
195
+ log(`[variant] ${plan.grepPatterns.length} grep pattern(s): ${plan.grepPatterns.join(" | ")}`);
196
+ const fixedSet = new Set(fixedPaths);
197
+ const hits = /* @__PURE__ */ new Map();
198
+ for (const pat of plan.grepPatterns) {
199
+ const files = grepFiles(pat, input.sourceRoot, includeGlobs);
200
+ if (files.length === 0)
201
+ warnings.push(`pattern matched nothing (or invalid): ${pat}`);
202
+ for (const f of files)
203
+ if (!fixedSet.has(f))
204
+ hits.set(f, (hits.get(f) ?? 0) + 1);
205
+ }
206
+ const ranked = [...hits.entries()].sort((a, b) => b[1] - a[1]).map(([path]) => path);
207
+ const gated = input.reachableOnly || input.reachablePrefer ? applyReachabilityGate(ranked, { reachableOnly: input.reachableOnly, reachablePrefer: input.reachablePrefer }) : { paths: ranked, unreachableCount: 0 };
208
+ if (gated.unreachableCount > 0) {
209
+ const verb = input.reachableOnly ? "dropped" : "deprioritized";
210
+ const msg = `${verb} ${gated.unreachableCount} unreachable candidate(s) (not built/zero-cap on kernelCTF COS \u2014 see hunt-reachability.ts)`;
211
+ warnings.push(msg);
212
+ log(`[variant] ${msg}`);
213
+ }
214
+ if (gated.paths.length > maxCandidates) {
215
+ warnings.push(`capped candidates ${gated.paths.length} -> ${maxCandidates} (raise maxCandidates to widen)`);
216
+ }
217
+ const candidates = gated.paths.slice(0, maxCandidates).map((path) => ({
218
+ path,
219
+ hint: `Variant site for: ${plan.bugClass}. Check whether ${plan.pattern} is guarded here.`
220
+ }));
221
+ log(`[variant] ${candidates.length} candidate site(s) after dedup/exclude`);
222
+ return {
223
+ brief: { bugClass: plan.bugClass, pattern: plan.pattern, fixReference: input.fix.reference ?? input.fix.commit },
224
+ candidates,
225
+ grepPatterns: plan.grepPatterns,
226
+ fixedPaths,
227
+ warnings
228
+ };
229
+ }
230
+
231
+ export {
232
+ classifyPathReachability,
233
+ applyReachabilityGate,
234
+ generateVariantCandidates
235
+ };