0sec-cli 0.18.0 → 0.22.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.
- package/{0sec.js → 0.js} +90 -82
- package/LICENSE +1 -1
- package/README.md +62 -46
- package/chunks/adapt-loop-7I57LXXR.js +18 -0
- package/chunks/{adgraph-JLGA6RYI.js → adgraph-AUDQTBYA.js} +5 -5
- package/chunks/agent/skills/frameworks/entra-id.yaml +2 -2
- package/chunks/agent/skills/techniques/assumption-mining.yaml +2 -2
- package/chunks/agent/skills/techniques/cve-poc-adaptation.yaml +2 -2
- package/chunks/agent/skills/techniques/entra-attack-paths.yaml +1 -1
- package/chunks/agent/skills/techniques/kernel-weaponization.yaml +1 -1
- package/chunks/agent/skills/techniques/llm-prompt-injection.yaml +2 -2
- package/chunks/agent/skills/techniques/npm-ecosystem.yaml +1 -1
- package/chunks/agent/skills/techniques/poc-verification.yaml +1 -1
- package/chunks/agent/skills/techniques/seedless-depth-review.yaml +20 -38
- package/chunks/{artifact-scraper-HQ7HLP6V.js → artifact-scraper-PYZ3RU3O.js} +6 -6
- package/chunks/{assumption-mining-5SNIVVDN.js → assumption-mining-MFLF43IR.js} +24 -26
- package/chunks/{chunk-7DQEV5QI.js → chunk-23DG5YKS.js} +4 -4
- package/chunks/{chunk-UR4ELX2Y.js → chunk-2GV4HQKE.js} +340 -703
- package/chunks/{chunk-P6WKNFWX.js → chunk-2OQOQ2FZ.js} +8 -8
- package/chunks/{chunk-5G3ZXFBW.js → chunk-2WNCI664.js} +3 -3
- package/chunks/{chunk-DW5UWPFY.js → chunk-4VGLO2YS.js} +6 -5
- package/chunks/{chunk-KLTNTE2Z.js → chunk-5IBCBQEH.js} +13 -14
- package/chunks/{chunk-N7BOUYEV.js → chunk-5MWAG3FT.js} +14 -14
- package/chunks/{chunk-OEFNRYI2.js → chunk-6GHSR47F.js} +3 -6
- package/chunks/chunk-6YNKRUMC.js +4474 -0
- package/chunks/{chunk-6L26OBWI.js → chunk-76KSBWOQ.js} +255 -35
- package/chunks/chunk-7DEHG7YT.js +2143 -0
- package/chunks/{chunk-AD7WXDH6.js → chunk-7UFUALR2.js} +288 -22
- package/chunks/{chunk-53G27VPS.js → chunk-AZ7L3HFD.js} +7 -2
- package/chunks/{chunk-TSK2AG6J.js → chunk-CAYANSH6.js} +173 -1757
- package/chunks/{chunk-SAFFWQW4.js → chunk-DKSCN2AB.js} +7 -7
- package/chunks/{chunk-2JCCA2JL.js → chunk-DM5C5L5D.js} +3 -3
- package/chunks/{chunk-RWONANDA.js → chunk-DQTNI3KY.js} +5 -5
- package/chunks/{chunk-5JI7L7KV.js → chunk-EGZGZFAH.js} +10036 -12938
- package/chunks/{chunk-6LKRLK2R.js → chunk-FHC2B7LN.js} +3 -3
- package/chunks/{chunk-MYVT64FN.js → chunk-GHWODZMR.js} +2 -2
- package/chunks/{chunk-2RMLOJVB.js → chunk-GVD6SJGH.js} +2 -2
- package/chunks/{chunk-SF4KZ4O3.js → chunk-HLFS6WV2.js} +2 -2
- package/chunks/chunk-HR2TU42B.js +1228 -0
- package/chunks/{chunk-BFRB6HB2.js → chunk-HUNAPTKV.js} +3 -3
- package/chunks/chunk-IO7R7WBM.js +114 -0
- package/chunks/{chunk-WEBMRWEG.js → chunk-IXT64DV5.js} +656 -1868
- package/chunks/{chunk-3MOLBTLS.js → chunk-K2PWUPUI.js} +2 -2
- package/chunks/{chunk-WXQ6BJ6A.js → chunk-LCRTAQN2.js} +7 -7
- package/chunks/{chunk-SVJEDVYK.js → chunk-LPTRPRZR.js} +5 -5
- package/chunks/{chunk-CMKXP5RR.js → chunk-LYUAM3TG.js} +3 -3
- package/chunks/{chunk-BVAZO4WA.js → chunk-MICHDTBK.js} +3 -3
- package/chunks/{chunk-UCYRA73C.js → chunk-OAYUPGF6.js} +4 -4
- package/chunks/{chunk-XM5NLHYU.js → chunk-OWLJYJ5O.js} +14 -14
- package/chunks/{chunk-BKZFDZ23.js → chunk-QCUFBVIZ.js} +3 -3
- package/chunks/chunk-RE5EZDG2.js +5523 -0
- package/chunks/{chunk-ASUR522M.js → chunk-RRD4XXPU.js} +9 -9
- package/chunks/{chunk-AHTBZC3E.js → chunk-RSIUX3RW.js} +4 -4
- package/chunks/{chunk-BZNEY2ZS.js → chunk-SJBCK2EI.js} +15268 -16053
- package/chunks/{chunk-IOL7D5YV.js → chunk-SQCBHFWN.js} +3 -3
- package/chunks/chunk-TH2LW437.js +7829 -0
- package/chunks/{chunk-WVTBZEQO.js → chunk-TM74SSHC.js} +8 -8
- package/chunks/{chunk-U5FLXGHT.js → chunk-TO35Y326.js} +13 -729
- package/chunks/chunk-TRH2PJSN.js +547 -0
- package/chunks/{chunk-YYRBQVFE.js → chunk-TZFIAMWZ.js} +70 -16
- package/chunks/chunk-UELXHK2Y.js +1950 -0
- package/chunks/{chunk-RJWOYEOG.js → chunk-UEX7PFSE.js} +5 -5
- package/chunks/{chunk-VQG4FT5D.js → chunk-UFHN6RIU.js} +2 -2
- package/chunks/{chunk-JQHOLLTD.js → chunk-UU7PQP6N.js} +9 -9
- package/chunks/{chunk-BFR2CDV5.js → chunk-UV5QWZGE.js} +283 -153
- package/chunks/{chunk-WU6AFRAZ.js → chunk-V36HVTUL.js} +12 -12
- package/chunks/{chunk-IR537GON.js → chunk-VCSFCHAJ.js} +2 -2
- package/chunks/{chunk-YLMN3N25.js → chunk-VIT5ALXF.js} +3 -3
- package/chunks/{chunk-BATRQBOR.js → chunk-VMRRNRMR.js} +41 -41
- package/chunks/{chunk-72CISA2D.js → chunk-VNRY6AWF.js} +89 -41
- package/chunks/chunk-W6UWZFKL.js +298 -0
- package/chunks/{chunk-3KPWWJCI.js → chunk-XFCTUKXR.js} +88 -88
- package/chunks/{chunk-GMT2AZKM.js → chunk-YIQSGNAA.js} +897 -130
- package/chunks/codex-models-7TLJSY3L.js +17 -0
- package/chunks/{commands-TBC2F5CE.js → commands-NEI7L3QA.js} +2836 -1424
- package/chunks/corpus-v1.json +2 -2
- package/chunks/data/appsec-archetypes.json +2 -2
- package/chunks/data/chromium-archetypes.json +1 -1
- package/chunks/data/freebsd-archetypes.json +1 -1
- package/chunks/data/kernel-archetypes.json +2 -2
- package/chunks/db-5AP3JB4W.js +16 -0
- package/chunks/{disclose-EKTWSCGW.js → disclose-LDNKAVRH.js} +13 -11
- package/chunks/{dist-4YICS2J2.js → dist-3FNZIBB4.js} +394 -404
- package/chunks/{dist-4MAW5X6L.js → dist-5YHYNLI3.js} +6 -6
- package/chunks/{dist-FMGSR3DW.js → dist-DD4H6HMQ.js} +13 -5
- package/chunks/dist-KEULE5IQ.js +21914 -0
- package/chunks/dist-XZB53PS5.js +53 -0
- package/chunks/eval-runner-43BO2C4T.js +26 -0
- package/chunks/example-manifest.json +2 -2
- package/chunks/{exploit-agent-45YQOHPJ.js → exploit-agent-N4A5ELBK.js} +4 -4
- package/chunks/{exploit-autoclimb-UZGEMGYT.js → exploit-autoclimb-N7P6KIF5.js} +6 -6
- package/chunks/{exploit-climb-SG23KB2G.js → exploit-climb-WNEJUB5W.js} +12 -12
- package/chunks/fix-Z452M4EC.js +26 -0
- package/chunks/{github-issues-MJ6OYOOU.js → github-issues-IGTTWM7V.js} +7 -7
- package/chunks/harness-QJJJVSG4.js +22 -0
- package/chunks/http-conformance-U5F6GKYG.js +11 -0
- package/chunks/http-sender-I5XFFY35.js +10 -0
- package/chunks/hunt-scan-K6D2V3PX.js +36 -0
- package/chunks/{identity-6ZAIIWOR.js → identity-62SVRW4H.js} +5 -5
- package/chunks/{kernel-primitive-7U6CWHXD.js → kernel-primitive-TKUACPKD.js} +6 -6
- package/chunks/{kernel-vm-runner-3JAU4O6J.js → kernel-vm-runner-RAAO2PGT.js} +6 -6
- package/chunks/memsafety-scan-FCPAXIZE.js +16 -0
- package/chunks/{native-loop-RJCJPOUS.js → native-loop-2ZRJENZ3.js} +17 -18
- package/chunks/{npm-detectors-KB5Y5ZFX.js → npm-detectors-Z2ZIQCN4.js} +6 -6
- package/chunks/npm-dynamic-discovery-5J7FT3XM.js +13 -0
- package/chunks/orchestrate-2GWGNTBF.js +58 -0
- package/chunks/{pre-recon-cve-66EB6G4M.js → pre-recon-cve-X2K2P5YQ.js} +4 -4
- package/chunks/prepare-KYHGFWCJ.js +13 -0
- package/chunks/process-N245QIQG.js +12 -0
- package/chunks/{run-D56MPDKY.js → run-U3XU4CTC.js} +11507 -13632
- package/chunks/{runtime-G7YWIO3B.js → runtime-G75TEWQY.js} +10 -11
- package/chunks/runtime-XFWJTBUO.js +12 -0
- package/chunks/{scan-stream-FHI2FYZE.js → scan-stream-BMOCPKQX.js} +7 -7
- package/chunks/scope-WXI46UHJ.js +21 -0
- package/chunks/{session-store-GZ4A4KYT.js → session-store-27L3ZU73.js} +6 -6
- package/chunks/source-files-REHOM44I.js +12 -0
- package/chunks/{specdrift-WCLH6TTQ.js → specdrift-AFS54WEJ.js} +5 -5
- package/chunks/token-KRWP5JYA.js +72 -0
- package/chunks/token-util-DWTL33S3.js +8 -0
- package/chunks/variant-candidates-YWZORZMK.js +13 -0
- package/chunks/{web-recon-prepass-JJGPEOLE.js → web-recon-prepass-YVMFXCIK.js} +9 -9
- package/cli-path-traversal-fixture.js +352 -0
- package/dashboard/assets/bot-Bg1Tp9_d.js +1 -0
- package/dashboard/assets/dist-B_dDCPbu.js +1 -0
- package/dashboard/assets/findings-page-mayGhNx6.js +5 -0
- package/dashboard/assets/format--susmHX7.js +1 -0
- package/dashboard/assets/input-TuVPR3Zl.js +1 -0
- package/dashboard/assets/jsx-runtime-MGseXjj5.js +3 -0
- package/dashboard/assets/live-page-HWWHX99U.js +1 -0
- package/dashboard/assets/meta-tile-ckHo-w7k.js +1 -0
- package/dashboard/assets/operations-BxZ0OoXO.js +10 -0
- package/dashboard/assets/operations-CMFVrXEe.css +2 -0
- package/dashboard/assets/operations-app-D3d35rgo.js +46 -0
- package/dashboard/assets/overview-page-W_4IteEr.js +1 -0
- package/dashboard/assets/page-header-Bw9AJgBV.js +1 -0
- package/dashboard/assets/scans-page-CgzVXHnn.js +1 -0
- package/dashboard/assets/siren-KQj8QAnJ.js +1 -0
- package/dashboard/assets/table-8EDklulN.js +1 -0
- package/dashboard/assets/tabs-CFHPOwg8.js +1 -0
- package/dashboard/index.html +4 -5
- package/node_modules/node-addon-api/LICENSE.md +9 -0
- package/node_modules/node-addon-api/README.md +95 -0
- package/node_modules/node-addon-api/common.gypi +21 -0
- package/node_modules/node-addon-api/except.gypi +25 -0
- package/node_modules/node-addon-api/index.js +14 -0
- package/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
- package/node_modules/node-addon-api/napi-inl.h +7250 -0
- package/node_modules/node-addon-api/napi.h +3463 -0
- package/node_modules/node-addon-api/node_addon_api.gyp +42 -0
- package/node_modules/node-addon-api/node_api.gyp +9 -0
- package/node_modules/node-addon-api/noexcept.gypi +26 -0
- package/node_modules/node-addon-api/nothing.c +0 -0
- package/node_modules/node-addon-api/package-support.json +21 -0
- package/node_modules/node-addon-api/package.json +481 -0
- package/node_modules/node-addon-api/tools/README.md +73 -0
- package/node_modules/node-addon-api/tools/check-napi.js +99 -0
- package/node_modules/node-addon-api/tools/clang-format.js +71 -0
- package/node_modules/node-addon-api/tools/conversion.js +301 -0
- package/node_modules/node-gyp-build/LICENSE +21 -0
- package/node_modules/node-gyp-build/README.md +58 -0
- package/node_modules/node-gyp-build/SECURITY.md +5 -0
- package/node_modules/node-gyp-build/bin.js +84 -0
- package/node_modules/node-gyp-build/build-test.js +19 -0
- package/node_modules/node-gyp-build/index.js +6 -0
- package/node_modules/node-gyp-build/node-gyp-build.js +207 -0
- package/node_modules/node-gyp-build/optional.js +7 -0
- package/node_modules/node-gyp-build/package.json +43 -0
- package/node_modules/tree-sitter/LICENSE +21 -0
- package/node_modules/tree-sitter/README.md +90 -0
- package/node_modules/tree-sitter/binding.gyp +76 -0
- package/node_modules/tree-sitter/index.js +895 -0
- package/node_modules/tree-sitter/package.json +62 -0
- package/node_modules/tree-sitter/prebuilds/darwin-arm64/tree-sitter.node +0 -0
- package/node_modules/tree-sitter/prebuilds/darwin-x64/tree-sitter.node +0 -0
- package/node_modules/tree-sitter/prebuilds/linux-x64/tree-sitter.node +0 -0
- package/node_modules/tree-sitter/prebuilds/win32-x64/tree-sitter.node +0 -0
- package/node_modules/tree-sitter/src/addon_data.h +47 -0
- package/node_modules/tree-sitter/src/binding.cc +35 -0
- package/node_modules/tree-sitter/src/conversions.cc +140 -0
- package/node_modules/tree-sitter/src/conversions.h +22 -0
- package/node_modules/tree-sitter/src/language.cc +106 -0
- package/node_modules/tree-sitter/src/language.h +17 -0
- package/node_modules/tree-sitter/src/logger.cc +70 -0
- package/node_modules/tree-sitter/src/logger.h +19 -0
- package/node_modules/tree-sitter/src/lookaheaditerator.cc +122 -0
- package/node_modules/tree-sitter/src/lookaheaditerator.h +33 -0
- package/node_modules/tree-sitter/src/node.cc +1056 -0
- package/node_modules/tree-sitter/src/node.h +30 -0
- package/node_modules/tree-sitter/src/parser.cc +298 -0
- package/node_modules/tree-sitter/src/parser.h +35 -0
- package/node_modules/tree-sitter/src/query.cc +383 -0
- package/node_modules/tree-sitter/src/query.h +39 -0
- package/node_modules/tree-sitter/src/tree.cc +307 -0
- package/node_modules/tree-sitter/src/tree.h +45 -0
- package/node_modules/tree-sitter/src/tree_cursor.cc +213 -0
- package/node_modules/tree-sitter/src/tree_cursor.h +52 -0
- package/node_modules/tree-sitter/tree-sitter.d.ts +211 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/LICENSE +21 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/README.md +21 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/README.md +6 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/include/tree_sitter/api.h +1266 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/alloc.c +48 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/alloc.h +41 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/array.h +290 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/atomic.h +68 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/clock.h +146 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/error_costs.h +11 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/get_changed_ranges.c +501 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/get_changed_ranges.h +36 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/host.h +21 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/language.c +221 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/language.h +299 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/length.h +52 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/lexer.c +419 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/lexer.h +49 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/lib.c +14 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/node.c +774 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/parser.c +2151 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/parser.h +230 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/point.h +62 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/query.c +4134 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/reduce_action.h +34 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/reusable_node.h +95 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/stack.c +899 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/stack.h +133 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/subtree.c +1060 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/subtree.h +382 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/tree.c +165 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/tree.h +31 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/tree_cursor.c +714 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/tree_cursor.h +48 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/ICU_SHA +1 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/LICENSE +414 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/README.md +29 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/ptypes.h +1 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/umachine.h +448 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/urename.h +1 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/utf.h +1 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/utf16.h +733 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/utf8.h +881 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode.h +50 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/wasm/stdlib-symbols.txt +23 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/wasm/stdlib.c +109 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/wasm/wasm-stdlib.h +1294 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/wasm_store.c +1823 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/lib/src/wasm_store.h +31 -0
- package/node_modules/tree-sitter/vendor/tree-sitter/test/fixtures/grammars/.gitkeep +0 -0
- package/node_modules/tree-sitter-c/LICENSE +21 -0
- package/node_modules/tree-sitter-c/README.md +18 -0
- package/node_modules/tree-sitter-c/binding.gyp +20 -0
- package/node_modules/tree-sitter-c/bindings/node/binding.cc +20 -0
- package/node_modules/tree-sitter-c/bindings/node/index.d.ts +28 -0
- package/node_modules/tree-sitter-c/bindings/node/index.js +7 -0
- package/node_modules/tree-sitter-c/grammar.js +1463 -0
- package/node_modules/tree-sitter-c/package.json +108 -0
- package/node_modules/tree-sitter-c/prebuilds/darwin-arm64/tree-sitter-c.node +0 -0
- package/node_modules/tree-sitter-c/prebuilds/darwin-x64/tree-sitter-c.node +0 -0
- package/node_modules/tree-sitter-c/prebuilds/linux-x64/tree-sitter-c.node +0 -0
- package/node_modules/tree-sitter-c/prebuilds/win32-x64/tree-sitter-c.node +0 -0
- package/node_modules/tree-sitter-c/queries/highlights.scm +81 -0
- package/node_modules/tree-sitter-c/queries/tags.scm +9 -0
- package/node_modules/tree-sitter-c/src/grammar.json +9682 -0
- package/node_modules/tree-sitter-c/src/node-types.json +4556 -0
- package/node_modules/tree-sitter-c/src/parser.c +113852 -0
- package/node_modules/tree-sitter-c/src/tree_sitter/alloc.h +54 -0
- package/node_modules/tree-sitter-c/src/tree_sitter/array.h +290 -0
- package/node_modules/tree-sitter-c/src/tree_sitter/parser.h +265 -0
- package/package.json +20 -13
- package/vendor/tree-sitter-0.21.1-node24.json +16 -0
- package/vendor/tree-sitter-0.21.1-node24.tgz +0 -0
- package/chunks/adapt-loop-SMH6V4PD.js +0 -18
- package/chunks/appsec-catalog-CBWHGTEL.js +0 -24
- package/chunks/chunk-242TMK6G.js +0 -110
- package/chunks/chunk-57ZENEX2.js +0 -169
- package/chunks/chunk-ADOLI6DT.js +0 -102
- package/chunks/chunk-ATATQACO.js +0 -471
- package/chunks/chunk-C2WXWFBB.js +0 -1070
- package/chunks/chunk-F3WBKITT.js +0 -687
- package/chunks/chunk-H2FFLZNK.js +0 -3
- package/chunks/chunk-LP3HHYQU.js +0 -245
- package/chunks/chunk-QI233I24.js +0 -333
- package/chunks/chunk-RRMJC3ZE.js +0 -105
- package/chunks/chunk-Z5FA2XOX.js +0 -2798
- package/chunks/cost-ledger-NU63MYZM.js +0 -13
- package/chunks/db-KFWOAUUB.js +0 -16
- package/chunks/eval-runner-7ZF472KV.js +0 -27
- package/chunks/fix-IG7LYXH3.js +0 -12
- package/chunks/harness-7ODOLOWU.js +0 -22
- package/chunks/http-conformance-DU66MZIU.js +0 -11
- package/chunks/http-sender-GWH2IYEA.js +0 -10
- package/chunks/hunt-scan-MMKUOETU.js +0 -38
- package/chunks/memsafety-scan-J7SECV5W.js +0 -16
- package/chunks/npm-dynamic-discovery-AHROOZDB.js +0 -13
- package/chunks/orchestrate-OC2VZN2X.js +0 -62
- package/chunks/pipeline-BZK5BLJR.js +0 -14
- package/chunks/prepare-MYY743TK.js +0 -13
- package/chunks/process-LQRCS5OY.js +0 -13
- package/chunks/replay-runner-45G3VU7X.js +0 -39
- package/chunks/runtime-J7PLXZNM.js +0 -12
- package/chunks/scope-BI7BF4ZY.js +0 -18
- package/chunks/source-files-PWRQR6LY.js +0 -12
- package/chunks/variant-candidates-YERCYMPD.js +0 -13
- package/dashboard/assets/0sec-icon-66SreztZ.gif +0 -0
- package/dashboard/assets/bot-yILPnRgD.js +0 -1
- package/dashboard/assets/chevron-down-BeDJ-Vka.js +0 -1
- package/dashboard/assets/circle-alert-BEnMBMT-.js +0 -1
- package/dashboard/assets/client-DRYsuOGl.js +0 -9
- package/dashboard/assets/copy-Bi5vzoF9.js +0 -1
- package/dashboard/assets/desktop-CXTQonNm.js +0 -32
- package/dashboard/assets/desktop-NOekk4QS.css +0 -1
- package/dashboard/assets/dist-CmX-h7a4.js +0 -1
- package/dashboard/assets/dist-DAwYQmf_.js +0 -45
- package/dashboard/assets/findings-page-CACW9nw2.js +0 -5
- package/dashboard/assets/format-PoISqsES.js +0 -1
- package/dashboard/assets/jsx-runtime-C7oxC63R.js +0 -1
- package/dashboard/assets/live-page-DrgQqQK5.js +0 -1
- package/dashboard/assets/meta-tile-CcskIV_o.js +0 -1
- package/dashboard/assets/operations-BKE8T-vY.css +0 -2
- package/dashboard/assets/operations-D8nUP5_m.js +0 -4
- package/dashboard/assets/operations-app-CQQhG54n.js +0 -2
- package/dashboard/assets/overview-page-DTGS8dPo.js +0 -1
- package/dashboard/assets/page-header-MFVvEtZW.js +0 -1
- package/dashboard/assets/play-Dwo-9xt6.js +0 -1
- package/dashboard/assets/scans-page-ChVtq8Us.js +0 -1
- package/dashboard/assets/search-CiooTLck.js +0 -1
- package/dashboard/assets/siren-CCBwTSuk.js +0 -1
- package/dashboard/assets/table-BYS-nIVA.js +0 -1
- package/dashboard/assets/tabs-DivxbhQy.js +0 -1
- package/dashboard/desktop.html +0 -21
|
@@ -1,68 +1,45 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createRequire as
|
|
3
|
-
const require =
|
|
2
|
+
import { createRequire as __0CreateRequire } from "node:module";
|
|
3
|
+
const require = __0CreateRequire(import.meta.url);
|
|
4
4
|
import {
|
|
5
5
|
isValidSessionId,
|
|
6
6
|
listSessions,
|
|
7
7
|
loadSession
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-MICHDTBK.js";
|
|
9
9
|
import {
|
|
10
|
-
consolePresentationOutput,
|
|
11
10
|
getSettings,
|
|
12
11
|
maybeLoadCodexAuth
|
|
13
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-YIQSGNAA.js";
|
|
14
13
|
import {
|
|
15
|
-
|
|
14
|
+
createConsoleJevRuntime,
|
|
16
15
|
createConsoleSession,
|
|
17
|
-
createEvolvedFinder,
|
|
18
16
|
inspectLensRegistry,
|
|
19
17
|
lastGreen,
|
|
20
18
|
loadLedger,
|
|
21
19
|
loadManifest,
|
|
22
20
|
makeFinderLensProbe,
|
|
23
21
|
retireArchetype,
|
|
24
|
-
runLensSynthesisLoop
|
|
25
|
-
|
|
22
|
+
runLensSynthesisLoop,
|
|
23
|
+
toToolContextJevRuntime
|
|
24
|
+
} from "./chunk-EGZGZFAH.js";
|
|
26
25
|
import {
|
|
27
26
|
source_default
|
|
28
|
-
} from "./chunk-
|
|
29
|
-
import {
|
|
30
|
-
stampDeploymentContext
|
|
31
|
-
} from "./chunk-BFR2CDV5.js";
|
|
27
|
+
} from "./chunk-HLFS6WV2.js";
|
|
32
28
|
import {
|
|
33
29
|
harvestMissesFromLedgerEntry,
|
|
34
|
-
loadEvolutionConfigFile,
|
|
35
30
|
mergeHarvestedMisses
|
|
36
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-SJBCK2EI.js";
|
|
32
|
+
import {
|
|
33
|
+
eventBus
|
|
34
|
+
} from "./chunk-RE5EZDG2.js";
|
|
37
35
|
import {
|
|
38
36
|
listOsecRunDatabasePaths,
|
|
39
37
|
osecDB,
|
|
40
|
-
resolveOsecDbPath
|
|
41
|
-
|
|
42
|
-
writeOsecRunReport
|
|
43
|
-
} from "./chunk-72CISA2D.js";
|
|
44
|
-
import {
|
|
45
|
-
loadAppsecFinderLenses
|
|
46
|
-
} from "./chunk-QI233I24.js";
|
|
47
|
-
import {
|
|
48
|
-
ScanCostLedger
|
|
49
|
-
} from "./chunk-242TMK6G.js";
|
|
38
|
+
resolveOsecDbPath
|
|
39
|
+
} from "./chunk-VNRY6AWF.js";
|
|
50
40
|
import {
|
|
51
41
|
redactSensitiveHeaders
|
|
52
|
-
} from "./chunk-
|
|
53
|
-
import {
|
|
54
|
-
CloudAuthMissingError,
|
|
55
|
-
CloudClient,
|
|
56
|
-
CloudError,
|
|
57
|
-
CloudForbiddenError,
|
|
58
|
-
CloudNetworkError,
|
|
59
|
-
CloudUnauthorizedError,
|
|
60
|
-
DEFAULT_CLOUD_HOST,
|
|
61
|
-
loadCloudCredentials
|
|
62
|
-
} from "./chunk-UR4ELX2Y.js";
|
|
63
|
-
import {
|
|
64
|
-
eventBus
|
|
65
|
-
} from "./chunk-ADOLI6DT.js";
|
|
42
|
+
} from "./chunk-UELXHK2Y.js";
|
|
66
43
|
import {
|
|
67
44
|
SEVERITY_RANK,
|
|
68
45
|
VERSION,
|
|
@@ -71,14 +48,13 @@ import {
|
|
|
71
48
|
createScanReportDocument,
|
|
72
49
|
findingSchema,
|
|
73
50
|
formatZodError,
|
|
74
|
-
homeStateDir,
|
|
75
51
|
severityRank
|
|
76
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-76KSBWOQ.js";
|
|
77
53
|
|
|
78
54
|
// packages/cli/src/utils.ts
|
|
79
55
|
import { gzipSync } from "zlib";
|
|
80
56
|
async function getRuntimeAvailability() {
|
|
81
|
-
const { detectAvailableRuntimes, LlmApiRuntime } = await import("./dist-
|
|
57
|
+
const { detectAvailableRuntimes, LlmApiRuntime } = await import("./dist-3FNZIBB4.js");
|
|
82
58
|
const apiRuntimeDiagnostics = new LlmApiRuntime({ type: "api", timeout: 5e3 }).getConfigurationDiagnostics();
|
|
83
59
|
const hasApiKey = apiRuntimeDiagnostics.valid;
|
|
84
60
|
const availableRuntimes = [...await detectAvailableRuntimes()];
|
|
@@ -110,1094 +86,14 @@ async function checkRuntimeAvailability(runtime) {
|
|
|
110
86
|
} else {
|
|
111
87
|
console.log(source_default.yellow(" Warning: No API key or local agent runtime detected. AI analysis will be skipped."));
|
|
112
88
|
}
|
|
113
|
-
console.log(source_default.gray(" Provider credentials:
|
|
89
|
+
console.log(source_default.gray(" Provider credentials: ZERO_CHATGPT_ACCESS_TOKEN | ZERO_CHATGPT_OAUTH_REFRESH_TOKEN, OPENROUTER_API_KEY, ANTHROPIC_API_KEY, AZURE_OPENAI_API_KEY, OPENAI_API_KEY"));
|
|
114
90
|
console.log("");
|
|
115
91
|
}
|
|
116
92
|
function buildShareUrl(report) {
|
|
117
93
|
const json = JSON.stringify(report);
|
|
118
94
|
const compressed = gzipSync(Buffer.from(json, "utf-8"));
|
|
119
95
|
const b64 = compressed.toString("base64url");
|
|
120
|
-
return `https://
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// packages/cli/src/commands/hunt.ts
|
|
124
|
-
import { readFileSync, writeFileSync } from "node:fs";
|
|
125
|
-
import { resolve } from "node:path";
|
|
126
|
-
function leadToCandidateFinding(finding, bugClass, seedRef, candidatePath) {
|
|
127
|
-
const evidence = finding.evidence ?? {};
|
|
128
|
-
const provenance = `Variant-hunt LEAD (bug class: ${bugClass}; seed: ${seedRef}). Surfaced by the recency hunt and gated by the adversarial skeptic \u2014 a HYPOTHESIS, not a confirmed bug. Verify the real sink + upstream-fix status (novelty) before any disclosure.`;
|
|
129
|
-
if (candidatePath) stampDeploymentContext(finding, candidatePath);
|
|
130
|
-
return {
|
|
131
|
-
...finding,
|
|
132
|
-
// LEADS are never confirmed/sendable: force candidate status so the cloud
|
|
133
|
-
// ingests them as verify candidates, never as confirmed findings.
|
|
134
|
-
status: "discovered",
|
|
135
|
-
templateId: "recency-hunt-lead",
|
|
136
|
-
evidence: {
|
|
137
|
-
request: evidence.request ?? "",
|
|
138
|
-
response: evidence.response ?? "",
|
|
139
|
-
analysis: evidence.analysis ? `${evidence.analysis}
|
|
140
|
-
|
|
141
|
-
${provenance}` : provenance
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
function parsePositive(flag, raw, dflt) {
|
|
146
|
-
if (raw === void 0) return dflt;
|
|
147
|
-
const n = parseInt(raw, 10);
|
|
148
|
-
if (!Number.isFinite(n) || n <= 0) throw new Error(`invalid ${flag} '${raw}' (expected positive integer)`);
|
|
149
|
-
return n;
|
|
150
|
-
}
|
|
151
|
-
var IMPACT_CEILINGS = ["dos-only", "info-leak", "oob-write", "uaf-control"];
|
|
152
|
-
function parseCeiling(raw) {
|
|
153
|
-
const v = raw.trim();
|
|
154
|
-
if (!IMPACT_CEILINGS.includes(v)) {
|
|
155
|
-
throw new Error(`invalid --prove-min-ceiling '${raw}' (expected one of: ${IMPACT_CEILINGS.join(", ")})`);
|
|
156
|
-
}
|
|
157
|
-
return v;
|
|
158
|
-
}
|
|
159
|
-
function parseNonNegative(flag, raw, dflt) {
|
|
160
|
-
if (raw === void 0) return dflt;
|
|
161
|
-
const n = parseInt(raw, 10);
|
|
162
|
-
if (!Number.isFinite(n) || n < 0) throw new Error(`invalid ${flag} '${raw}' (expected non-negative integer)`);
|
|
163
|
-
return n;
|
|
164
|
-
}
|
|
165
|
-
function parseEnvBestOfN(raw) {
|
|
166
|
-
if (raw === void 0 || raw.trim() === "") return void 0;
|
|
167
|
-
const n = Number.parseInt(raw, 10);
|
|
168
|
-
return Number.isFinite(n) && n > 1 ? n : void 0;
|
|
169
|
-
}
|
|
170
|
-
function parseEnvPositiveInt(raw) {
|
|
171
|
-
if (raw === void 0 || raw.trim() === "") return void 0;
|
|
172
|
-
const n = Number.parseInt(raw, 10);
|
|
173
|
-
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
174
|
-
}
|
|
175
|
-
async function runHunt(opts) {
|
|
176
|
-
const {
|
|
177
|
-
generateVariantCandidates,
|
|
178
|
-
runHuntScan,
|
|
179
|
-
makeSkepticVerifier,
|
|
180
|
-
loadKnownNegativesFromEnv,
|
|
181
|
-
buildInvariantHuntContext,
|
|
182
|
-
buildGraphSliceHuntContext,
|
|
183
|
-
localMirrors,
|
|
184
|
-
syncLoreMirror,
|
|
185
|
-
makeLloreJudge,
|
|
186
|
-
makeHuntProveStage,
|
|
187
|
-
prepare,
|
|
188
|
-
getCloudSinkConfig,
|
|
189
|
-
postFinding
|
|
190
|
-
} = await import("./dist-4YICS2J2.js");
|
|
191
|
-
const log = opts.log ?? (() => {
|
|
192
|
-
});
|
|
193
|
-
const runtime = opts.runtime ?? "api";
|
|
194
|
-
const seedDiff = readFileSync(resolve(opts.seedPath), "utf8");
|
|
195
|
-
const prepared = await prepare(opts.sourceRoot, "source-code", { ...opts.timeoutMs ? { timeout: opts.timeoutMs } : {} }, (e) => {
|
|
196
|
-
if (e.message) log(`[hunt:source] ${e.message}`);
|
|
197
|
-
});
|
|
198
|
-
const sourceRoot = resolve(prepared.resolvedTarget);
|
|
199
|
-
const noveltyRoot = opts.novelty?.rootDir ?? process.env["0SEC_LORE_MIRROR_ROOT"] ?? "/root/lore-mirror";
|
|
200
|
-
const noveltyLists = opts.novelty?.lists ?? (process.env["0SEC_LORE_LISTS"] ?? "linux-media").split(",").map((s) => s.trim()).filter(Boolean);
|
|
201
|
-
const noveltyRecentEpochs = opts.novelty?.recentEpochs ?? 1;
|
|
202
|
-
const noveltyWarnings = [];
|
|
203
|
-
const sinkCfg = getCloudSinkConfig();
|
|
204
|
-
const savedCloudSink = process.env["0SEC_CLOUD_SINK"];
|
|
205
|
-
if (sinkCfg) delete process.env["0SEC_CLOUD_SINK"];
|
|
206
|
-
try {
|
|
207
|
-
let noveltyMirrors = [];
|
|
208
|
-
if (opts.novelty && noveltyLists.length > 0) {
|
|
209
|
-
try {
|
|
210
|
-
noveltyMirrors = opts.novelty.sync ? await syncLoreMirror({
|
|
211
|
-
rootDir: noveltyRoot,
|
|
212
|
-
lists: noveltyLists,
|
|
213
|
-
recentEpochs: noveltyRecentEpochs,
|
|
214
|
-
log
|
|
215
|
-
}) : localMirrors(noveltyRoot, noveltyLists);
|
|
216
|
-
} catch (err) {
|
|
217
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
218
|
-
noveltyWarnings.push(`hunt: novelty sync failed: ${reason.slice(0, 160)}`);
|
|
219
|
-
log(`[hunt] ${noveltyWarnings[noveltyWarnings.length - 1]}`);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
if (opts.novelty && noveltyMirrors.length === 0) {
|
|
223
|
-
const message = `novelty requested but no lore mirrors found under ${noveltyRoot} for ${noveltyLists.join(",") || "(no lists)"}`;
|
|
224
|
-
return {
|
|
225
|
-
exitCode: 3,
|
|
226
|
-
result: {
|
|
227
|
-
mode: "hunt",
|
|
228
|
-
seed: opts.ref ?? opts.seedPath,
|
|
229
|
-
candidates: 0,
|
|
230
|
-
novelty: { enabled: true, required: true, mirrors: [] },
|
|
231
|
-
warnings: [...noveltyWarnings, `hunt: ${message}; aborting fail-closed`],
|
|
232
|
-
note: "Discovery did not run because requested novelty evidence was unavailable."
|
|
233
|
-
}
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
const skipCandidates = opts.skipCandidates ?? 0;
|
|
237
|
-
const maxCandidates = opts.maxCandidates ?? 40;
|
|
238
|
-
const reachableOnly = opts.reachableOnly ?? process.env.HUNT_REACHABLE_ONLY === "1";
|
|
239
|
-
const reachablePrefer = opts.reachablePrefer ?? (opts.methodology ? true : process.env.HUNT_REACHABLE_PREFER === "1");
|
|
240
|
-
let invariantCtx = null;
|
|
241
|
-
const invariantWarnings = [];
|
|
242
|
-
if (opts.invariant) {
|
|
243
|
-
try {
|
|
244
|
-
invariantCtx = await buildInvariantHuntContext({ sourceRoot, seedDiff, runtime, log });
|
|
245
|
-
if (!invariantCtx) {
|
|
246
|
-
invariantWarnings.push("hunt: --invariant set but no subsystem scope/files derivable from the seed \u2014 continuing without invariant context");
|
|
247
|
-
log(`[hunt] ${invariantWarnings[invariantWarnings.length - 1]}`);
|
|
248
|
-
}
|
|
249
|
-
} catch (err) {
|
|
250
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
251
|
-
invariantWarnings.push(`hunt: invariant-model stage failed, continuing without it: ${reason.slice(0, 160)}`);
|
|
252
|
-
log(`[hunt] ${invariantWarnings[invariantWarnings.length - 1]}`);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
let graphSliceCtx = null;
|
|
256
|
-
const graphSliceWarnings = [];
|
|
257
|
-
if (opts.graphSlice) {
|
|
258
|
-
try {
|
|
259
|
-
graphSliceCtx = buildGraphSliceHuntContext({
|
|
260
|
-
sourceRoot,
|
|
261
|
-
seedDiff,
|
|
262
|
-
...opts.cpgPath ? { cpgPath: opts.cpgPath } : {},
|
|
263
|
-
log,
|
|
264
|
-
...opts.opsHarvestSourceFiles !== void 0 ? { opsHarvestSourceFiles: opts.opsHarvestSourceFiles } : {},
|
|
265
|
-
...opts.graphSliceHops !== void 0 ? { hops: opts.graphSliceHops } : {}
|
|
266
|
-
});
|
|
267
|
-
if (!graphSliceCtx) {
|
|
268
|
-
graphSliceWarnings.push("hunt: --graph-slice set but no CPG/scope/slice derivable \u2014 continuing with the flat-text finder");
|
|
269
|
-
log(`[hunt] ${graphSliceWarnings[graphSliceWarnings.length - 1]}`);
|
|
270
|
-
}
|
|
271
|
-
} catch (err) {
|
|
272
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
273
|
-
graphSliceWarnings.push(`hunt: graph-slice stage failed, continuing without it: ${reason.slice(0, 160)}`);
|
|
274
|
-
log(`[hunt] ${graphSliceWarnings[graphSliceWarnings.length - 1]}`);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
const plan = await generateVariantCandidates({
|
|
278
|
-
sourceRoot,
|
|
279
|
-
fix: { diff: seedDiff, reference: opts.ref ?? opts.seedPath },
|
|
280
|
-
runtime,
|
|
281
|
-
maxCandidates: skipCandidates + maxCandidates,
|
|
282
|
-
...opts.models ? { models: opts.models } : {},
|
|
283
|
-
...reachableOnly ? { reachableOnly } : {},
|
|
284
|
-
...reachablePrefer ? { reachablePrefer } : {},
|
|
285
|
-
log
|
|
286
|
-
});
|
|
287
|
-
const selectedCandidates = plan.candidates.slice(skipCandidates, skipCandidates + maxCandidates);
|
|
288
|
-
if (selectedCandidates.length === 0) {
|
|
289
|
-
return {
|
|
290
|
-
exitCode: 2,
|
|
291
|
-
result: {
|
|
292
|
-
mode: "hunt",
|
|
293
|
-
seed: opts.ref ?? opts.seedPath,
|
|
294
|
-
bug_class: plan.brief.bugClass,
|
|
295
|
-
grep_patterns: plan.grepPatterns,
|
|
296
|
-
candidates: 0,
|
|
297
|
-
skipped_candidates: Math.min(skipCandidates, plan.candidates.length),
|
|
298
|
-
warnings: [...noveltyWarnings, ...plan.warnings],
|
|
299
|
-
note: plan.candidates.length === 0 ? "no candidate sites generated \u2014 seed too narrow or surface already clean" : "no candidate sites left after --skip-candidates"
|
|
300
|
-
}
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
const candidates = selectedCandidates.map((c) => ({ ...c, path: `${sourceRoot}/${c.path}` }));
|
|
304
|
-
const attemptsPerCandidate = opts.attemptsPerCandidate ?? parseEnvBestOfN(process.env.HUNT_BEST_OF_N) ?? (opts.methodology ? 4 : void 0);
|
|
305
|
-
const judgeTopK = opts.judgeTopK ?? parseEnvPositiveInt(process.env.HUNT_JUDGE_TOP_K) ?? (opts.methodology ? 2 : void 0);
|
|
306
|
-
const judgeModel = opts.judgeModel ?? process.env.HUNT_JUDGE_MODEL;
|
|
307
|
-
const baseBrief = opts.methodology ? {
|
|
308
|
-
...plan.brief,
|
|
309
|
-
pattern: `${plan.brief.pattern}
|
|
310
|
-
|
|
311
|
-
METHODOLOGY LENSES: map the full object lifecycle across handlers, callbacks, error/teardown paths and concurrent sessions; trace buffer/page provenance across zero-copy and in-place subsystem boundaries; look for a fix applied to one sibling path but bypassed by another. Refute capability, configuration and state-machine reachability before claiming impact. Treat source reasoning as a hypothesis until a sanitizer-backed reproducer proves it.`
|
|
312
|
-
} : plan.brief;
|
|
313
|
-
let huntPattern = baseBrief.pattern;
|
|
314
|
-
if (invariantCtx) huntPattern = `${huntPattern}
|
|
315
|
-
|
|
316
|
-
${invariantCtx.promptBlock}`;
|
|
317
|
-
if (graphSliceCtx) huntPattern = `${huntPattern}
|
|
318
|
-
|
|
319
|
-
${graphSliceCtx.promptBlock}`;
|
|
320
|
-
const huntBrief = huntPattern === baseBrief.pattern ? baseBrief : { ...baseBrief, pattern: huntPattern };
|
|
321
|
-
const res = await runHuntScan({
|
|
322
|
-
sourceRoot,
|
|
323
|
-
candidates,
|
|
324
|
-
brief: huntBrief,
|
|
325
|
-
runtime,
|
|
326
|
-
concurrency: opts.concurrency ?? 4,
|
|
327
|
-
...opts.models ? { models: opts.models } : {},
|
|
328
|
-
...attemptsPerCandidate ? { attemptsPerCandidate } : {},
|
|
329
|
-
...judgeTopK ? { judgeTopK } : {},
|
|
330
|
-
...judgeModel ? { judgeModel } : {},
|
|
331
|
-
// The skeptic is handed `models[0]` — i.e. by default the refute pass runs
|
|
332
|
-
// on the SAME MODEL that found the bug, which is the correlated-error
|
|
333
|
-
// problem in #661. `finderModels` lets the cross-family selector avoid
|
|
334
|
-
// every family in the finder fan-out (not just the first) and swap the
|
|
335
|
-
// refuter to a different family when one is configured; when only one
|
|
336
|
-
// provider is configured it passes through to exactly this model. `log`
|
|
337
|
-
// makes that decision visible in the run log instead of implicit.
|
|
338
|
-
...opts.verify === false ? {} : {
|
|
339
|
-
verify: makeSkepticVerifier({
|
|
340
|
-
sourceRoot,
|
|
341
|
-
runtime,
|
|
342
|
-
...opts.models?.[0] ? { model: opts.models[0] } : {},
|
|
343
|
-
...opts.models && opts.models.length > 0 ? { finderModels: opts.models } : {},
|
|
344
|
-
negatives: loadKnownNegativesFromEnv(),
|
|
345
|
-
log
|
|
346
|
-
})
|
|
347
|
-
},
|
|
348
|
-
// PROVE stage (#1119) — the terminal gate. Only wired on --exploitability,
|
|
349
|
-
// because it boots REAL QEMU per confirmed finding. runHuntScan itself
|
|
350
|
-
// ignores it (with a warning) when `verify` is absent, so --no-verify
|
|
351
|
-
// --exploitability degrades safely instead of booting anything.
|
|
352
|
-
...opts.exploitability ? {
|
|
353
|
-
exploitability: makeHuntProveStage({
|
|
354
|
-
// Cheap VM-free pre-filter: never spend a QEMU slot on a dos-only bug.
|
|
355
|
-
escalation: opts.proveMinCeiling ? { minCeiling: opts.proveMinCeiling } : {},
|
|
356
|
-
onVerdict: (f, _c, v) => log(
|
|
357
|
-
`[hunt:prove] ${f.title}: maxObserved=${v.maxObservedClass} upgraded=${v.upgraded} reachesPrivesc=${v.reachesPrivesc} proven=${v.provenExploitability}`
|
|
358
|
-
),
|
|
359
|
-
onEscalation: (f, _c, v) => log(`[hunt:prove] ${f.title}: impact ceiling ${v.ceiling} (${v.basis}, conf ${v.confidence})`),
|
|
360
|
-
log
|
|
361
|
-
})
|
|
362
|
-
} : {},
|
|
363
|
-
...opts.novelty && noveltyMirrors.length > 0 ? {
|
|
364
|
-
novelty: {
|
|
365
|
-
mirrors: noveltyMirrors,
|
|
366
|
-
...opts.novelty.model ? { judge: makeLloreJudge({ model: opts.novelty.model }) } : {}
|
|
367
|
-
}
|
|
368
|
-
} : {},
|
|
369
|
-
log
|
|
370
|
-
});
|
|
371
|
-
const gated = opts.verify !== false;
|
|
372
|
-
const leads = gated ? res.confirmed : res.findings;
|
|
373
|
-
let ingested = 0;
|
|
374
|
-
if (sinkCfg) {
|
|
375
|
-
const seedRef = opts.ref ?? opts.seedPath;
|
|
376
|
-
const pathForId = /* @__PURE__ */ new Map();
|
|
377
|
-
for (const rec of res.records) pathForId.set(rec.finding.id, rec.candidatePath);
|
|
378
|
-
for (const lead of leads) {
|
|
379
|
-
const candidatePath = pathForId.get(lead.id);
|
|
380
|
-
await postFinding(leadToCandidateFinding(lead, plan.brief.bugClass, seedRef, candidatePath), sinkCfg);
|
|
381
|
-
ingested++;
|
|
382
|
-
}
|
|
383
|
-
log(`[hunt] posted ${ingested} lead(s) to the cloud-sink as candidate findings`);
|
|
384
|
-
}
|
|
385
|
-
return {
|
|
386
|
-
exitCode: leads.length > 0 ? 0 : 1,
|
|
387
|
-
result: {
|
|
388
|
-
mode: "hunt",
|
|
389
|
-
seed: opts.ref ?? opts.seedPath,
|
|
390
|
-
bug_class: plan.brief.bugClass,
|
|
391
|
-
source: sourceRoot,
|
|
392
|
-
candidate_sites: selectedCandidates.map((c) => c.path),
|
|
393
|
-
skipped_candidates: skipCandidates,
|
|
394
|
-
scanned: res.scanned,
|
|
395
|
-
findings: res.findings.length,
|
|
396
|
-
confirmed: gated ? res.confirmed.length : null,
|
|
397
|
-
novelty: opts.novelty ? {
|
|
398
|
-
enabled: true,
|
|
399
|
-
required: true,
|
|
400
|
-
root: noveltyRoot,
|
|
401
|
-
lists: noveltyLists,
|
|
402
|
-
mirrors: noveltyMirrors.map((m) => ({ list: m.list, epoch: m.epoch, dir: m.dir })),
|
|
403
|
-
duplicates: res.duplicates.map((d) => ({
|
|
404
|
-
title: d.finding.title,
|
|
405
|
-
matches: d.novelty.duplicates
|
|
406
|
-
}))
|
|
407
|
-
} : { enabled: false },
|
|
408
|
-
leads: leads.map((f) => ({
|
|
409
|
-
title: f.title,
|
|
410
|
-
severity: f.severity,
|
|
411
|
-
analysis: f.evidence.analysis ?? ""
|
|
412
|
-
})),
|
|
413
|
-
dropped: res.dropped.map((d) => ({
|
|
414
|
-
title: d.finding.title,
|
|
415
|
-
severity: d.finding.severity,
|
|
416
|
-
candidatePath: d.candidatePath,
|
|
417
|
-
lensId: d.lensId,
|
|
418
|
-
dropReason: d.dropReason,
|
|
419
|
-
detail: d.detail
|
|
420
|
-
})),
|
|
421
|
-
invariant: invariantCtx ? {
|
|
422
|
-
enabled: true,
|
|
423
|
-
subsystem: invariantCtx.subsystem,
|
|
424
|
-
model_path: invariantCtx.modelPath,
|
|
425
|
-
model_loaded: invariantCtx.modelLoaded,
|
|
426
|
-
objects: invariantCtx.model.objects.length,
|
|
427
|
-
violations: invariantCtx.violations.length
|
|
428
|
-
} : { enabled: false },
|
|
429
|
-
graph_slice: graphSliceCtx ? {
|
|
430
|
-
enabled: true,
|
|
431
|
-
subsystem: graphSliceCtx.subsystem,
|
|
432
|
-
cpg_path: graphSliceCtx.cpgPath,
|
|
433
|
-
target_functions: graphSliceCtx.targetFunctions,
|
|
434
|
-
resolved_targets: graphSliceCtx.resolvedTargets,
|
|
435
|
-
ops_edges: graphSliceCtx.opsEdges,
|
|
436
|
-
functions: graphSliceCtx.stats.functions,
|
|
437
|
-
files: graphSliceCtx.stats.files.length,
|
|
438
|
-
call_edges: graphSliceCtx.stats.callEdges,
|
|
439
|
-
slice_chars: graphSliceCtx.stats.chars
|
|
440
|
-
} : { enabled: false },
|
|
441
|
-
ingested: sinkCfg ? ingested : null,
|
|
442
|
-
gated,
|
|
443
|
-
methodology: opts.methodology === true,
|
|
444
|
-
warnings: [...invariantWarnings, ...graphSliceWarnings, ...noveltyWarnings, ...plan.warnings, ...res.warnings].slice(0, 10),
|
|
445
|
-
note: opts.novelty ? "LEADS, not confirmed 0-days. Novelty-duplicate leads were dropped when lore mirrors matched; still verify the real sink before disclosure." : "LEADS, not confirmed 0-days. Verify the real sink + upstream-fix (novelty) before disclosure."
|
|
446
|
-
}
|
|
447
|
-
};
|
|
448
|
-
} finally {
|
|
449
|
-
if (savedCloudSink !== void 0) process.env["0SEC_CLOUD_SINK"] = savedCloudSink;
|
|
450
|
-
prepared.cleanup();
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
async function huntAction(opts) {
|
|
454
|
-
if (!opts.source) throw new Error("missing required flag: --source <kernel/src tree>");
|
|
455
|
-
if (!opts.seed) throw new Error("missing required flag: --seed <fix diff/patch to hunt variants of>");
|
|
456
|
-
const outcome = await runHunt({
|
|
457
|
-
sourceRoot: opts.source,
|
|
458
|
-
seedPath: opts.seed,
|
|
459
|
-
...opts.ref ? { ref: opts.ref } : {},
|
|
460
|
-
concurrency: parsePositive("--concurrency", opts.concurrency, 4),
|
|
461
|
-
skipCandidates: parseNonNegative("--skip-candidates", opts.skipCandidates, 0),
|
|
462
|
-
...opts.models ? { models: opts.models.split(",").map((s) => s.trim()).filter(Boolean) } : {},
|
|
463
|
-
...opts.reachableOnly ? { reachableOnly: true } : {},
|
|
464
|
-
...opts.reachablePrefer ? { reachablePrefer: true } : {},
|
|
465
|
-
verify: opts.verify,
|
|
466
|
-
...opts.novelty || opts.noveltyRequired || opts.methodology ? {
|
|
467
|
-
novelty: {
|
|
468
|
-
...opts.noveltyRoot ? { rootDir: opts.noveltyRoot } : {},
|
|
469
|
-
...opts.noveltyLists ? { lists: opts.noveltyLists.split(",").map((s) => s.trim()).filter(Boolean) } : {},
|
|
470
|
-
recentEpochs: parsePositive("--novelty-recent-epochs", opts.noveltyRecentEpochs, 1),
|
|
471
|
-
sync: opts.noveltySync === true || opts.methodology === true,
|
|
472
|
-
...opts.noveltyModel ? { model: opts.noveltyModel } : {},
|
|
473
|
-
required: opts.noveltyRequired === true || opts.methodology === true
|
|
474
|
-
}
|
|
475
|
-
} : {},
|
|
476
|
-
methodology: opts.methodology === true,
|
|
477
|
-
invariant: opts.invariant === true,
|
|
478
|
-
graphSlice: opts.graphSlice === true,
|
|
479
|
-
...opts.cpg ? { cpgPath: opts.cpg } : {},
|
|
480
|
-
...opts.opsHarvest !== void 0 ? {
|
|
481
|
-
opsHarvestSourceFiles: opts.opsHarvest.split(",").map((path) => path.trim()).filter(Boolean)
|
|
482
|
-
} : {},
|
|
483
|
-
...opts.graphSliceHops ? { graphSliceHops: parsePositive("--graph-slice-hops", opts.graphSliceHops, 3) } : {},
|
|
484
|
-
exploitability: opts.exploitability === true,
|
|
485
|
-
...opts.proveMinCeiling ? { proveMinCeiling: parseCeiling(opts.proveMinCeiling) } : {},
|
|
486
|
-
...opts.runtime ? { runtime: opts.runtime } : {},
|
|
487
|
-
timeoutMs: parsePositive("--timeout", opts.timeout, 6e5),
|
|
488
|
-
log: (m) => process.stderr.write(m + "\n")
|
|
489
|
-
});
|
|
490
|
-
const json = JSON.stringify(outcome.result, null, 2);
|
|
491
|
-
if (opts.output) writeFileSync(resolve(opts.output), json + "\n", "utf8");
|
|
492
|
-
else process.stdout.write(json + "\n");
|
|
493
|
-
process.exitCode = outcome.exitCode;
|
|
494
|
-
}
|
|
495
|
-
function registerHuntCommand(program) {
|
|
496
|
-
program.command("hunt").description(
|
|
497
|
-
"Hunt a bug CLASS across a source tree, seeded by a proven fix: generate variant candidate sites from the fix, fan finders out over them, and gate each finding through an adversarial skeptic. Emits LEADS to verify (not confirmed 0-days). Exit 0=lead(s), 1=none, 2=no candidates, 3=error."
|
|
498
|
-
).requiredOption("--source <path>", "Source tree to hunt in (e.g. a linux checkout)").requiredOption("--seed <path>", "Fix diff / .patch whose bug class to hunt variants of").option("--ref <name>", "Provenance label for the seed (e.g. the CVE / commit)").option("--concurrency <N>", "Max finders in flight (default 4)").option("--max-candidates <N>", "Cap candidate sites hunted (default 40)").option("--skip-candidates <N>", "Skip the first N ranked candidate sites before hunting (default 0)").option("--models <a,b>", "Comma-separated finder models for diversity (default: provider default)").option("--reachable-only", "Restrict candidates to paths built + zero-cap reachable on the kernelCTF COS target (default: HUNT_REACHABLE_ONLY env)").option("--reachable-prefer", "Sort kernelCTF-reachable candidates first, without dropping any (default: HUNT_REACHABLE_PREFER env)").option("--no-verify", "Skip the skeptic gate (emit all raw findings \u2014 triage only, never disclosure)").option("--novelty", "Require lore.kernel.org duplicate suppression; abort before discovery when evidence is unavailable").option("--novelty-root <path>", "Lore mirror root (default: 0SEC_LORE_MIRROR_ROOT or /root/lore-mirror)").option("--novelty-lists <a,b>", "Comma-separated lore lists to search (default: 0SEC_LORE_LISTS or linux-media)").option("--novelty-recent-epochs <N>", "Newest public-inbox epochs to sync per list when --novelty-sync is set (default 1)").option("--novelty-sync", "Clone/fetch lore mirrors before running the novelty gate").option("--novelty-model <model>", "Optional model override for the lore duplicate judge").option("--novelty-required", "Legacy alias; --novelty already aborts when evidence is unavailable").option("--methodology", "Use the kernel-LPE methodology preset: lifecycle/provenance lenses, best-of-4, top-2 skeptic gate, reachable-first").option("--invariant", "Engine A: build (or load) the seed-touched subsystem's stored invariant model and inject its rules + deterministic violation hypotheses into every finder prompt").option("--graph-slice", "Load the seed-touched subsystem's pre-exported Joern CPG and inject a compact interprocedural reachability slice around the fix site into every finder prompt (needs scripts/provision-cpg.sh; fail-open to flat-text)").option("--cpg <path>", "Explicit CPG graphson JSON path for --graph-slice (default: <source>/.0sec/cpg/<subsystem>.json)").option(
|
|
499
|
-
"--ops-harvest <paths>",
|
|
500
|
-
"[--graph-slice] Comma-separated repo-relative C files to harvest static ops-struct initializers from; overrides a precomputed .ops.json"
|
|
501
|
-
).option(
|
|
502
|
-
"--graph-slice-hops <N>",
|
|
503
|
-
"[--graph-slice] Call-graph radius around the seed functions (default 3; use 8 for the exp527 known answer)"
|
|
504
|
-
).option(
|
|
505
|
-
"--exploitability",
|
|
506
|
-
"PROVE stage: after the skeptic+prover gate, run the execution-verified exploitability oracle on each confirmed finding (GREBE diversify + SCAVY differential). BOOTS REAL QEMU VMs \u2014 requires staged kernel-VM artifacts and is ignored under --no-verify. Never rejects a finding; it stamps a proven verdict and gates the weaponize budget."
|
|
507
|
-
).option(
|
|
508
|
-
"--prove-min-ceiling <ceiling>",
|
|
509
|
-
"[--exploitability] Minimum assessed impact ceiling worth a VM slot: dos-only|info-leak|oob-write|uaf-control (default info-leak \u2014 filters out dos-only before QEMU is touched)"
|
|
510
|
-
).option("--output <path>", "Write the hunt result JSON to this path instead of stdout").option("--runtime <mode>", "Engine runtime (default api)").option("--timeout <ms>", "Accepted cloud agent timeout budget in milliseconds", "600000").action(async (opts) => {
|
|
511
|
-
try {
|
|
512
|
-
await huntAction(opts);
|
|
513
|
-
} catch (err) {
|
|
514
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
515
|
-
const json = JSON.stringify({ mode: "hunt", error: reason }, null, 2);
|
|
516
|
-
if (opts.output) {
|
|
517
|
-
try {
|
|
518
|
-
writeFileSync(resolve(opts.output), json + "\n", "utf8");
|
|
519
|
-
} catch {
|
|
520
|
-
process.stderr.write(json + "\n");
|
|
521
|
-
}
|
|
522
|
-
} else process.stdout.write(json + "\n");
|
|
523
|
-
process.exitCode = 3;
|
|
524
|
-
}
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// packages/cli/src/commands/deep-review.ts
|
|
529
|
-
import { readFileSync as readFileSync2, realpathSync, statSync } from "node:fs";
|
|
530
|
-
import { resolve as resolve2, join, sep, relative } from "node:path";
|
|
531
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
532
|
-
var DEEP_REVIEW_FILE_CAP = 5e3;
|
|
533
|
-
var DEFAULT_MAX_CANDIDATES = 8;
|
|
534
|
-
function defaultMaxCandidates() {
|
|
535
|
-
const raw = process.env["0SEC_DEEP_REVIEW_MAX_CANDIDATES"];
|
|
536
|
-
if (!raw) return DEFAULT_MAX_CANDIDATES;
|
|
537
|
-
const n = Number.parseInt(raw, 10);
|
|
538
|
-
return Number.isFinite(n) && n > 0 ? n : DEFAULT_MAX_CANDIDATES;
|
|
539
|
-
}
|
|
540
|
-
function scaleMaxCandidates(fileCount) {
|
|
541
|
-
if (fileCount > 2e3) return 20;
|
|
542
|
-
if (fileCount > 500) return 16;
|
|
543
|
-
if (fileCount > 200) return 12;
|
|
544
|
-
return DEFAULT_MAX_CANDIDATES;
|
|
545
|
-
}
|
|
546
|
-
var DEFAULT_ATTEMPTS = 1;
|
|
547
|
-
function defaultAttempts() {
|
|
548
|
-
const raw = process.env["0SEC_DEEP_REVIEW_ATTEMPTS"];
|
|
549
|
-
if (!raw) return DEFAULT_ATTEMPTS;
|
|
550
|
-
const n = Number.parseInt(raw, 10);
|
|
551
|
-
return Number.isFinite(n) && n > 0 ? n : DEFAULT_ATTEMPTS;
|
|
552
|
-
}
|
|
553
|
-
function defaultModels() {
|
|
554
|
-
const raw = process.env["0SEC_DEEP_REVIEW_MODELS"];
|
|
555
|
-
if (!raw) return void 0;
|
|
556
|
-
const models = raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
557
|
-
return models.length > 0 ? models : void 0;
|
|
558
|
-
}
|
|
559
|
-
var DEFAULT_CONCURRENCY = 8;
|
|
560
|
-
var genericFinderLenses = [
|
|
561
|
-
{
|
|
562
|
-
id: "memory-safety",
|
|
563
|
-
challengeHint: "Hunt MEMORY-SAFETY bugs only: out-of-bounds read/write, use-after-free, double-free, uninitialized read, integer overflow feeding an allocation or index, and unchecked length/size arithmetic before a copy. Cite the exact unguarded sink (file:line) and the attacker-controlled path that reaches it."
|
|
564
|
-
},
|
|
565
|
-
{
|
|
566
|
-
id: "input-validation",
|
|
567
|
-
challengeHint: "Hunt INPUT-VALIDATION and injection bugs only: untrusted input reaching a dangerous sink without validation \u2014 command/SQL/path/template injection, unsafe deserialization, SSRF, unchecked redirects, or a parser that trusts attacker-supplied structure. Trace the taint from the entry point to the sink."
|
|
568
|
-
},
|
|
569
|
-
{
|
|
570
|
-
id: "auth-logic",
|
|
571
|
-
challengeHint: "Hunt AUTHZ / LOGIC bugs only: a missing or wrong permission/ownership check, a broken state machine, a TOCTOU race, an off-by-one or boundary error in a security-relevant decision, or a check that can be bypassed. Prove the guard is absent or mis-scoped, not merely that the keywords appear."
|
|
572
|
-
},
|
|
573
|
-
{
|
|
574
|
-
id: "secrets-crypto",
|
|
575
|
-
challengeHint: "Hunt SECRETS / CRYPTO misuse only: hardcoded credentials, a weak/predictable RNG used for security, a broken or misused cryptographic primitive (ECB, static IV/nonce, missing MAC verification, non-constant-time compare), or a signature/token check that can be forged or replayed. Cite the exact misuse."
|
|
576
|
-
},
|
|
577
|
-
{
|
|
578
|
-
id: "cross-component",
|
|
579
|
-
challengeHint: "Hunt CROSS-COMPONENT bugs only: a bug whose exploit path spans two or more subsystems touching DIFFERENT trust boundaries \u2014 e.g. an API layer that passes unvalidated user input to a kernel driver, a storage layer whose consistency assumptions are violated by a separate async path, or a UX component whose state bleeds into an auth decision in a sibling module. Trace the path ACROSS the boundary and prove the attacker can reach BOTH sides. Ignore bugs fully contained within a single component."
|
|
580
|
-
}
|
|
581
|
-
];
|
|
582
|
-
function lensSetDigest(lenses) {
|
|
583
|
-
return createHash("sha256").update(JSON.stringify(lenses.map(({ id, challengeHint }) => ({ id, challengeHint })))).digest("hex");
|
|
584
|
-
}
|
|
585
|
-
function createDefaultFinderLenses() {
|
|
586
|
-
return [...genericFinderLenses, ...loadAppsecFinderLenses()];
|
|
587
|
-
}
|
|
588
|
-
var defaultFinderLenses = createDefaultFinderLenses();
|
|
589
|
-
var defaultVerifyLenses = [
|
|
590
|
-
{
|
|
591
|
-
id: "reachability",
|
|
592
|
-
challengeHint: "REACHABILITY: is the vulnerable code actually reachable by an attacker from a real entry point (exported/public API, request handler, CLI, parser) with no gate it cannot pass? Trace the concrete path from the entry to the sink. If the path is dead code, disabled, or privileged-only, refute it."
|
|
593
|
-
},
|
|
594
|
-
{
|
|
595
|
-
id: "completeness",
|
|
596
|
-
challengeHint: "COMPLETENESS: is the 'missing' check actually enforced elsewhere on the path \u2014 a validation earlier in the flow, a guard in the only caller, a wrapper that sanitizes first? Read the whole call path and the full file/module. If the guard is present AND correctly scoped, refute it; keep it only if genuinely absent or mis-scoped."
|
|
597
|
-
},
|
|
598
|
-
{
|
|
599
|
-
id: "novelty-known-issue",
|
|
600
|
-
challengeHint: "NOVELTY / KNOWN-GUARD: is the standard guard for this class present and correct (a framework escaper, a safe API, a bounds check, a working auth middleware)? Is this a well-known already-mitigated pattern in this codebase or its dependencies? If a correct standard guard covers it, refute it."
|
|
601
|
-
},
|
|
602
|
-
{
|
|
603
|
-
id: "scope",
|
|
604
|
-
challengeHint: "SCOPE / IMPACT: does exploiting this actually corrupt memory, leak secrets, escalate privilege, execute code, or move value / break a security invariant? A cosmetic missing check with no real impact is info/low, not a high-severity finding \u2014 refute it as such."
|
|
605
|
-
},
|
|
606
|
-
{
|
|
607
|
-
id: "deployment-context",
|
|
608
|
-
challengeHint: "DEPLOYMENT CONTEXT: is this finding's vulnerable code path actually deployed to a production runtime, or is it restricted to development, test, or build-only surfaces? \n\nYour job is to examine the finding's evidence AND your own reading of the candidate source to decide: can an attacker reach this code from a production entry point? \n\n- If the code is in a test file, a dev-only config (seeds, .dev.vars, dev-server), or build scaffolding (webpack, CI, vendored deps) with NO trust-boundary crossing from prod, refute it as dev/test/build-only.\n- If you find a trust-boundary bypass \u2014 a prod-routed entry point that reaches this code despite it being in a dev/test/build file \u2014 DO NOT refute; the finding is valid per the HackerOne rule.\n- If you are UNCERTAIN (the evidence is ambiguous or you cannot assess the deployment topology), ABSTAIN by returning an error. Never falsely refute a finding with weak evidence.\nPRIORITY: the mechanical path heuristic (file path classification) is the DETERMINISTIC FLOOR and overrides this lens on conflict. If the heuristic already tagged the finding as dev_only/test_only/build_only, the severity cap has already been applied \u2014 this lens exists to catch cases the heuristic misses (ambiguous paths, model-extracted evidence, cross-boundary reachability)."
|
|
609
|
-
}
|
|
610
|
-
];
|
|
611
|
-
function selectProfileLenses(profile, sets, defaultFinderSnapshot = defaultFinderLenses) {
|
|
612
|
-
switch ((profile ?? "").trim().toLowerCase()) {
|
|
613
|
-
case "evm-onchain":
|
|
614
|
-
return { finderLenses: sets.evmFinderLenses, verifyLenses: sets.evmVerifyLenses, matchedProfile: "evm-onchain" };
|
|
615
|
-
case "solana-onchain":
|
|
616
|
-
return { finderLenses: sets.solanaFinderLenses, verifyLenses: sets.solanaVerifyLenses, matchedProfile: "solana-onchain" };
|
|
617
|
-
case "cardano-onchain":
|
|
618
|
-
return { finderLenses: sets.cardanoFinderLenses, verifyLenses: sets.cardanoVerifyLenses, matchedProfile: "cardano-onchain" };
|
|
619
|
-
case "cairo-onchain":
|
|
620
|
-
return { finderLenses: sets.cairoFinderLenses, verifyLenses: sets.cairoVerifyLenses, matchedProfile: "cairo-onchain" };
|
|
621
|
-
case "move-onchain":
|
|
622
|
-
return { finderLenses: sets.moveFinderLenses, verifyLenses: sets.moveVerifyLenses, matchedProfile: "move-onchain" };
|
|
623
|
-
default:
|
|
624
|
-
return { finderLenses: defaultFinderSnapshot, verifyLenses: defaultVerifyLenses, matchedProfile: "default" };
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
var NATIVE_SOURCE_EXTS = /* @__PURE__ */ new Set([
|
|
628
|
-
"c",
|
|
629
|
-
"h",
|
|
630
|
-
"cc",
|
|
631
|
-
"cpp",
|
|
632
|
-
"cxx",
|
|
633
|
-
"hpp",
|
|
634
|
-
"hh",
|
|
635
|
-
"hxx",
|
|
636
|
-
"rs"
|
|
637
|
-
]);
|
|
638
|
-
var MANAGED_WEB_EXTS = /* @__PURE__ */ new Set([
|
|
639
|
-
"cs",
|
|
640
|
-
"csproj",
|
|
641
|
-
"sln",
|
|
642
|
-
"vbproj",
|
|
643
|
-
"fsproj",
|
|
644
|
-
// .NET / C#
|
|
645
|
-
"ts",
|
|
646
|
-
"tsx",
|
|
647
|
-
"js",
|
|
648
|
-
"jsx",
|
|
649
|
-
"mjs",
|
|
650
|
-
"cjs",
|
|
651
|
-
"vue",
|
|
652
|
-
"svelte",
|
|
653
|
-
// JS/TS front+back
|
|
654
|
-
"java",
|
|
655
|
-
"kt",
|
|
656
|
-
"kts",
|
|
657
|
-
"scala",
|
|
658
|
-
// JVM
|
|
659
|
-
"py",
|
|
660
|
-
"rb",
|
|
661
|
-
"php",
|
|
662
|
-
"go",
|
|
663
|
-
// scripting / Go
|
|
664
|
-
"html",
|
|
665
|
-
"htm",
|
|
666
|
-
"cshtml",
|
|
667
|
-
"razor",
|
|
668
|
-
"aspx",
|
|
669
|
-
"jsp"
|
|
670
|
-
// web templates
|
|
671
|
-
]);
|
|
672
|
-
function fileExt(path) {
|
|
673
|
-
const base = path.split(/[/\\]+/).pop() ?? path;
|
|
674
|
-
const dot = base.lastIndexOf(".");
|
|
675
|
-
return dot > 0 ? base.slice(dot + 1).toLowerCase() : "";
|
|
676
|
-
}
|
|
677
|
-
function detectStack(files) {
|
|
678
|
-
let native = 0;
|
|
679
|
-
let managed = 0;
|
|
680
|
-
for (const f of files) {
|
|
681
|
-
const ext = fileExt(f);
|
|
682
|
-
if (NATIVE_SOURCE_EXTS.has(ext)) native++;
|
|
683
|
-
else if (MANAGED_WEB_EXTS.has(ext)) managed++;
|
|
684
|
-
}
|
|
685
|
-
if (native === 0 && managed === 0) return "unknown";
|
|
686
|
-
if (native === 0) return "managed-web";
|
|
687
|
-
if (managed === 0) return "native";
|
|
688
|
-
return "mixed";
|
|
689
|
-
}
|
|
690
|
-
var MEMORY_SAFETY_LENS_ID = "memory-safety";
|
|
691
|
-
function selectDefaultFinderLensesForStack(files, defaultFinder) {
|
|
692
|
-
if (detectStack(files) === "managed-web") {
|
|
693
|
-
return defaultFinder.filter((l) => l.id !== MEMORY_SAFETY_LENS_ID);
|
|
694
|
-
}
|
|
695
|
-
return defaultFinder;
|
|
696
|
-
}
|
|
697
|
-
var EVM_EXCLUDE_DIR_SEGMENTS = /* @__PURE__ */ new Set([
|
|
698
|
-
"test",
|
|
699
|
-
"tests",
|
|
700
|
-
"mock",
|
|
701
|
-
"mocks",
|
|
702
|
-
"script",
|
|
703
|
-
"scripts",
|
|
704
|
-
// Foundry deploy scripts
|
|
705
|
-
"lib",
|
|
706
|
-
// Foundry vendored deps (forge-std / openzeppelin / solmate …)
|
|
707
|
-
"node_modules"
|
|
708
|
-
// Hardhat/JS vendored deps (also skipped by the core walker)
|
|
709
|
-
]);
|
|
710
|
-
function isNonProtocolEvmPath(absPath, scopeRoot) {
|
|
711
|
-
const rel = relative(scopeRoot, absPath);
|
|
712
|
-
if (rel === "" || rel.startsWith("..")) return false;
|
|
713
|
-
const segments = rel.split(/[/\\]+/).filter(Boolean);
|
|
714
|
-
if (segments.length === 0) return false;
|
|
715
|
-
for (let i = 0; i < segments.length - 1; i++) {
|
|
716
|
-
if (EVM_EXCLUDE_DIR_SEGMENTS.has(segments[i].toLowerCase())) return true;
|
|
717
|
-
}
|
|
718
|
-
const fileName = segments[segments.length - 1].toLowerCase();
|
|
719
|
-
if (fileName.endsWith(".t.sol")) return true;
|
|
720
|
-
if (fileName.includes(".test.") || fileName.includes(".spec.")) return true;
|
|
721
|
-
return false;
|
|
722
|
-
}
|
|
723
|
-
function topLevelModule(absPath, scopeRoot) {
|
|
724
|
-
const rel = relative(scopeRoot, absPath);
|
|
725
|
-
if (rel === "" || rel.startsWith("..")) return ".";
|
|
726
|
-
const segments = rel.split(/[/\\]+/).filter(Boolean);
|
|
727
|
-
return segments.length > 1 ? segments[0] : ".";
|
|
728
|
-
}
|
|
729
|
-
function selectCandidatesModuleSpread(entries, scopeRoot, maxCandidates) {
|
|
730
|
-
const cmp = (a, b) => b.size - a.size || a.p.localeCompare(b.p);
|
|
731
|
-
const byModule = /* @__PURE__ */ new Map();
|
|
732
|
-
for (const e of entries) {
|
|
733
|
-
const key = topLevelModule(e.p, scopeRoot);
|
|
734
|
-
let group = byModule.get(key);
|
|
735
|
-
if (!group) byModule.set(key, group = []);
|
|
736
|
-
group.push(e);
|
|
737
|
-
}
|
|
738
|
-
const groups = [...byModule.values()];
|
|
739
|
-
for (const g of groups) g.sort(cmp);
|
|
740
|
-
groups.sort((a, b) => cmp(a[0], b[0]));
|
|
741
|
-
const cap = Math.max(1, maxCandidates);
|
|
742
|
-
const out = [];
|
|
743
|
-
for (let round = 0; out.length < cap; round++) {
|
|
744
|
-
let advanced = false;
|
|
745
|
-
for (const g of groups) {
|
|
746
|
-
if (round >= g.length) continue;
|
|
747
|
-
out.push(g[round].p);
|
|
748
|
-
advanced = true;
|
|
749
|
-
if (out.length >= cap) break;
|
|
750
|
-
}
|
|
751
|
-
if (!advanced) break;
|
|
752
|
-
}
|
|
753
|
-
return out;
|
|
754
|
-
}
|
|
755
|
-
function enumerateDeepReviewCandidates(scopeRoot, helpers, opts) {
|
|
756
|
-
const fileCap = opts.fileCap ?? DEEP_REVIEW_FILE_CAP;
|
|
757
|
-
const exclude = opts.exclude ?? (() => false);
|
|
758
|
-
const fileSize = helpers.fileSize ?? ((p) => {
|
|
759
|
-
try {
|
|
760
|
-
return statSync(p).size;
|
|
761
|
-
} catch {
|
|
762
|
-
return 0;
|
|
763
|
-
}
|
|
764
|
-
});
|
|
765
|
-
const totalFiles = helpers.countScopeFilesUpTo(scopeRoot, fileCap);
|
|
766
|
-
const overCap = totalFiles > fileCap;
|
|
767
|
-
const files = helpers.collectScopeFiles(scopeRoot, { maxFiles: fileCap });
|
|
768
|
-
const entries = files.filter((p) => !exclude(p)).map((p) => ({ p, size: fileSize(p) }));
|
|
769
|
-
const candidates = selectCandidatesModuleSpread(entries, scopeRoot, opts.maxCandidates);
|
|
770
|
-
return { candidates, totalFiles, overCap };
|
|
771
|
-
}
|
|
772
|
-
async function runDeepReview(opts) {
|
|
773
|
-
const defaultFinderSnapshot = createDefaultFinderLenses();
|
|
774
|
-
const evolvedFinder = opts.evolutionConfig ? await createEvolvedFinder(opts.evolutionConfig, `deep-review-${randomUUID()}`) : void 0;
|
|
775
|
-
const {
|
|
776
|
-
runHuntScan,
|
|
777
|
-
makeMultiLensVerifier,
|
|
778
|
-
prepare,
|
|
779
|
-
collectScopeFiles,
|
|
780
|
-
countScopeFilesUpTo,
|
|
781
|
-
getCloudSinkConfig,
|
|
782
|
-
postFinding,
|
|
783
|
-
evmFinderLenses,
|
|
784
|
-
evmVerifyLenses,
|
|
785
|
-
solanaFinderLenses,
|
|
786
|
-
solanaVerifyLenses,
|
|
787
|
-
cardanoFinderLenses,
|
|
788
|
-
cardanoVerifyLenses,
|
|
789
|
-
cairoFinderLenses,
|
|
790
|
-
cairoVerifyLenses,
|
|
791
|
-
moveFinderLenses,
|
|
792
|
-
moveVerifyLenses
|
|
793
|
-
} = await import("./dist-4YICS2J2.js");
|
|
794
|
-
const costLedger = new ScanCostLedger();
|
|
795
|
-
const scanStartedAt = Date.now();
|
|
796
|
-
const log = opts.log ?? (() => {
|
|
797
|
-
});
|
|
798
|
-
const runtime = opts.runtime ?? "api";
|
|
799
|
-
const explicitMaxCandidates = opts.maxCandidates;
|
|
800
|
-
const hasEnvMaxOverride = !!process.env["0SEC_DEEP_REVIEW_MAX_CANDIDATES"];
|
|
801
|
-
const baseMaxCandidates = explicitMaxCandidates ?? defaultMaxCandidates();
|
|
802
|
-
if (evolvedFinder && opts.models?.length) throw new Error("--models selects native finders and cannot be combined with --evolution-config");
|
|
803
|
-
const models = evolvedFinder ? ["evolved-source"] : opts.models ?? defaultModels();
|
|
804
|
-
const attemptsPerCandidate = opts.attemptsPerCandidate ?? defaultAttempts();
|
|
805
|
-
const prepared = await prepare(opts.target, "source-code", { ...opts.timeoutMs ? { timeout: opts.timeoutMs } : {} }, (e) => {
|
|
806
|
-
if (e.message) log(`[deep-review:source] ${e.message}`);
|
|
807
|
-
});
|
|
808
|
-
const sourceRoot = resolve2(prepared.resolvedTarget);
|
|
809
|
-
let scopeRoot = sourceRoot;
|
|
810
|
-
if (opts.subsystem && opts.subsystem.trim() !== "") {
|
|
811
|
-
const scoped = resolve2(join(sourceRoot, opts.subsystem));
|
|
812
|
-
if (scoped !== sourceRoot && !scoped.startsWith(sourceRoot + sep)) {
|
|
813
|
-
prepared.cleanup();
|
|
814
|
-
throw new Error(`--subsystem '${opts.subsystem}' escapes the source tree`);
|
|
815
|
-
}
|
|
816
|
-
try {
|
|
817
|
-
const realRoot = realpathSync(sourceRoot);
|
|
818
|
-
const realScoped = realpathSync(scoped);
|
|
819
|
-
if (realScoped !== realRoot && !realScoped.startsWith(realRoot + sep)) {
|
|
820
|
-
throw new Error(`--subsystem '${opts.subsystem}' escapes the source tree`);
|
|
821
|
-
}
|
|
822
|
-
scopeRoot = scoped;
|
|
823
|
-
} catch (error) {
|
|
824
|
-
prepared.cleanup();
|
|
825
|
-
throw error;
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
const sinkCfg = getCloudSinkConfig();
|
|
829
|
-
const savedCloudSink = process.env["0SEC_CLOUD_SINK"];
|
|
830
|
-
if (sinkCfg) delete process.env["0SEC_CLOUD_SINK"];
|
|
831
|
-
try {
|
|
832
|
-
const isEvmProfile = (opts.profile ?? "").trim().toLowerCase() === "evm-onchain";
|
|
833
|
-
const totalFiles = countScopeFilesUpTo(scopeRoot, DEEP_REVIEW_FILE_CAP);
|
|
834
|
-
const maxCandidates = explicitMaxCandidates ?? (hasEnvMaxOverride ? baseMaxCandidates : scaleMaxCandidates(totalFiles));
|
|
835
|
-
const overCap = totalFiles > DEEP_REVIEW_FILE_CAP;
|
|
836
|
-
if (explicitMaxCandidates === void 0 && maxCandidates !== baseMaxCandidates) {
|
|
837
|
-
log(`[deep-review] auto-scaled candidate cap from ${baseMaxCandidates} to ${maxCandidates} (${totalFiles} scope files)`);
|
|
838
|
-
}
|
|
839
|
-
let lanes = null;
|
|
840
|
-
let allocateAcrossLanes;
|
|
841
|
-
if (opts.useThreatModel) {
|
|
842
|
-
const threatModule = await import("./dist-4YICS2J2.js");
|
|
843
|
-
allocateAcrossLanes = threatModule.allocateCandidatesAcrossLanes;
|
|
844
|
-
const rtType = runtime !== "auto" ? runtime : "api";
|
|
845
|
-
const plannerRuntime = threatModule.createRuntime({
|
|
846
|
-
type: rtType,
|
|
847
|
-
timeout: opts.timeoutMs ?? 6e5,
|
|
848
|
-
model: models?.[0]
|
|
849
|
-
});
|
|
850
|
-
lanes = await threatModule.runThreatModelPlanner(sourceRoot, plannerRuntime, log);
|
|
851
|
-
if (!lanes || lanes.length === 0) {
|
|
852
|
-
log("[threat-model] no lanes produced \u2014 continuing with default selection");
|
|
853
|
-
} else {
|
|
854
|
-
log(`[threat-model] planner returned ${lanes.length} lane(s) \u2014 will allocate candidates across boundaries`);
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
if (overCap) {
|
|
858
|
-
return {
|
|
859
|
-
exitCode: 2,
|
|
860
|
-
result: {
|
|
861
|
-
mode: "deep_review",
|
|
862
|
-
source: sourceRoot,
|
|
863
|
-
subsystem: opts.subsystem ?? null,
|
|
864
|
-
scope_files: totalFiles,
|
|
865
|
-
note: `scope exceeds the ${DEEP_REVIEW_FILE_CAP}-file review cap \u2014 narrow it with --subsystem <path> to a specific directory.`
|
|
866
|
-
}
|
|
867
|
-
};
|
|
868
|
-
}
|
|
869
|
-
const { candidates: initialCandidates } = enumerateDeepReviewCandidates(
|
|
870
|
-
scopeRoot,
|
|
871
|
-
{ collectScopeFiles, countScopeFilesUpTo },
|
|
872
|
-
{
|
|
873
|
-
maxCandidates,
|
|
874
|
-
fileCap: DEEP_REVIEW_FILE_CAP,
|
|
875
|
-
...isEvmProfile ? { exclude: (p) => isNonProtocolEvmPath(p, scopeRoot) } : {}
|
|
876
|
-
}
|
|
877
|
-
);
|
|
878
|
-
const candidatePaths = initialCandidates.slice();
|
|
879
|
-
if (lanes && lanes.length > 0 && allocateAcrossLanes) {
|
|
880
|
-
const allFiles = collectScopeFiles(scopeRoot, { maxFiles: DEEP_REVIEW_FILE_CAP });
|
|
881
|
-
const exclude = isEvmProfile ? (p) => isNonProtocolEvmPath(p, scopeRoot) : () => false;
|
|
882
|
-
const entries = allFiles.filter((p) => !exclude(p)).map((p) => {
|
|
883
|
-
try {
|
|
884
|
-
return { p, size: statSync(p).size };
|
|
885
|
-
} catch {
|
|
886
|
-
return { p, size: 0 };
|
|
887
|
-
}
|
|
888
|
-
});
|
|
889
|
-
const laneCandidates = allocateAcrossLanes(entries, lanes, scopeRoot, maxCandidates, log);
|
|
890
|
-
if (laneCandidates.length > 0) {
|
|
891
|
-
const prevModules = new Set(initialCandidates.map((p) => relative(scopeRoot, p).split(/[/\\]+/)[0]));
|
|
892
|
-
const laneModules = new Set(laneCandidates.map((p) => relative(scopeRoot, p).split(/[/\\]+/)[0]));
|
|
893
|
-
log(`[deep-review] threat-model re-allocated ${initialCandidates.length} \u2192 ${laneCandidates.length} candidate(s); modules ${prevModules.size} \u2192 ${laneModules.size}`);
|
|
894
|
-
candidatePaths.length = 0;
|
|
895
|
-
candidatePaths.push(...laneCandidates);
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
if (candidatePaths.length === 0) {
|
|
899
|
-
return {
|
|
900
|
-
exitCode: 2,
|
|
901
|
-
result: {
|
|
902
|
-
mode: "deep_review",
|
|
903
|
-
source: sourceRoot,
|
|
904
|
-
subsystem: opts.subsystem ?? null,
|
|
905
|
-
scope_files: totalFiles,
|
|
906
|
-
candidates: 0,
|
|
907
|
-
note: "no reviewable source files found under the scope (empty tree or all filtered)."
|
|
908
|
-
}
|
|
909
|
-
};
|
|
910
|
-
}
|
|
911
|
-
const { finderLenses: selectedFinderLenses, verifyLenses, matchedProfile } = selectProfileLenses(opts.profile, {
|
|
912
|
-
evmFinderLenses,
|
|
913
|
-
evmVerifyLenses,
|
|
914
|
-
solanaFinderLenses,
|
|
915
|
-
solanaVerifyLenses,
|
|
916
|
-
cardanoFinderLenses,
|
|
917
|
-
cardanoVerifyLenses,
|
|
918
|
-
cairoFinderLenses,
|
|
919
|
-
cairoVerifyLenses,
|
|
920
|
-
moveFinderLenses,
|
|
921
|
-
moveVerifyLenses
|
|
922
|
-
}, defaultFinderSnapshot);
|
|
923
|
-
const finderLenses = matchedProfile === "default" ? selectDefaultFinderLensesForStack(candidatePaths, selectedFinderLenses) : selectedFinderLenses;
|
|
924
|
-
const lensDigest = lensSetDigest(finderLenses);
|
|
925
|
-
const scanId = randomUUID();
|
|
926
|
-
let sourceRevisionDigest = null;
|
|
927
|
-
let sourceFingerprintWarning;
|
|
928
|
-
try {
|
|
929
|
-
const files = [...candidatePaths].sort().map((path) => ({
|
|
930
|
-
path: relative(sourceRoot, path),
|
|
931
|
-
digest: createHash("sha256").update(readFileSync2(path)).digest("hex")
|
|
932
|
-
}));
|
|
933
|
-
sourceRevisionDigest = `sha256:${createHash("sha256").update(JSON.stringify(files)).digest("hex")}`;
|
|
934
|
-
} catch (error) {
|
|
935
|
-
sourceFingerprintWarning = `learning feedback skipped: reviewed inputs could not be fingerprinted (${error instanceof Error ? error.message : String(error)})`;
|
|
936
|
-
log(`[deep-review] ${sourceFingerprintWarning}`);
|
|
937
|
-
}
|
|
938
|
-
log(
|
|
939
|
-
`[deep-review] ${candidatePaths.length} candidate file(s) (of ${totalFiles}${overCap ? "+" : ""}) \xD7 ${finderLenses.length} finder-lens(es); verify quorum over ${verifyLenses.length} lens(es); profile=${matchedProfile}; lens-snapshot=${lensDigest.slice(0, 12)}`
|
|
940
|
-
);
|
|
941
|
-
const verify = makeMultiLensVerifier(verifyLenses, {
|
|
942
|
-
sourceRoot,
|
|
943
|
-
runtime,
|
|
944
|
-
...!evolvedFinder && models?.[0] ? { model: models[0] } : {},
|
|
945
|
-
// Every verify lens is a refute pass on `models[0]` — the same model the
|
|
946
|
-
// finders ran on. Passing the full finder set lets the cross-family
|
|
947
|
-
// selector move the refutation off ALL finder families when a second
|
|
948
|
-
// provider is configured (#661), and `log` records what it decided —
|
|
949
|
-
// including "it could not", which is the case worth knowing about.
|
|
950
|
-
...opts.costCeilingUsd !== void 0 ? { costCeilingUsd: opts.costCeilingUsd } : {},
|
|
951
|
-
costLedger,
|
|
952
|
-
...!evolvedFinder && models && models.length > 0 ? { finderModels: models } : {},
|
|
953
|
-
...opts.quorum ? { quorum: opts.quorum } : {},
|
|
954
|
-
log
|
|
955
|
-
});
|
|
956
|
-
const postedIds = /* @__PURE__ */ new Set();
|
|
957
|
-
let ingested = 0;
|
|
958
|
-
const provenance = `deep_review (${matchedProfile} lenses)`;
|
|
959
|
-
const postLead = async (lead) => {
|
|
960
|
-
if (!sinkCfg || postedIds.has(lead.id)) return;
|
|
961
|
-
postedIds.add(lead.id);
|
|
962
|
-
await postFinding(leadToCandidateFinding(lead, `deep-review:${matchedProfile}`, provenance), sinkCfg);
|
|
963
|
-
ingested++;
|
|
964
|
-
};
|
|
965
|
-
const res = await runHuntScan({
|
|
966
|
-
sourceRoot,
|
|
967
|
-
candidates: candidatePaths.map((path) => ({ path })),
|
|
968
|
-
lenses: finderLenses,
|
|
969
|
-
runtime,
|
|
970
|
-
...evolvedFinder ? { finder: (input) => evolvedFinder.find(sourceRoot, input) } : {},
|
|
971
|
-
concurrency: opts.concurrency ?? DEFAULT_CONCURRENCY,
|
|
972
|
-
...opts.costCeilingUsd !== void 0 ? { costCeilingUsd: opts.costCeilingUsd } : {},
|
|
973
|
-
costLedger,
|
|
974
|
-
attemptsPerCandidate,
|
|
975
|
-
...models ? { models } : {},
|
|
976
|
-
verify,
|
|
977
|
-
...sinkCfg ? { onConfirmed: postLead } : {},
|
|
978
|
-
log
|
|
979
|
-
});
|
|
980
|
-
const leads = res.confirmed;
|
|
981
|
-
if (sourceFingerprintWarning) res.warnings.push(sourceFingerprintWarning);
|
|
982
|
-
if (sourceRevisionDigest) {
|
|
983
|
-
try {
|
|
984
|
-
for (const gap of res.incompleteCoverage ?? []) {
|
|
985
|
-
captureObservation({
|
|
986
|
-
classHint: gap.lensId || "generic-finder-timeout",
|
|
987
|
-
sinkPattern: "",
|
|
988
|
-
exampleFileLine: gap.file,
|
|
989
|
-
whyMissed: `finder ${gap.reason}; budget ${gap.budgetMs}ms`,
|
|
990
|
-
source: "incomplete-coverage",
|
|
991
|
-
scanId,
|
|
992
|
-
sourceRevisionDigest,
|
|
993
|
-
consent: false,
|
|
994
|
-
detectorLensId: gap.lensId || void 0,
|
|
995
|
-
detectorLensVersionDigest: gap.lensVersionDigest
|
|
996
|
-
});
|
|
997
|
-
}
|
|
998
|
-
for (const dropped of res.dropped ?? []) {
|
|
999
|
-
if (dropped.dropReason === "verify_refuted" || dropped.dropReason === "novelty_duplicate" || dropped.dropReason === "judge_truncation" || dropped.dropReason === "late_resolution") continue;
|
|
1000
|
-
captureObservation({
|
|
1001
|
-
classHint: dropped.lensId || "unknown-lens",
|
|
1002
|
-
sinkPattern: "",
|
|
1003
|
-
exampleFileLine: dropped.candidatePath,
|
|
1004
|
-
whyMissed: `dropped: ${dropped.dropReason}`,
|
|
1005
|
-
source: "incomplete-coverage",
|
|
1006
|
-
scanId,
|
|
1007
|
-
sourceRevisionDigest,
|
|
1008
|
-
consent: false,
|
|
1009
|
-
detectorLensId: dropped.lensId || void 0,
|
|
1010
|
-
detectorLensVersionDigest: dropped.lensVersionDigest
|
|
1011
|
-
});
|
|
1012
|
-
}
|
|
1013
|
-
} catch (error) {
|
|
1014
|
-
const warning = `learning feedback was not persisted: ${error instanceof Error ? error.message : String(error)}`;
|
|
1015
|
-
res.warnings.push(warning);
|
|
1016
|
-
log(`[deep-review] ${warning}`);
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
if (sinkCfg) {
|
|
1020
|
-
for (const lead of leads) await postLead(lead);
|
|
1021
|
-
log(`[deep-review] posted ${ingested} lead(s) to the cloud-sink as candidate findings (incremental)`);
|
|
1022
|
-
}
|
|
1023
|
-
const sweptNothing = res.scanned > 0 && typeof res.finderCompleted === "number" && res.finderCompleted === 0;
|
|
1024
|
-
const cost = costLedger.costBreakdown();
|
|
1025
|
-
eventBus.emit("scan_completed", {
|
|
1026
|
-
exit_reason: sweptNothing ? "failed" : res.costCeilingExceeded ? "cost_exceeded" : "completed",
|
|
1027
|
-
findings: res.findings.length,
|
|
1028
|
-
findings_count: res.findings.length,
|
|
1029
|
-
duration_ms: Date.now() - scanStartedAt,
|
|
1030
|
-
summary: res.costCeilingExceeded ? `Deep review stopped at the $${opts.costCeilingUsd?.toFixed(2) ?? "configured"} cost ceiling.` : `Deep review completed: ${res.scanned} finder runs, ${res.findings.length} findings, ${leads.length} leads.`,
|
|
1031
|
-
...cost ? { cost_usd: cost.costUsd, cost_breakdown: cost.breakdown } : {}
|
|
1032
|
-
});
|
|
1033
|
-
if (sweptNothing) {
|
|
1034
|
-
return {
|
|
1035
|
-
exitCode: 3,
|
|
1036
|
-
result: {
|
|
1037
|
-
mode: "deep_review",
|
|
1038
|
-
profile: matchedProfile,
|
|
1039
|
-
scan_id: scanId,
|
|
1040
|
-
source_revision_digest: sourceRevisionDigest,
|
|
1041
|
-
lens_snapshot_digest: lensDigest,
|
|
1042
|
-
...evolvedFinder ? { evolution_version_id: evolvedFinder.versionId, evolution_compute_cost_usd: evolvedFinder.computeCostUsd } : {},
|
|
1043
|
-
source: sourceRoot,
|
|
1044
|
-
subsystem: opts.subsystem ?? null,
|
|
1045
|
-
scope_files: totalFiles,
|
|
1046
|
-
candidates: candidatePaths.length,
|
|
1047
|
-
scanned: res.scanned,
|
|
1048
|
-
finder_completed: res.finderCompleted,
|
|
1049
|
-
finder_timed_out: res.finderTimedOut ?? null,
|
|
1050
|
-
finder_errored: res.finderErrored ?? null,
|
|
1051
|
-
error: `every finder run failed (0 of ${res.scanned} completed) \u2014 no coverage was produced; treating as a backend/LLM failure, not a clean 0-finding result.`,
|
|
1052
|
-
warnings: res.warnings.slice(0, 10)
|
|
1053
|
-
}
|
|
1054
|
-
};
|
|
1055
|
-
}
|
|
1056
|
-
const completedAt = /* @__PURE__ */ new Date();
|
|
1057
|
-
const candidateFindings = leads.map((lead) => ({
|
|
1058
|
-
...lead,
|
|
1059
|
-
status: "discovered"
|
|
1060
|
-
}));
|
|
1061
|
-
const report = {
|
|
1062
|
-
target: opts.target,
|
|
1063
|
-
scanDepth: "deep",
|
|
1064
|
-
startedAt: new Date(scanStartedAt).toISOString(),
|
|
1065
|
-
completedAt: completedAt.toISOString(),
|
|
1066
|
-
durationMs: completedAt.getTime() - scanStartedAt,
|
|
1067
|
-
summary: {
|
|
1068
|
-
totalAttacks: res.scanned,
|
|
1069
|
-
totalFindings: candidateFindings.length,
|
|
1070
|
-
critical: candidateFindings.filter((finding) => finding.severity === "critical").length,
|
|
1071
|
-
high: candidateFindings.filter((finding) => finding.severity === "high").length,
|
|
1072
|
-
medium: candidateFindings.filter((finding) => finding.severity === "medium").length,
|
|
1073
|
-
low: candidateFindings.filter((finding) => finding.severity === "low").length,
|
|
1074
|
-
info: candidateFindings.filter((finding) => finding.severity === "info").length
|
|
1075
|
-
},
|
|
1076
|
-
findings: candidateFindings,
|
|
1077
|
-
warnings: res.warnings.slice(0, 10).map((message) => ({
|
|
1078
|
-
stage: "attack",
|
|
1079
|
-
message
|
|
1080
|
-
})),
|
|
1081
|
-
...res.costCeilingExceeded ? { costCeilingExceeded: true, exitReason: "cost_ceiling_exceeded" } : {}
|
|
1082
|
-
};
|
|
1083
|
-
return {
|
|
1084
|
-
exitCode: 0,
|
|
1085
|
-
report,
|
|
1086
|
-
result: {
|
|
1087
|
-
mode: "deep_review",
|
|
1088
|
-
profile: matchedProfile,
|
|
1089
|
-
scan_id: scanId,
|
|
1090
|
-
source_revision_digest: sourceRevisionDigest,
|
|
1091
|
-
source: sourceRoot,
|
|
1092
|
-
subsystem: opts.subsystem ?? null,
|
|
1093
|
-
scope_files: totalFiles,
|
|
1094
|
-
lens_snapshot_digest: lensDigest,
|
|
1095
|
-
...evolvedFinder ? { evolution_version_id: evolvedFinder.versionId, evolution_compute_cost_usd: evolvedFinder.computeCostUsd } : {},
|
|
1096
|
-
candidates: candidatePaths.length,
|
|
1097
|
-
finder_lenses: finderLenses.map((l) => l.id),
|
|
1098
|
-
verify_lenses: verifyLenses.map((l) => l.id),
|
|
1099
|
-
scanned: res.scanned,
|
|
1100
|
-
findings: res.findings.length,
|
|
1101
|
-
confirmed: leads.length,
|
|
1102
|
-
leads: leads.map((f) => ({
|
|
1103
|
-
title: f.title,
|
|
1104
|
-
severity: f.severity,
|
|
1105
|
-
analysis: f.evidence.analysis ?? ""
|
|
1106
|
-
})),
|
|
1107
|
-
dropped: res.dropped.map((d) => ({
|
|
1108
|
-
title: d.finding.title,
|
|
1109
|
-
severity: d.finding.severity,
|
|
1110
|
-
candidatePath: d.candidatePath,
|
|
1111
|
-
lensId: d.lensId,
|
|
1112
|
-
dropReason: d.dropReason,
|
|
1113
|
-
detail: d.detail
|
|
1114
|
-
})),
|
|
1115
|
-
ingested: sinkCfg ? ingested : null,
|
|
1116
|
-
warnings: res.warnings.slice(0, 10),
|
|
1117
|
-
note: "LEADS, not confirmed bugs. Each survived the multi-lens refute quorum; verify the real sink + impact before disclosure."
|
|
1118
|
-
}
|
|
1119
|
-
};
|
|
1120
|
-
} catch (error) {
|
|
1121
|
-
const cost = costLedger.costBreakdown();
|
|
1122
|
-
eventBus.emit("scan_completed", {
|
|
1123
|
-
exit_reason: "failed",
|
|
1124
|
-
findings: 0,
|
|
1125
|
-
findings_count: 0,
|
|
1126
|
-
duration_ms: Date.now() - scanStartedAt,
|
|
1127
|
-
summary: `Deep review failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1128
|
-
...cost ? { cost_usd: cost.costUsd, cost_breakdown: cost.breakdown } : {}
|
|
1129
|
-
});
|
|
1130
|
-
throw error;
|
|
1131
|
-
} finally {
|
|
1132
|
-
if (savedCloudSink !== void 0) process.env["0SEC_CLOUD_SINK"] = savedCloudSink;
|
|
1133
|
-
prepared.cleanup();
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
function parsePositive2(flag, raw, dflt) {
|
|
1137
|
-
if (raw === void 0) return dflt;
|
|
1138
|
-
const n = parseInt(raw, 10);
|
|
1139
|
-
if (!Number.isFinite(n) || n <= 0) throw new Error(`invalid ${flag} '${raw}' (expected positive integer)`);
|
|
1140
|
-
return n;
|
|
1141
|
-
}
|
|
1142
|
-
async function deepReviewAction(target, opts) {
|
|
1143
|
-
if (!target || target.trim() === "") throw new Error("missing required argument: <target> (source tree path or git URL)");
|
|
1144
|
-
const ceilingSource = opts.costCeiling ?? process.env["0SEC_COST_CEILING_USD"];
|
|
1145
|
-
let costCeilingUsd;
|
|
1146
|
-
if (ceilingSource !== void 0 && ceilingSource !== "") {
|
|
1147
|
-
const parsed = Number(ceilingSource);
|
|
1148
|
-
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
1149
|
-
throw new Error(`invalid --cost-ceiling '${ceilingSource}' (expected positive USD amount)`);
|
|
1150
|
-
}
|
|
1151
|
-
costCeilingUsd = parsed;
|
|
1152
|
-
}
|
|
1153
|
-
const { writeFileSync: writeFileSync3 } = await import("node:fs");
|
|
1154
|
-
const outcome = await runDeepReview({
|
|
1155
|
-
target,
|
|
1156
|
-
...costCeilingUsd !== void 0 ? { costCeilingUsd } : {},
|
|
1157
|
-
...opts.profile ? { profile: opts.profile } : {},
|
|
1158
|
-
...opts.subsystem ? { subsystem: opts.subsystem } : {},
|
|
1159
|
-
// Flag > env > single provider-default model. Only pass `models` when the
|
|
1160
|
-
// flag is set; otherwise let runDeepReview resolve $0SEC_DEEP_REVIEW_MODELS.
|
|
1161
|
-
...opts.models ? { models: opts.models.split(",").map((s) => s.trim()).filter(Boolean) } : {},
|
|
1162
|
-
...opts.attempts ? { attemptsPerCandidate: parsePositive2("--attempts", opts.attempts, defaultAttempts()) } : {},
|
|
1163
|
-
concurrency: parsePositive2("--concurrency", opts.concurrency, DEFAULT_CONCURRENCY),
|
|
1164
|
-
maxCandidates: parsePositive2("--max-candidates", opts.maxCandidates, defaultMaxCandidates()),
|
|
1165
|
-
...opts.quorum ? { quorum: parsePositive2("--quorum", opts.quorum, 1) } : {},
|
|
1166
|
-
...opts.threatModel ? { useThreatModel: true } : {},
|
|
1167
|
-
...opts.runtime ? { runtime: opts.runtime } : {},
|
|
1168
|
-
...opts.evolutionConfig ? { evolutionConfig: loadEvolutionConfigFile(opts.evolutionConfig) } : {},
|
|
1169
|
-
timeoutMs: parsePositive2("--timeout", opts.timeout, 6e5),
|
|
1170
|
-
log: (m) => process.stderr.write(m + "\n")
|
|
1171
|
-
});
|
|
1172
|
-
if (outcome.report) {
|
|
1173
|
-
const storage = resolveOsecRunStorage();
|
|
1174
|
-
writeOsecRunReport(storage, outcome.report);
|
|
1175
|
-
}
|
|
1176
|
-
const json = JSON.stringify(outcome.result, null, 2);
|
|
1177
|
-
if (opts.output) writeFileSync3(resolve2(opts.output), json + "\n", "utf8");
|
|
1178
|
-
else process.stdout.write(json + "\n");
|
|
1179
|
-
process.exitCode = outcome.exitCode;
|
|
1180
|
-
}
|
|
1181
|
-
function registerDeepReviewCommand(program) {
|
|
1182
|
-
program.command("deep-review").description(
|
|
1183
|
-
"Seedless DEPTH review of a source tree: enumerate candidate files, re-hunt each through the profile's specialized finder lenses, and gate survivors through the multi-lens verify quorum. Emits LEADS to verify (not confirmed bugs). Exit 0=sweep completed (with or without leads), 2=skipped (no files / over the review cap), 3=error (bad flags / unreadable target / all finders failed)."
|
|
1184
|
-
).argument("<target>", "Source tree to review (a local path or a git URL)").option("--profile <p>", "Lens profile: evm-onchain | solana-onchain | cardano-onchain | cairo-onchain | move-onchain (else a generic default lens set)").option("--subsystem <path>", "Narrow the review scope to a subdirectory (respects the 5000-file review cap)").option("--evolution-config <path>", "Use the active source finder with private local execution receipts").option("--models <a,b>", "Comma-separated finder models for diversity (default: single provider model, or $0SEC_DEEP_REVIEW_MODELS)").option("--attempts <N>", `Finder attempts per candidate\xD7lens\xD7model, best-of-N (default ${DEFAULT_ATTEMPTS}, or $0SEC_DEEP_REVIEW_ATTEMPTS)`).option("--concurrency <N>", `Max finders in flight (default ${DEFAULT_CONCURRENCY})`).option("--cost-ceiling <usd>", "Hard scan-wide USD ceiling. Overrides $0SEC_COST_CEILING_USD.").option("--max-candidates <N>", `Cap candidate files hunted, largest-first (default ${DEFAULT_MAX_CANDIDATES}, or $0SEC_DEEP_REVIEW_MAX_CANDIDATES)`).option("--threat-model", "Enable pre-selection threat-model planner pass (trust-boundary lanes); default OFF").option("--quorum <N>", "Multi-lens verify quorum (default: majority of the verify-lens count)").option("--format <fmt>", "Output format (json)", "json").option("--output <path>", "Write the result JSON to this path instead of stdout").option("--runtime <mode>", "Engine runtime (default api)").option("--timeout <ms>", "Cloud agent timeout budget in milliseconds", "600000").action(async (target, opts) => {
|
|
1185
|
-
try {
|
|
1186
|
-
await deepReviewAction(target, opts);
|
|
1187
|
-
} catch (err) {
|
|
1188
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
1189
|
-
const json = JSON.stringify({ mode: "deep_review", error: reason }, null, 2);
|
|
1190
|
-
if (opts.output) {
|
|
1191
|
-
const { writeFileSync: writeFileSync3 } = await import("node:fs");
|
|
1192
|
-
try {
|
|
1193
|
-
writeFileSync3(resolve2(opts.output), json + "\n", "utf8");
|
|
1194
|
-
} catch {
|
|
1195
|
-
process.stderr.write(json + "\n");
|
|
1196
|
-
}
|
|
1197
|
-
} else process.stdout.write(json + "\n");
|
|
1198
|
-
process.exitCode = 3;
|
|
1199
|
-
}
|
|
1200
|
-
});
|
|
96
|
+
return `https://0.security/r#${b64}`;
|
|
1201
97
|
}
|
|
1202
98
|
|
|
1203
99
|
// packages/cli/src/finding-handoff.ts
|
|
@@ -1285,7 +181,7 @@ function shellQuote(value) {
|
|
|
1285
181
|
}
|
|
1286
182
|
function buildFindingConsoleCommand(finding, dbPath, intent = "investigate") {
|
|
1287
183
|
const args = [
|
|
1288
|
-
"
|
|
184
|
+
"0 console",
|
|
1289
185
|
"--finding",
|
|
1290
186
|
shellQuote(finding.id),
|
|
1291
187
|
"--finding-intent",
|
|
@@ -1296,7 +192,7 @@ function buildFindingConsoleCommand(finding, dbPath, intent = "investigate") {
|
|
|
1296
192
|
}
|
|
1297
193
|
|
|
1298
194
|
// packages/cli/src/finding-focus.ts
|
|
1299
|
-
import { resolve
|
|
195
|
+
import { resolve } from "node:path";
|
|
1300
196
|
import { existsSync } from "node:fs";
|
|
1301
197
|
function parseJson(value) {
|
|
1302
198
|
if (value == null) return void 0;
|
|
@@ -1308,11 +204,11 @@ function parseJson(value) {
|
|
|
1308
204
|
}
|
|
1309
205
|
}
|
|
1310
206
|
function databasePaths(dbPath) {
|
|
1311
|
-
if (dbPath?.trim()) return [
|
|
207
|
+
if (dbPath?.trim()) return [resolve(dbPath)];
|
|
1312
208
|
return [...new Set([
|
|
1313
209
|
...listOsecRunDatabasePaths(),
|
|
1314
210
|
resolveOsecDbPath()
|
|
1315
|
-
].map((path) =>
|
|
211
|
+
].map((path) => resolve(path)))];
|
|
1316
212
|
}
|
|
1317
213
|
function findingFromRow(row, reviewFields) {
|
|
1318
214
|
const id = typeof row.id === "string" ? row.id : "";
|
|
@@ -1408,9 +304,9 @@ function loadFindingFocus(id, options = {}) {
|
|
|
1408
304
|
}
|
|
1409
305
|
|
|
1410
306
|
// packages/cli/src/commands/lens-synth.ts
|
|
1411
|
-
import { createHash
|
|
1412
|
-
import { readFileSync
|
|
1413
|
-
import { resolve as
|
|
307
|
+
import { createHash } from "node:crypto";
|
|
308
|
+
import { readFileSync } from "node:fs";
|
|
309
|
+
import { resolve as resolve2 } from "node:path";
|
|
1414
310
|
function isRecord(value) {
|
|
1415
311
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1416
312
|
}
|
|
@@ -1466,10 +362,10 @@ function parseMissInputFile(raw) {
|
|
|
1466
362
|
return { misses, corpus };
|
|
1467
363
|
}
|
|
1468
364
|
async function harvestBenchMisses(fromBench, manifestPath) {
|
|
1469
|
-
const ledger = await loadLedger(
|
|
365
|
+
const ledger = await loadLedger(resolve2(fromBench));
|
|
1470
366
|
const entry = lastGreen(ledger) ?? ledger.entries[ledger.entries.length - 1];
|
|
1471
367
|
if (!entry) throw new Error(`--from-bench: ledger ${fromBench} has no entries to harvest`);
|
|
1472
|
-
const manifest = await loadManifest(
|
|
368
|
+
const manifest = await loadManifest(resolve2(manifestPath));
|
|
1473
369
|
const result = harvestMissesFromLedgerEntry(entry, manifest);
|
|
1474
370
|
return { misses: result.misses, result };
|
|
1475
371
|
}
|
|
@@ -1487,7 +383,7 @@ async function runLensSynthesisInput(input, opts, deps = {}) {
|
|
|
1487
383
|
...deps.model ? { model: deps.model } : {},
|
|
1488
384
|
...opts.model ? { modelId: opts.model } : {},
|
|
1489
385
|
probe,
|
|
1490
|
-
...opts.registry ? { registryPath:
|
|
386
|
+
...opts.registry ? { registryPath: resolve2(opts.registry) } : {},
|
|
1491
387
|
maxRegistrations: opts.maxRegister ?? 1,
|
|
1492
388
|
dryRun: !opts.promote,
|
|
1493
389
|
trials,
|
|
@@ -1496,7 +392,7 @@ async function runLensSynthesisInput(input, opts, deps = {}) {
|
|
|
1496
392
|
});
|
|
1497
393
|
}
|
|
1498
394
|
async function runLensSynthCommand(opts, deps = {}) {
|
|
1499
|
-
const raw = JSON.parse(
|
|
395
|
+
const raw = JSON.parse(readFileSync(resolve2(opts.missInput), "utf8"));
|
|
1500
396
|
let input = parseMissInputFile(raw);
|
|
1501
397
|
if (opts.fromBench) {
|
|
1502
398
|
if (!opts.manifest) throw new Error("--from-bench requires --manifest");
|
|
@@ -1507,7 +403,7 @@ async function runLensSynthCommand(opts, deps = {}) {
|
|
|
1507
403
|
return runLensSynthesisInput(input, opts, deps);
|
|
1508
404
|
}
|
|
1509
405
|
async function watchLensSynthCommand(opts, deps = {}) {
|
|
1510
|
-
const inputPath =
|
|
406
|
+
const inputPath = resolve2(opts.missInput);
|
|
1511
407
|
const log = deps.log ?? (() => {
|
|
1512
408
|
});
|
|
1513
409
|
const requestedInterval = opts.pollIntervalMs ?? 2e3;
|
|
@@ -1516,8 +412,8 @@ async function watchLensSynthCommand(opts, deps = {}) {
|
|
|
1516
412
|
let previousRevision;
|
|
1517
413
|
while (!deps.signal?.aborted) {
|
|
1518
414
|
try {
|
|
1519
|
-
const bytes =
|
|
1520
|
-
const revision =
|
|
415
|
+
const bytes = readFileSync(inputPath);
|
|
416
|
+
const revision = createHash("sha256").update(bytes).digest("hex");
|
|
1521
417
|
if (revision !== previousRevision) {
|
|
1522
418
|
previousRevision = revision;
|
|
1523
419
|
const input = parseMissInputFile(JSON.parse(bytes.toString("utf8")));
|
|
@@ -1584,9 +480,9 @@ function formatLensRegistryStatus(status) {
|
|
|
1584
480
|
return lines.join("\n");
|
|
1585
481
|
}
|
|
1586
482
|
function registerLensSynthCommand(program) {
|
|
1587
|
-
program.command("lens-synth").description("Evolve appsec finder coverage from curated misses; promotion is corpus-gated and active reviews stay pinned").option("--miss-input <path>", "curated miss-input JSON ({ misses, corpus })").option("--registry <path>", "durable overlay path (default: ~/.
|
|
483
|
+
program.command("lens-synth").description("Evolve appsec finder coverage from curated misses; promotion is corpus-gated and active reviews stay pinned").option("--miss-input <path>", "curated miss-input JSON ({ misses, corpus })").option("--registry <path>", "durable overlay path (default: ~/.0/lenses/appsec-archetypes.json)").option("--max-register <n>", "cap promoted champions per input revision", (value) => Number.parseInt(value, 10)).option("-m, --model <id>", "synthesis model override").option("--promote", "persist a validated champion to the durable overlay", false).option("--trials <n>", "repeated validation trials (2\u201310; default 2)", Number).option("--from-bench <ledger>", "harvest the champion's false-negatives from a benchmark ledger into the curated misses (requires --manifest)").option("--manifest <path>", "bench manifest path (ground-truth vuln class + sink); required with --from-bench").option("--watch", "poll the miss-input and process each new content revision", false).option("--poll-interval <ms>", "watch polling interval (minimum 100ms)", "2000").option("--status", "show the active durable overlay and promotion ledger", false).option("--rollback <lens-id>", "retire one previously promoted overlay lens").option("--json", "print machine-readable output", false).action(async (opts) => {
|
|
1588
484
|
try {
|
|
1589
|
-
const registry = opts.registry ?
|
|
485
|
+
const registry = opts.registry ? resolve2(opts.registry) : void 0;
|
|
1590
486
|
if (opts.status && opts.rollback) throw new Error("--status and --rollback cannot be combined");
|
|
1591
487
|
if (opts.status) {
|
|
1592
488
|
const status = inspectLensRegistry(registry);
|
|
@@ -1625,7 +521,7 @@ function registerLensSynthCommand(program) {
|
|
|
1625
521
|
promote: Boolean(opts.promote),
|
|
1626
522
|
trials: trialCount(opts.trials),
|
|
1627
523
|
...opts.fromBench ? { fromBench: String(opts.fromBench) } : {},
|
|
1628
|
-
...opts.manifest ? { manifest:
|
|
524
|
+
...opts.manifest ? { manifest: resolve2(String(opts.manifest)) } : {}
|
|
1629
525
|
};
|
|
1630
526
|
const printResult = (result) => {
|
|
1631
527
|
process.stdout.write(
|
|
@@ -1669,388 +565,12 @@ function registerLensSynthCommand(program) {
|
|
|
1669
565
|
});
|
|
1670
566
|
}
|
|
1671
567
|
|
|
1672
|
-
// packages/cli/src/commands/auth.ts
|
|
1673
|
-
import { spawn } from "node:child_process";
|
|
1674
|
-
import { mkdirSync, writeFileSync as writeFileSync2, chmodSync, unlinkSync, existsSync as existsSync2 } from "node:fs";
|
|
1675
|
-
import { homedir, platform } from "node:os";
|
|
1676
|
-
import { join as join2 } from "node:path";
|
|
1677
|
-
import { randomBytes } from "node:crypto";
|
|
1678
|
-
import { setTimeout as delay } from "node:timers/promises";
|
|
1679
|
-
var EXIT_OK = 0;
|
|
1680
|
-
var EXIT_USER_ERROR = 1;
|
|
1681
|
-
var EXIT_AUTH = 2;
|
|
1682
|
-
var EXIT_NET = 3;
|
|
1683
|
-
function registerAuthCommand(program) {
|
|
1684
|
-
const auth = program.command("auth").description("0sec-cloud authentication");
|
|
1685
|
-
auth.command("login").description("Log in to 0sec-cloud (opens browser; --token to paste directly)").option("--host <url>", `Cloud host (default ${DEFAULT_CLOUD_HOST})`).option("--token <value>", "Skip the browser flow and persist this token directly").action(async (opts) => {
|
|
1686
|
-
await runLogin(opts);
|
|
1687
|
-
});
|
|
1688
|
-
auth.command("logout").description("Delete ~/.0sec/cloud.env").action(() => {
|
|
1689
|
-
runLogout({});
|
|
1690
|
-
});
|
|
1691
|
-
auth.command("status").description("Verify 0sec-cloud credentials against /health").action(async () => {
|
|
1692
|
-
await runStatus({});
|
|
1693
|
-
});
|
|
1694
|
-
}
|
|
1695
|
-
async function hostedBrowserLoginFlow(opts = {}) {
|
|
1696
|
-
const host = normaliseHostArg(opts.host ?? DEFAULT_CLOUD_HOST);
|
|
1697
|
-
if (host === null) {
|
|
1698
|
-
return { ok: false, error: "Cloud host must be an http(s) URL without credentials, query or fragment." };
|
|
1699
|
-
}
|
|
1700
|
-
const attempts = opts.pollAttempts ?? 150;
|
|
1701
|
-
const intervalMs = opts.pollIntervalMs ?? 2e3;
|
|
1702
|
-
if (!Number.isSafeInteger(attempts) || attempts <= 0 || !Number.isFinite(intervalMs) || intervalMs < 0) {
|
|
1703
|
-
return { ok: false, error: "Invalid sign-in polling budget." };
|
|
1704
|
-
}
|
|
1705
|
-
const cancelled = () => {
|
|
1706
|
-
opts.onStatus?.("cancelled", "Sign-in cancelled.");
|
|
1707
|
-
return { ok: false, error: "Sign-in cancelled.", recoverable: true, cancelled: true };
|
|
1708
|
-
};
|
|
1709
|
-
if (opts.signal?.aborted) return cancelled();
|
|
1710
|
-
const deadline = new AbortController();
|
|
1711
|
-
const timer = setTimeout(() => deadline.abort(), 3e5);
|
|
1712
|
-
const signal = opts.signal ? AbortSignal.any([opts.signal, deadline.signal]) : deadline.signal;
|
|
1713
|
-
const session = randomBytes(9).toString("base64url").slice(0, 12);
|
|
1714
|
-
const loginUrl = `${host}/cli-auth?session=${session}`;
|
|
1715
|
-
const pollUrl = `${host}/cli-auth/sessions/${session}`;
|
|
1716
|
-
const timedOut = () => {
|
|
1717
|
-
opts.onStatus?.("timeout", "Sign-in timed out. Try again.");
|
|
1718
|
-
return { ok: false, error: "Sign-in timed out. Try again.", recoverable: true };
|
|
1719
|
-
};
|
|
1720
|
-
try {
|
|
1721
|
-
opts.onStatus?.("opening", "Opening browser for 0sec Cloud sign-in.", loginUrl);
|
|
1722
|
-
signal.throwIfAborted();
|
|
1723
|
-
try {
|
|
1724
|
-
await awaitLoginStep(Promise.resolve().then(() => {
|
|
1725
|
-
signal.throwIfAborted();
|
|
1726
|
-
return (opts.openBrowser ?? defaultOpenBrowser)(loginUrl);
|
|
1727
|
-
}), signal);
|
|
1728
|
-
} catch {
|
|
1729
|
-
signal.throwIfAborted();
|
|
1730
|
-
opts.onStatus?.("opener-failed", "Could not open a browser. Open this sign-in URL manually.", loginUrl);
|
|
1731
|
-
}
|
|
1732
|
-
const fetcher = opts.fetchImpl ?? fetch;
|
|
1733
|
-
for (let i = 0; i < attempts; i++) {
|
|
1734
|
-
signal.throwIfAborted();
|
|
1735
|
-
opts.onStatus?.("polling", "Waiting for browser sign-in.", loginUrl);
|
|
1736
|
-
signal.throwIfAborted();
|
|
1737
|
-
await awaitLoginStep(
|
|
1738
|
-
opts.sleep ? opts.sleep(intervalMs) : delay(intervalMs, void 0, { signal }),
|
|
1739
|
-
signal
|
|
1740
|
-
);
|
|
1741
|
-
signal.throwIfAborted();
|
|
1742
|
-
const request = new AbortController();
|
|
1743
|
-
const requestTimer = setTimeout(() => request.abort(), 1e4);
|
|
1744
|
-
const requestSignal = AbortSignal.any([signal, request.signal]);
|
|
1745
|
-
try {
|
|
1746
|
-
let res;
|
|
1747
|
-
try {
|
|
1748
|
-
res = await awaitLoginStep(fetcher(pollUrl, {
|
|
1749
|
-
method: "GET",
|
|
1750
|
-
headers: { Accept: "application/json" },
|
|
1751
|
-
signal: requestSignal,
|
|
1752
|
-
redirect: "error"
|
|
1753
|
-
}), requestSignal);
|
|
1754
|
-
} catch {
|
|
1755
|
-
signal.throwIfAborted();
|
|
1756
|
-
continue;
|
|
1757
|
-
}
|
|
1758
|
-
signal.throwIfAborted();
|
|
1759
|
-
if (res.status === 200) {
|
|
1760
|
-
let body;
|
|
1761
|
-
try {
|
|
1762
|
-
body = await awaitLoginStep(res.json(), requestSignal);
|
|
1763
|
-
} catch {
|
|
1764
|
-
signal.throwIfAborted();
|
|
1765
|
-
if (requestSignal.aborted) continue;
|
|
1766
|
-
return { ok: false, error: "Login response was not valid JSON." };
|
|
1767
|
-
}
|
|
1768
|
-
signal.throwIfAborted();
|
|
1769
|
-
const rawStatus = body && typeof body === "object" ? body.status : void 0;
|
|
1770
|
-
const status = sessionStatus(body);
|
|
1771
|
-
const token = extractToken(body);
|
|
1772
|
-
if (token) {
|
|
1773
|
-
if (rawStatus !== void 0 && status !== "ready") {
|
|
1774
|
-
return { ok: false, error: "Login returned credentials before the session was ready." };
|
|
1775
|
-
}
|
|
1776
|
-
try {
|
|
1777
|
-
persistCredentials(host, token, opts.homeDir);
|
|
1778
|
-
} catch {
|
|
1779
|
-
return { ok: false, error: "Could not save 0sec Cloud credentials." };
|
|
1780
|
-
}
|
|
1781
|
-
opts.onStatus?.("ready", "Signed in to 0sec Cloud.");
|
|
1782
|
-
return { ok: true, host };
|
|
1783
|
-
}
|
|
1784
|
-
if (status === "pending") continue;
|
|
1785
|
-
if (status === "expired") return { ok: false, error: "Sign-in request expired. Try again.", recoverable: true };
|
|
1786
|
-
return { ok: false, error: "Login response did not contain valid credentials." };
|
|
1787
|
-
}
|
|
1788
|
-
if ([202, 204, 404].includes(res.status)) continue;
|
|
1789
|
-
if (res.status === 410) return { ok: false, error: "Sign-in request expired. Try again.", recoverable: true };
|
|
1790
|
-
return {
|
|
1791
|
-
ok: false,
|
|
1792
|
-
error: `0sec Cloud sign-in unavailable (HTTP ${res.status}). Use your own provider or try again later.`,
|
|
1793
|
-
recoverable: res.status === 429 || res.status >= 500
|
|
1794
|
-
};
|
|
1795
|
-
} finally {
|
|
1796
|
-
clearTimeout(requestTimer);
|
|
1797
|
-
request.abort();
|
|
1798
|
-
}
|
|
1799
|
-
}
|
|
1800
|
-
return timedOut();
|
|
1801
|
-
} catch (error) {
|
|
1802
|
-
if (opts.signal?.aborted) return cancelled();
|
|
1803
|
-
if (deadline.signal.aborted) return timedOut();
|
|
1804
|
-
throw error;
|
|
1805
|
-
} finally {
|
|
1806
|
-
clearTimeout(timer);
|
|
1807
|
-
}
|
|
1808
|
-
}
|
|
1809
|
-
async function awaitLoginStep(operation, signal) {
|
|
1810
|
-
return new Promise((resolve6, reject) => {
|
|
1811
|
-
const onAbort = () => {
|
|
1812
|
-
signal.removeEventListener("abort", onAbort);
|
|
1813
|
-
reject(signal.reason);
|
|
1814
|
-
};
|
|
1815
|
-
operation.then(
|
|
1816
|
-
(value) => {
|
|
1817
|
-
signal.removeEventListener("abort", onAbort);
|
|
1818
|
-
resolve6(value);
|
|
1819
|
-
},
|
|
1820
|
-
(error) => {
|
|
1821
|
-
signal.removeEventListener("abort", onAbort);
|
|
1822
|
-
reject(error);
|
|
1823
|
-
}
|
|
1824
|
-
);
|
|
1825
|
-
if (signal.aborted) onAbort();
|
|
1826
|
-
else signal.addEventListener("abort", onAbort, { once: true });
|
|
1827
|
-
});
|
|
1828
|
-
}
|
|
1829
|
-
async function runLogin(opts) {
|
|
1830
|
-
const host = normaliseHostArg(opts.host ?? DEFAULT_CLOUD_HOST);
|
|
1831
|
-
if (host === null) {
|
|
1832
|
-
consolePresentationOutput.stderr(source_default.red("Error: Cloud host must be an http(s) URL without credentials, query or fragment."), "auth.login.host-error");
|
|
1833
|
-
process.exitCode = EXIT_USER_ERROR;
|
|
1834
|
-
return;
|
|
1835
|
-
}
|
|
1836
|
-
if (opts.token !== void 0) {
|
|
1837
|
-
const tok = opts.token.trim();
|
|
1838
|
-
if (!validCloudToken(tok)) {
|
|
1839
|
-
consolePresentationOutput.stderr(source_default.red("Error: --token must be a nonempty credential without whitespace."), "auth.login.token-empty");
|
|
1840
|
-
process.exitCode = EXIT_USER_ERROR;
|
|
1841
|
-
return;
|
|
1842
|
-
}
|
|
1843
|
-
try {
|
|
1844
|
-
persistCredentials(host, tok, opts.homeDir);
|
|
1845
|
-
} catch {
|
|
1846
|
-
consolePresentationOutput.stderr(source_default.red("Could not save 0sec Cloud credentials."), "auth.login.save-error");
|
|
1847
|
-
process.exitCode = EXIT_USER_ERROR;
|
|
1848
|
-
return;
|
|
1849
|
-
}
|
|
1850
|
-
consolePresentationOutput.stdout(`Logged in (host=${host})`, "auth.login.logged-in");
|
|
1851
|
-
process.exitCode = EXIT_OK;
|
|
1852
|
-
return;
|
|
1853
|
-
}
|
|
1854
|
-
const result = await hostedBrowserLoginFlow({
|
|
1855
|
-
host,
|
|
1856
|
-
pollAttempts: opts.pollAttempts,
|
|
1857
|
-
pollIntervalMs: opts.pollIntervalMs,
|
|
1858
|
-
openBrowser: opts.openBrowser,
|
|
1859
|
-
fetchImpl: opts.fetchImpl,
|
|
1860
|
-
homeDir: opts.homeDir,
|
|
1861
|
-
sleep: opts.sleep,
|
|
1862
|
-
onStatus: (phase, msg, url) => {
|
|
1863
|
-
if (phase === "opening") {
|
|
1864
|
-
consolePresentationOutput.stdout(source_default.dim("Opening browser..."), "auth.login.opening");
|
|
1865
|
-
if (url) consolePresentationOutput.stdout(source_default.dim(` ${url}`), "auth.login.url");
|
|
1866
|
-
consolePresentationOutput.stdout("", "auth.login.empty-line");
|
|
1867
|
-
consolePresentationOutput.stdout(source_default.dim("Complete sign-in in the opened browser, then return here."), "auth.login.browser-hint");
|
|
1868
|
-
consolePresentationOutput.stdout("", "auth.login.empty-line");
|
|
1869
|
-
}
|
|
1870
|
-
if (phase === "opener-failed") {
|
|
1871
|
-
consolePresentationOutput.stderr(source_default.yellow(msg), "auth.login.browser-error");
|
|
1872
|
-
}
|
|
1873
|
-
}
|
|
1874
|
-
});
|
|
1875
|
-
if (result.ok) {
|
|
1876
|
-
consolePresentationOutput.stdout(`Logged in (host=${result.host})`, "auth.login.logged-in");
|
|
1877
|
-
process.exitCode = EXIT_OK;
|
|
1878
|
-
} else {
|
|
1879
|
-
consolePresentationOutput.stderr(source_default.red(result.error), "auth.login.error");
|
|
1880
|
-
process.exitCode = result.recoverable ? EXIT_NET : EXIT_USER_ERROR;
|
|
1881
|
-
}
|
|
1882
|
-
}
|
|
1883
|
-
function runLogout(opts) {
|
|
1884
|
-
const home = opts.homeDir ?? homedir();
|
|
1885
|
-
const osecPath = join2(homeStateDir(opts.homeDir), "cloud.env");
|
|
1886
|
-
const cloudCredsPath = join2(home, ".0cloud", "credentials.json");
|
|
1887
|
-
let deletedAny = false;
|
|
1888
|
-
try {
|
|
1889
|
-
unlinkSync(osecPath);
|
|
1890
|
-
deletedAny = true;
|
|
1891
|
-
} catch (err) {
|
|
1892
|
-
const code = err.code;
|
|
1893
|
-
if (code !== "ENOENT") {
|
|
1894
|
-
consolePresentationOutput.stderr(source_default.red(`Could not delete ${osecPath}: ${err instanceof Error ? err.message : String(err)}`), "auth.logout.delete-error");
|
|
1895
|
-
process.exitCode = EXIT_USER_ERROR;
|
|
1896
|
-
return;
|
|
1897
|
-
}
|
|
1898
|
-
}
|
|
1899
|
-
try {
|
|
1900
|
-
unlinkSync(cloudCredsPath);
|
|
1901
|
-
deletedAny = true;
|
|
1902
|
-
} catch (err) {
|
|
1903
|
-
const code = err.code;
|
|
1904
|
-
if (code !== "ENOENT") {
|
|
1905
|
-
consolePresentationOutput.stderr(source_default.red(`Could not delete ${cloudCredsPath}: ${err instanceof Error ? err.message : String(err)}`), "auth.logout.delete-error");
|
|
1906
|
-
process.exitCode = EXIT_USER_ERROR;
|
|
1907
|
-
return;
|
|
1908
|
-
}
|
|
1909
|
-
}
|
|
1910
|
-
if (deletedAny) {
|
|
1911
|
-
consolePresentationOutput.stdout("Logged out", "auth.logout.deleted");
|
|
1912
|
-
} else {
|
|
1913
|
-
consolePresentationOutput.stdout("Logged out (no credentials file was present)", "auth.logout.not-found");
|
|
1914
|
-
}
|
|
1915
|
-
process.exitCode = EXIT_OK;
|
|
1916
|
-
}
|
|
1917
|
-
async function runStatus(opts) {
|
|
1918
|
-
let creds;
|
|
1919
|
-
try {
|
|
1920
|
-
const c = loadCloudCredentials();
|
|
1921
|
-
creds = { host: c.host, token: c.token };
|
|
1922
|
-
} catch (err) {
|
|
1923
|
-
if (err instanceof CloudAuthMissingError) {
|
|
1924
|
-
consolePresentationOutput.stderr(source_default.red(err.message), "auth.status.auth-missing");
|
|
1925
|
-
process.exitCode = EXIT_AUTH;
|
|
1926
|
-
return;
|
|
1927
|
-
}
|
|
1928
|
-
consolePresentationOutput.stderr(source_default.red(err instanceof Error ? err.message : String(err)), "auth.status.load-error");
|
|
1929
|
-
process.exitCode = EXIT_AUTH;
|
|
1930
|
-
return;
|
|
1931
|
-
}
|
|
1932
|
-
const client = new CloudClient({ ...creds, fetchImpl: opts.fetchImpl });
|
|
1933
|
-
try {
|
|
1934
|
-
await client.pingHealth();
|
|
1935
|
-
consolePresentationOutput.stdout(`OK (host=${creds.host})`, "auth.status.ok");
|
|
1936
|
-
process.exitCode = EXIT_OK;
|
|
1937
|
-
} catch (err) {
|
|
1938
|
-
handleStatusError(err);
|
|
1939
|
-
}
|
|
1940
|
-
}
|
|
1941
|
-
function handleStatusError(err) {
|
|
1942
|
-
if (err instanceof CloudUnauthorizedError) {
|
|
1943
|
-
consolePresentationOutput.stderr("FAIL (HTTP 401)", "auth.status.unauthorized");
|
|
1944
|
-
process.exitCode = EXIT_AUTH;
|
|
1945
|
-
return;
|
|
1946
|
-
}
|
|
1947
|
-
if (err instanceof CloudForbiddenError) {
|
|
1948
|
-
consolePresentationOutput.stderr(source_default.red(`FAIL (HTTP 403) \u2014 status: ${err.message}`), "auth.status.forbidden");
|
|
1949
|
-
process.exitCode = EXIT_AUTH;
|
|
1950
|
-
return;
|
|
1951
|
-
}
|
|
1952
|
-
if (err instanceof CloudNetworkError) {
|
|
1953
|
-
consolePresentationOutput.stderr(source_default.red(`FAIL (network) \u2014 status: ${err.message}`), "auth.status.network-error");
|
|
1954
|
-
process.exitCode = EXIT_NET;
|
|
1955
|
-
return;
|
|
1956
|
-
}
|
|
1957
|
-
if (err instanceof CloudError) {
|
|
1958
|
-
consolePresentationOutput.stderr(source_default.red(`FAIL (HTTP ${err.status ?? "?"}) \u2014 status`), "auth.status.cloud-error");
|
|
1959
|
-
process.exitCode = EXIT_USER_ERROR;
|
|
1960
|
-
return;
|
|
1961
|
-
}
|
|
1962
|
-
consolePresentationOutput.stderr(source_default.red(`FAIL \u2014 status: ${err instanceof Error ? err.message : String(err)}`), "auth.status.error");
|
|
1963
|
-
process.exitCode = EXIT_USER_ERROR;
|
|
1964
|
-
}
|
|
1965
|
-
function normaliseHostArg(host) {
|
|
1966
|
-
if (!host?.trim()) return null;
|
|
1967
|
-
try {
|
|
1968
|
-
const url = new URL(host.trim());
|
|
1969
|
-
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) return null;
|
|
1970
|
-
return url.href.replace(/\/+$/, "");
|
|
1971
|
-
} catch {
|
|
1972
|
-
return null;
|
|
1973
|
-
}
|
|
1974
|
-
}
|
|
1975
|
-
function persistCredentials(host, token, homeDirOverride) {
|
|
1976
|
-
if (!validCloudToken(token)) throw new Error("Invalid Cloud credential");
|
|
1977
|
-
const home = homeDirOverride ?? homedir();
|
|
1978
|
-
const dir = homeStateDir(homeDirOverride);
|
|
1979
|
-
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
1980
|
-
const path = join2(dir, "cloud.env");
|
|
1981
|
-
const body = `# 0sec-cloud credentials. Managed by \`0sec auth\`.
|
|
1982
|
-
# DO NOT commit this file or share its contents.
|
|
1983
|
-
0SEC_CLOUD_HOST=${host}
|
|
1984
|
-
0SEC_CLOUD_TOKEN=${token}
|
|
1985
|
-
`;
|
|
1986
|
-
writeFileSync2(path, body, { mode: 384 });
|
|
1987
|
-
chmodSync(path, 384);
|
|
1988
|
-
if (!existsSync2(path)) {
|
|
1989
|
-
return;
|
|
1990
|
-
}
|
|
1991
|
-
try {
|
|
1992
|
-
const cloudDir = join2(home, ".0cloud");
|
|
1993
|
-
mkdirSync(cloudDir, { recursive: true });
|
|
1994
|
-
const cloudCredsPath = join2(cloudDir, "credentials.json");
|
|
1995
|
-
const trimmedHost = host.replace(/\/+$/, "");
|
|
1996
|
-
const apiEndpoint = trimmedHost.endsWith("/api") ? trimmedHost : `${trimmedHost}/api`;
|
|
1997
|
-
const cloudCreds = JSON.stringify(
|
|
1998
|
-
{
|
|
1999
|
-
token,
|
|
2000
|
-
orgId: "",
|
|
2001
|
-
endpoint: apiEndpoint,
|
|
2002
|
-
dashboardUrl: host,
|
|
2003
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2004
|
-
},
|
|
2005
|
-
null,
|
|
2006
|
-
2
|
|
2007
|
-
) + "\n";
|
|
2008
|
-
writeFileSync2(cloudCredsPath, cloudCreds, { mode: 384 });
|
|
2009
|
-
chmodSync(cloudCredsPath, 384);
|
|
2010
|
-
} catch {
|
|
2011
|
-
}
|
|
2012
|
-
}
|
|
2013
|
-
function sessionStatus(body) {
|
|
2014
|
-
if (!body || typeof body !== "object") return void 0;
|
|
2015
|
-
const status = body.status;
|
|
2016
|
-
return status === "pending" || status === "ready" || status === "expired" ? status : void 0;
|
|
2017
|
-
}
|
|
2018
|
-
function validCloudToken(value) {
|
|
2019
|
-
return typeof value === "string" && value.trim().length > 0 && !/[\s\x00-\x1f\x7f]/.test(value.trim());
|
|
2020
|
-
}
|
|
2021
|
-
function extractToken(body) {
|
|
2022
|
-
if (!body || typeof body !== "object") return null;
|
|
2023
|
-
const obj = body;
|
|
2024
|
-
const candidates = [obj.token, obj.access_token];
|
|
2025
|
-
for (const c of candidates) {
|
|
2026
|
-
if (validCloudToken(c)) return c.trim();
|
|
2027
|
-
}
|
|
2028
|
-
return null;
|
|
2029
|
-
}
|
|
2030
|
-
function defaultOpenBrowser(url) {
|
|
2031
|
-
const plat = platform();
|
|
2032
|
-
return new Promise((resolve6, reject) => {
|
|
2033
|
-
const options = { detached: true, stdio: "ignore", shell: false };
|
|
2034
|
-
const child = plat === "darwin" ? spawn("open", [url], options) : plat === "win32" ? spawn("powershell.exe", [
|
|
2035
|
-
"-NoProfile",
|
|
2036
|
-
"-NonInteractive",
|
|
2037
|
-
"-Command",
|
|
2038
|
-
"Start-Process -FilePath $env:OSEC_BROWSER_LOGIN_URL"
|
|
2039
|
-
], { ...options, env: { ...process.env, OSEC_BROWSER_LOGIN_URL: url } }) : spawn("xdg-open", [url], options);
|
|
2040
|
-
child.once("error", reject);
|
|
2041
|
-
child.once("spawn", () => {
|
|
2042
|
-
child.unref();
|
|
2043
|
-
resolve6();
|
|
2044
|
-
});
|
|
2045
|
-
});
|
|
2046
|
-
}
|
|
2047
|
-
|
|
2048
568
|
// packages/cli/src/commands/run.ts
|
|
2049
569
|
import { writeFile } from "node:fs/promises";
|
|
2050
|
-
import { join
|
|
570
|
+
import { join, resolve as resolve3 } from "node:path";
|
|
2051
571
|
import { tmpdir } from "node:os";
|
|
2052
572
|
import { execFile } from "node:child_process";
|
|
2053
|
-
import { existsSync as
|
|
573
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
2054
574
|
import { fileURLToPath } from "node:url";
|
|
2055
575
|
|
|
2056
576
|
// packages/cli/src/formatters/terminal.ts
|
|
@@ -2177,7 +697,7 @@ var EVIDENCE_BLOCK_LIMIT = 4e3;
|
|
|
2177
697
|
var POC_STEP_LIMIT = 20;
|
|
2178
698
|
function formatMarkdown(report) {
|
|
2179
699
|
const lines = [];
|
|
2180
|
-
lines.push("#
|
|
700
|
+
lines.push("# 0 Scan Report");
|
|
2181
701
|
lines.push("");
|
|
2182
702
|
lines.push(`| Field | Value |`);
|
|
2183
703
|
lines.push(`|-------|-------|`);
|
|
@@ -2434,7 +954,7 @@ function formatHtml(report) {
|
|
|
2434
954
|
<head>
|
|
2435
955
|
<meta charset="UTF-8">
|
|
2436
956
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2437
|
-
<title>
|
|
957
|
+
<title>0 Report \u2014 ${escapeHtml(report.target)}</title>
|
|
2438
958
|
<style>
|
|
2439
959
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
2440
960
|
body {
|
|
@@ -2698,7 +1218,7 @@ function formatHtml(report) {
|
|
|
2698
1218
|
<div class="header">
|
|
2699
1219
|
<div class="header-top">
|
|
2700
1220
|
<div>
|
|
2701
|
-
<div class="brand">
|
|
1221
|
+
<div class="brand">ZERO</div>
|
|
2702
1222
|
<div class="brand-sub">Security Scan Report</div>
|
|
2703
1223
|
</div>
|
|
2704
1224
|
<div class="risk-badge" style="background:${risk.color};color:#fff">${risk.label} RISK</div>
|
|
@@ -2739,7 +1259,7 @@ function formatHtml(report) {
|
|
|
2739
1259
|
</div>
|
|
2740
1260
|
|
|
2741
1261
|
<div class="footer">
|
|
2742
|
-
Generated by
|
|
1262
|
+
Generated by 0 · ${escapeHtml((/* @__PURE__ */ new Date()).toISOString())}
|
|
2743
1263
|
</div>
|
|
2744
1264
|
</body>
|
|
2745
1265
|
</html>`;
|
|
@@ -2875,9 +1395,9 @@ function formatSarif(report) {
|
|
|
2875
1395
|
{
|
|
2876
1396
|
tool: {
|
|
2877
1397
|
driver: {
|
|
2878
|
-
name: "
|
|
1398
|
+
name: "0",
|
|
2879
1399
|
version: VERSION,
|
|
2880
|
-
informationUri: "https://github.com/0sec-labs/
|
|
1400
|
+
informationUri: "https://github.com/0sec-labs/0",
|
|
2881
1401
|
rules: Array.from(rulesMap.values())
|
|
2882
1402
|
}
|
|
2883
1403
|
},
|
|
@@ -2953,13 +1473,13 @@ function truncate(text, maxLen) {
|
|
|
2953
1473
|
async function generatePdfReport(report, outputPath) {
|
|
2954
1474
|
const require_ = createRequire(import.meta.url);
|
|
2955
1475
|
const PDFDocument = require_("pdfkit");
|
|
2956
|
-
return new Promise((
|
|
1476
|
+
return new Promise((resolve4, reject) => {
|
|
2957
1477
|
const doc = new PDFDocument({
|
|
2958
1478
|
size: "LETTER",
|
|
2959
1479
|
margins: { top: PAGE_MARGIN, bottom: PAGE_MARGIN, left: PAGE_MARGIN, right: PAGE_MARGIN },
|
|
2960
1480
|
info: {
|
|
2961
|
-
Title: `
|
|
2962
|
-
Author: "
|
|
1481
|
+
Title: `0 Pentest Report - ${report.target}`,
|
|
1482
|
+
Author: "0",
|
|
2963
1483
|
Subject: "Automated Penetration Test Report",
|
|
2964
1484
|
CreationDate: /* @__PURE__ */ new Date()
|
|
2965
1485
|
},
|
|
@@ -2967,7 +1487,7 @@ async function generatePdfReport(report, outputPath) {
|
|
|
2967
1487
|
});
|
|
2968
1488
|
const stream = createWriteStream(outputPath);
|
|
2969
1489
|
stream.on("error", reject);
|
|
2970
|
-
stream.on("finish",
|
|
1490
|
+
stream.on("finish", resolve4);
|
|
2971
1491
|
doc.pipe(stream);
|
|
2972
1492
|
renderCoverPage(doc, report);
|
|
2973
1493
|
renderExecutiveSummary(doc, report);
|
|
@@ -2980,7 +1500,7 @@ async function generatePdfReport(report, outputPath) {
|
|
|
2980
1500
|
}
|
|
2981
1501
|
function renderCoverPage(doc, report) {
|
|
2982
1502
|
doc.rect(0, 0, 612, 792).fill(COLORS.primary);
|
|
2983
|
-
doc.fontSize(48).font("Helvetica-Bold").fillColor(COLORS.accent).text("
|
|
1503
|
+
doc.fontSize(48).font("Helvetica-Bold").fillColor(COLORS.accent).text("0", PAGE_MARGIN, 200, { align: "center" });
|
|
2984
1504
|
doc.fontSize(14).font("Helvetica").fillColor(COLORS.white).text("Automated Penetration Test Report", PAGE_MARGIN, 260, { align: "center" });
|
|
2985
1505
|
doc.moveTo(200, 300).lineTo(412, 300).strokeColor(COLORS.accent).lineWidth(2).stroke();
|
|
2986
1506
|
doc.fontSize(18).font("Helvetica-Bold").fillColor(COLORS.white).text(report.target, PAGE_MARGIN, 330, { align: "center" });
|
|
@@ -3107,7 +1627,7 @@ function renderMethodology(doc, report) {
|
|
|
3107
1627
|
doc.addPage();
|
|
3108
1628
|
sectionTitle(doc, "Methodology");
|
|
3109
1629
|
doc.fontSize(10).font("Helvetica").fillColor(COLORS.text).text(
|
|
3110
|
-
"This report was generated by
|
|
1630
|
+
"This report was generated by 0, a fully autonomous agentic pentesting framework. The tool uses AI-driven attack generation and multi-agent consensus verification to identify security vulnerabilities.",
|
|
3111
1631
|
PAGE_MARGIN,
|
|
3112
1632
|
doc.y + 10,
|
|
3113
1633
|
{ width: CONTENT_WIDTH }
|
|
@@ -3184,7 +1704,7 @@ function renderFooters(doc) {
|
|
|
3184
1704
|
doc.switchToPage(i);
|
|
3185
1705
|
if (i === 0) continue;
|
|
3186
1706
|
doc.fontSize(8).font("Helvetica").fillColor(COLORS.textLight).text(
|
|
3187
|
-
`
|
|
1707
|
+
`0 Pentest Report | Page ${i + 1} of ${pageCount} | CONFIDENTIAL`,
|
|
3188
1708
|
PAGE_MARGIN,
|
|
3189
1709
|
760,
|
|
3190
1710
|
{ width: CONTENT_WIDTH, align: "center" }
|
|
@@ -3194,7 +1714,7 @@ function renderFooters(doc) {
|
|
|
3194
1714
|
|
|
3195
1715
|
// packages/cli/src/formatters/replay.ts
|
|
3196
1716
|
function sleep(ms) {
|
|
3197
|
-
return new Promise((
|
|
1717
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
3198
1718
|
}
|
|
3199
1719
|
function stripAnsi(s) {
|
|
3200
1720
|
return s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
@@ -3383,7 +1903,7 @@ function buildReplayLines(data) {
|
|
|
3383
1903
|
const duration = data.durationMs < 1e3 ? `${data.durationMs}ms` : `${(data.durationMs / 1e3).toFixed(1)}s`;
|
|
3384
1904
|
lines.push({
|
|
3385
1905
|
text: boxRow(
|
|
3386
|
-
source_default.white(`Completed in ${duration}`) + source_default.dim(" \u2192 ") + source_default.gray("./
|
|
1906
|
+
source_default.white(`Completed in ${duration}`) + source_default.dim(" \u2192 ") + source_default.gray("./0-report.json")
|
|
3387
1907
|
),
|
|
3388
1908
|
delay: MED
|
|
3389
1909
|
});
|
|
@@ -3394,7 +1914,7 @@ async function renderReplay(data) {
|
|
|
3394
1914
|
const lines = buildReplayLines(data);
|
|
3395
1915
|
process.stdout.write("\n");
|
|
3396
1916
|
process.stdout.write(
|
|
3397
|
-
source_default.red.bold(" \u25C6
|
|
1917
|
+
source_default.red.bold(" \u25C6 0") + source_default.gray(" attack replay") + "\n"
|
|
3398
1918
|
);
|
|
3399
1919
|
process.stdout.write(
|
|
3400
1920
|
source_default.dim(" " + "\u2500".repeat(BOX_WIDTH + 1)) + "\n"
|
|
@@ -3619,8 +2139,8 @@ var coreModulePromise = null;
|
|
|
3619
2139
|
async function loadCoreModule() {
|
|
3620
2140
|
if (!coreModulePromise) {
|
|
3621
2141
|
const srcUrl = new URL("../../../core/src/index.ts", import.meta.url);
|
|
3622
|
-
const srcExists = process.versions.bun &&
|
|
3623
|
-
coreModulePromise = srcExists ? import(srcUrl.href) : import("./dist-
|
|
2142
|
+
const srcExists = process.versions.bun && existsSync2(fileURLToPath(srcUrl));
|
|
2143
|
+
coreModulePromise = srcExists ? import(srcUrl.href) : import("./dist-3FNZIBB4.js");
|
|
3624
2144
|
}
|
|
3625
2145
|
return coreModulePromise;
|
|
3626
2146
|
}
|
|
@@ -3664,16 +2184,8 @@ function getTargetType(report, opts) {
|
|
|
3664
2184
|
return report?.targetType ?? opts.targetType;
|
|
3665
2185
|
}
|
|
3666
2186
|
function emitResultLine(payload) {
|
|
3667
|
-
if (process.env["
|
|
3668
|
-
console.log(`
|
|
3669
|
-
}
|
|
3670
|
-
function getCloudFinalSinkConfig() {
|
|
3671
|
-
if (process.env["0SEC_FEATURE_CLOUD_SINK"] === "0") return null;
|
|
3672
|
-
const sinkUrl = process.env["0SEC_CLOUD_SINK"]?.trim();
|
|
3673
|
-
const scanId = process.env["0SEC_CLOUD_SCAN_ID"]?.trim();
|
|
3674
|
-
if (!sinkUrl || !scanId) return null;
|
|
3675
|
-
const token = process.env["0SEC_CLOUD_TOKEN"]?.trim() || void 0;
|
|
3676
|
-
return { sinkUrl, scanId, token };
|
|
2187
|
+
if (process.env["ZERO_EMIT_RESULT_LINE"] !== "1") return;
|
|
2188
|
+
console.log(`ZERO_RESULT=${JSON.stringify(payload)}`);
|
|
3677
2189
|
}
|
|
3678
2190
|
function isCostBreakdownEntry(value) {
|
|
3679
2191
|
if (!value || typeof value !== "object") return false;
|
|
@@ -3746,55 +2258,6 @@ function printCrossValidatedLeads(summary) {
|
|
|
3746
2258
|
console.log(source_default.gray(` +${summary.moreCount} more`));
|
|
3747
2259
|
}
|
|
3748
2260
|
}
|
|
3749
|
-
async function postFinalResultToCloud(report) {
|
|
3750
|
-
const config = getCloudFinalSinkConfig();
|
|
3751
|
-
if (!config) return;
|
|
3752
|
-
const url = `${config.sinkUrl.replace(/\/+$/, "")}/scans/${encodeURIComponent(config.scanId)}/findings`;
|
|
3753
|
-
const headers = {
|
|
3754
|
-
"Content-Type": "application/json",
|
|
3755
|
-
"X-0sec-Scan-Id": config.scanId
|
|
3756
|
-
};
|
|
3757
|
-
if (config.token) headers.Authorization = `Bearer ${config.token}`;
|
|
3758
|
-
try {
|
|
3759
|
-
const res = await fetch(url, {
|
|
3760
|
-
method: "POST",
|
|
3761
|
-
headers,
|
|
3762
|
-
body: JSON.stringify({ report, final: true })
|
|
3763
|
-
});
|
|
3764
|
-
if (!res.ok) {
|
|
3765
|
-
const text = await res.text().catch(() => "");
|
|
3766
|
-
process.stderr.write(
|
|
3767
|
-
`[0sec cloud-sink] report POST ${url} returned ${res.status}: ${text.slice(0, 200)}
|
|
3768
|
-
`
|
|
3769
|
-
);
|
|
3770
|
-
}
|
|
3771
|
-
} catch (err) {
|
|
3772
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3773
|
-
process.stderr.write(`[0sec cloud-sink] report POST ${url} failed: ${msg}
|
|
3774
|
-
`);
|
|
3775
|
-
}
|
|
3776
|
-
}
|
|
3777
|
-
function skippedLensReviewReport(target, message) {
|
|
3778
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3779
|
-
return {
|
|
3780
|
-
target,
|
|
3781
|
-
scanDepth: "deep",
|
|
3782
|
-
startedAt: now,
|
|
3783
|
-
completedAt: now,
|
|
3784
|
-
durationMs: 0,
|
|
3785
|
-
summary: {
|
|
3786
|
-
totalAttacks: 0,
|
|
3787
|
-
totalFindings: 0,
|
|
3788
|
-
critical: 0,
|
|
3789
|
-
high: 0,
|
|
3790
|
-
medium: 0,
|
|
3791
|
-
low: 0,
|
|
3792
|
-
info: 0
|
|
3793
|
-
},
|
|
3794
|
-
findings: [],
|
|
3795
|
-
warnings: [{ stage: "attack", message }]
|
|
3796
|
-
};
|
|
3797
|
-
}
|
|
3798
2261
|
async function runUnified(opts) {
|
|
3799
2262
|
const { target, depth, format, runtime, timeout } = opts;
|
|
3800
2263
|
const core = await loadCoreModule();
|
|
@@ -3804,7 +2267,7 @@ async function runUnified(opts) {
|
|
|
3804
2267
|
console.error(source_default.red("--branch-from requires --resume <run-id>"));
|
|
3805
2268
|
process.exit(2);
|
|
3806
2269
|
}
|
|
3807
|
-
const { branchJournal } = await import("./dist-
|
|
2270
|
+
const { branchJournal } = await import("./dist-3FNZIBB4.js");
|
|
3808
2271
|
const result = branchJournal({
|
|
3809
2272
|
runId: effectiveResumeScanId,
|
|
3810
2273
|
fromEntry: opts.branchFromEntry
|
|
@@ -3840,7 +2303,7 @@ async function runUnified(opts) {
|
|
|
3840
2303
|
console.error(source_default.red(`Unknown runtime '${runtime}'. Valid: ${validRuntimes.join(", ")}`));
|
|
3841
2304
|
process.exit(2);
|
|
3842
2305
|
}
|
|
3843
|
-
const directCodexProviderConfigured = runtime === "codex" && (!!process.env["
|
|
2306
|
+
const directCodexProviderConfigured = runtime === "codex" && (!!process.env["ZERO_CHATGPT_ACCESS_TOKEN"]?.trim() || !!process.env["ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"]?.trim());
|
|
3844
2307
|
if (runtime !== "api" && runtime !== "auto" && !directCodexProviderConfigured) {
|
|
3845
2308
|
const rt = core.createRuntime({ type: runtime, timeout });
|
|
3846
2309
|
const available = await rt.isAvailable();
|
|
@@ -3859,9 +2322,9 @@ async function runUnified(opts) {
|
|
|
3859
2322
|
if (opts.sessionUiFactory) {
|
|
3860
2323
|
inkUI = await opts.sessionUiFactory({ target, depth, mode });
|
|
3861
2324
|
} else {
|
|
3862
|
-
const { isBunRuntime } = await import("./runtime-
|
|
2325
|
+
const { isBunRuntime } = await import("./runtime-XFWJTBUO.js");
|
|
3863
2326
|
if (isBunRuntime()) {
|
|
3864
|
-
const { createOpenTuiSession } = await import("./run-
|
|
2327
|
+
const { createOpenTuiSession } = await import("./run-U3XU4CTC.js");
|
|
3865
2328
|
const availability = await getRuntimeAvailability();
|
|
3866
2329
|
inkUI = await createOpenTuiSession({
|
|
3867
2330
|
target,
|
|
@@ -3875,7 +2338,7 @@ async function runUnified(opts) {
|
|
|
3875
2338
|
model: opts.model
|
|
3876
2339
|
});
|
|
3877
2340
|
} else {
|
|
3878
|
-
const { renderScanStream } = await import("./scan-stream-
|
|
2341
|
+
const { renderScanStream } = await import("./scan-stream-BMOCPKQX.js");
|
|
3879
2342
|
inkUI = renderScanStream({ version: VERSION, target, depth, mode });
|
|
3880
2343
|
}
|
|
3881
2344
|
}
|
|
@@ -3883,44 +2346,8 @@ async function runUnified(opts) {
|
|
|
3883
2346
|
getPendingUserMessages = inkUI.getPendingUserMessages;
|
|
3884
2347
|
}
|
|
3885
2348
|
try {
|
|
3886
|
-
let runnerExitCode = 0;
|
|
3887
2349
|
let report;
|
|
3888
|
-
if (opts.targetType === "
|
|
3889
|
-
eventHandler({
|
|
3890
|
-
type: "stage:start",
|
|
3891
|
-
stage: "source-analysis",
|
|
3892
|
-
message: "capturing the validated finder-lens snapshot"
|
|
3893
|
-
});
|
|
3894
|
-
const outcome = await runDeepReview({
|
|
3895
|
-
target,
|
|
3896
|
-
profile: opts.reviewProfile,
|
|
3897
|
-
subsystem: opts.subsystem,
|
|
3898
|
-
models: opts.model ? [opts.model] : void 0,
|
|
3899
|
-
runtime,
|
|
3900
|
-
timeoutMs: timeout,
|
|
3901
|
-
costCeilingUsd: opts.costCeilingUsd,
|
|
3902
|
-
log: (message) => eventHandler({
|
|
3903
|
-
type: "stage:start",
|
|
3904
|
-
stage: "attack",
|
|
3905
|
-
message
|
|
3906
|
-
})
|
|
3907
|
-
});
|
|
3908
|
-
runnerExitCode = outcome.exitCode;
|
|
3909
|
-
const lensReport = outcome.report ?? skippedLensReviewReport(
|
|
3910
|
-
target,
|
|
3911
|
-
typeof outcome.result === "object" && outcome.result !== null && "note" in outcome.result ? String(outcome.result.note) : `lens review ended with exit code ${outcome.exitCode}`
|
|
3912
|
-
);
|
|
3913
|
-
report = lensReport;
|
|
3914
|
-
writeOsecRunReport(
|
|
3915
|
-
resolveOsecRunStorage({ ...opts.dbPath ? { dbPath: opts.dbPath } : {} }),
|
|
3916
|
-
lensReport
|
|
3917
|
-
);
|
|
3918
|
-
eventHandler({
|
|
3919
|
-
type: "stage:end",
|
|
3920
|
-
stage: "attack",
|
|
3921
|
-
message: outcome.exitCode === 0 ? "validated finder-lens review completed" : `validated finder-lens review ended with exit code ${outcome.exitCode}`
|
|
3922
|
-
});
|
|
3923
|
-
} else if (opts.targetType === "url" || opts.targetType === "web-app") {
|
|
2350
|
+
if (opts.targetType === "url" || opts.targetType === "web-app") {
|
|
3924
2351
|
report = await core.agenticScan({
|
|
3925
2352
|
config: {
|
|
3926
2353
|
target,
|
|
@@ -3986,16 +2413,13 @@ async function runUnified(opts) {
|
|
|
3986
2413
|
}
|
|
3987
2414
|
const reportAny = report;
|
|
3988
2415
|
const canonicalReport = toScanReport(report);
|
|
3989
|
-
if (opts.targetType !== "url" && opts.targetType !== "web-app") {
|
|
3990
|
-
await postFinalResultToCloud(reportAny);
|
|
3991
|
-
}
|
|
3992
2416
|
if (inkUI) {
|
|
3993
2417
|
inkUI.setReport(report);
|
|
3994
2418
|
await inkUI.waitForExit();
|
|
3995
2419
|
} else {
|
|
3996
2420
|
if (format === "html" || format === "pdf") {
|
|
3997
2421
|
const extension = format === "pdf" ? "pdf" : "html";
|
|
3998
|
-
const filePath = opts.reportPath ?
|
|
2422
|
+
const filePath = opts.reportPath ? resolve3(opts.reportPath) : join(tmpdir(), `0-report-${Date.now()}.${extension}`);
|
|
3999
2423
|
if (format === "pdf") {
|
|
4000
2424
|
await generatePdfReport(toScanReport(reportAny), filePath);
|
|
4001
2425
|
} else {
|
|
@@ -4015,7 +2439,7 @@ async function runUnified(opts) {
|
|
|
4015
2439
|
console.log("");
|
|
4016
2440
|
console.log(source_default.gray(" --tui post-scan view is no longer bundled in the npm package."));
|
|
4017
2441
|
console.log(source_default.gray(" Install the standalone binary for the full OpenTUI experience:"));
|
|
4018
|
-
console.log(source_default.gray(" curl -fsSL https://raw.githubusercontent.com/0sec-labs/
|
|
2442
|
+
console.log(source_default.gray(" curl -fsSL https://raw.githubusercontent.com/0sec-labs/0/main/install.sh | bash"));
|
|
4019
2443
|
console.log("");
|
|
4020
2444
|
}
|
|
4021
2445
|
if (crossValidatedLeads) {
|
|
@@ -4025,14 +2449,14 @@ async function runUnified(opts) {
|
|
|
4025
2449
|
printCostSummary(scanCompletedCost);
|
|
4026
2450
|
}
|
|
4027
2451
|
unsubscribeCost();
|
|
4028
|
-
let exitCode =
|
|
2452
|
+
let exitCode = 0;
|
|
4029
2453
|
const estimatedCostUsd = getEstimatedCost(reportAny);
|
|
4030
2454
|
const usage = getUsage(reportAny);
|
|
4031
2455
|
if (opts.emit === "pr") {
|
|
4032
2456
|
const findings = report.findings ?? [];
|
|
4033
2457
|
const core2 = await loadCoreModule();
|
|
4034
|
-
const repoRoot = opts.targetType === "source-code" &&
|
|
4035
|
-
const outDir = opts.emitOutDir ??
|
|
2458
|
+
const repoRoot = opts.targetType === "source-code" && existsSync2(target) ? resolve3(target) : process.cwd();
|
|
2459
|
+
const outDir = opts.emitOutDir ?? join(tmpdir(), `0-emit-${Date.now()}`);
|
|
4036
2460
|
console.log(source_default.blue(`[emit pr] ${findings.length} finding(s); repo=${repoRoot} base=${opts.emitPrBase ?? "main"}${opts.emitPrDryRun ? " (dry-run)" : ""}`));
|
|
4037
2461
|
const emitReport = await core2.emitFindingsAsPRs(findings, {
|
|
4038
2462
|
repoRoot,
|
|
@@ -4063,7 +2487,7 @@ async function runUnified(opts) {
|
|
|
4063
2487
|
if (findings.length === 0) {
|
|
4064
2488
|
console.log(source_default.yellow("No findings to export."));
|
|
4065
2489
|
} else {
|
|
4066
|
-
const { exportToGitHubIssues } = await import("./github-issues-
|
|
2490
|
+
const { exportToGitHubIssues } = await import("./github-issues-IGTTWM7V.js");
|
|
4067
2491
|
console.log(source_default.blue(`Exporting ${findings.length} finding(s) to GitHub Issues on ${repo}...`));
|
|
4068
2492
|
const result = await exportToGitHubIssues(findings, repo);
|
|
4069
2493
|
console.log(
|
|
@@ -4071,7 +2495,7 @@ async function runUnified(opts) {
|
|
|
4071
2495
|
);
|
|
4072
2496
|
}
|
|
4073
2497
|
}
|
|
4074
|
-
const ceilingRaw = process.env["
|
|
2498
|
+
const ceilingRaw = process.env["ZERO_COST_CEILING_USD"]?.trim();
|
|
4075
2499
|
if (ceilingRaw) {
|
|
4076
2500
|
const ceiling = Number(ceilingRaw);
|
|
4077
2501
|
if (Number.isFinite(ceiling) && estimatedCostUsd !== void 0 && estimatedCostUsd > ceiling) {
|
|
@@ -4143,13 +2567,18 @@ var DEFAULT_ENCODED_RUN_MAX = 140;
|
|
|
4143
2567
|
var ENCODED_RUN_MAX_CAP = 1e6;
|
|
4144
2568
|
var ELLIPSIS = "...";
|
|
4145
2569
|
var ENCODED_PLACEHOLDER = "[encoded payload omitted]";
|
|
2570
|
+
var DEFAULT_ENCODED_SEGMENT = new RegExp(`[A-Za-z0-9+_-]{${DEFAULT_ENCODED_RUN_MAX},}={0,2}`, "g");
|
|
2571
|
+
var DEFAULT_ENCODED_DATA_URI = new RegExp(`(data:[^\\s,]*;base64,)[A-Za-z0-9+/_-]{${DEFAULT_ENCODED_RUN_MAX},}={0,2}`, "gi");
|
|
4146
2572
|
function stripTerminalControl(text) {
|
|
4147
2573
|
return text.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "").replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").replace(/[\x00-\x08\x0b-\x1f\x7f]/g, "");
|
|
4148
2574
|
}
|
|
4149
2575
|
function replaceLargeEncodedChunks(text, maxEncodedRun) {
|
|
4150
2576
|
const normalized = Number.isFinite(maxEncodedRun) ? Math.trunc(maxEncodedRun) : DEFAULT_ENCODED_RUN_MAX;
|
|
4151
2577
|
const minRun = Math.min(Math.max(32, normalized), ENCODED_RUN_MAX_CAP);
|
|
4152
|
-
|
|
2578
|
+
if (text.length < minRun) return text;
|
|
2579
|
+
const segment = minRun === DEFAULT_ENCODED_RUN_MAX ? DEFAULT_ENCODED_SEGMENT : new RegExp(`[A-Za-z0-9+_-]{${minRun},}={0,2}`, "g");
|
|
2580
|
+
const dataUri = minRun === DEFAULT_ENCODED_RUN_MAX ? DEFAULT_ENCODED_DATA_URI : new RegExp(`(data:[^\\s,]*;base64,)[A-Za-z0-9+/_-]{${minRun},}={0,2}`, "gi");
|
|
2581
|
+
return text.replace(dataUri, `$1${ENCODED_PLACEHOLDER}`).replace(segment, ENCODED_PLACEHOLDER);
|
|
4153
2582
|
}
|
|
4154
2583
|
function sanitizeTuiText(value, options = {}) {
|
|
4155
2584
|
return stripTerminalControl(replaceLargeEncodedChunks(String(value ?? ""), options.maxEncodedRun ?? DEFAULT_ENCODED_RUN_MAX)).replace(/\s+/g, " ").trim();
|
|
@@ -4287,20 +2716,6 @@ var SLASH_COMMANDS = [
|
|
|
4287
2716
|
category: "info",
|
|
4288
2717
|
description: "List available tools"
|
|
4289
2718
|
},
|
|
4290
|
-
{
|
|
4291
|
-
name: "agents",
|
|
4292
|
-
aliases: [],
|
|
4293
|
-
category: "info",
|
|
4294
|
-
description: "List available agents",
|
|
4295
|
-
tuiOnly: true
|
|
4296
|
-
},
|
|
4297
|
-
{
|
|
4298
|
-
name: "harness",
|
|
4299
|
-
aliases: [],
|
|
4300
|
-
category: "navigation",
|
|
4301
|
-
description: "Live harness: views, commands, settings, rollback and workspace trust",
|
|
4302
|
-
tuiOnly: true
|
|
4303
|
-
},
|
|
4304
2719
|
{
|
|
4305
2720
|
name: "new-chat",
|
|
4306
2721
|
aliases: ["new"],
|
|
@@ -4308,13 +2723,6 @@ var SLASH_COMMANDS = [
|
|
|
4308
2723
|
description: "Create an independent audit using the staged model and connection",
|
|
4309
2724
|
tuiOnly: true
|
|
4310
2725
|
},
|
|
4311
|
-
{
|
|
4312
|
-
name: "audits",
|
|
4313
|
-
aliases: [],
|
|
4314
|
-
category: "navigation",
|
|
4315
|
-
description: "Switch between live audits without stopping their work",
|
|
4316
|
-
tuiOnly: true
|
|
4317
|
-
},
|
|
4318
2726
|
{
|
|
4319
2727
|
name: "onboard",
|
|
4320
2728
|
aliases: [],
|
|
@@ -4322,14 +2730,6 @@ var SLASH_COMMANDS = [
|
|
|
4322
2730
|
description: "Reopen guided setup without replacing the current audit",
|
|
4323
2731
|
tuiOnly: true
|
|
4324
2732
|
},
|
|
4325
|
-
{
|
|
4326
|
-
name: "stop",
|
|
4327
|
-
aliases: [],
|
|
4328
|
-
category: "session",
|
|
4329
|
-
description: "Stop this audit's work or one owned worker and its descendants",
|
|
4330
|
-
usage: "/stop audit | /stop worker <exact name or id>",
|
|
4331
|
-
tuiOnly: true
|
|
4332
|
-
},
|
|
4333
2733
|
// ── session ─────────────────────────────────────────────────────────────
|
|
4334
2734
|
{
|
|
4335
2735
|
name: "clear",
|
|
@@ -4344,13 +2744,6 @@ var SLASH_COMMANDS = [
|
|
|
4344
2744
|
description: "Browse previous scan runs and their results",
|
|
4345
2745
|
tuiOnly: true
|
|
4346
2746
|
},
|
|
4347
|
-
{
|
|
4348
|
-
name: "transcript",
|
|
4349
|
-
aliases: ["review"],
|
|
4350
|
-
category: "session",
|
|
4351
|
-
description: "Show this conversation, including tool details",
|
|
4352
|
-
tuiOnly: true
|
|
4353
|
-
},
|
|
4354
2747
|
{
|
|
4355
2748
|
name: "findings",
|
|
4356
2749
|
aliases: ["finds"],
|
|
@@ -4359,11 +2752,11 @@ var SLASH_COMMANDS = [
|
|
|
4359
2752
|
tuiOnly: true
|
|
4360
2753
|
},
|
|
4361
2754
|
{
|
|
4362
|
-
name: "
|
|
4363
|
-
aliases: [
|
|
2755
|
+
name: "fix",
|
|
2756
|
+
aliases: [],
|
|
4364
2757
|
category: "session",
|
|
4365
|
-
description: "
|
|
4366
|
-
usage: "/
|
|
2758
|
+
description: "Generate a verified local source fix; review before explicitly publishing a draft PR",
|
|
2759
|
+
usage: "/fix [finding-id] | /fix publish <finding-id> | /fix cancel",
|
|
4367
2760
|
tuiOnly: true
|
|
4368
2761
|
},
|
|
4369
2762
|
{
|
|
@@ -4383,33 +2776,11 @@ var SLASH_COMMANDS = [
|
|
|
4383
2776
|
tuiOnly: true
|
|
4384
2777
|
},
|
|
4385
2778
|
{
|
|
4386
|
-
name: "
|
|
4387
|
-
aliases: [],
|
|
4388
|
-
category: "session",
|
|
4389
|
-
description: "Replay a previous turn or session",
|
|
4390
|
-
tuiOnly: true
|
|
4391
|
-
},
|
|
4392
|
-
// ── mode ────────────────────────────────────────────────────────────────
|
|
4393
|
-
{
|
|
4394
|
-
name: "mode",
|
|
2779
|
+
name: "sessions",
|
|
4395
2780
|
aliases: [],
|
|
4396
|
-
category: "mode",
|
|
4397
|
-
description: "Set Standard (approve actions), Co-pilot (in scope), YOLO (public-network autonomy), or Recon (passive)",
|
|
4398
|
-
usage: "/mode [standard|copilot|yolo|recon]"
|
|
4399
|
-
},
|
|
4400
|
-
{
|
|
4401
|
-
name: "resume",
|
|
4402
|
-
aliases: ["sessions"],
|
|
4403
2781
|
category: "session",
|
|
4404
|
-
description: "
|
|
4405
|
-
usage: "/
|
|
4406
|
-
},
|
|
4407
|
-
{
|
|
4408
|
-
name: "providers",
|
|
4409
|
-
aliases: [],
|
|
4410
|
-
category: "system",
|
|
4411
|
-
description: "Connection: 0sec Cloud, API keys, or provider subscriptions",
|
|
4412
|
-
usage: "/providers",
|
|
2782
|
+
description: "Switch open sessions or resume saved conversations",
|
|
2783
|
+
usage: "/sessions",
|
|
4413
2784
|
tuiOnly: true
|
|
4414
2785
|
},
|
|
4415
2786
|
{
|
|
@@ -4423,7 +2794,7 @@ var SLASH_COMMANDS = [
|
|
|
4423
2794
|
name: "feedback",
|
|
4424
2795
|
aliases: [],
|
|
4425
2796
|
category: "system",
|
|
4426
|
-
description: "Record feedback about
|
|
2797
|
+
description: "Record feedback about 0 to a local file you control, with optional HTTPS submission",
|
|
4427
2798
|
usage: "/feedback <message> | /feedback submit <message> | /feedback send | /feedback cancel"
|
|
4428
2799
|
},
|
|
4429
2800
|
{
|
|
@@ -4452,7 +2823,7 @@ var SLASH_COMMANDS = [
|
|
|
4452
2823
|
{
|
|
4453
2824
|
name: "model",
|
|
4454
2825
|
aliases: ["models"],
|
|
4455
|
-
category: "
|
|
2826
|
+
category: "session",
|
|
4456
2827
|
description: "Show or select a model (TUI: applies to New chat)",
|
|
4457
2828
|
usage: "/model [id]"
|
|
4458
2829
|
},
|
|
@@ -4478,13 +2849,6 @@ var SLASH_COMMANDS = [
|
|
|
4478
2849
|
description: "View active and recent operations",
|
|
4479
2850
|
tuiOnly: true
|
|
4480
2851
|
},
|
|
4481
|
-
{
|
|
4482
|
-
name: "herd",
|
|
4483
|
-
aliases: ["workers"],
|
|
4484
|
-
category: "navigation",
|
|
4485
|
-
description: "Inspect the active harness worker herd",
|
|
4486
|
-
tuiOnly: true
|
|
4487
|
-
},
|
|
4488
2852
|
{
|
|
4489
2853
|
name: "comms",
|
|
4490
2854
|
aliases: ["messages"],
|
|
@@ -4501,9 +2865,9 @@ var SLASH_COMMANDS = [
|
|
|
4501
2865
|
},
|
|
4502
2866
|
{
|
|
4503
2867
|
name: "connect",
|
|
4504
|
-
aliases: [
|
|
2868
|
+
aliases: [],
|
|
4505
2869
|
category: "navigation",
|
|
4506
|
-
description: "Connect
|
|
2870
|
+
description: "Connect your API key or provider subscription",
|
|
4507
2871
|
usage: "/connect",
|
|
4508
2872
|
tuiOnly: true
|
|
4509
2873
|
},
|
|
@@ -4605,7 +2969,7 @@ function filterCommands(query) {
|
|
|
4605
2969
|
}
|
|
4606
2970
|
|
|
4607
2971
|
// packages/cli/src/console-session.ts
|
|
4608
|
-
import { randomUUID
|
|
2972
|
+
import { randomUUID } from "node:crypto";
|
|
4609
2973
|
|
|
4610
2974
|
// packages/cli/src/conversation-history.ts
|
|
4611
2975
|
var MAX_PAGE_CHARS = 16e3;
|
|
@@ -4677,10 +3041,10 @@ function createConversationHistory(options = {}) {
|
|
|
4677
3041
|
|
|
4678
3042
|
// packages/cli/src/dev-engine-updates.ts
|
|
4679
3043
|
import { execFile as execFile2 } from "node:child_process";
|
|
4680
|
-
import { createHash as
|
|
4681
|
-
import { appendFileSync, readFileSync as
|
|
3044
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
3045
|
+
import { appendFileSync, readFileSync as readFileSync2, readdirSync, realpathSync, statSync } from "node:fs";
|
|
4682
3046
|
import { mkdir, mkdtemp, rm } from "node:fs/promises";
|
|
4683
|
-
import { isAbsolute, join as
|
|
3047
|
+
import { isAbsolute, join as join2, relative, sep } from "node:path";
|
|
4684
3048
|
import { pathToFileURL } from "node:url";
|
|
4685
3049
|
import { isDeepStrictEqual, promisify } from "node:util";
|
|
4686
3050
|
var execute = promisify(execFile2);
|
|
@@ -4708,12 +3072,12 @@ function assertSessionContract(session) {
|
|
|
4708
3072
|
}
|
|
4709
3073
|
}
|
|
4710
3074
|
function sourceDigest(root) {
|
|
4711
|
-
const hash =
|
|
3075
|
+
const hash = createHash2("sha256");
|
|
4712
3076
|
const visit = (directory) => {
|
|
4713
3077
|
const entries = readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
4714
3078
|
for (const entry of entries) {
|
|
4715
3079
|
if (entry.name.startsWith(".") || Object.hasOwn(ignoredSourceNames, entry.name)) continue;
|
|
4716
|
-
const path =
|
|
3080
|
+
const path = join2(directory, entry.name);
|
|
4717
3081
|
if (entry.isDirectory()) {
|
|
4718
3082
|
visit(path);
|
|
4719
3083
|
continue;
|
|
@@ -4721,23 +3085,23 @@ function sourceDigest(root) {
|
|
|
4721
3085
|
if (!entry.isFile() && !entry.isSymbolicLink() || /\.(test|spec)\.[cm]?[jt]sx?$/.test(entry.name) || entry.name.endsWith(".d.ts")) continue;
|
|
4722
3086
|
let readable = path;
|
|
4723
3087
|
if (entry.isSymbolicLink()) {
|
|
4724
|
-
readable =
|
|
4725
|
-
const target =
|
|
4726
|
-
if (target === ".." || target.startsWith(`..${
|
|
3088
|
+
readable = realpathSync(path);
|
|
3089
|
+
const target = relative(root, readable);
|
|
3090
|
+
if (target === ".." || target.startsWith(`..${sep}`) || isAbsolute(target) || !statSync(readable).isFile()) {
|
|
4727
3091
|
throw new Error(`Source links must resolve to files inside the core source tree: ${path}`);
|
|
4728
3092
|
}
|
|
4729
3093
|
}
|
|
4730
|
-
hash.update(
|
|
3094
|
+
hash.update(relative(root, path)).update("\0").update(readFileSync2(readable)).update("\0");
|
|
4731
3095
|
}
|
|
4732
3096
|
};
|
|
4733
3097
|
visit(root);
|
|
4734
3098
|
return hash.digest("hex");
|
|
4735
3099
|
}
|
|
4736
3100
|
function withDevEngineUpdates(initial, config, closeStore) {
|
|
4737
|
-
const requestedRoot = process.env["
|
|
4738
|
-
const root = requestedRoot ?
|
|
4739
|
-
if (root && JSON.parse(
|
|
4740
|
-
throw new Error("
|
|
3101
|
+
const requestedRoot = process.env["ZERO_DEV_SOURCE_ROOT"];
|
|
3102
|
+
const root = requestedRoot ? realpathSync(requestedRoot) : void 0;
|
|
3103
|
+
if (root && JSON.parse(readFileSync2(join2(root, "packages/core/package.json"), "utf8")).name !== "@0/core") {
|
|
3104
|
+
throw new Error("ZERO_DEV_SOURCE_ROOT must identify a 0 development checkout");
|
|
4741
3105
|
}
|
|
4742
3106
|
let current = initial;
|
|
4743
3107
|
let activeDigest;
|
|
@@ -4763,15 +3127,15 @@ function withDevEngineUpdates(initial, config, closeStore) {
|
|
|
4763
3127
|
let committed = false;
|
|
4764
3128
|
let pendingHarnessSnapshot;
|
|
4765
3129
|
try {
|
|
4766
|
-
const source =
|
|
3130
|
+
const source = join2(root, "packages/core/src");
|
|
4767
3131
|
const digest = sourceDigest(source);
|
|
4768
3132
|
if (digest === activeDigest || digest === rejectedDigest) return;
|
|
4769
3133
|
rejectedDigest = digest;
|
|
4770
3134
|
notify("Building changed development engine source; the current session remains active until handoff.");
|
|
4771
|
-
const generationRoot =
|
|
3135
|
+
const generationRoot = join2(root, "packages/core/.0/dev-engines");
|
|
4772
3136
|
await mkdir(generationRoot, { recursive: true, mode: 448 });
|
|
4773
|
-
directory = await mkdtemp(
|
|
4774
|
-
const result = await execute(process.execPath, [
|
|
3137
|
+
directory = await mkdtemp(join2(generationRoot, "generation-"));
|
|
3138
|
+
const result = await execute(process.execPath, [join2(root, "scripts/build-dev-engine.mjs"), source, directory], {
|
|
4775
3139
|
cwd: root,
|
|
4776
3140
|
timeout: 12e4,
|
|
4777
3141
|
maxBuffer: 2 * 1024 * 1024,
|
|
@@ -4801,7 +3165,7 @@ function withDevEngineUpdates(initial, config, closeStore) {
|
|
|
4801
3165
|
if (!isDeepStrictEqual(comparableCheckpoint(candidate.exportCheckpoint()), comparableCheckpoint(checkpoint))) {
|
|
4802
3166
|
throw new Error("Candidate engine did not preserve the session checkpoint");
|
|
4803
3167
|
}
|
|
4804
|
-
const candidateBus = await import(pathToFileURL(
|
|
3168
|
+
const candidateBus = await import(pathToFileURL(join2(directory, "runtime/events/bus.js")).href);
|
|
4805
3169
|
signal?.throwIfAborted();
|
|
4806
3170
|
if (closingStarted || !getSettings().allowDevSourceUpdates) throw new Error("Development engine update cancelled before handoff");
|
|
4807
3171
|
if (!isDeepStrictEqual(current.exportCheckpoint(), checkpoint)) throw new Error("Session changed during candidate preparation; keeping the current engine");
|
|
@@ -4879,7 +3243,7 @@ function withDevEngineUpdates(initial, config, closeStore) {
|
|
|
4879
3243
|
};
|
|
4880
3244
|
const devlog = (o) => {
|
|
4881
3245
|
try {
|
|
4882
|
-
appendFileSync(process.env["
|
|
3246
|
+
appendFileSync(process.env["ZERO_TUI_LOG"] ?? "/tmp/0-tui.log", JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), kind: "dev-engine", ...o }) + "\n");
|
|
4883
3247
|
} catch {
|
|
4884
3248
|
}
|
|
4885
3249
|
};
|
|
@@ -4946,9 +3310,25 @@ function withDevEngineUpdates(initial, config, closeStore) {
|
|
|
4946
3310
|
}
|
|
4947
3311
|
|
|
4948
3312
|
// packages/cli/src/console-session.ts
|
|
3313
|
+
var CONSOLE_JEV_FEATURES = /* @__PURE__ */ new Set([
|
|
3314
|
+
"browser",
|
|
3315
|
+
"memory",
|
|
3316
|
+
"dedupe",
|
|
3317
|
+
"redteam",
|
|
3318
|
+
"kernel",
|
|
3319
|
+
"crash",
|
|
3320
|
+
"radar",
|
|
3321
|
+
"foxguard"
|
|
3322
|
+
]);
|
|
3323
|
+
function consoleJevRuntimeFromEnvironment() {
|
|
3324
|
+
const features = [...new Set(
|
|
3325
|
+
(process.env["ZERO_JEV_FEATURES"] ?? "").split(",").map((feature) => feature.trim()).filter((feature) => CONSOLE_JEV_FEATURES.has(feature))
|
|
3326
|
+
)];
|
|
3327
|
+
return toToolContextJevRuntime(createConsoleJevRuntime({ provider: "direct", features }));
|
|
3328
|
+
}
|
|
4949
3329
|
function createLocalConsoleSession(config, dbPath) {
|
|
4950
3330
|
const db = new osecDB(dbPath);
|
|
4951
|
-
const scanId = config.scanId ?? `console-${
|
|
3331
|
+
const scanId = config.scanId ?? `console-${randomUUID()}`;
|
|
4952
3332
|
let ownsScan = false;
|
|
4953
3333
|
try {
|
|
4954
3334
|
if (!db.getScan(scanId)) {
|
|
@@ -4964,7 +3344,8 @@ function createLocalConsoleSession(config, dbPath) {
|
|
|
4964
3344
|
...config,
|
|
4965
3345
|
scanId,
|
|
4966
3346
|
db,
|
|
4967
|
-
conversationHistory: config.conversationHistory ?? createConversationHistory()
|
|
3347
|
+
conversationHistory: config.conversationHistory ?? createConversationHistory(),
|
|
3348
|
+
jevRuntime: config.jevRuntime ?? consoleJevRuntimeFromEnvironment()
|
|
4968
3349
|
};
|
|
4969
3350
|
const session = createConsoleSession(engineConfig);
|
|
4970
3351
|
return withDevEngineUpdates(session, engineConfig, (completed) => {
|
|
@@ -4986,6 +3367,29 @@ function createLocalConsoleSession(config, dbPath) {
|
|
|
4986
3367
|
|
|
4987
3368
|
// packages/cli/src/tui/codex-device-auth.ts
|
|
4988
3369
|
import { spawn as spawn2 } from "node:child_process";
|
|
3370
|
+
|
|
3371
|
+
// packages/cli/src/open-browser.ts
|
|
3372
|
+
import { spawn } from "node:child_process";
|
|
3373
|
+
import { platform } from "node:os";
|
|
3374
|
+
function defaultOpenBrowser(url) {
|
|
3375
|
+
const plat = platform();
|
|
3376
|
+
return new Promise((resolve4, reject) => {
|
|
3377
|
+
const options = { detached: true, stdio: "ignore", shell: false };
|
|
3378
|
+
const child = plat === "darwin" ? spawn("open", [url], options) : plat === "win32" ? spawn("powershell.exe", [
|
|
3379
|
+
"-NoProfile",
|
|
3380
|
+
"-NonInteractive",
|
|
3381
|
+
"-Command",
|
|
3382
|
+
"Start-Process -FilePath $env:OSEC_BROWSER_LOGIN_URL"
|
|
3383
|
+
], { ...options, env: { ...process.env, OSEC_BROWSER_LOGIN_URL: url } }) : spawn("xdg-open", [url], options);
|
|
3384
|
+
child.once("error", reject);
|
|
3385
|
+
child.once("spawn", () => {
|
|
3386
|
+
child.unref();
|
|
3387
|
+
resolve4();
|
|
3388
|
+
});
|
|
3389
|
+
});
|
|
3390
|
+
}
|
|
3391
|
+
|
|
3392
|
+
// packages/cli/src/tui/codex-device-auth.ts
|
|
4989
3393
|
var MAX_VISIBLE_LINES = 8;
|
|
4990
3394
|
function defaultSpawnCodexDeviceAuth(command, args, options) {
|
|
4991
3395
|
const child = spawn2(command, args, {
|
|
@@ -5001,22 +3405,39 @@ function defaultSpawnCodexDeviceAuth(command, args, options) {
|
|
|
5001
3405
|
function startCodexDeviceAuth(options) {
|
|
5002
3406
|
const env = options.env ?? process.env;
|
|
5003
3407
|
const launch = options.spawn ?? defaultSpawnCodexDeviceAuth;
|
|
3408
|
+
const openBrowser = options.openBrowser ?? defaultOpenBrowser;
|
|
5004
3409
|
const lines = [];
|
|
5005
3410
|
let pending = "";
|
|
5006
3411
|
let settled = false;
|
|
5007
3412
|
let cancelled = false;
|
|
3413
|
+
let browserOpened = false;
|
|
5008
3414
|
const publish = (phase, message) => {
|
|
5009
3415
|
options.onUpdate({ phase, lines: [...lines], message });
|
|
5010
3416
|
};
|
|
3417
|
+
const tryOpenBrowser = (text) => {
|
|
3418
|
+
if (browserOpened) return;
|
|
3419
|
+
const rawUrl = text.match(/https:\/\/auth\.openai\.com\/codex\/device[^\s\x1b]*/)?.[0];
|
|
3420
|
+
if (!rawUrl) return;
|
|
3421
|
+
const url = rawUrl.replace(/[),.;]+$/, "");
|
|
3422
|
+
browserOpened = true;
|
|
3423
|
+
void Promise.resolve(openBrowser(url)).catch(() => {
|
|
3424
|
+
lines.push("Could not open a browser automatically; open the URL above.");
|
|
3425
|
+
if (lines.length > MAX_VISIBLE_LINES) lines.shift();
|
|
3426
|
+
publish("running", "Complete the ChatGPT Codex device sign-in in your browser.");
|
|
3427
|
+
});
|
|
3428
|
+
};
|
|
5011
3429
|
const pushOutput = (chunk) => {
|
|
5012
|
-
const
|
|
5013
|
-
|
|
3430
|
+
const text = chunk.toString("utf8");
|
|
3431
|
+
tryOpenBrowser(text);
|
|
3432
|
+
const raw = `${pending}${text}`;
|
|
3433
|
+
const parts = raw.split(/\r?\n/);
|
|
5014
3434
|
pending = parts.pop() ?? "";
|
|
5015
3435
|
for (const line of parts) {
|
|
5016
|
-
const value = line
|
|
3436
|
+
const value = sanitizeTuiText(line);
|
|
5017
3437
|
if (value.length === 0) continue;
|
|
5018
3438
|
lines.push(value);
|
|
5019
3439
|
if (lines.length > MAX_VISIBLE_LINES) lines.shift();
|
|
3440
|
+
tryOpenBrowser(value);
|
|
5020
3441
|
}
|
|
5021
3442
|
publish("running", "Complete the ChatGPT Codex device sign-in in your browser.");
|
|
5022
3443
|
};
|
|
@@ -5052,7 +3473,7 @@ function startCodexDeviceAuth(options) {
|
|
|
5052
3473
|
return;
|
|
5053
3474
|
}
|
|
5054
3475
|
maybeLoadCodexAuth({ env, home: options.homeDir, force: true });
|
|
5055
|
-
if (!env["
|
|
3476
|
+
if (!env["ZERO_CHATGPT_ACCESS_TOKEN"] && !env["ZERO_CHATGPT_OAUTH_REFRESH_TOKEN"]) {
|
|
5056
3477
|
finish("failed", "Codex completed without a readable ChatGPT subscription credential.");
|
|
5057
3478
|
return;
|
|
5058
3479
|
}
|
|
@@ -5077,8 +3498,6 @@ export {
|
|
|
5077
3498
|
TIMELINE_FORMATS,
|
|
5078
3499
|
isTimelineFormat,
|
|
5079
3500
|
formatTimeline,
|
|
5080
|
-
registerHuntCommand,
|
|
5081
|
-
registerDeepReviewCommand,
|
|
5082
3501
|
resolveFindingChatIntent,
|
|
5083
3502
|
buildFindingChatPrompt,
|
|
5084
3503
|
buildFindingConsoleCommand,
|
|
@@ -5099,10 +3518,7 @@ export {
|
|
|
5099
3518
|
watchLensSynthCommand,
|
|
5100
3519
|
registerLensSynthCommand,
|
|
5101
3520
|
createLocalConsoleSession,
|
|
5102
|
-
startCodexDeviceAuth,
|
|
5103
|
-
registerAuthCommand,
|
|
5104
|
-
hostedBrowserLoginFlow,
|
|
5105
|
-
runLogin,
|
|
5106
3521
|
defaultOpenBrowser,
|
|
3522
|
+
startCodexDeviceAuth,
|
|
5107
3523
|
runUnified
|
|
5108
3524
|
};
|