0sec-cli 0.21.4 → 0.22.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 (251) hide show
  1. package/0.js +56 -56
  2. package/README.md +48 -63
  3. package/chunks/{adapt-loop-IGBRQAXO.js → adapt-loop-J6QCZZTB.js} +4 -4
  4. package/chunks/{artifact-scraper-LNQJUORS.js → artifact-scraper-MFKT6OI4.js} +2 -2
  5. package/chunks/{assumption-mining-4MJTP6H5.js → assumption-mining-646LYSJY.js} +16 -16
  6. package/chunks/{chunk-Z3IKZX3X.js → chunk-23DG5YKS.js} +1 -1
  7. package/chunks/{chunk-2WSXFFJZ.js → chunk-3E2KALZ5.js} +2 -2
  8. package/chunks/{chunk-TF46UWFU.js → chunk-4G7OVYJX.js} +1 -1
  9. package/chunks/{chunk-E2UOY5PJ.js → chunk-5BTJMINS.js} +12 -70
  10. package/chunks/{chunk-QZVG3BG4.js → chunk-5IBCBQEH.js} +1 -2
  11. package/chunks/{chunk-YA4PUM4H.js → chunk-7XCKYNLO.js} +1 -1
  12. package/chunks/{chunk-BACXRTQ4.js → chunk-AV2LG2L7.js} +87 -767
  13. package/chunks/{chunk-IMGWPNQM.js → chunk-BCSRRQG5.js} +76 -593
  14. package/chunks/{chunk-NOPBXZUV.js → chunk-BOUGKEKA.js} +1 -1
  15. package/chunks/{chunk-RF5QGKLR.js → chunk-CPWA6RWE.js} +3 -3
  16. package/chunks/{chunk-P6YG6TEZ.js → chunk-DKSCN2AB.js} +1 -1
  17. package/chunks/{chunk-YPOA3W4O.js → chunk-GOEA3NW6.js} +877 -110
  18. package/chunks/{chunk-ANDKY54G.js → chunk-HHPWRY2M.js} +4019 -7299
  19. package/chunks/{chunk-NDJU5ZZ7.js → chunk-IFME676H.js} +3 -3
  20. package/chunks/{chunk-HBKCGJAV.js → chunk-IVQV574Y.js} +1 -1
  21. package/chunks/{chunk-W4JOXE4N.js → chunk-IXT64DV5.js} +709 -1929
  22. package/chunks/{chunk-PC6RCKQV.js → chunk-KAU6NLGP.js} +59 -26
  23. package/chunks/{chunk-FAH6V4QM.js → chunk-LGCASF2C.js} +1117 -2291
  24. package/chunks/{chunk-CAJJRUTV.js → chunk-NLH4LMWH.js} +1 -1
  25. package/chunks/chunk-OHHCGDJ2.js +5523 -0
  26. package/chunks/{chunk-YHWWF4QY.js → chunk-OSBPGWTE.js} +3 -3
  27. package/chunks/{chunk-G34QP2XW.js → chunk-TM74SSHC.js} +1 -1
  28. package/chunks/{chunk-VOAXLO4A.js → chunk-TO35Y326.js} +5 -721
  29. package/chunks/{chunk-FWAYJ2IV.js → chunk-TV27OWIW.js} +8 -8
  30. package/chunks/chunk-UELXHK2Y.js +1950 -0
  31. package/chunks/{chunk-CL5KAUM6.js → chunk-V6GHX34G.js} +274 -8
  32. package/chunks/{chunk-WZISK5VC.js → chunk-VWITLWXB.js} +53 -7
  33. package/chunks/{chunk-ER3SUMYJ.js → chunk-W6UWZFKL.js} +0 -195
  34. package/chunks/chunk-WSDDGFA6.js +114 -0
  35. package/chunks/{chunk-5T6D5BVY.js → chunk-WZRVS7SB.js} +1 -1
  36. package/chunks/chunk-X7LAEEUW.js +2143 -0
  37. package/chunks/chunk-XA2O4WXA.js +1228 -0
  38. package/chunks/{chunk-Y5H6E24H.js → chunk-XCISCI4T.js} +1 -1
  39. package/chunks/{chunk-WPQLBN4S.js → chunk-XDKB2P5X.js} +1 -1
  40. package/chunks/{chunk-SBMB4OL3.js → chunk-YL6YAGLL.js} +62 -8
  41. package/chunks/{chunk-O6SD4A36.js → chunk-ZKQ2I3S2.js} +1 -1
  42. package/chunks/{codex-models-TYYV4JNN.js → codex-models-NHQW45SO.js} +5 -3
  43. package/chunks/{commands-WF5RG4I2.js → commands-N6GN5SBF.js} +2152 -3643
  44. package/chunks/{db-U3WQZVAO.js → db-5IWEJUYV.js} +3 -3
  45. package/chunks/{disclose-67GZCDBO.js → disclose-LDNKAVRH.js} +10 -8
  46. package/chunks/{dist-ACC3DZUZ.js → dist-BS4CY4RG.js} +3 -1
  47. package/chunks/{dist-EVTN3UTT.js → dist-FSS7MBDF.js} +2 -2
  48. package/chunks/{dist-F6X62TQK.js → dist-L3XJWWZ6.js} +225 -192
  49. package/chunks/{eval-runner-SA6OUGHZ.js → eval-runner-EMEGF24P.js} +11 -11
  50. package/chunks/{exploit-agent-36Q7XQFP.js → exploit-agent-KW4EB7BE.js} +1 -1
  51. package/chunks/{exploit-autoclimb-J4OTSTZ6.js → exploit-autoclimb-CXJ2ERRO.js} +1 -1
  52. package/chunks/{exploit-climb-PYEJDIC4.js → exploit-climb-TJWLY4XC.js} +2 -2
  53. package/chunks/fix-UUKPXCFB.js +26 -0
  54. package/chunks/{harness-GDUMKGSE.js → harness-C5HOKIEV.js} +3 -3
  55. package/chunks/{hunt-scan-UDJSCISU.js → hunt-scan-KZZ444V2.js} +14 -14
  56. package/chunks/{identity-JTIVJSV7.js → identity-62SVRW4H.js} +1 -1
  57. package/chunks/{kernel-primitive-F2IXUFAX.js → kernel-primitive-Z77IR7YS.js} +2 -2
  58. package/chunks/{kernel-vm-runner-276LHIOV.js → kernel-vm-runner-JW77P276.js} +2 -2
  59. package/chunks/{memsafety-scan-NHUOGTEX.js → memsafety-scan-FCPAXIZE.js} +2 -2
  60. package/chunks/{native-loop-3BSYVDUI.js → native-loop-P6P5GYVJ.js} +10 -10
  61. package/chunks/{npm-detectors-VIYJXKFQ.js → npm-detectors-Z2ZIQCN4.js} +2 -2
  62. package/chunks/orchestrate-DWWUT5RC.js +58 -0
  63. package/chunks/{process-PNMC36JI.js → process-N245QIQG.js} +1 -4
  64. package/chunks/{run-CBGE6DRQ.js → run-GAFKBCTN.js} +14935 -17598
  65. package/chunks/{runtime-PXO5G6UV.js → runtime-H7YBR5IZ.js} +4 -5
  66. package/chunks/{scope-NJHIDDTN.js → scope-QWY4VQZ7.js} +4 -1
  67. package/chunks/{session-store-46WBZM22.js → session-store-XIDFGG7K.js} +2 -2
  68. package/chunks/{variant-candidates-UH2WC2HA.js → variant-candidates-7D4F5E4H.js} +3 -3
  69. package/chunks/{web-recon-prepass-SFXU44SS.js → web-recon-prepass-VXXBPDAV.js} +3 -3
  70. package/cli-path-traversal-fixture.js +352 -0
  71. package/dashboard/assets/bot-Bg1Tp9_d.js +1 -0
  72. package/dashboard/assets/dist-B_dDCPbu.js +1 -0
  73. package/dashboard/assets/findings-page-mayGhNx6.js +5 -0
  74. package/dashboard/assets/format--susmHX7.js +1 -0
  75. package/dashboard/assets/input-TuVPR3Zl.js +1 -0
  76. package/dashboard/assets/jsx-runtime-MGseXjj5.js +3 -0
  77. package/dashboard/assets/live-page-HWWHX99U.js +1 -0
  78. package/dashboard/assets/meta-tile-ckHo-w7k.js +1 -0
  79. package/dashboard/assets/operations-BxZ0OoXO.js +10 -0
  80. package/dashboard/assets/operations-app-D3d35rgo.js +46 -0
  81. package/dashboard/assets/overview-page-W_4IteEr.js +1 -0
  82. package/dashboard/assets/page-header-Bw9AJgBV.js +1 -0
  83. package/dashboard/assets/scans-page-CgzVXHnn.js +1 -0
  84. package/dashboard/assets/siren-KQj8QAnJ.js +1 -0
  85. package/dashboard/assets/table-8EDklulN.js +1 -0
  86. package/dashboard/assets/tabs-CFHPOwg8.js +1 -0
  87. package/dashboard/index.html +2 -3
  88. package/node_modules/node-addon-api/LICENSE.md +9 -0
  89. package/node_modules/node-addon-api/README.md +95 -0
  90. package/node_modules/node-addon-api/common.gypi +21 -0
  91. package/node_modules/node-addon-api/except.gypi +25 -0
  92. package/node_modules/node-addon-api/index.js +14 -0
  93. package/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
  94. package/node_modules/node-addon-api/napi-inl.h +7250 -0
  95. package/node_modules/node-addon-api/napi.h +3463 -0
  96. package/node_modules/node-addon-api/node_addon_api.gyp +42 -0
  97. package/node_modules/node-addon-api/node_api.gyp +9 -0
  98. package/node_modules/node-addon-api/noexcept.gypi +26 -0
  99. package/node_modules/node-addon-api/nothing.c +0 -0
  100. package/node_modules/node-addon-api/package-support.json +21 -0
  101. package/node_modules/node-addon-api/package.json +481 -0
  102. package/node_modules/node-addon-api/tools/README.md +73 -0
  103. package/node_modules/node-addon-api/tools/check-napi.js +99 -0
  104. package/node_modules/node-addon-api/tools/clang-format.js +71 -0
  105. package/node_modules/node-addon-api/tools/conversion.js +301 -0
  106. package/node_modules/node-gyp-build/LICENSE +21 -0
  107. package/node_modules/node-gyp-build/README.md +58 -0
  108. package/node_modules/node-gyp-build/SECURITY.md +5 -0
  109. package/node_modules/node-gyp-build/bin.js +84 -0
  110. package/node_modules/node-gyp-build/build-test.js +19 -0
  111. package/node_modules/node-gyp-build/index.js +6 -0
  112. package/node_modules/node-gyp-build/node-gyp-build.js +207 -0
  113. package/node_modules/node-gyp-build/optional.js +7 -0
  114. package/node_modules/node-gyp-build/package.json +43 -0
  115. package/node_modules/tree-sitter/LICENSE +21 -0
  116. package/node_modules/tree-sitter/README.md +90 -0
  117. package/node_modules/tree-sitter/binding.gyp +76 -0
  118. package/node_modules/tree-sitter/index.js +895 -0
  119. package/node_modules/tree-sitter/package.json +62 -0
  120. package/node_modules/tree-sitter/prebuilds/darwin-arm64/tree-sitter.node +0 -0
  121. package/node_modules/tree-sitter/prebuilds/darwin-x64/tree-sitter.node +0 -0
  122. package/node_modules/tree-sitter/prebuilds/linux-x64/tree-sitter.node +0 -0
  123. package/node_modules/tree-sitter/prebuilds/win32-x64/tree-sitter.node +0 -0
  124. package/node_modules/tree-sitter/src/addon_data.h +47 -0
  125. package/node_modules/tree-sitter/src/binding.cc +35 -0
  126. package/node_modules/tree-sitter/src/conversions.cc +140 -0
  127. package/node_modules/tree-sitter/src/conversions.h +22 -0
  128. package/node_modules/tree-sitter/src/language.cc +106 -0
  129. package/node_modules/tree-sitter/src/language.h +17 -0
  130. package/node_modules/tree-sitter/src/logger.cc +70 -0
  131. package/node_modules/tree-sitter/src/logger.h +19 -0
  132. package/node_modules/tree-sitter/src/lookaheaditerator.cc +122 -0
  133. package/node_modules/tree-sitter/src/lookaheaditerator.h +33 -0
  134. package/node_modules/tree-sitter/src/node.cc +1056 -0
  135. package/node_modules/tree-sitter/src/node.h +30 -0
  136. package/node_modules/tree-sitter/src/parser.cc +298 -0
  137. package/node_modules/tree-sitter/src/parser.h +35 -0
  138. package/node_modules/tree-sitter/src/query.cc +383 -0
  139. package/node_modules/tree-sitter/src/query.h +39 -0
  140. package/node_modules/tree-sitter/src/tree.cc +307 -0
  141. package/node_modules/tree-sitter/src/tree.h +45 -0
  142. package/node_modules/tree-sitter/src/tree_cursor.cc +213 -0
  143. package/node_modules/tree-sitter/src/tree_cursor.h +52 -0
  144. package/node_modules/tree-sitter/tree-sitter.d.ts +211 -0
  145. package/node_modules/tree-sitter/vendor/tree-sitter/LICENSE +21 -0
  146. package/node_modules/tree-sitter/vendor/tree-sitter/README.md +21 -0
  147. package/node_modules/tree-sitter/vendor/tree-sitter/lib/README.md +6 -0
  148. package/node_modules/tree-sitter/vendor/tree-sitter/lib/include/tree_sitter/api.h +1266 -0
  149. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/alloc.c +48 -0
  150. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/alloc.h +41 -0
  151. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/array.h +290 -0
  152. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/atomic.h +68 -0
  153. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/clock.h +146 -0
  154. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/error_costs.h +11 -0
  155. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/get_changed_ranges.c +501 -0
  156. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/get_changed_ranges.h +36 -0
  157. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/host.h +21 -0
  158. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/language.c +221 -0
  159. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/language.h +299 -0
  160. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/length.h +52 -0
  161. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/lexer.c +419 -0
  162. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/lexer.h +49 -0
  163. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/lib.c +14 -0
  164. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/node.c +774 -0
  165. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/parser.c +2151 -0
  166. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/parser.h +230 -0
  167. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/point.h +62 -0
  168. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/query.c +4134 -0
  169. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/reduce_action.h +34 -0
  170. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/reusable_node.h +95 -0
  171. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/stack.c +899 -0
  172. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/stack.h +133 -0
  173. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/subtree.c +1060 -0
  174. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/subtree.h +382 -0
  175. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/tree.c +165 -0
  176. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/tree.h +31 -0
  177. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/tree_cursor.c +714 -0
  178. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/tree_cursor.h +48 -0
  179. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/ICU_SHA +1 -0
  180. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/LICENSE +414 -0
  181. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/README.md +29 -0
  182. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/ptypes.h +1 -0
  183. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/umachine.h +448 -0
  184. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/urename.h +1 -0
  185. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/utf.h +1 -0
  186. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/utf16.h +733 -0
  187. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/utf8.h +881 -0
  188. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode.h +50 -0
  189. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/wasm/stdlib-symbols.txt +23 -0
  190. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/wasm/stdlib.c +109 -0
  191. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/wasm/wasm-stdlib.h +1294 -0
  192. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/wasm_store.c +1823 -0
  193. package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/wasm_store.h +31 -0
  194. package/node_modules/tree-sitter/vendor/tree-sitter/test/fixtures/grammars/.gitkeep +0 -0
  195. package/node_modules/tree-sitter-c/LICENSE +21 -0
  196. package/node_modules/tree-sitter-c/README.md +18 -0
  197. package/node_modules/tree-sitter-c/binding.gyp +20 -0
  198. package/node_modules/tree-sitter-c/bindings/node/binding.cc +20 -0
  199. package/node_modules/tree-sitter-c/bindings/node/index.d.ts +28 -0
  200. package/node_modules/tree-sitter-c/bindings/node/index.js +7 -0
  201. package/node_modules/tree-sitter-c/grammar.js +1463 -0
  202. package/node_modules/tree-sitter-c/package.json +108 -0
  203. package/node_modules/tree-sitter-c/prebuilds/darwin-arm64/tree-sitter-c.node +0 -0
  204. package/node_modules/tree-sitter-c/prebuilds/darwin-x64/tree-sitter-c.node +0 -0
  205. package/node_modules/tree-sitter-c/prebuilds/linux-x64/tree-sitter-c.node +0 -0
  206. package/node_modules/tree-sitter-c/prebuilds/win32-x64/tree-sitter-c.node +0 -0
  207. package/node_modules/tree-sitter-c/queries/highlights.scm +81 -0
  208. package/node_modules/tree-sitter-c/queries/tags.scm +9 -0
  209. package/node_modules/tree-sitter-c/src/grammar.json +9682 -0
  210. package/node_modules/tree-sitter-c/src/node-types.json +4556 -0
  211. package/node_modules/tree-sitter-c/src/parser.c +113852 -0
  212. package/node_modules/tree-sitter-c/src/tree_sitter/alloc.h +54 -0
  213. package/node_modules/tree-sitter-c/src/tree_sitter/array.h +290 -0
  214. package/node_modules/tree-sitter-c/src/tree_sitter/parser.h +265 -0
  215. package/package.json +10 -3
  216. package/vendor/tree-sitter-0.21.1-node24.json +16 -0
  217. package/vendor/tree-sitter-0.21.1-node24.tgz +0 -0
  218. package/chunks/chunk-CGHCEN7W.js +0 -169
  219. package/chunks/chunk-CXKDWFNO.js +0 -102
  220. package/chunks/chunk-GIQKESMN.js +0 -687
  221. package/chunks/chunk-HPQZHOIU.js +0 -67
  222. package/chunks/chunk-PD2BBBKT.js +0 -245
  223. package/chunks/chunk-WF5W75WY.js +0 -1070
  224. package/chunks/fix-J2KBRJDV.js +0 -12
  225. package/chunks/orchestrate-DHBFRTSI.js +0 -59
  226. package/chunks/replay-runner-LK2XFIVG.js +0 -39
  227. package/dashboard/assets/bot-yILPnRgD.js +0 -1
  228. package/dashboard/assets/chevron-down-BeDJ-Vka.js +0 -1
  229. package/dashboard/assets/circle-alert-BEnMBMT-.js +0 -1
  230. package/dashboard/assets/client-DRYsuOGl.js +0 -9
  231. package/dashboard/assets/copy-Bi5vzoF9.js +0 -1
  232. package/dashboard/assets/desktop-BT7RkC4q.js +0 -32
  233. package/dashboard/assets/desktop-OaBI0E0e.css +0 -1
  234. package/dashboard/assets/dist-CmX-h7a4.js +0 -1
  235. package/dashboard/assets/dist-DAwYQmf_.js +0 -45
  236. package/dashboard/assets/findings-page-B4rwF6fZ.js +0 -5
  237. package/dashboard/assets/format-LNQSSpn6.js +0 -1
  238. package/dashboard/assets/jsx-runtime-C7oxC63R.js +0 -1
  239. package/dashboard/assets/live-page-DoNwpPAO.js +0 -1
  240. package/dashboard/assets/meta-tile-BMtBC-Qq.js +0 -1
  241. package/dashboard/assets/operations-app-ER0gg7GN.js +0 -2
  242. package/dashboard/assets/operations-e2CGYKB1.js +0 -4
  243. package/dashboard/assets/overview-page-CmeWH17A.js +0 -1
  244. package/dashboard/assets/page-header-CIJyycfz.js +0 -1
  245. package/dashboard/assets/play-Dwo-9xt6.js +0 -1
  246. package/dashboard/assets/scans-page-CuhzWhHx.js +0 -1
  247. package/dashboard/assets/search-CiooTLck.js +0 -1
  248. package/dashboard/assets/siren-CCBwTSuk.js +0 -1
  249. package/dashboard/assets/table-BgsSBdke.js +0 -1
  250. package/dashboard/assets/tabs-vn6v7Bk6.js +0 -1
  251. package/dashboard/desktop.html +0 -21
@@ -1,1070 +0,0 @@
1
- #!/usr/bin/env node
2
- import { createRequire as __0CreateRequire } from "node:module";
3
- const require = __0CreateRequire(import.meta.url);
4
- import {
5
- allowlistedChildEnv
6
- } from "./chunk-QZVG3BG4.js";
7
- import {
8
- VERSION
9
- } from "./chunk-PC6RCKQV.js";
10
-
11
- // packages/core/dist/verify/replay-runner.js
12
- import { spawn } from "node:child_process";
13
- import { createHash, randomUUID } from "node:crypto";
14
- import { mkdirSync, mkdtempSync, writeFileSync, existsSync, statSync, readFileSync, rmSync } from "node:fs";
15
- import { tmpdir } from "node:os";
16
- import { join, resolve, isAbsolute, posix, win32 } from "node:path";
17
- import { arch as nodeArch, platform as nodePlatform } from "node:process";
18
- import { gzipSync } from "node:zlib";
19
- var STREAM_EXCERPT_BYTES = 8 * 1024;
20
- var DEFAULT_STEP_TIMEOUT_MS = 3e4;
21
- var MAX_STREAM_CAPTURE_BYTES = 1 * 1024 * 1024;
22
- var promiseWithResolvers = Promise;
23
- var LocalShellRunner = class {
24
- kind = "local";
25
- async exec(step, ctx) {
26
- const start = Date.now();
27
- if (step.action.type !== "shell") {
28
- return {
29
- argv: argvForStep(step),
30
- exitCode: null,
31
- stdoutFull: "",
32
- stderrFull: "",
33
- durationMs: Date.now() - start,
34
- launchError: `LocalShellRunner only executes shell steps; got '${step.action.type}'`
35
- };
36
- }
37
- const cmd = step.action.cmd;
38
- const stepCwd = step.action.cwd ? resolveStepCwd(step.action.cwd, ctx.runDir) : ctx.runDir;
39
- return new Promise((resolveP) => {
40
- const argv = ["/bin/sh", "-c", cmd];
41
- let stdout = "";
42
- let stderr = "";
43
- let stdoutBytes = 0;
44
- let stderrBytes = 0;
45
- let timedOut = false;
46
- let settled = false;
47
- let child;
48
- try {
49
- child = spawn("/bin/sh", ["-c", cmd], {
50
- cwd: stepCwd,
51
- // `cmd` is a PoC-authored shell step (model/target-derived), so this
52
- // child must not inherit the harness's provider/cloud credentials.
53
- // Build from the allowlist (PATH/HOME/TMPDIR + target-auth vars a
54
- // reproduction legitimately needs) rather than copying process.env.
55
- env: allowlistedChildEnv({ "ZERO_VERIFY": "1" }),
56
- stdio: ["ignore", "pipe", "pipe"],
57
- // On POSIX, isolate the shell and all descendants into a process
58
- // group so a timeout cannot leave a grandchild holding stdout open.
59
- detached: nodePlatform !== "win32"
60
- });
61
- } catch (err) {
62
- resolveP({
63
- argv,
64
- exitCode: null,
65
- stdoutFull: "",
66
- stderrFull: "",
67
- durationMs: Date.now() - start,
68
- launchError: err instanceof Error ? err.message : String(err)
69
- });
70
- return;
71
- }
72
- const timer = setTimeout(() => {
73
- timedOut = true;
74
- try {
75
- if (nodePlatform !== "win32" && child.pid) {
76
- process.kill(-child.pid, "SIGKILL");
77
- } else {
78
- child.kill("SIGKILL");
79
- }
80
- } catch {
81
- }
82
- }, ctx.stepTimeoutMs);
83
- child.stdout?.on("data", (chunk) => {
84
- if (stdoutBytes >= MAX_STREAM_CAPTURE_BYTES)
85
- return;
86
- const remaining = MAX_STREAM_CAPTURE_BYTES - stdoutBytes;
87
- const slice = chunk.length > remaining ? chunk.subarray(0, remaining) : chunk;
88
- stdout += slice.toString("utf8");
89
- stdoutBytes += slice.length;
90
- });
91
- child.stderr?.on("data", (chunk) => {
92
- if (stderrBytes >= MAX_STREAM_CAPTURE_BYTES)
93
- return;
94
- const remaining = MAX_STREAM_CAPTURE_BYTES - stderrBytes;
95
- const slice = chunk.length > remaining ? chunk.subarray(0, remaining) : chunk;
96
- stderr += slice.toString("utf8");
97
- stderrBytes += slice.length;
98
- });
99
- child.on("error", (err) => {
100
- if (settled)
101
- return;
102
- settled = true;
103
- clearTimeout(timer);
104
- resolveP({
105
- argv,
106
- exitCode: null,
107
- stdoutFull: stdout,
108
- stderrFull: stderr,
109
- durationMs: Date.now() - start,
110
- launchError: err.message
111
- });
112
- });
113
- child.on("close", (code) => {
114
- if (settled)
115
- return;
116
- settled = true;
117
- clearTimeout(timer);
118
- resolveP({
119
- argv,
120
- exitCode: typeof code === "number" ? code : null,
121
- stdoutFull: stdout,
122
- stderrFull: stderr,
123
- durationMs: Date.now() - start,
124
- timedOut
125
- });
126
- });
127
- });
128
- }
129
- };
130
- function resolveStepCwd(cwd, runDir) {
131
- if (isAbsolute(cwd)) {
132
- return runDir;
133
- }
134
- const resolved = resolve(runDir, cwd);
135
- if (!resolved.startsWith(runDir))
136
- return runDir;
137
- return resolved;
138
- }
139
- var DEFAULT_DOCKER_SHELL_IMAGE = "alpine:3.20";
140
- var DEFAULT_DOCKER_HTTP_IMAGE = "curlimages/curl:8.12.1";
141
- var DOCKER_HTTP_STATUS_MARKER = "\n__ZERO_HTTP_STATUS__:";
142
- var CONTAINER_ID_RE = /^[a-f0-9]{12,64}$/i;
143
- var DOCKER_NETWORK_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/;
144
- var DockerRunner = class {
145
- kind = "docker";
146
- dockerBinary;
147
- shellImage;
148
- httpImage;
149
- network;
150
- memoryMb;
151
- pidsLimit;
152
- cpus;
153
- constructor(options = {}) {
154
- this.dockerBinary = options.dockerBinary ?? "docker";
155
- this.shellImage = options.shellImage ?? DEFAULT_DOCKER_SHELL_IMAGE;
156
- this.httpImage = options.httpImage ?? DEFAULT_DOCKER_HTTP_IMAGE;
157
- this.network = options.network ?? "none";
158
- this.memoryMb = options.memoryMb ?? 256;
159
- this.pidsLimit = options.pidsLimit ?? 128;
160
- this.cpus = options.cpus ?? 1;
161
- if (!this.dockerBinary || !this.shellImage || !this.httpImage || !Number.isInteger(this.memoryMb) || this.memoryMb < 16 || !Number.isInteger(this.pidsLimit) || this.pidsLimit < 1 || !Number.isFinite(this.cpus) || this.cpus <= 0) {
162
- throw new Error("invalid DockerRunner configuration");
163
- }
164
- if (this.network !== "none" && this.network !== "bridge" && (!DOCKER_NETWORK_RE.test(this.network) || this.network === "host")) {
165
- throw new Error("DockerRunner network must be none, bridge, or a simple custom network name (host is refused)");
166
- }
167
- }
168
- async exec(step, ctx) {
169
- const startedAt = Date.now();
170
- const preflightError = this.networkPreflight(step, ctx.scope);
171
- if (preflightError) {
172
- return failedStep(step, startedAt, preflightError);
173
- }
174
- const command = this.commandForStep(step, ctx.stepTimeoutMs);
175
- if (typeof command === "string") {
176
- return failedStep(step, startedAt, command);
177
- }
178
- const safeStepId = step.id.replace(/[^a-zA-Z0-9_.-]/g, "_").slice(0, 64) || "step";
179
- const cidPath = join(ctx.runDir, `.0-docker-${safeStepId}-${randomUUID()}.cid`);
180
- const args = this.dockerRunArgs(command, ctx.runDir, cidPath);
181
- const result = await runDockerCommand({
182
- dockerBinary: this.dockerBinary,
183
- args,
184
- runDir: ctx.runDir,
185
- cidPath,
186
- stepTimeoutMs: ctx.stepTimeoutMs
187
- });
188
- if (!command.parseHttpStatus)
189
- return result;
190
- const parsed = parseDockerHttpStatus(result.stdoutFull);
191
- return {
192
- ...result,
193
- stdoutFull: parsed.stdout,
194
- ...parsed.httpStatus === void 0 ? {} : { httpStatus: parsed.httpStatus }
195
- };
196
- }
197
- networkPreflight(step, scope) {
198
- if (this.network === "none")
199
- return void 0;
200
- if (step.action.type !== "http") {
201
- return "networked Docker replay only permits declarative HTTP steps; shell and docker actions always run with network=none";
202
- }
203
- let parsed;
204
- try {
205
- parsed = new URL(step.action.url);
206
- } catch {
207
- return `invalid HTTP replay URL: ${step.action.url}`;
208
- }
209
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
210
- return `Docker HTTP replay only permits http(s) URLs, got ${parsed.protocol}`;
211
- }
212
- if (!scope) {
213
- return "networked Docker replay requires an explicit engagement scope";
214
- }
215
- const scopeMatch = scope.match(step.action.url);
216
- return scopeMatch.allowed ? void 0 : `Docker replay refused ${step.action.url}: ${scopeMatch.reason}`;
217
- }
218
- commandForStep(step, stepTimeoutMs) {
219
- switch (step.action.type) {
220
- case "shell": {
221
- const cwd = step.action.cwd ?? ".";
222
- const workdir = posix.resolve("/work", cwd);
223
- if (isAbsolute(cwd) || win32.isAbsolute(cwd) || /[\\\0]/.test(cwd) || workdir !== "/work" && !workdir.startsWith("/work/")) {
224
- return "Docker shell cwd must be relative and remain inside the replay workspace";
225
- }
226
- return {
227
- image: this.shellImage,
228
- command: ["/bin/sh", "-lc", step.action.cmd],
229
- parseHttpStatus: false,
230
- workdir
231
- };
232
- }
233
- case "docker":
234
- if (!step.action.image.trim())
235
- return "Docker action is missing an image";
236
- if (step.action.image.trimStart().startsWith("-"))
237
- return "Docker image must not be a command-line option";
238
- return {
239
- image: step.action.image,
240
- // These arguments come after the image and therefore cannot alter
241
- // Docker's hardening flags (for example, they cannot add --privileged).
242
- command: step.action.args,
243
- parseHttpStatus: false
244
- };
245
- case "http": {
246
- const method = step.action.method.trim().toUpperCase();
247
- if (!/^[A-Z]+$/.test(method)) {
248
- return `invalid HTTP method for Docker replay: ${step.action.method}`;
249
- }
250
- const headers = Object.entries(step.action.headers ?? {});
251
- if (headers.some(([name, value]) => !name.trim() || /[\r\n]/.test(name) || /[\r\n]/.test(value))) {
252
- return "HTTP replay headers must be non-empty single-line values";
253
- }
254
- const command = [
255
- "--silent",
256
- "--show-error",
257
- "--request",
258
- method,
259
- "--max-time",
260
- String(Math.max(1, Math.ceil(stepTimeoutMs / 1e3))),
261
- "--output",
262
- "-",
263
- "--write-out",
264
- `${DOCKER_HTTP_STATUS_MARKER}%{http_code}
265
- `
266
- ];
267
- for (const [name, value] of headers) {
268
- command.push("--header", `${name}: ${value}`);
269
- }
270
- if (step.action.body !== void 0) {
271
- command.push("--data-binary", step.action.body);
272
- }
273
- command.push(step.action.url);
274
- return {
275
- image: this.httpImage,
276
- dockerOptions: ["--entrypoint", "curl"],
277
- command,
278
- parseHttpStatus: true
279
- };
280
- }
281
- case "note":
282
- return `DockerRunner only executes shell, docker, and http steps; got '${step.action.type}'`;
283
- default: {
284
- const _exhaustive = step.action;
285
- void _exhaustive;
286
- return "unsupported Docker replay action";
287
- }
288
- }
289
- }
290
- dockerRunArgs(command, runDir, cidPath) {
291
- const args = [
292
- "run",
293
- "--rm",
294
- "--pull",
295
- "never",
296
- "--init",
297
- "--read-only",
298
- "--cap-drop",
299
- "ALL",
300
- "--security-opt",
301
- "no-new-privileges:true",
302
- "--pids-limit",
303
- String(this.pidsLimit),
304
- "--memory",
305
- `${this.memoryMb}m`,
306
- "--cpus",
307
- String(this.cpus),
308
- "--network",
309
- this.network,
310
- "--workdir",
311
- command.workdir ?? "/work",
312
- "--tmpfs",
313
- "/tmp:rw,noexec,nosuid,size=64m",
314
- "--mount",
315
- `type=bind,src=${resolve(runDir)},dst=/work`,
316
- "--cidfile",
317
- cidPath,
318
- ...command.dockerOptions ?? []
319
- ];
320
- if (typeof process.getuid === "function" && typeof process.getgid === "function") {
321
- args.push("--user", `${process.getuid()}:${process.getgid()}`);
322
- }
323
- args.push("--", command.image, ...command.command);
324
- return args;
325
- }
326
- };
327
- function failedStep(step, startedAt, launchError) {
328
- return {
329
- argv: argvForStep(step),
330
- exitCode: null,
331
- stdoutFull: "",
332
- stderrFull: "",
333
- durationMs: Date.now() - startedAt,
334
- launchError
335
- };
336
- }
337
- function parseDockerHttpStatus(stdout) {
338
- const markerIndex = stdout.lastIndexOf(DOCKER_HTTP_STATUS_MARKER);
339
- if (markerIndex === -1)
340
- return { stdout };
341
- const status = /^(\d{3})\r?\n?$/.exec(stdout.slice(markerIndex + DOCKER_HTTP_STATUS_MARKER.length));
342
- if (!status)
343
- return { stdout };
344
- return {
345
- stdout: stdout.slice(0, markerIndex),
346
- httpStatus: Number(status[1])
347
- };
348
- }
349
- async function runDockerCommand(args) {
350
- const startedAt = Date.now();
351
- const { promise, resolve: resolveResult } = promiseWithResolvers.withResolvers();
352
- let timer;
353
- const argv = [args.dockerBinary, ...args.args];
354
- let stdout = "";
355
- let stderr = "";
356
- let stdoutBytes = 0;
357
- let stderrBytes = 0;
358
- let timedOut = false;
359
- let settled = false;
360
- let timeoutCleanup;
361
- let child;
362
- const finish = async (exitCode, launchError) => {
363
- if (settled)
364
- return;
365
- settled = true;
366
- clearTimeout(timer);
367
- if (timeoutCleanup)
368
- await timeoutCleanup;
369
- const containerStarted = existsSync(args.cidPath);
370
- rmSync(args.cidPath, { force: true });
371
- const startError = launchError ?? (exitCode !== 0 && !timedOut && !containerStarted ? `Docker sandbox did not start (exit ${exitCode ?? "unknown"}): ${excerpt(stderr, 512)}` : void 0);
372
- resolveResult({
373
- argv,
374
- exitCode,
375
- stdoutFull: stdout,
376
- stderrFull: stderr,
377
- durationMs: Date.now() - startedAt,
378
- ...timedOut ? { timedOut: true } : {},
379
- ...startError ? { launchError: startError } : {}
380
- });
381
- };
382
- try {
383
- child = spawn(args.dockerBinary, args.args, {
384
- cwd: args.runDir,
385
- env: allowlistedChildEnv({ "ZERO_VERIFY": "1" }),
386
- stdio: ["ignore", "pipe", "pipe"],
387
- detached: nodePlatform !== "win32"
388
- });
389
- } catch (err) {
390
- void finish(null, err instanceof Error ? err.message : String(err));
391
- return promise;
392
- }
393
- timer = setTimeout(() => {
394
- timedOut = true;
395
- timeoutCleanup = stopDockerContainer(args.dockerBinary, args.cidPath, args.runDir).finally(() => killProcessGroup(child));
396
- }, args.stepTimeoutMs);
397
- child.stdout?.on("data", (chunk) => {
398
- if (stdoutBytes >= MAX_STREAM_CAPTURE_BYTES)
399
- return;
400
- const remaining = MAX_STREAM_CAPTURE_BYTES - stdoutBytes;
401
- const slice = chunk.length > remaining ? chunk.subarray(0, remaining) : chunk;
402
- stdout += slice.toString("utf8");
403
- stdoutBytes += slice.length;
404
- });
405
- child.stderr?.on("data", (chunk) => {
406
- if (stderrBytes >= MAX_STREAM_CAPTURE_BYTES)
407
- return;
408
- const remaining = MAX_STREAM_CAPTURE_BYTES - stderrBytes;
409
- const slice = chunk.length > remaining ? chunk.subarray(0, remaining) : chunk;
410
- stderr += slice.toString("utf8");
411
- stderrBytes += slice.length;
412
- });
413
- child.on("error", (err) => {
414
- void finish(null, err.message);
415
- });
416
- child.on("close", (code) => {
417
- void finish(typeof code === "number" ? code : null);
418
- });
419
- return promise;
420
- }
421
- async function stopDockerContainer(dockerBinary, cidPath, runDir) {
422
- let containerId;
423
- for (let attempt = 0; attempt < 20; attempt += 1) {
424
- try {
425
- const candidate = readFileSync(cidPath, "utf8").trim();
426
- if (CONTAINER_ID_RE.test(candidate)) {
427
- containerId = candidate;
428
- break;
429
- }
430
- } catch {
431
- }
432
- const { promise, resolve: resolve2 } = promiseWithResolvers.withResolvers();
433
- setTimeout(resolve2, 25);
434
- await promise;
435
- }
436
- if (!containerId)
437
- return;
438
- await runDockerControl(dockerBinary, ["kill", containerId], runDir);
439
- await runDockerControl(dockerBinary, ["rm", "--force", containerId], runDir);
440
- }
441
- function runDockerControl(dockerBinary, args, cwd) {
442
- const { promise, resolve: resolve2 } = promiseWithResolvers.withResolvers();
443
- let child;
444
- try {
445
- child = spawn(dockerBinary, args, {
446
- cwd,
447
- env: allowlistedChildEnv({ "ZERO_VERIFY": "1" }),
448
- stdio: "ignore",
449
- detached: nodePlatform !== "win32"
450
- });
451
- } catch {
452
- resolve2();
453
- return promise;
454
- }
455
- const timer = setTimeout(() => {
456
- killProcessGroup(child);
457
- }, 5e3);
458
- child.once("error", () => {
459
- clearTimeout(timer);
460
- resolve2();
461
- });
462
- child.once("close", () => {
463
- clearTimeout(timer);
464
- resolve2();
465
- });
466
- return promise;
467
- }
468
- function killProcessGroup(child) {
469
- try {
470
- if (nodePlatform !== "win32" && child.pid) {
471
- process.kill(-child.pid, "SIGKILL");
472
- } else {
473
- child.kill("SIGKILL");
474
- }
475
- } catch {
476
- }
477
- }
478
- var QemuRunner = class {
479
- kind = "qemu";
480
- qemuBinary;
481
- kernelImage;
482
- busyboxPath;
483
- memoryMb;
484
- cpus;
485
- constructor(options = {}) {
486
- this.qemuBinary = options.qemuBinary ?? process.env["ZERO_REPLAY_QEMU_BINARY"]?.trim() ?? (nodeArch === "arm64" ? "qemu-system-aarch64" : "qemu-system-x86_64");
487
- this.kernelImage = options.kernelImage ?? process.env["ZERO_REPLAY_QEMU_KERNEL"]?.trim() ?? "";
488
- this.busyboxPath = options.busyboxPath ?? process.env["ZERO_REPLAY_QEMU_BUSYBOX"]?.trim() ?? "";
489
- this.memoryMb = options.memoryMb ?? 512;
490
- this.cpus = options.cpus ?? 1;
491
- if (!this.qemuBinary || !Number.isInteger(this.memoryMb) || this.memoryMb < 128 || !Number.isInteger(this.cpus) || this.cpus < 1) {
492
- throw new Error("invalid QemuRunner configuration");
493
- }
494
- }
495
- async exec(step, ctx) {
496
- const startedAt = Date.now();
497
- if (!this.kernelImage || !this.busyboxPath) {
498
- return failedStep(step, startedAt, "QEMU replay requires kernelImage and busyboxPath (or ZERO_REPLAY_QEMU_KERNEL and ZERO_REPLAY_QEMU_BUSYBOX)");
499
- }
500
- if (!existsSync(this.kernelImage) || !statSync(this.kernelImage).isFile()) {
501
- return failedStep(step, startedAt, `QEMU kernel image is not a file: ${this.kernelImage}`);
502
- }
503
- if (!existsSync(this.busyboxPath) || !statSync(this.busyboxPath).isFile()) {
504
- return failedStep(step, startedAt, `QEMU BusyBox binary is not a file: ${this.busyboxPath}`);
505
- }
506
- if (step.action.type !== "shell") {
507
- return failedStep(step, startedAt, `QemuRunner only executes shell steps in an offline guest; got '${step.action.type}'`);
508
- }
509
- const guestCwd = qemuGuestWorkingDirectory(step.action.cwd);
510
- if (!guestCwd) {
511
- return failedStep(step, startedAt, "QEMU replay refuses an absolute step cwd");
512
- }
513
- const safeStepId = step.id.replace(/[^a-zA-Z0-9_.-]/g, "_").slice(0, 64) || "step";
514
- const workspaceName = `.0-qemu-${safeStepId}-${randomUUID()}`;
515
- const workspace = join(ctx.runDir, workspaceName);
516
- mkdirSync(workspace, { recursive: true });
517
- writeFileSync(join(workspace, "step.sh"), step.action.cmd, "utf8");
518
- const initrdPath = buildQemuInitramfs({
519
- runDir: ctx.runDir,
520
- busyboxPath: this.busyboxPath,
521
- workspaceName,
522
- guestCwd
523
- });
524
- const args = this.qemuArgs(ctx.runDir, initrdPath);
525
- const qemu = await runQemuCommand({
526
- qemuBinary: this.qemuBinary,
527
- args,
528
- runDir: ctx.runDir,
529
- stepTimeoutMs: ctx.stepTimeoutMs
530
- });
531
- const stdout = readQemuCapture(join(workspace, "stdout.log"));
532
- const guestStderr = readQemuCapture(join(workspace, "stderr.log"));
533
- const guestExitCode = readQemuExitCode(join(workspace, "exit-code"));
534
- const serial = qemu.stdoutFull ? `
535
- [qemu serial]
536
- ${qemu.stdoutFull}` : "";
537
- const stderr = `${guestStderr}${qemu.stderrFull}${serial}`;
538
- const missingExitMarker = !qemu.timedOut && guestExitCode === null ? "QEMU guest did not write an exit marker; inspect serial evidence" : void 0;
539
- return {
540
- argv: [this.qemuBinary, ...args],
541
- exitCode: guestExitCode,
542
- stdoutFull: stdout,
543
- stderrFull: stderr,
544
- durationMs: qemu.durationMs,
545
- ...qemu.timedOut ? { timedOut: true } : {},
546
- ...qemu.launchError || missingExitMarker ? { launchError: qemu.launchError ?? missingExitMarker } : {}
547
- };
548
- }
549
- qemuArgs(runDir, initrdPath) {
550
- return [
551
- "-nodefaults",
552
- "-no-reboot",
553
- "-display",
554
- "none",
555
- "-monitor",
556
- "none",
557
- "-serial",
558
- "stdio",
559
- "-m",
560
- String(this.memoryMb),
561
- "-smp",
562
- String(this.cpus),
563
- "-kernel",
564
- this.kernelImage,
565
- "-initrd",
566
- initrdPath,
567
- "-append",
568
- "console=ttyS0 rdinit=/init panic=-1",
569
- "-virtfs",
570
- `local,path=${resolve(runDir)},mount_tag=0-replay,security_model=none,id=osecshare`,
571
- "-net",
572
- "none",
573
- "-sandbox",
574
- "on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny"
575
- ];
576
- }
577
- };
578
- function qemuGuestWorkingDirectory(cwd) {
579
- if (!cwd)
580
- return "/mnt/0";
581
- if (isAbsolute(cwd))
582
- return void 0;
583
- const relative = resolve("/", cwd).slice(1);
584
- return relative ? `/mnt/0/${relative}` : "/mnt/0";
585
- }
586
- function buildQemuInitramfs(args) {
587
- const empty = Buffer.alloc(0);
588
- const init = Buffer.from(renderQemuInit(args.workspaceName, args.guestCwd), "utf8");
589
- const entries = [
590
- { name: "bin", mode: 16877, body: empty },
591
- { name: "dev", mode: 16877, body: empty },
592
- { name: "mnt", mode: 16877, body: empty },
593
- { name: "mnt/0", mode: 16877, body: empty },
594
- { name: "proc", mode: 16877, body: empty },
595
- { name: "sys", mode: 16877, body: empty },
596
- { name: "tmp", mode: 16877, body: empty },
597
- { name: "bin/busybox", mode: 33261, body: readFileSync(args.busyboxPath) },
598
- { name: "init", mode: 33261, body: init }
599
- ];
600
- const chunks = [];
601
- for (let index = 0; index < entries.length; index += 1) {
602
- appendNewcEntry(chunks, entries[index], index + 1);
603
- }
604
- appendNewcEntry(chunks, { name: "TRAILER!!!", mode: 0, body: empty }, 0);
605
- const initrdPath = join(args.runDir, `.0-qemu-initrd-${randomUUID()}.cpio.gz`);
606
- writeFileSync(initrdPath, gzipSync(Buffer.concat(chunks)));
607
- return initrdPath;
608
- }
609
- function appendNewcEntry(chunks, entry, inode) {
610
- const name = Buffer.from(`${entry.name}\0`, "utf8");
611
- const fields = [
612
- inode,
613
- entry.mode,
614
- 0,
615
- 0,
616
- 1,
617
- 0,
618
- entry.body.length,
619
- 0,
620
- 0,
621
- 0,
622
- 0,
623
- name.length,
624
- 0
625
- ];
626
- const header = Buffer.from(`070701${fields.map((field) => field.toString(16).padStart(8, "0")).join("")}`, "ascii");
627
- chunks.push(header, name);
628
- const headerPadding = (4 - (header.length + name.length) % 4) % 4;
629
- if (headerPadding > 0)
630
- chunks.push(Buffer.alloc(headerPadding));
631
- chunks.push(entry.body);
632
- const bodyPadding = (4 - entry.body.length % 4) % 4;
633
- if (bodyPadding > 0)
634
- chunks.push(Buffer.alloc(bodyPadding));
635
- }
636
- function renderQemuInit(workspaceName, guestCwd) {
637
- const workspace = `/mnt/0/${workspaceName}`;
638
- const step = `${workspace}/step.sh`;
639
- const stdout = `${workspace}/stdout.log`;
640
- const stderr = `${workspace}/stderr.log`;
641
- const exitCode = `${workspace}/exit-code`;
642
- return [
643
- "#!/bin/busybox sh",
644
- "/bin/busybox mkdir -p /proc /sys /dev /tmp /mnt/0",
645
- "/bin/busybox mount -t proc proc /proc",
646
- "/bin/busybox mount -t sysfs sysfs /sys",
647
- "/bin/busybox mount -t devtmpfs devtmpfs /dev 2>/dev/null || true",
648
- "if ! /bin/busybox mount -t 9p -o trans=virtio,version=9p2000.L 0-replay /mnt/0; then",
649
- ' echo "__ZERO_QEMU_MOUNT_FAILED__"',
650
- " /bin/busybox poweroff -f",
651
- "fi",
652
- `(
653
- cd ${shellQuote(guestCwd)} || exit 125
654
- /bin/busybox sh ${shellQuote(step)}
655
- ) > ${shellQuote(stdout)} 2> ${shellQuote(stderr)}`,
656
- "rc=$?",
657
- `printf '%s\\n' "$rc" > ${shellQuote(exitCode)}`,
658
- "/bin/busybox sync",
659
- "/bin/busybox poweroff -f"
660
- ].join("\n");
661
- }
662
- function shellQuote(value) {
663
- return `'${value.replace(/'/g, `'\\''`)}'`;
664
- }
665
- function readQemuCapture(path) {
666
- try {
667
- return readFileSync(path).subarray(0, MAX_STREAM_CAPTURE_BYTES).toString("utf8");
668
- } catch {
669
- return "";
670
- }
671
- }
672
- function readQemuExitCode(path) {
673
- try {
674
- const raw = readFileSync(path, "utf8").trim();
675
- if (!/^(0|[1-9][0-9]{0,2})$/.test(raw))
676
- return null;
677
- const value = Number(raw);
678
- return value <= 255 ? value : null;
679
- } catch {
680
- return null;
681
- }
682
- }
683
- function runQemuCommand(args) {
684
- const startedAt = Date.now();
685
- const { promise, resolve: resolveResult } = promiseWithResolvers.withResolvers();
686
- let timer;
687
- let stdout = "";
688
- let stderr = "";
689
- let stdoutBytes = 0;
690
- let stderrBytes = 0;
691
- let timedOut = false;
692
- let settled = false;
693
- let child;
694
- const finish = (exitCode, launchError) => {
695
- if (settled)
696
- return;
697
- settled = true;
698
- clearTimeout(timer);
699
- resolveResult({
700
- exitCode,
701
- stdoutFull: stdout,
702
- stderrFull: stderr,
703
- durationMs: Date.now() - startedAt,
704
- ...timedOut ? { timedOut: true } : {},
705
- ...launchError ? { launchError } : {}
706
- });
707
- };
708
- try {
709
- child = spawn(args.qemuBinary, args.args, {
710
- cwd: args.runDir,
711
- env: allowlistedChildEnv({ "ZERO_VERIFY": "1" }),
712
- stdio: ["ignore", "pipe", "pipe"],
713
- detached: nodePlatform !== "win32"
714
- });
715
- } catch (err) {
716
- finish(null, err instanceof Error ? err.message : String(err));
717
- return promise;
718
- }
719
- timer = setTimeout(() => {
720
- timedOut = true;
721
- killProcessGroup(child);
722
- }, args.stepTimeoutMs);
723
- child.stdout?.on("data", (chunk) => {
724
- if (stdoutBytes >= MAX_STREAM_CAPTURE_BYTES)
725
- return;
726
- const remaining = MAX_STREAM_CAPTURE_BYTES - stdoutBytes;
727
- const slice = chunk.length > remaining ? chunk.subarray(0, remaining) : chunk;
728
- stdout += slice.toString("utf8");
729
- stdoutBytes += slice.length;
730
- });
731
- child.stderr?.on("data", (chunk) => {
732
- if (stderrBytes >= MAX_STREAM_CAPTURE_BYTES)
733
- return;
734
- const remaining = MAX_STREAM_CAPTURE_BYTES - stderrBytes;
735
- const slice = chunk.length > remaining ? chunk.subarray(0, remaining) : chunk;
736
- stderr += slice.toString("utf8");
737
- stderrBytes += slice.length;
738
- });
739
- child.on("error", (err) => {
740
- finish(null, err.message);
741
- });
742
- child.on("close", (code) => {
743
- const exitCode = typeof code === "number" ? code : null;
744
- finish(exitCode, exitCode !== 0 && !timedOut ? `QEMU exited before guest completion (exit ${exitCode ?? "unknown"})` : void 0);
745
- });
746
- return promise;
747
- }
748
- function argvForStep(step) {
749
- switch (step.action.type) {
750
- case "shell":
751
- return ["/bin/sh", "-c", step.action.cmd];
752
- case "docker":
753
- return ["docker", "run", "--rm", ...step.action.args, step.action.image];
754
- case "http":
755
- return [step.action.method, step.action.url];
756
- case "note":
757
- return ["note", step.id];
758
- default: {
759
- const _exhaustive = step.action;
760
- void _exhaustive;
761
- return ["unknown"];
762
- }
763
- }
764
- }
765
- function assertionFromStepExpect(step, expect, result) {
766
- switch (expect.type) {
767
- case "exit-zero": {
768
- const passed = result.exitCode === 0;
769
- return {
770
- kind: "exit_code",
771
- target: step.id,
772
- expected: 0,
773
- actual: result.exitCode,
774
- passed
775
- };
776
- }
777
- case "http-status": {
778
- const expectedStatuses = Array.isArray(expect.status) ? expect.status : [expect.status];
779
- const actual = result.httpStatus ?? null;
780
- return {
781
- kind: "http_status",
782
- target: step.id,
783
- expected: Array.isArray(expect.status) ? expect.status.join(",") : expect.status,
784
- actual,
785
- passed: actual !== null && expectedStatuses.includes(actual)
786
- };
787
- }
788
- case "body-contains": {
789
- const haystack = result.stdoutFull;
790
- const passed = haystack.includes(expect.text);
791
- return {
792
- kind: "string_in_output",
793
- target: step.id,
794
- expected: expect.text,
795
- actual: passed ? expect.text : null,
796
- passed
797
- };
798
- }
799
- case "body-matches": {
800
- let passed = false;
801
- try {
802
- passed = new RegExp(expect.pattern).test(result.stdoutFull);
803
- } catch {
804
- passed = false;
805
- }
806
- return {
807
- kind: "string_in_output",
808
- target: step.id,
809
- expected: expect.pattern,
810
- actual: passed ? "matched" : null,
811
- passed
812
- };
813
- }
814
- case "file-exists": {
815
- const passed = existsSync(expect.path);
816
- return {
817
- kind: "file_exists",
818
- target: expect.path,
819
- expected: true,
820
- actual: passed,
821
- passed
822
- };
823
- }
824
- default: {
825
- const _exhaustive = expect;
826
- void _exhaustive;
827
- return {
828
- kind: "string_in_output",
829
- target: step.id,
830
- expected: "",
831
- actual: null,
832
- passed: false
833
- };
834
- }
835
- }
836
- }
837
- function evaluateAssertion(input, ctx) {
838
- switch (input.kind) {
839
- case "exit_code": {
840
- const actual = ctx.lastExitCode;
841
- return {
842
- ...input,
843
- actual,
844
- passed: actual === input.expected
845
- };
846
- }
847
- case "string_in_output": {
848
- const needle = String(input.expected);
849
- const passed = ctx.aggregatedStdout.includes(needle);
850
- return {
851
- ...input,
852
- actual: passed ? needle : null,
853
- passed
854
- };
855
- }
856
- case "file_exists": {
857
- const path = isAbsolute(input.target) ? input.target : join(ctx.runDir, input.target);
858
- const passed = existsSync(path);
859
- return {
860
- ...input,
861
- actual: passed,
862
- passed
863
- };
864
- }
865
- case "http_status": {
866
- const actual = ctx.lastHttpStatus;
867
- const expectedStatuses = String(input.expected).split(",").map((status) => Number(status.trim())).filter((status) => Number.isInteger(status));
868
- return {
869
- ...input,
870
- actual,
871
- passed: actual !== null && expectedStatuses.includes(actual)
872
- };
873
- }
874
- default: {
875
- const _exhaustive = input.kind;
876
- void _exhaustive;
877
- return {
878
- ...input,
879
- actual: null,
880
- passed: false
881
- };
882
- }
883
- }
884
- }
885
- function excerpt(text, max = STREAM_EXCERPT_BYTES) {
886
- if (!text)
887
- return "";
888
- if (Buffer.byteLength(text, "utf8") <= max)
889
- return text;
890
- const buf = Buffer.from(text, "utf8");
891
- return buf.subarray(0, max).toString("utf8") + "\u2026[truncated]";
892
- }
893
- function sha256Hex(input) {
894
- return createHash("sha256").update(input).digest("hex");
895
- }
896
- function persistArtifact(args) {
897
- if (!args.body)
898
- return null;
899
- const artifactsDir = join(args.runDir, "artifacts");
900
- mkdirSync(artifactsDir, { recursive: true });
901
- const sha = sha256Hex(args.body);
902
- const safeHint = args.filenameHint.replace(/[^a-zA-Z0-9._-]/g, "_");
903
- const fname = `${safeHint}-${sha.slice(0, 16)}`;
904
- const fullPath = join(artifactsDir, fname);
905
- writeFileSync(fullPath, args.body, "utf8");
906
- return {
907
- kind: args.kind,
908
- path: join("artifacts", fname),
909
- sha256: sha,
910
- bytes: statSync(fullPath).size
911
- };
912
- }
913
- async function runDeterministicReplay(finding, opts = {}) {
914
- const runner = opts.runner ?? new LocalShellRunner();
915
- const stepTimeoutMs = opts.stepTimeoutMs ?? DEFAULT_STEP_TIMEOUT_MS;
916
- const engineVersion = opts.engineVersion ?? VERSION;
917
- const runDir = opts.runDir ?? mkdtempSync(join(tmpdir(), "0-replay-"));
918
- mkdirSync(runDir, { recursive: true });
919
- const startedAt = /* @__PURE__ */ new Date();
920
- const startMs = Date.now();
921
- const commands = [];
922
- const assertions = [];
923
- const evidenceArtifacts = [];
924
- const steps = finding.pocSteps ?? [];
925
- if (steps.length === 0) {
926
- const completedAt2 = /* @__PURE__ */ new Date();
927
- return {
928
- runDir,
929
- result: {
930
- status: "skipped",
931
- mode: "deterministic_replay",
932
- finding_id: finding.id,
933
- engine_version: engineVersion,
934
- started_at: startedAt.toISOString(),
935
- completed_at: completedAt2.toISOString(),
936
- duration_ms: completedAt2.getTime() - startMs,
937
- commands: [],
938
- assertions: [],
939
- evidence_artifacts: [],
940
- engine_metadata: {
941
- os: nodePlatform,
942
- arch: nodeArch,
943
- runner: runner.kind
944
- },
945
- summary: "no pocSteps to execute"
946
- }
947
- };
948
- }
949
- let lastExit = null;
950
- let lastHttpStatus = null;
951
- let aggregatedStdout = "";
952
- let runnerLaunchError = null;
953
- for (const step of steps) {
954
- let stepResult;
955
- try {
956
- stepResult = await runner.exec(step, { runDir, stepTimeoutMs, scope: opts.scope });
957
- } catch (err) {
958
- stepResult = {
959
- argv: argvForStep(step),
960
- exitCode: null,
961
- stdoutFull: "",
962
- stderrFull: "",
963
- durationMs: 0,
964
- launchError: err instanceof Error ? err.message : String(err)
965
- };
966
- runnerLaunchError = stepResult.launchError ?? "runner exec failed";
967
- }
968
- lastExit = stepResult.exitCode;
969
- aggregatedStdout += stepResult.stdoutFull;
970
- if (stepResult.launchError) {
971
- runnerLaunchError = stepResult.launchError;
972
- }
973
- lastHttpStatus = stepResult.httpStatus ?? null;
974
- commands.push({
975
- argv: stepResult.argv,
976
- exit_code: stepResult.exitCode,
977
- stdout_excerpt: excerpt(stepResult.stdoutFull),
978
- stderr_excerpt: excerpt(stepResult.stderrFull),
979
- duration_ms: stepResult.durationMs
980
- });
981
- const stdoutArt = persistArtifact({
982
- runDir,
983
- kind: "stdout",
984
- filenameHint: `step-${step.id}.stdout`,
985
- body: stepResult.stdoutFull
986
- });
987
- if (stdoutArt)
988
- evidenceArtifacts.push(stdoutArt);
989
- const stderrArt = persistArtifact({
990
- runDir,
991
- kind: "stderr",
992
- filenameHint: `step-${step.id}.stderr`,
993
- body: stepResult.stderrFull
994
- });
995
- if (stderrArt)
996
- evidenceArtifacts.push(stderrArt);
997
- if (step.expect) {
998
- assertions.push(assertionFromStepExpect(step, step.expect, stepResult));
999
- }
1000
- if (runnerLaunchError)
1001
- break;
1002
- }
1003
- for (const a of opts.assertions ?? []) {
1004
- assertions.push(evaluateAssertion(a, {
1005
- lastExitCode: lastExit,
1006
- aggregatedStdout,
1007
- runDir,
1008
- lastHttpStatus
1009
- }));
1010
- }
1011
- const completedAt = /* @__PURE__ */ new Date();
1012
- const allAssertionsPassed = assertions.length === 0 ? false : assertions.every((a) => a.passed);
1013
- const status = runnerLaunchError ? "error" : allAssertionsPassed ? "reproduced" : "not_reproduced";
1014
- return {
1015
- runDir,
1016
- result: {
1017
- status,
1018
- mode: "deterministic_replay",
1019
- finding_id: finding.id,
1020
- engine_version: engineVersion,
1021
- started_at: startedAt.toISOString(),
1022
- completed_at: completedAt.toISOString(),
1023
- duration_ms: completedAt.getTime() - startMs,
1024
- commands,
1025
- assertions,
1026
- evidence_artifacts: evidenceArtifacts,
1027
- engine_metadata: {
1028
- os: nodePlatform,
1029
- arch: nodeArch,
1030
- runner: runner.kind
1031
- },
1032
- error_reason: runnerLaunchError ?? null,
1033
- summary: summariseOutcome({
1034
- status,
1035
- ran: commands.length,
1036
- total: steps.length,
1037
- runnerLaunchError
1038
- })
1039
- }
1040
- };
1041
- }
1042
- function summariseOutcome(args) {
1043
- switch (args.status) {
1044
- case "reproduced":
1045
- return `replay reproduced the finding (${args.ran}/${args.total} steps executed)`;
1046
- case "not_reproduced":
1047
- return `replay completed but assertions failed (${args.ran}/${args.total} steps executed)`;
1048
- case "error":
1049
- return args.runnerLaunchError ? `runner error: ${args.runnerLaunchError}` : "runner error";
1050
- case "skipped":
1051
- return "no pocSteps to execute";
1052
- }
1053
- }
1054
-
1055
- export {
1056
- STREAM_EXCERPT_BYTES,
1057
- DEFAULT_STEP_TIMEOUT_MS,
1058
- MAX_STREAM_CAPTURE_BYTES,
1059
- LocalShellRunner,
1060
- DEFAULT_DOCKER_SHELL_IMAGE,
1061
- DEFAULT_DOCKER_HTTP_IMAGE,
1062
- DockerRunner,
1063
- QemuRunner,
1064
- argvForStep,
1065
- assertionFromStepExpect,
1066
- evaluateAssertion,
1067
- excerpt,
1068
- persistArtifact,
1069
- runDeterministicReplay
1070
- };