0sec-cli 0.22.1 → 0.23.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/0.js +64 -47
- package/README.md +73 -53
- package/chunks/{adapt-loop-J6QCZZTB.js → adapt-loop-V3JFWOKC.js} +4 -4
- package/chunks/{artifact-scraper-MFKT6OI4.js → artifact-scraper-NYKUF2M5.js} +2 -2
- package/chunks/{assumption-mining-646LYSJY.js → assumption-mining-ZX3YVWEH.js} +19 -15
- package/chunks/backend-connections-H52NIVH2.js +23 -0
- package/chunks/{chunk-NLH4LMWH.js → chunk-3E54JYS4.js} +1 -1
- package/chunks/chunk-3P3XXSIJ.js +463 -0
- package/chunks/{chunk-CPWA6RWE.js → chunk-3ZNI7F42.js} +3 -3
- package/chunks/{chunk-LGCASF2C.js → chunk-4HGGNBKK.js} +7405 -2225
- package/chunks/{chunk-TM74SSHC.js → chunk-62ENS6XO.js} +1 -1
- package/chunks/{chunk-4VGLO2YS.js → chunk-65H3MRBF.js} +5 -2
- package/chunks/chunk-A2ADXAC5.js +5329 -0
- package/chunks/chunk-A5QO7N3S.js +74 -0
- package/chunks/chunk-AAA4PRWK.js +990 -0
- package/chunks/{chunk-AV2LG2L7.js → chunk-BF5FIK7H.js} +257 -145
- package/chunks/chunk-CP6HANBE.js +355 -0
- package/chunks/{chunk-HHPWRY2M.js → chunk-EH2WTNY7.js} +23474 -22746
- package/chunks/{chunk-OSBPGWTE.js → chunk-EJH2TNVH.js} +3 -3
- package/chunks/chunk-EWXOG6D3.js +1695 -0
- package/chunks/{chunk-WZRVS7SB.js → chunk-F344NYUN.js} +1 -1
- package/chunks/chunk-FXR3BT2G.js +226 -0
- package/chunks/{chunk-GOEA3NW6.js → chunk-HFDSIHTE.js} +1603 -1295
- package/chunks/chunk-I3OCJ2OA.js +309 -0
- package/chunks/{chunk-X7LAEEUW.js → chunk-ITES7BCY.js} +1 -1
- package/chunks/{chunk-YL6YAGLL.js → chunk-JEYIWMGL.js} +1 -1
- package/chunks/{chunk-WSDDGFA6.js → chunk-K55B4OH4.js} +1 -1
- package/chunks/{chunk-TH2LW437.js → chunk-KCA35IL3.js} +2 -190
- package/chunks/chunk-KHPOSLCF.js +1659 -0
- package/chunks/chunk-LT7BFGXA.js +733 -0
- package/chunks/{chunk-KAU6NLGP.js → chunk-LTEE2JWK.js} +786 -20
- package/chunks/{chunk-ZKQ2I3S2.js → chunk-LVGZGG63.js} +1 -1
- package/chunks/chunk-MQJVU2GZ.js +277 -0
- package/chunks/{chunk-OHHCGDJ2.js → chunk-NI6QYQSK.js} +126 -1089
- package/chunks/{chunk-6YNKRUMC.js → chunk-NJLKFC66.js} +321 -5
- package/chunks/{chunk-3E2KALZ5.js → chunk-NX4H6L4M.js} +2 -2
- package/chunks/{chunk-W6UWZFKL.js → chunk-O6KPVYOI.js} +100 -5
- package/chunks/{chunk-IVQV574Y.js → chunk-O6Z4XPOM.js} +1 -1
- package/chunks/{chunk-V6GHX34G.js → chunk-OLSKXTQZ.js} +8 -274
- package/chunks/{chunk-5BTJMINS.js → chunk-P7KDN5PK.js} +208 -46
- package/chunks/chunk-PIR6BNE7.js +83 -0
- package/chunks/{chunk-XCISCI4T.js → chunk-QMICC5O2.js} +30 -12
- package/chunks/{chunk-6GHSR47F.js → chunk-R35KKJA4.js} +1 -58
- package/chunks/chunk-RS4MAN3Q.js +73 -0
- package/chunks/{chunk-VWITLWXB.js → chunk-T7ZEVY5G.js} +307 -34
- package/chunks/chunk-TPUJZ74W.js +36 -0
- package/chunks/{chunk-IFME676H.js → chunk-UZUELPFW.js} +79 -18
- package/chunks/{chunk-TV27OWIW.js → chunk-V2BWXPZP.js} +9 -9
- package/chunks/chunk-VDOWARMJ.js +63 -0
- package/chunks/{chunk-XDKB2P5X.js → chunk-W5VNCB57.js} +1 -1
- package/chunks/{chunk-4G7OVYJX.js → chunk-WIYNYNL2.js} +1 -1
- package/chunks/chunk-WYNZFNMF.js +457 -0
- package/chunks/{chunk-7XCKYNLO.js → chunk-XORZUR2H.js} +1 -1
- package/chunks/{chunk-2OQOQ2FZ.js → chunk-Y4HJRUFE.js} +20 -4
- package/chunks/{chunk-V36HVTUL.js → chunk-YAEJAZYW.js} +21 -18
- package/chunks/{chunk-XA2O4WXA.js → chunk-YYS2H33A.js} +169 -15
- package/chunks/{codex-models-NHQW45SO.js → codex-models-Y457ND6Q.js} +9 -3
- package/chunks/{commands-N6GN5SBF.js → commands-PBDAOZKA.js} +7019 -5937
- package/chunks/{db-5IWEJUYV.js → db-TPSI552V.js} +3 -3
- package/chunks/deep-review-DKED6EN5.js +81 -0
- package/chunks/{dist-L3XJWWZ6.js → dist-4LGRIBOC.js} +209 -114
- package/chunks/{dist-FSS7MBDF.js → dist-7Q5A6TQM.js} +12 -2
- package/chunks/{dist-KEULE5IQ.js → dist-LNXGATR2.js} +2 -2
- package/chunks/{dist-XZB53PS5.js → dist-M2X367VW.js} +2 -2
- package/chunks/{dist-BS4CY4RG.js → dist-RLPNLRZD.js} +79 -1
- package/chunks/eval-runner-RZKDOQ2G.js +30 -0
- package/chunks/{exploit-agent-KW4EB7BE.js → exploit-agent-RMFYNG7H.js} +1 -1
- package/chunks/{exploit-autoclimb-CXJ2ERRO.js → exploit-autoclimb-2A2PQI4E.js} +1 -1
- package/chunks/{exploit-climb-TJWLY4XC.js → exploit-climb-DEJ232QN.js} +2 -2
- package/chunks/{fix-UUKPXCFB.js → fix-JGBW7XUN.js} +12 -6
- package/chunks/{harness-C5HOKIEV.js → harness-CBOLMUHB.js} +3 -3
- package/chunks/{hunt-scan-KZZ444V2.js → hunt-scan-MIRMABY4.js} +17 -13
- package/chunks/{kernel-primitive-Z77IR7YS.js → kernel-primitive-NAPDMJC7.js} +2 -2
- package/chunks/{kernel-vm-runner-JW77P276.js → kernel-vm-runner-PR5OSDQ2.js} +2 -2
- package/chunks/managed-operation-YNQQEA5V.js +59 -0
- package/chunks/{memsafety-scan-FCPAXIZE.js → memsafety-scan-RPS446CD.js} +2 -2
- package/chunks/{native-loop-P6P5GYVJ.js → native-loop-CGOJMNHV.js} +19 -9
- package/chunks/orchestrate-5NK34H3W.js +62 -0
- package/chunks/{prepare-KYHGFWCJ.js → prepare-YVNENPNL.js} +6 -2
- package/chunks/{process-N245QIQG.js → process-MJYJ2K5E.js} +2 -1
- package/chunks/remote-workflow-runtime-RGQ5ECMN.js +96 -0
- package/chunks/{run-GAFKBCTN.js → run-VH4OIDKF.js} +1919 -4817
- package/chunks/{runtime-XFWJTBUO.js → runtime-4TAJFVF7.js} +7 -1
- package/chunks/{runtime-H7YBR5IZ.js → runtime-HWL6HG7A.js} +10 -4
- package/chunks/{scan-stream-BMOCPKQX.js → scan-stream-HJAZJONE.js} +7 -1
- package/chunks/{scope-QWY4VQZ7.js → scope-Q5PIX2IS.js} +2 -2
- package/chunks/service-plugins-QYFITODK.js +63 -0
- package/chunks/{session-store-XIDFGG7K.js → session-store-2FEIZGSV.js} +6 -4
- package/chunks/{variant-candidates-7D4F5E4H.js → variant-candidates-TX5V6XOT.js} +9 -3
- package/chunks/{web-recon-prepass-VXXBPDAV.js → web-recon-prepass-6HOUGF7Q.js} +11 -6
- package/chunks/workflow-runtime-ZEEBJFQG.js +64 -0
- package/dashboard/apple-touch-icon.png +0 -0
- package/dashboard/assets/abnfDiagram-VCTEODGH-BuvumFYR.js +1 -0
- package/dashboard/assets/arc-DZzWDuCG.js +1 -0
- package/dashboard/assets/architecture-7GRP2DOG-DK9bv1Md.js +1 -0
- package/dashboard/assets/architectureDiagram-5GKGNRK7-Bw5el_EL.js +36 -0
- package/dashboard/assets/array-BifhSqXX.js +1 -0
- package/dashboard/assets/blockDiagram-I7D4REHJ-BjVLEApp.js +129 -0
- package/dashboard/assets/c4Diagram-7LVT6UL2-B9nNzx2J.js +38 -0
- package/dashboard/assets/channel-Ck17eKdX.js +1 -0
- package/dashboard/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- package/dashboard/assets/chunk-4HAMMTFA-y72Q95nW.js +62 -0
- package/dashboard/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- package/dashboard/assets/chunk-75Z2AOVW-BOhrOGMO.js +2 -0
- package/dashboard/assets/chunk-DU6HZSFF-B0EhAaNb.js +127 -0
- package/dashboard/assets/chunk-F27PBJKO-BR-hiDvM.js +1 -0
- package/dashboard/assets/chunk-FOHPRMQF-DHwB1DNv.js +161 -0
- package/dashboard/assets/chunk-GMAD6QVW-H15vJYXg.js +72 -0
- package/dashboard/assets/chunk-GVQU2GXP-BRndps0o.js +1 -0
- package/dashboard/assets/chunk-IMKFNOWR-CIzC_OWx.js +231 -0
- package/dashboard/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- package/dashboard/assets/chunk-L3NEJ4N5-NR4P1Vko.js +1 -0
- package/dashboard/assets/chunk-OSK3NFVY-DtjTDf8S.js +10 -0
- package/dashboard/assets/chunk-P2QGCYS3-CYT11Qjx.js +1 -0
- package/dashboard/assets/chunk-POPQ4Y6H-Duj2Ygwu.js +1 -0
- package/dashboard/assets/chunk-PWAF6VOD-z4p5Tf_y.js +1 -0
- package/dashboard/assets/chunk-SHT3W25Y-DYeLYLS3.js +168 -0
- package/dashboard/assets/chunk-SVP7TREG-BKowxCVI.js +88 -0
- package/dashboard/assets/chunk-TICWLB2K-GDAwEFlH.js +206 -0
- package/dashboard/assets/chunk-XXDRQBXY-DRD79Mjx.js +1 -0
- package/dashboard/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- package/dashboard/assets/classDiagram-ZZMXUADV-BaeZA6LE.js +1 -0
- package/dashboard/assets/classDiagram-v2-VYDZK3BY-BaeZA6LE.js +1 -0
- package/dashboard/assets/common-vnzd7UOF.js +5 -0
- package/dashboard/assets/cose-bilkent-JH36ORCC-GqCLsRyf.js +1 -0
- package/dashboard/assets/cynefin-OW5HDTMX-CJN9YwWN.js +1 -0
- package/dashboard/assets/cynefinDiagram-5FMLGOSQ-DVJHCYQY.js +62 -0
- package/dashboard/assets/cytoscape.esm-CGd-uY3x.js +321 -0
- package/dashboard/assets/dagre-GXQ25YYZ-BMCrrJJR.js +4 -0
- package/dashboard/assets/dagre-PrKaheQc.js +1 -0
- package/dashboard/assets/defaultLocale-BFoDCU3G.js +1 -0
- package/dashboard/assets/diagram-S7CK7UJ4-CLYDdOEm.js +30 -0
- package/dashboard/assets/diagram-UQ7AKVKN-CnPuAZK7.js +41 -0
- package/dashboard/assets/diagram-VSXAHHWV-Cr2I2DnG.js +3 -0
- package/dashboard/assets/diagram-VX7I27RA-_mGW1wpp.js +24 -0
- package/dashboard/assets/diagram-Z3DM3KII-igybB0l9.js +24 -0
- package/dashboard/assets/dist-Bb7_-x5y.js +1 -0
- package/dashboard/assets/ebnfDiagram-PWID7BFC-CVCICf2R.js +1 -0
- package/dashboard/assets/erDiagram-RLTQ6QDP-CRjnfgxj.js +99 -0
- package/dashboard/assets/eventmodeling-NTZA5JFV-po5oDydv.js +1 -0
- package/dashboard/assets/flowDiagram-HODETNUW-TSV9s7q4.js +1 -0
- package/dashboard/assets/ganttDiagram-EL5Y4UJY-DppD7sD7.js +292 -0
- package/dashboard/assets/gitGraph-4MIJSDKK-Tw0vNnQc.js +1 -0
- package/dashboard/assets/gitGraphDiagram-WWUBYQGX-Cpf96-z3.js +106 -0
- package/dashboard/assets/graphlib-DS17s2tU.js +1 -0
- package/dashboard/assets/info-A6RAGUB7-DLyt9arx.js +1 -0
- package/dashboard/assets/infoDiagram-27XIBGKW-B6_piSNS.js +2 -0
- package/dashboard/assets/init-C-OQMol4.js +1 -0
- package/dashboard/assets/ishikawaDiagram-5VMMS53U-DvMO0B0X.js +70 -0
- package/dashboard/assets/journeyDiagram-3NMN7TZE-f7ZK98b6.js +139 -0
- package/dashboard/assets/kanban-definition-UXKFOSKX-Dso0nAxL.js +89 -0
- package/dashboard/assets/katex-CXMH3UgJ.js +257 -0
- package/dashboard/assets/line-VcKaFG0G.js +1 -0
- package/dashboard/assets/linear-D7YnpiWh.js +1 -0
- package/dashboard/assets/mermaid-parser.core-DriL2hrV.js +7 -0
- package/dashboard/assets/mermaid.core-DJWYDh_P.js +44 -0
- package/dashboard/assets/mindmap-definition-YA3MSWOX-DZsoIeac.js +96 -0
- package/dashboard/assets/operations-BIaUSnYv.js +121 -0
- package/dashboard/assets/operations-BeCdkT4S.css +1 -0
- package/dashboard/assets/ordinal-BDEzSJ7C.js +1 -0
- package/dashboard/assets/packet-AYTQ26CC-BQBIXKxk.js +1 -0
- package/dashboard/assets/path-fybaL0A-.js +1 -0
- package/dashboard/assets/pegDiagram-XKGWAZYB-B0-lslgW.js +1 -0
- package/dashboard/assets/pie-WAS4IAKB-DoY6dwQs.js +1 -0
- package/dashboard/assets/pieDiagram-E7YTZNPT-ElmdRjp5.js +39 -0
- package/dashboard/assets/quadrantDiagram-AXDQQJYC-Ci1h98Q2.js +7 -0
- package/dashboard/assets/radar-RG4KPBEZ-DHod2WgB.js +1 -0
- package/dashboard/assets/railroad-74A4TZTK-90JxB4xV.js +1 -0
- package/dashboard/assets/railroad-abnf-HS5TGJTU-BkXGl114.js +1 -0
- package/dashboard/assets/railroad-ebnf-LZEXJU2U-QVAERn2C.js +1 -0
- package/dashboard/assets/railroad-peg-WCYAUIDC-B1-PM01z.js +1 -0
- package/dashboard/assets/railroadDiagram-O6MQD6OU-k3190A6L.js +1 -0
- package/dashboard/assets/requirementDiagram-BXWQKSXE-C3-eR7I5.js +84 -0
- package/dashboard/assets/rough.esm-Dy-Kn_BL.js +1 -0
- package/dashboard/assets/sankeyDiagram-P5KCCOFB-Bc86pi8w.js +40 -0
- package/dashboard/assets/sequenceDiagram-WJ2MYXX4-Bv_MvEmz.js +162 -0
- package/dashboard/assets/sizeCapture-INFHLROL-B0uUizjq.js +1 -0
- package/dashboard/assets/src-D7Dqq5yP.js +1 -0
- package/dashboard/assets/stateDiagram-D77RDMKH-Bq_GVmeW.js +1 -0
- package/dashboard/assets/stateDiagram-v2-MP3YSRHH-z1XRaftz.js +1 -0
- package/dashboard/assets/swimlanes-42K2YHIH-q0iK-e0P.js +1 -0
- package/dashboard/assets/swimlanesDiagram-VR7AAH4N-C9L6XBQj.js +8 -0
- package/dashboard/assets/timeline-definition-24CTP7MA-CNfF07hM.js +120 -0
- package/dashboard/assets/treeView-Q6P3EWNA-m629ePnS.js +1 -0
- package/dashboard/assets/treemap-WGGIJYW6-D9Cb2ETv.js +1 -0
- package/dashboard/assets/vennDiagram-4TSXK5OY-v4dKF-FE.js +34 -0
- package/dashboard/assets/wardley-WFR3VGLG-DkwH_nRe.js +1 -0
- package/dashboard/assets/wardleyDiagram-VM6X3IG4-3ImohVu4.js +78 -0
- package/dashboard/assets/xychartDiagram-S5SC5T6Z-QJu3dm7K.js +7 -0
- package/dashboard/assets/zero-connect-BGDY0PMZ.webp +0 -0
- package/dashboard/assets/zero-curious-DovTLOs8.webp +0 -0
- package/dashboard/assets/zero-peek-Dzm3MT3X.png +0 -0
- package/dashboard/assets/zero-planning-CBeHDrG_.webp +0 -0
- package/dashboard/favicon.ico +0 -0
- package/dashboard/favicon.svg +10 -0
- package/dashboard/index.html +5 -3
- package/package.json +1 -1
- package/chunks/chunk-BCSRRQG5.js +0 -3514
- package/chunks/chunk-BOUGKEKA.js +0 -193
- package/chunks/chunk-GHWODZMR.js +0 -16
- package/chunks/eval-runner-EMEGF24P.js +0 -26
- package/chunks/orchestrate-DWWUT5RC.js +0 -58
- package/dashboard/assets/bot-Bg1Tp9_d.js +0 -1
- package/dashboard/assets/dist-B_dDCPbu.js +0 -1
- package/dashboard/assets/findings-page-mayGhNx6.js +0 -5
- package/dashboard/assets/format--susmHX7.js +0 -1
- package/dashboard/assets/input-TuVPR3Zl.js +0 -1
- package/dashboard/assets/jsx-runtime-MGseXjj5.js +0 -3
- package/dashboard/assets/live-page-HWWHX99U.js +0 -1
- package/dashboard/assets/meta-tile-ckHo-w7k.js +0 -1
- package/dashboard/assets/operations-BxZ0OoXO.js +0 -10
- package/dashboard/assets/operations-CMFVrXEe.css +0 -2
- package/dashboard/assets/operations-app-D3d35rgo.js +0 -46
- package/dashboard/assets/overview-page-W_4IteEr.js +0 -1
- package/dashboard/assets/page-header-Bw9AJgBV.js +0 -1
- package/dashboard/assets/scans-page-CgzVXHnn.js +0 -1
- package/dashboard/assets/siren-KQj8QAnJ.js +0 -1
- package/dashboard/assets/table-8EDklulN.js +0 -1
- package/dashboard/assets/tabs-CFHPOwg8.js +0 -1
|
@@ -0,0 +1,733 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __0CreateRequire } from "node:module";
|
|
3
|
+
const require = __0CreateRequire(import.meta.url);
|
|
4
|
+
import {
|
|
5
|
+
captureObservation,
|
|
6
|
+
createEvolvedFinder,
|
|
7
|
+
loadAppsecFinderLenses
|
|
8
|
+
} from "./chunk-4HGGNBKK.js";
|
|
9
|
+
import {
|
|
10
|
+
loadEvolutionConfigFile
|
|
11
|
+
} from "./chunk-AAA4PRWK.js";
|
|
12
|
+
import {
|
|
13
|
+
resolveOsecRunStorage,
|
|
14
|
+
writeOsecRunReport
|
|
15
|
+
} from "./chunk-T7ZEVY5G.js";
|
|
16
|
+
import {
|
|
17
|
+
ScanCostLedger
|
|
18
|
+
} from "./chunk-CP6HANBE.js";
|
|
19
|
+
import {
|
|
20
|
+
eventBus
|
|
21
|
+
} from "./chunk-A5QO7N3S.js";
|
|
22
|
+
|
|
23
|
+
// packages/cli/src/commands/deep-review.ts
|
|
24
|
+
import { readFileSync, realpathSync, statSync } from "node:fs";
|
|
25
|
+
import { resolve, join, sep, relative } from "node:path";
|
|
26
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
27
|
+
var DEEP_REVIEW_FILE_CAP = 5e3;
|
|
28
|
+
var DEFAULT_MAX_CANDIDATES = 8;
|
|
29
|
+
function defaultMaxCandidates() {
|
|
30
|
+
const raw = process.env["ZERO_DEEP_REVIEW_MAX_CANDIDATES"];
|
|
31
|
+
if (!raw) return DEFAULT_MAX_CANDIDATES;
|
|
32
|
+
const n = Number.parseInt(raw, 10);
|
|
33
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_MAX_CANDIDATES;
|
|
34
|
+
}
|
|
35
|
+
function scaleMaxCandidates(fileCount) {
|
|
36
|
+
if (fileCount > 2e3) return 20;
|
|
37
|
+
if (fileCount > 500) return 16;
|
|
38
|
+
if (fileCount > 200) return 12;
|
|
39
|
+
return DEFAULT_MAX_CANDIDATES;
|
|
40
|
+
}
|
|
41
|
+
var DEFAULT_ATTEMPTS = 1;
|
|
42
|
+
function defaultAttempts() {
|
|
43
|
+
const raw = process.env["ZERO_DEEP_REVIEW_ATTEMPTS"];
|
|
44
|
+
if (!raw) return DEFAULT_ATTEMPTS;
|
|
45
|
+
const n = Number.parseInt(raw, 10);
|
|
46
|
+
return Number.isFinite(n) && n > 0 ? n : DEFAULT_ATTEMPTS;
|
|
47
|
+
}
|
|
48
|
+
function defaultModels() {
|
|
49
|
+
const raw = process.env["ZERO_DEEP_REVIEW_MODELS"];
|
|
50
|
+
if (!raw) return void 0;
|
|
51
|
+
const models = raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
52
|
+
return models.length > 0 ? models : void 0;
|
|
53
|
+
}
|
|
54
|
+
var DEFAULT_CONCURRENCY = 8;
|
|
55
|
+
var genericFinderLenses = [
|
|
56
|
+
{
|
|
57
|
+
id: "memory-safety",
|
|
58
|
+
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."
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "input-validation",
|
|
62
|
+
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."
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "auth-logic",
|
|
66
|
+
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."
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "secrets-crypto",
|
|
70
|
+
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."
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: "cross-component",
|
|
74
|
+
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."
|
|
75
|
+
}
|
|
76
|
+
];
|
|
77
|
+
function lensSetDigest(lenses) {
|
|
78
|
+
return createHash("sha256").update(JSON.stringify(lenses.map(({ id, challengeHint }) => ({ id, challengeHint })))).digest("hex");
|
|
79
|
+
}
|
|
80
|
+
function createDefaultFinderLenses() {
|
|
81
|
+
return [...genericFinderLenses, ...loadAppsecFinderLenses()];
|
|
82
|
+
}
|
|
83
|
+
var defaultFinderLenses = createDefaultFinderLenses();
|
|
84
|
+
var defaultVerifyLenses = [
|
|
85
|
+
{
|
|
86
|
+
id: "reachability",
|
|
87
|
+
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."
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: "completeness",
|
|
91
|
+
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."
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: "novelty-known-issue",
|
|
95
|
+
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."
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: "scope",
|
|
99
|
+
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."
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: "deployment-context",
|
|
103
|
+
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)."
|
|
104
|
+
}
|
|
105
|
+
];
|
|
106
|
+
function selectProfileLenses(profile, sets, defaultFinderSnapshot = defaultFinderLenses) {
|
|
107
|
+
switch ((profile ?? "").trim().toLowerCase()) {
|
|
108
|
+
case "evm-onchain":
|
|
109
|
+
return { finderLenses: sets.evmFinderLenses, verifyLenses: sets.evmVerifyLenses, matchedProfile: "evm-onchain" };
|
|
110
|
+
case "solana-onchain":
|
|
111
|
+
return { finderLenses: sets.solanaFinderLenses, verifyLenses: sets.solanaVerifyLenses, matchedProfile: "solana-onchain" };
|
|
112
|
+
case "cardano-onchain":
|
|
113
|
+
return { finderLenses: sets.cardanoFinderLenses, verifyLenses: sets.cardanoVerifyLenses, matchedProfile: "cardano-onchain" };
|
|
114
|
+
case "cairo-onchain":
|
|
115
|
+
return { finderLenses: sets.cairoFinderLenses, verifyLenses: sets.cairoVerifyLenses, matchedProfile: "cairo-onchain" };
|
|
116
|
+
case "move-onchain":
|
|
117
|
+
return { finderLenses: sets.moveFinderLenses, verifyLenses: sets.moveVerifyLenses, matchedProfile: "move-onchain" };
|
|
118
|
+
default:
|
|
119
|
+
return { finderLenses: defaultFinderSnapshot, verifyLenses: defaultVerifyLenses, matchedProfile: "default" };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
var NATIVE_SOURCE_EXTS = /* @__PURE__ */ new Set([
|
|
123
|
+
"c",
|
|
124
|
+
"h",
|
|
125
|
+
"cc",
|
|
126
|
+
"cpp",
|
|
127
|
+
"cxx",
|
|
128
|
+
"hpp",
|
|
129
|
+
"hh",
|
|
130
|
+
"hxx",
|
|
131
|
+
"rs"
|
|
132
|
+
]);
|
|
133
|
+
var MANAGED_WEB_EXTS = /* @__PURE__ */ new Set([
|
|
134
|
+
"cs",
|
|
135
|
+
"csproj",
|
|
136
|
+
"sln",
|
|
137
|
+
"vbproj",
|
|
138
|
+
"fsproj",
|
|
139
|
+
// .NET / C#
|
|
140
|
+
"ts",
|
|
141
|
+
"tsx",
|
|
142
|
+
"js",
|
|
143
|
+
"jsx",
|
|
144
|
+
"mjs",
|
|
145
|
+
"cjs",
|
|
146
|
+
"vue",
|
|
147
|
+
"svelte",
|
|
148
|
+
// JS/TS front+back
|
|
149
|
+
"java",
|
|
150
|
+
"kt",
|
|
151
|
+
"kts",
|
|
152
|
+
"scala",
|
|
153
|
+
// JVM
|
|
154
|
+
"py",
|
|
155
|
+
"rb",
|
|
156
|
+
"php",
|
|
157
|
+
"go",
|
|
158
|
+
// scripting / Go
|
|
159
|
+
"html",
|
|
160
|
+
"htm",
|
|
161
|
+
"cshtml",
|
|
162
|
+
"razor",
|
|
163
|
+
"aspx",
|
|
164
|
+
"jsp"
|
|
165
|
+
// web templates
|
|
166
|
+
]);
|
|
167
|
+
function fileExt(path) {
|
|
168
|
+
const base = path.split(/[/\\]+/).pop() ?? path;
|
|
169
|
+
const dot = base.lastIndexOf(".");
|
|
170
|
+
return dot > 0 ? base.slice(dot + 1).toLowerCase() : "";
|
|
171
|
+
}
|
|
172
|
+
function detectStack(files) {
|
|
173
|
+
let native = 0;
|
|
174
|
+
let managed = 0;
|
|
175
|
+
for (const f of files) {
|
|
176
|
+
const ext = fileExt(f);
|
|
177
|
+
if (NATIVE_SOURCE_EXTS.has(ext)) native++;
|
|
178
|
+
else if (MANAGED_WEB_EXTS.has(ext)) managed++;
|
|
179
|
+
}
|
|
180
|
+
if (native === 0 && managed === 0) return "unknown";
|
|
181
|
+
if (native === 0) return "managed-web";
|
|
182
|
+
if (managed === 0) return "native";
|
|
183
|
+
return "mixed";
|
|
184
|
+
}
|
|
185
|
+
var MEMORY_SAFETY_LENS_ID = "memory-safety";
|
|
186
|
+
function selectDefaultFinderLensesForStack(files, defaultFinder) {
|
|
187
|
+
if (detectStack(files) === "managed-web") {
|
|
188
|
+
return defaultFinder.filter((l) => l.id !== MEMORY_SAFETY_LENS_ID);
|
|
189
|
+
}
|
|
190
|
+
return defaultFinder;
|
|
191
|
+
}
|
|
192
|
+
var EVM_EXCLUDE_DIR_SEGMENTS = /* @__PURE__ */ new Set([
|
|
193
|
+
"test",
|
|
194
|
+
"tests",
|
|
195
|
+
"mock",
|
|
196
|
+
"mocks",
|
|
197
|
+
"script",
|
|
198
|
+
"scripts",
|
|
199
|
+
// Foundry deploy scripts
|
|
200
|
+
"lib",
|
|
201
|
+
// Foundry vendored deps (forge-std / openzeppelin / solmate …)
|
|
202
|
+
"node_modules"
|
|
203
|
+
// Hardhat/JS vendored deps (also skipped by the core walker)
|
|
204
|
+
]);
|
|
205
|
+
function isNonProtocolEvmPath(absPath, scopeRoot) {
|
|
206
|
+
const rel = relative(scopeRoot, absPath);
|
|
207
|
+
if (rel === "" || rel.startsWith("..")) return false;
|
|
208
|
+
const segments = rel.split(/[/\\]+/).filter(Boolean);
|
|
209
|
+
if (segments.length === 0) return false;
|
|
210
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
211
|
+
if (EVM_EXCLUDE_DIR_SEGMENTS.has(segments[i].toLowerCase())) return true;
|
|
212
|
+
}
|
|
213
|
+
const fileName = segments[segments.length - 1].toLowerCase();
|
|
214
|
+
if (fileName.endsWith(".t.sol")) return true;
|
|
215
|
+
if (fileName.includes(".test.") || fileName.includes(".spec.")) return true;
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
function topLevelModule(absPath, scopeRoot) {
|
|
219
|
+
const rel = relative(scopeRoot, absPath);
|
|
220
|
+
if (rel === "" || rel.startsWith("..")) return ".";
|
|
221
|
+
const segments = rel.split(/[/\\]+/).filter(Boolean);
|
|
222
|
+
return segments.length > 1 ? segments[0] : ".";
|
|
223
|
+
}
|
|
224
|
+
function selectCandidatesModuleSpread(entries, scopeRoot, maxCandidates) {
|
|
225
|
+
const cmp = (a, b) => b.size - a.size || a.p.localeCompare(b.p);
|
|
226
|
+
const byModule = /* @__PURE__ */ new Map();
|
|
227
|
+
for (const e of entries) {
|
|
228
|
+
const key = topLevelModule(e.p, scopeRoot);
|
|
229
|
+
let group = byModule.get(key);
|
|
230
|
+
if (!group) byModule.set(key, group = []);
|
|
231
|
+
group.push(e);
|
|
232
|
+
}
|
|
233
|
+
const groups = [...byModule.values()];
|
|
234
|
+
for (const g of groups) g.sort(cmp);
|
|
235
|
+
groups.sort((a, b) => cmp(a[0], b[0]));
|
|
236
|
+
const cap = Math.max(1, maxCandidates);
|
|
237
|
+
const out = [];
|
|
238
|
+
for (let round = 0; out.length < cap; round++) {
|
|
239
|
+
let advanced = false;
|
|
240
|
+
for (const g of groups) {
|
|
241
|
+
if (round >= g.length) continue;
|
|
242
|
+
out.push(g[round].p);
|
|
243
|
+
advanced = true;
|
|
244
|
+
if (out.length >= cap) break;
|
|
245
|
+
}
|
|
246
|
+
if (!advanced) break;
|
|
247
|
+
}
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
250
|
+
function enumerateDeepReviewCandidates(scopeRoot, helpers, opts) {
|
|
251
|
+
const fileCap = opts.fileCap ?? DEEP_REVIEW_FILE_CAP;
|
|
252
|
+
const exclude = opts.exclude ?? (() => false);
|
|
253
|
+
const fileSize = helpers.fileSize ?? ((p) => {
|
|
254
|
+
try {
|
|
255
|
+
return statSync(p).size;
|
|
256
|
+
} catch {
|
|
257
|
+
return 0;
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
const totalFiles = helpers.countScopeFilesUpTo(scopeRoot, fileCap);
|
|
261
|
+
const overCap = totalFiles > fileCap;
|
|
262
|
+
const files = helpers.collectScopeFiles(scopeRoot, { maxFiles: fileCap });
|
|
263
|
+
const entries = files.filter((p) => !exclude(p)).map((p) => ({ p, size: fileSize(p) }));
|
|
264
|
+
const candidates = selectCandidatesModuleSpread(entries, scopeRoot, opts.maxCandidates);
|
|
265
|
+
return { candidates, totalFiles, overCap };
|
|
266
|
+
}
|
|
267
|
+
async function runDeepReview(opts) {
|
|
268
|
+
const defaultFinderSnapshot = createDefaultFinderLenses();
|
|
269
|
+
const evolvedFinder = opts.evolutionConfig ? await createEvolvedFinder(opts.evolutionConfig, `deep-review-${randomUUID()}`) : void 0;
|
|
270
|
+
const {
|
|
271
|
+
runHuntScan,
|
|
272
|
+
makeMultiLensVerifier,
|
|
273
|
+
prepare,
|
|
274
|
+
collectScopeFiles,
|
|
275
|
+
countScopeFilesUpTo,
|
|
276
|
+
evmFinderLenses,
|
|
277
|
+
evmVerifyLenses,
|
|
278
|
+
solanaFinderLenses,
|
|
279
|
+
solanaVerifyLenses,
|
|
280
|
+
cardanoFinderLenses,
|
|
281
|
+
cardanoVerifyLenses,
|
|
282
|
+
cairoFinderLenses,
|
|
283
|
+
cairoVerifyLenses,
|
|
284
|
+
moveFinderLenses,
|
|
285
|
+
moveVerifyLenses
|
|
286
|
+
} = await import("./dist-4LGRIBOC.js");
|
|
287
|
+
opts.signal?.throwIfAborted();
|
|
288
|
+
const costLedger = opts.costLedger ?? new ScanCostLedger();
|
|
289
|
+
const scanStartedAt = Date.now();
|
|
290
|
+
const log = opts.log ?? (() => {
|
|
291
|
+
});
|
|
292
|
+
const runtime = opts.runtime ?? "api";
|
|
293
|
+
const explicitMaxCandidates = opts.maxCandidates;
|
|
294
|
+
const hasEnvMaxOverride = !!process.env["ZERO_DEEP_REVIEW_MAX_CANDIDATES"];
|
|
295
|
+
const baseMaxCandidates = explicitMaxCandidates ?? defaultMaxCandidates();
|
|
296
|
+
if (evolvedFinder && opts.models?.length) throw new Error("--models selects native finders and cannot be combined with --evolution-config");
|
|
297
|
+
const models = evolvedFinder ? ["evolved-source"] : opts.models ?? defaultModels();
|
|
298
|
+
const attemptsPerCandidate = opts.attemptsPerCandidate ?? defaultAttempts();
|
|
299
|
+
const prepared = await prepare(opts.target, "source-code", { ...opts.timeoutMs ? { timeout: opts.timeoutMs } : {} }, (e) => {
|
|
300
|
+
if (e.message) log(`[deep-review:source] ${e.message}`);
|
|
301
|
+
});
|
|
302
|
+
const sourceRoot = resolve(prepared.resolvedTarget);
|
|
303
|
+
let scopeRoot = sourceRoot;
|
|
304
|
+
if (opts.subsystem && opts.subsystem.trim() !== "") {
|
|
305
|
+
const scoped = resolve(join(sourceRoot, opts.subsystem));
|
|
306
|
+
if (scoped !== sourceRoot && !scoped.startsWith(sourceRoot + sep)) {
|
|
307
|
+
prepared.cleanup();
|
|
308
|
+
throw new Error(`--subsystem '${opts.subsystem}' escapes the source tree`);
|
|
309
|
+
}
|
|
310
|
+
try {
|
|
311
|
+
const realRoot = realpathSync(sourceRoot);
|
|
312
|
+
const realScoped = realpathSync(scoped);
|
|
313
|
+
if (realScoped !== realRoot && !realScoped.startsWith(realRoot + sep)) {
|
|
314
|
+
throw new Error(`--subsystem '${opts.subsystem}' escapes the source tree`);
|
|
315
|
+
}
|
|
316
|
+
scopeRoot = scoped;
|
|
317
|
+
} catch (error) {
|
|
318
|
+
prepared.cleanup();
|
|
319
|
+
throw error;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
const isEvmProfile = (opts.profile ?? "").trim().toLowerCase() === "evm-onchain";
|
|
324
|
+
const totalFiles = countScopeFilesUpTo(scopeRoot, DEEP_REVIEW_FILE_CAP);
|
|
325
|
+
const maxCandidates = explicitMaxCandidates ?? (hasEnvMaxOverride ? baseMaxCandidates : scaleMaxCandidates(totalFiles));
|
|
326
|
+
const overCap = totalFiles > DEEP_REVIEW_FILE_CAP;
|
|
327
|
+
if (explicitMaxCandidates === void 0 && maxCandidates !== baseMaxCandidates) {
|
|
328
|
+
log(`[deep-review] auto-scaled candidate cap from ${baseMaxCandidates} to ${maxCandidates} (${totalFiles} scope files)`);
|
|
329
|
+
}
|
|
330
|
+
let lanes = null;
|
|
331
|
+
let allocateAcrossLanes;
|
|
332
|
+
if (opts.useThreatModel) {
|
|
333
|
+
const threatModule = await import("./dist-4LGRIBOC.js");
|
|
334
|
+
allocateAcrossLanes = threatModule.allocateCandidatesAcrossLanes;
|
|
335
|
+
const rtType = runtime !== "auto" ? runtime : "api";
|
|
336
|
+
const plannerRuntime = opts.nativeRuntime ? {
|
|
337
|
+
type: opts.nativeRuntime.type,
|
|
338
|
+
isAvailable: () => opts.nativeRuntime.isAvailable(),
|
|
339
|
+
execute: async (prompt, context) => {
|
|
340
|
+
const budgeted = threatModule.budgetNativeRuntime(opts.nativeRuntime, costLedger, opts.signal, opts.costCeilingUsd, opts.plan);
|
|
341
|
+
const started = Date.now();
|
|
342
|
+
const result = await budgeted.executeNative(context?.systemPrompt ?? "", [{ role: "user", content: [{ type: "text", text: prompt }] }], [], void 0, opts.signal);
|
|
343
|
+
return { output: result.content.filter((block) => block.type === "text").map((block) => block.type === "text" ? block.text : "").join("\n"), exitCode: result.stopReason === "error" ? 1 : 0, timedOut: false, durationMs: Date.now() - started };
|
|
344
|
+
}
|
|
345
|
+
} : threatModule.createRuntime({
|
|
346
|
+
type: rtType,
|
|
347
|
+
timeout: opts.timeoutMs ?? 6e5,
|
|
348
|
+
model: models?.[0]
|
|
349
|
+
});
|
|
350
|
+
const trackedPlanner = !opts.nativeRuntime && opts.plan ? {
|
|
351
|
+
...plannerRuntime,
|
|
352
|
+
type: plannerRuntime.type,
|
|
353
|
+
isAvailable: () => plannerRuntime.isAvailable(),
|
|
354
|
+
execute: async (prompt, context) => {
|
|
355
|
+
opts.signal?.throwIfAborted();
|
|
356
|
+
const result = await plannerRuntime.execute(prompt, { ...context, signal: opts.signal });
|
|
357
|
+
if (result.usageByModel?.length) for (const bucket of result.usageByModel) costLedger.add(bucket.usage, bucket.model);
|
|
358
|
+
else if (result.usage) costLedger.add(result.usage, models?.[0]);
|
|
359
|
+
else costLedger.markUnpricedUsage();
|
|
360
|
+
return result;
|
|
361
|
+
}
|
|
362
|
+
} : plannerRuntime;
|
|
363
|
+
lanes = await threatModule.runThreatModelPlanner(sourceRoot, trackedPlanner, log);
|
|
364
|
+
if (!lanes || lanes.length === 0) {
|
|
365
|
+
log("[threat-model] no lanes produced \u2014 continuing with default selection");
|
|
366
|
+
} else {
|
|
367
|
+
log(`[threat-model] planner returned ${lanes.length} lane(s) \u2014 will allocate candidates across boundaries`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (overCap) {
|
|
371
|
+
return {
|
|
372
|
+
exitCode: 2,
|
|
373
|
+
result: {
|
|
374
|
+
mode: "deep_review",
|
|
375
|
+
source: sourceRoot,
|
|
376
|
+
subsystem: opts.subsystem ?? null,
|
|
377
|
+
scope_files: totalFiles,
|
|
378
|
+
note: `scope exceeds the ${DEEP_REVIEW_FILE_CAP}-file review cap \u2014 narrow it with --subsystem <path> to a specific directory.`
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
const { candidates: initialCandidates } = enumerateDeepReviewCandidates(
|
|
383
|
+
scopeRoot,
|
|
384
|
+
{ collectScopeFiles, countScopeFilesUpTo },
|
|
385
|
+
{
|
|
386
|
+
maxCandidates,
|
|
387
|
+
fileCap: DEEP_REVIEW_FILE_CAP,
|
|
388
|
+
...isEvmProfile ? { exclude: (p) => isNonProtocolEvmPath(p, scopeRoot) } : {}
|
|
389
|
+
}
|
|
390
|
+
);
|
|
391
|
+
const candidatePaths = initialCandidates.slice();
|
|
392
|
+
if (lanes && lanes.length > 0 && allocateAcrossLanes) {
|
|
393
|
+
const allFiles = collectScopeFiles(scopeRoot, { maxFiles: DEEP_REVIEW_FILE_CAP });
|
|
394
|
+
const exclude = isEvmProfile ? (p) => isNonProtocolEvmPath(p, scopeRoot) : () => false;
|
|
395
|
+
const entries = allFiles.filter((p) => !exclude(p)).map((p) => {
|
|
396
|
+
try {
|
|
397
|
+
return { p, size: statSync(p).size };
|
|
398
|
+
} catch {
|
|
399
|
+
return { p, size: 0 };
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
const laneCandidates = allocateAcrossLanes(entries, lanes, scopeRoot, maxCandidates, log);
|
|
403
|
+
if (laneCandidates.length > 0) {
|
|
404
|
+
const prevModules = new Set(initialCandidates.map((p) => relative(scopeRoot, p).split(/[/\\]+/)[0]));
|
|
405
|
+
const laneModules = new Set(laneCandidates.map((p) => relative(scopeRoot, p).split(/[/\\]+/)[0]));
|
|
406
|
+
log(`[deep-review] threat-model re-allocated ${initialCandidates.length} \u2192 ${laneCandidates.length} candidate(s); modules ${prevModules.size} \u2192 ${laneModules.size}`);
|
|
407
|
+
candidatePaths.length = 0;
|
|
408
|
+
candidatePaths.push(...laneCandidates);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (candidatePaths.length === 0) {
|
|
412
|
+
return {
|
|
413
|
+
exitCode: 2,
|
|
414
|
+
result: {
|
|
415
|
+
mode: "deep_review",
|
|
416
|
+
source: sourceRoot,
|
|
417
|
+
subsystem: opts.subsystem ?? null,
|
|
418
|
+
scope_files: totalFiles,
|
|
419
|
+
candidates: 0,
|
|
420
|
+
note: "no reviewable source files found under the scope (empty tree or all filtered)."
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
const { finderLenses: selectedFinderLenses, verifyLenses, matchedProfile } = selectProfileLenses(opts.profile, {
|
|
425
|
+
evmFinderLenses,
|
|
426
|
+
evmVerifyLenses,
|
|
427
|
+
solanaFinderLenses,
|
|
428
|
+
solanaVerifyLenses,
|
|
429
|
+
cardanoFinderLenses,
|
|
430
|
+
cardanoVerifyLenses,
|
|
431
|
+
cairoFinderLenses,
|
|
432
|
+
cairoVerifyLenses,
|
|
433
|
+
moveFinderLenses,
|
|
434
|
+
moveVerifyLenses
|
|
435
|
+
}, defaultFinderSnapshot);
|
|
436
|
+
const finderLenses = matchedProfile === "default" ? selectDefaultFinderLensesForStack(candidatePaths, selectedFinderLenses) : selectedFinderLenses;
|
|
437
|
+
const lensDigest = lensSetDigest(finderLenses);
|
|
438
|
+
const scanId = randomUUID();
|
|
439
|
+
let sourceRevisionDigest = null;
|
|
440
|
+
let sourceFingerprintWarning;
|
|
441
|
+
try {
|
|
442
|
+
const files = [...candidatePaths].sort().map((path) => ({
|
|
443
|
+
path: relative(sourceRoot, path),
|
|
444
|
+
digest: createHash("sha256").update(readFileSync(path)).digest("hex")
|
|
445
|
+
}));
|
|
446
|
+
sourceRevisionDigest = `sha256:${createHash("sha256").update(JSON.stringify(files)).digest("hex")}`;
|
|
447
|
+
} catch (error) {
|
|
448
|
+
sourceFingerprintWarning = `learning feedback skipped: reviewed inputs could not be fingerprinted (${error instanceof Error ? error.message : String(error)})`;
|
|
449
|
+
log(`[deep-review] ${sourceFingerprintWarning}`);
|
|
450
|
+
}
|
|
451
|
+
log(
|
|
452
|
+
`[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)}`
|
|
453
|
+
);
|
|
454
|
+
const verify = makeMultiLensVerifier(verifyLenses, {
|
|
455
|
+
sourceRoot,
|
|
456
|
+
runtime,
|
|
457
|
+
...!evolvedFinder && models?.[0] ? { model: models[0] } : {},
|
|
458
|
+
// Every verify lens is a refute pass on `models[0]` — the same model the
|
|
459
|
+
// finders ran on. Passing the full finder set lets the cross-family
|
|
460
|
+
// selector move the refutation off ALL finder families when a second
|
|
461
|
+
// provider is configured (#661), and `log` records what it decided —
|
|
462
|
+
// including "it could not", which is the case worth knowing about.
|
|
463
|
+
...opts.costCeilingUsd !== void 0 ? { costCeilingUsd: opts.costCeilingUsd } : {},
|
|
464
|
+
costLedger,
|
|
465
|
+
...opts.plan ? { plan: opts.plan } : {},
|
|
466
|
+
...opts.signal ? { signal: opts.signal } : {},
|
|
467
|
+
...opts.nativeRuntime ? { nativeRuntime: opts.nativeRuntime, crossFamilyRefute: false } : {},
|
|
468
|
+
...!evolvedFinder && models && models.length > 0 ? { finderModels: models } : {},
|
|
469
|
+
...opts.quorum ? { quorum: opts.quorum } : {},
|
|
470
|
+
log
|
|
471
|
+
});
|
|
472
|
+
const res = await runHuntScan({
|
|
473
|
+
sourceRoot,
|
|
474
|
+
candidates: candidatePaths.map((path) => ({ path })),
|
|
475
|
+
lenses: finderLenses,
|
|
476
|
+
runtime,
|
|
477
|
+
...evolvedFinder ? { finder: (input) => evolvedFinder.find(sourceRoot, input) } : {},
|
|
478
|
+
concurrency: opts.concurrency ?? DEFAULT_CONCURRENCY,
|
|
479
|
+
...opts.costCeilingUsd !== void 0 ? { costCeilingUsd: opts.costCeilingUsd } : {},
|
|
480
|
+
costLedger,
|
|
481
|
+
...opts.plan ? { plan: opts.plan } : {},
|
|
482
|
+
...opts.signal ? { signal: opts.signal } : {},
|
|
483
|
+
...opts.nativeRuntime ? { nativeRuntime: opts.nativeRuntime } : {},
|
|
484
|
+
attemptsPerCandidate,
|
|
485
|
+
...models ? { models } : {},
|
|
486
|
+
verify,
|
|
487
|
+
log
|
|
488
|
+
});
|
|
489
|
+
const leads = res.confirmed;
|
|
490
|
+
if (sourceFingerprintWarning) res.warnings.push(sourceFingerprintWarning);
|
|
491
|
+
if (sourceRevisionDigest) {
|
|
492
|
+
try {
|
|
493
|
+
for (const gap of res.incompleteCoverage ?? []) {
|
|
494
|
+
captureObservation({
|
|
495
|
+
classHint: gap.lensId || "generic-finder-timeout",
|
|
496
|
+
sinkPattern: "",
|
|
497
|
+
exampleFileLine: gap.file,
|
|
498
|
+
whyMissed: `finder ${gap.reason}; budget ${gap.budgetMs}ms`,
|
|
499
|
+
source: "incomplete-coverage",
|
|
500
|
+
scanId,
|
|
501
|
+
sourceRevisionDigest,
|
|
502
|
+
consent: false,
|
|
503
|
+
detectorLensId: gap.lensId || void 0,
|
|
504
|
+
detectorLensVersionDigest: gap.lensVersionDigest
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
for (const dropped of res.dropped ?? []) {
|
|
508
|
+
if (dropped.dropReason === "verify_refuted" || dropped.dropReason === "novelty_duplicate" || dropped.dropReason === "judge_truncation" || dropped.dropReason === "late_resolution") continue;
|
|
509
|
+
captureObservation({
|
|
510
|
+
classHint: dropped.lensId || "unknown-lens",
|
|
511
|
+
sinkPattern: "",
|
|
512
|
+
exampleFileLine: dropped.candidatePath,
|
|
513
|
+
whyMissed: `dropped: ${dropped.dropReason}`,
|
|
514
|
+
source: "incomplete-coverage",
|
|
515
|
+
scanId,
|
|
516
|
+
sourceRevisionDigest,
|
|
517
|
+
consent: false,
|
|
518
|
+
detectorLensId: dropped.lensId || void 0,
|
|
519
|
+
detectorLensVersionDigest: dropped.lensVersionDigest
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
} catch (error) {
|
|
523
|
+
const warning = `learning feedback was not persisted: ${error instanceof Error ? error.message : String(error)}`;
|
|
524
|
+
res.warnings.push(warning);
|
|
525
|
+
log(`[deep-review] ${warning}`);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
const sweptNothing = res.scanned > 0 && typeof res.finderCompleted === "number" && res.finderCompleted === 0;
|
|
529
|
+
const cost = costLedger.costBreakdown();
|
|
530
|
+
eventBus.emit("scan_completed", {
|
|
531
|
+
exit_reason: sweptNothing ? "failed" : res.costCeilingExceeded ? "cost_exceeded" : "completed",
|
|
532
|
+
findings: res.findings.length,
|
|
533
|
+
findings_count: res.findings.length,
|
|
534
|
+
duration_ms: Date.now() - scanStartedAt,
|
|
535
|
+
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.`,
|
|
536
|
+
...cost ? { cost_usd: cost.costUsd, cost_breakdown: cost.breakdown } : {}
|
|
537
|
+
});
|
|
538
|
+
if (sweptNothing) {
|
|
539
|
+
return {
|
|
540
|
+
exitCode: 3,
|
|
541
|
+
result: {
|
|
542
|
+
mode: "deep_review",
|
|
543
|
+
profile: matchedProfile,
|
|
544
|
+
scan_id: scanId,
|
|
545
|
+
source_revision_digest: sourceRevisionDigest,
|
|
546
|
+
lens_snapshot_digest: lensDigest,
|
|
547
|
+
...evolvedFinder ? { evolution_version_id: evolvedFinder.versionId, evolution_compute_cost_usd: evolvedFinder.computeCostUsd } : {},
|
|
548
|
+
source: sourceRoot,
|
|
549
|
+
subsystem: opts.subsystem ?? null,
|
|
550
|
+
scope_files: totalFiles,
|
|
551
|
+
candidates: candidatePaths.length,
|
|
552
|
+
scanned: res.scanned,
|
|
553
|
+
finder_completed: res.finderCompleted,
|
|
554
|
+
finder_timed_out: res.finderTimedOut ?? null,
|
|
555
|
+
finder_errored: res.finderErrored ?? null,
|
|
556
|
+
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.`,
|
|
557
|
+
warnings: res.warnings.slice(0, 10)
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
const completedAt = /* @__PURE__ */ new Date();
|
|
562
|
+
const candidateFindings = leads.map((lead) => ({
|
|
563
|
+
...lead,
|
|
564
|
+
status: "discovered"
|
|
565
|
+
}));
|
|
566
|
+
const report = {
|
|
567
|
+
target: opts.target,
|
|
568
|
+
scanDepth: "deep",
|
|
569
|
+
startedAt: new Date(scanStartedAt).toISOString(),
|
|
570
|
+
completedAt: completedAt.toISOString(),
|
|
571
|
+
durationMs: completedAt.getTime() - scanStartedAt,
|
|
572
|
+
summary: {
|
|
573
|
+
totalAttacks: res.scanned,
|
|
574
|
+
totalFindings: candidateFindings.length,
|
|
575
|
+
critical: candidateFindings.filter((finding) => finding.severity === "critical").length,
|
|
576
|
+
high: candidateFindings.filter((finding) => finding.severity === "high").length,
|
|
577
|
+
medium: candidateFindings.filter((finding) => finding.severity === "medium").length,
|
|
578
|
+
low: candidateFindings.filter((finding) => finding.severity === "low").length,
|
|
579
|
+
info: candidateFindings.filter((finding) => finding.severity === "info").length
|
|
580
|
+
},
|
|
581
|
+
findings: candidateFindings,
|
|
582
|
+
warnings: res.warnings.slice(0, 10).map((message) => ({
|
|
583
|
+
stage: "attack",
|
|
584
|
+
message
|
|
585
|
+
})),
|
|
586
|
+
...res.costCeilingExceeded ? { costCeilingExceeded: true, exitReason: "cost_ceiling_exceeded" } : {}
|
|
587
|
+
};
|
|
588
|
+
return {
|
|
589
|
+
exitCode: 0,
|
|
590
|
+
report,
|
|
591
|
+
result: {
|
|
592
|
+
mode: "deep_review",
|
|
593
|
+
profile: matchedProfile,
|
|
594
|
+
scan_id: scanId,
|
|
595
|
+
source_revision_digest: sourceRevisionDigest,
|
|
596
|
+
source: sourceRoot,
|
|
597
|
+
subsystem: opts.subsystem ?? null,
|
|
598
|
+
scope_files: totalFiles,
|
|
599
|
+
lens_snapshot_digest: lensDigest,
|
|
600
|
+
...evolvedFinder ? { evolution_version_id: evolvedFinder.versionId, evolution_compute_cost_usd: evolvedFinder.computeCostUsd } : {},
|
|
601
|
+
candidates: candidatePaths.length,
|
|
602
|
+
finder_lenses: finderLenses.map((l) => l.id),
|
|
603
|
+
verify_lenses: verifyLenses.map((l) => l.id),
|
|
604
|
+
scanned: res.scanned,
|
|
605
|
+
findings: res.findings.length,
|
|
606
|
+
confirmed: leads.length,
|
|
607
|
+
leads: leads.map((f) => ({
|
|
608
|
+
title: f.title,
|
|
609
|
+
severity: f.severity,
|
|
610
|
+
analysis: f.evidence.analysis ?? ""
|
|
611
|
+
})),
|
|
612
|
+
dropped: res.dropped.map((d) => ({
|
|
613
|
+
title: d.finding.title,
|
|
614
|
+
severity: d.finding.severity,
|
|
615
|
+
candidatePath: d.candidatePath,
|
|
616
|
+
lensId: d.lensId,
|
|
617
|
+
dropReason: d.dropReason,
|
|
618
|
+
detail: d.detail
|
|
619
|
+
})),
|
|
620
|
+
warnings: res.warnings.slice(0, 10),
|
|
621
|
+
note: "LEADS, not confirmed bugs. Each survived the multi-lens refute quorum; verify the real sink + impact before disclosure."
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
} catch (error) {
|
|
625
|
+
const cost = costLedger.costBreakdown();
|
|
626
|
+
eventBus.emit("scan_completed", {
|
|
627
|
+
exit_reason: "failed",
|
|
628
|
+
findings: 0,
|
|
629
|
+
findings_count: 0,
|
|
630
|
+
duration_ms: Date.now() - scanStartedAt,
|
|
631
|
+
summary: `Deep review failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
632
|
+
...cost ? { cost_usd: cost.costUsd, cost_breakdown: cost.breakdown } : {}
|
|
633
|
+
});
|
|
634
|
+
throw error;
|
|
635
|
+
} finally {
|
|
636
|
+
prepared.cleanup();
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
function parsePositive(flag, raw, dflt) {
|
|
640
|
+
if (raw === void 0) return dflt;
|
|
641
|
+
const n = parseInt(raw, 10);
|
|
642
|
+
if (!Number.isFinite(n) || n <= 0) throw new Error(`invalid ${flag} '${raw}' (expected positive integer)`);
|
|
643
|
+
return n;
|
|
644
|
+
}
|
|
645
|
+
async function deepReviewAction(target, opts) {
|
|
646
|
+
if (!target || target.trim() === "") throw new Error("missing required argument: <target> (source tree path or git URL)");
|
|
647
|
+
const ceilingSource = opts.costCeiling ?? process.env["ZERO_COST_CEILING_USD"];
|
|
648
|
+
let costCeilingUsd;
|
|
649
|
+
if (ceilingSource !== void 0 && ceilingSource !== "") {
|
|
650
|
+
const parsed = Number(ceilingSource);
|
|
651
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
652
|
+
throw new Error(`invalid --cost-ceiling '${ceilingSource}' (expected positive USD amount)`);
|
|
653
|
+
}
|
|
654
|
+
costCeilingUsd = parsed;
|
|
655
|
+
}
|
|
656
|
+
const { writeFileSync } = await import("node:fs");
|
|
657
|
+
const { executeManagedOperation } = await import("./managed-operation-YNQQEA5V.js");
|
|
658
|
+
const timeoutMs = parsePositive("--timeout", opts.timeout, 6e5);
|
|
659
|
+
const outcome = await executeManagedOperation({
|
|
660
|
+
type: "deep-review",
|
|
661
|
+
name: "Deep source review",
|
|
662
|
+
target,
|
|
663
|
+
timeCapMs: timeoutMs,
|
|
664
|
+
costCapUsd: costCeilingUsd,
|
|
665
|
+
execute: (context) => runDeepReview({
|
|
666
|
+
target,
|
|
667
|
+
signal: context.signal,
|
|
668
|
+
costLedger: context.costLedger,
|
|
669
|
+
plan: context.plan,
|
|
670
|
+
costCeilingUsd: context.plan.costCapUsd,
|
|
671
|
+
...opts.profile ? { profile: opts.profile } : {},
|
|
672
|
+
...opts.subsystem ? { subsystem: opts.subsystem } : {},
|
|
673
|
+
// Flag > env > single provider-default model. Only pass `models` when the
|
|
674
|
+
// flag is set; otherwise let runDeepReview resolve $ZERO_DEEP_REVIEW_MODELS.
|
|
675
|
+
...opts.models ? { models: opts.models.split(",").map((s) => s.trim()).filter(Boolean) } : {},
|
|
676
|
+
...opts.attempts ? { attemptsPerCandidate: parsePositive("--attempts", opts.attempts, defaultAttempts()) } : {},
|
|
677
|
+
concurrency: parsePositive("--concurrency", opts.concurrency, DEFAULT_CONCURRENCY),
|
|
678
|
+
maxCandidates: parsePositive("--max-candidates", opts.maxCandidates, defaultMaxCandidates()),
|
|
679
|
+
...opts.quorum ? { quorum: parsePositive("--quorum", opts.quorum, 1) } : {},
|
|
680
|
+
...opts.threatModel ? { useThreatModel: true } : {},
|
|
681
|
+
...opts.runtime ? { runtime: opts.runtime } : {},
|
|
682
|
+
...opts.evolutionConfig ? { evolutionConfig: loadEvolutionConfigFile(opts.evolutionConfig) } : {},
|
|
683
|
+
timeoutMs: Math.max(1, context.deadline - Date.now()),
|
|
684
|
+
log: (m) => process.stderr.write(m + "\n")
|
|
685
|
+
}),
|
|
686
|
+
status: (outcome2) => outcome2.exitCode === 0 ? "completed" : "failed",
|
|
687
|
+
reports: (outcome2) => outcome2.report ? [outcome2.report] : []
|
|
688
|
+
});
|
|
689
|
+
if (outcome.report) {
|
|
690
|
+
const storage = resolveOsecRunStorage();
|
|
691
|
+
writeOsecRunReport(storage, outcome.report);
|
|
692
|
+
}
|
|
693
|
+
const json = JSON.stringify(outcome.result, null, 2);
|
|
694
|
+
if (opts.output) writeFileSync(resolve(opts.output), json + "\n", "utf8");
|
|
695
|
+
else process.stdout.write(json + "\n");
|
|
696
|
+
process.exitCode = outcome.exitCode;
|
|
697
|
+
}
|
|
698
|
+
function registerDeepReviewCommand(program) {
|
|
699
|
+
program.command("deep-review").description(
|
|
700
|
+
"Seedless deep security review of a source tree. Emits leads to verify, not confirmed bugs."
|
|
701
|
+
).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 $ZERO_DEEP_REVIEW_MODELS)").option("--attempts <N>", `Finder attempts per candidate\xD7lens\xD7model, best-of-N (default ${DEFAULT_ATTEMPTS}, or $ZERO_DEEP_REVIEW_ATTEMPTS)`).option("--concurrency <N>", `Max finders in flight (default ${DEFAULT_CONCURRENCY})`).option("--cost-ceiling <usd>", "Hard scan-wide USD ceiling. Overrides $ZERO_COST_CEILING_USD.").option("--max-candidates <N>", `Cap candidate files hunted, largest-first (default ${DEFAULT_MAX_CANDIDATES}, or $ZERO_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) => {
|
|
702
|
+
try {
|
|
703
|
+
await deepReviewAction(target, opts);
|
|
704
|
+
} catch (err) {
|
|
705
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
706
|
+
const json = JSON.stringify({ mode: "deep_review", error: reason }, null, 2);
|
|
707
|
+
if (opts.output) {
|
|
708
|
+
const { writeFileSync } = await import("node:fs");
|
|
709
|
+
try {
|
|
710
|
+
writeFileSync(resolve(opts.output), json + "\n", "utf8");
|
|
711
|
+
} catch {
|
|
712
|
+
process.stderr.write(json + "\n");
|
|
713
|
+
}
|
|
714
|
+
} else process.stdout.write(json + "\n");
|
|
715
|
+
process.exitCode = 3;
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
export {
|
|
721
|
+
lensSetDigest,
|
|
722
|
+
createDefaultFinderLenses,
|
|
723
|
+
defaultFinderLenses,
|
|
724
|
+
defaultVerifyLenses,
|
|
725
|
+
selectProfileLenses,
|
|
726
|
+
detectStack,
|
|
727
|
+
selectDefaultFinderLensesForStack,
|
|
728
|
+
isNonProtocolEvmPath,
|
|
729
|
+
selectCandidatesModuleSpread,
|
|
730
|
+
enumerateDeepReviewCandidates,
|
|
731
|
+
runDeepReview,
|
|
732
|
+
registerDeepReviewCommand
|
|
733
|
+
};
|