0sec-cli 0.15.0 → 0.18.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 +327 -0
- package/README.md +68 -48
- package/attacks/data-exfiltration/pii-leakage.yaml +27 -0
- package/attacks/encoding-bypass/base64-encoding.yaml +24 -0
- package/attacks/jailbreak/dan-roleplay.yaml +27 -0
- package/attacks/jailbreak/hypothetical-scenario.yaml +25 -0
- package/attacks/jailbreak/multilingual-bypass.yaml +22 -0
- package/attacks/output-manipulation/harmful-content.yaml +25 -0
- package/attacks/prompt-injection/context-manipulation.yaml +32 -0
- package/attacks/prompt-injection/direct-injection.yaml +28 -0
- package/attacks/prompt-injection/indirect-injection.yaml +33 -0
- package/attacks/system-prompt-extraction/direct-ask.yaml +30 -0
- package/attacks/system-prompt-extraction/markdown-exfil.yaml +26 -0
- package/attacks/tool-misuse/ssrf-via-tools.yaml +27 -0
- package/chunks/adapt-loop-SMH6V4PD.js +18 -0
- package/chunks/adgraph-JLGA6RYI.js +54 -0
- package/chunks/agent/skills/frameworks/entra-id.yaml +63 -0
- package/chunks/agent/skills/frameworks/graphql-introspection.yaml +116 -0
- package/chunks/agent/skills/frameworks/nextjs.yaml +82 -0
- package/chunks/agent/skills/frameworks/python-web.yaml +85 -0
- package/chunks/agent/skills/frameworks/supabase.yaml +83 -0
- package/chunks/agent/skills/frameworks/wordpress-deep.yaml +122 -0
- package/chunks/agent/skills/techniques/ad-attack-paths.yaml +57 -0
- package/chunks/agent/skills/techniques/advisory-disclosure.yaml +59 -0
- package/chunks/agent/skills/techniques/assumption-mining.yaml +53 -0
- package/chunks/agent/skills/techniques/blind-exploitation.yaml +143 -0
- package/chunks/agent/skills/techniques/crypto-misuse.yaml +88 -0
- package/chunks/agent/skills/techniques/cve-poc-adaptation.yaml +82 -0
- package/chunks/agent/skills/techniques/entra-attack-paths.yaml +55 -0
- package/chunks/agent/skills/techniques/http-conformance-diff.yaml +48 -0
- package/chunks/agent/skills/techniques/jwt-attacks.yaml +137 -0
- package/chunks/agent/skills/techniques/kernel-weaponization.yaml +53 -0
- package/chunks/agent/skills/techniques/llm-excessive-agency.yaml +55 -0
- package/chunks/agent/skills/techniques/llm-insecure-output-handling.yaml +56 -0
- package/chunks/agent/skills/techniques/llm-prompt-injection.yaml +54 -0
- package/chunks/agent/skills/techniques/llm-prompt-layer-write.yaml +68 -0
- package/chunks/agent/skills/techniques/llm-rag-poisoning.yaml +57 -0
- package/chunks/agent/skills/techniques/llm-safety-eval.yaml +51 -0
- package/chunks/agent/skills/techniques/npm-ecosystem.yaml +51 -0
- package/chunks/agent/skills/techniques/poc-verification.yaml +55 -0
- package/chunks/agent/skills/techniques/race-condition.yaml +122 -0
- package/chunks/agent/skills/techniques/scoped-fix.yaml +49 -0
- package/chunks/agent/skills/techniques/seedless-depth-review.yaml +59 -0
- package/chunks/agent/skills/techniques/spec-differential.yaml +48 -0
- package/chunks/agent/skills/techniques/variant-hunting.yaml +55 -0
- package/chunks/agent/skills/vulnerabilities/cardano-eutxo-validators.yaml +72 -0
- package/chunks/agent/skills/vulnerabilities/command-injection.yaml +99 -0
- package/chunks/agent/skills/vulnerabilities/deserialization-chains.yaml +106 -0
- package/chunks/agent/skills/vulnerabilities/native-memory-safety.yaml +93 -0
- package/chunks/agent/skills/vulnerabilities/path-traversal.yaml +78 -0
- package/chunks/agent/skills/vulnerabilities/prototype-pollution.yaml +132 -0
- package/chunks/agent/skills/vulnerabilities/request-smuggling.yaml +129 -0
- package/chunks/agent/skills/vulnerabilities/sqli-advanced.yaml +102 -0
- package/chunks/agent/skills/vulnerabilities/ssrf-bypass.yaml +84 -0
- package/chunks/agent/skills/vulnerabilities/ssti-exploitation.yaml +112 -0
- package/chunks/agent/skills/vulnerabilities/structural-sqli.yaml +91 -0
- package/chunks/appsec-catalog-CBWHGTEL.js +24 -0
- package/chunks/artifact-scraper-HQ7HLP6V.js +34 -0
- package/chunks/assumption-mining-5SNIVVDN.js +79 -0
- package/chunks/chunk-242TMK6G.js +110 -0
- package/chunks/chunk-2JCCA2JL.js +381 -0
- package/chunks/chunk-2RMLOJVB.js +47 -0
- package/chunks/chunk-3KPWWJCI.js +1422 -0
- package/chunks/chunk-3MOLBTLS.js +953 -0
- package/chunks/chunk-53G27VPS.js +136 -0
- package/chunks/chunk-57ZENEX2.js +169 -0
- package/chunks/chunk-5G3ZXFBW.js +182 -0
- package/chunks/chunk-5JI7L7KV.js +74240 -0
- package/chunks/chunk-6L26OBWI.js +1094 -0
- package/chunks/chunk-6LKRLK2R.js +542 -0
- package/chunks/chunk-72CISA2D.js +2597 -0
- package/chunks/chunk-7DQEV5QI.js +287 -0
- package/chunks/chunk-AD7WXDH6.js +3250 -0
- package/chunks/chunk-ADOLI6DT.js +102 -0
- package/chunks/chunk-AHTBZC3E.js +596 -0
- package/chunks/chunk-ASUR522M.js +518 -0
- package/chunks/chunk-ATATQACO.js +471 -0
- package/chunks/chunk-BATRQBOR.js +1732 -0
- package/chunks/chunk-BFR2CDV5.js +5735 -0
- package/chunks/chunk-BFRB6HB2.js +235 -0
- package/chunks/chunk-BKZFDZ23.js +361 -0
- package/chunks/chunk-BVAZO4WA.js +193 -0
- package/chunks/chunk-BZNEY2ZS.js +33225 -0
- package/chunks/chunk-C2WXWFBB.js +1070 -0
- package/chunks/chunk-CMKXP5RR.js +917 -0
- package/chunks/chunk-DW5UWPFY.js +275 -0
- package/chunks/chunk-F3WBKITT.js +687 -0
- package/chunks/chunk-GMT2AZKM.js +2439 -0
- package/chunks/chunk-H2FFLZNK.js +3 -0
- package/chunks/chunk-IOL7D5YV.js +515 -0
- package/chunks/chunk-IR537GON.js +49 -0
- package/chunks/chunk-JQHOLLTD.js +3729 -0
- package/chunks/chunk-KLTNTE2Z.js +121 -0
- package/chunks/chunk-LP3HHYQU.js +245 -0
- package/chunks/chunk-MYVT64FN.js +16 -0
- package/chunks/chunk-N7BOUYEV.js +852 -0
- package/chunks/chunk-OEFNRYI2.js +150 -0
- package/chunks/chunk-P6WKNFWX.js +1968 -0
- package/chunks/chunk-QI233I24.js +333 -0
- package/chunks/chunk-RJWOYEOG.js +271 -0
- package/chunks/chunk-RRMJC3ZE.js +105 -0
- package/chunks/chunk-RWONANDA.js +973 -0
- package/chunks/chunk-SAFFWQW4.js +5921 -0
- package/chunks/chunk-SF4KZ4O3.js +502 -0
- package/chunks/chunk-SVJEDVYK.js +808 -0
- package/chunks/chunk-TSK2AG6J.js +5108 -0
- package/chunks/chunk-U5FLXGHT.js +2010 -0
- package/chunks/chunk-UCYRA73C.js +662 -0
- package/chunks/chunk-UR4ELX2Y.js +4784 -0
- package/chunks/chunk-VQG4FT5D.js +685 -0
- package/chunks/chunk-WEBMRWEG.js +9337 -0
- package/chunks/chunk-WU6AFRAZ.js +1167 -0
- package/chunks/chunk-WVTBZEQO.js +1531 -0
- package/chunks/chunk-WXQ6BJ6A.js +596 -0
- package/chunks/chunk-XM5NLHYU.js +600 -0
- package/chunks/chunk-YLMN3N25.js +4054 -0
- package/chunks/chunk-YYRBQVFE.js +2572 -0
- package/chunks/chunk-Z5FA2XOX.js +2798 -0
- package/chunks/commands-TBC2F5CE.js +16999 -0
- package/chunks/corpus-v1.json +403 -0
- package/chunks/cost-ledger-NU63MYZM.js +13 -0
- package/chunks/data/appsec-archetypes.json +102 -0
- package/chunks/data/chromium-archetypes.json +204 -0
- package/chunks/data/freebsd-archetypes.json +171 -0
- package/chunks/data/kernel-archetypes.json +611 -0
- package/chunks/db-KFWOAUUB.js +16 -0
- package/chunks/disclose-EKTWSCGW.js +132 -0
- package/chunks/dist-4MAW5X6L.js +74 -0
- package/chunks/dist-4YICS2J2.js +2776 -0
- package/chunks/dist-FMGSR3DW.js +159 -0
- package/chunks/eval-runner-7ZF472KV.js +27 -0
- package/chunks/example-manifest.json +95 -0
- package/chunks/exploit-agent-45YQOHPJ.js +98 -0
- package/chunks/exploit-autoclimb-UZGEMGYT.js +81 -0
- package/chunks/exploit-climb-SG23KB2G.js +358 -0
- package/chunks/fix-IG7LYXH3.js +12 -0
- package/chunks/github-issues-MJ6OYOOU.js +157 -0
- package/chunks/harness-7ODOLOWU.js +22 -0
- package/chunks/http-conformance-DU66MZIU.js +11 -0
- package/chunks/http-sender-GWH2IYEA.js +10 -0
- package/chunks/hunt-scan-MMKUOETU.js +38 -0
- package/chunks/identity-6ZAIIWOR.js +191 -0
- package/chunks/kernel-primitive-7U6CWHXD.js +44 -0
- package/chunks/kernel-vm-runner-3JAU4O6J.js +62 -0
- package/chunks/memsafety-scan-J7SECV5W.js +16 -0
- package/chunks/native-loop-RJCJPOUS.js +54 -0
- package/chunks/npm-detectors-KB5Y5ZFX.js +69 -0
- package/chunks/npm-dynamic-discovery-AHROOZDB.js +13 -0
- package/chunks/orchestrate-OC2VZN2X.js +62 -0
- package/chunks/pipeline-BZK5BLJR.js +14 -0
- package/chunks/pre-recon-cve-66EB6G4M.js +351 -0
- package/chunks/prepare-MYY743TK.js +13 -0
- package/chunks/process-LQRCS5OY.js +13 -0
- package/chunks/replay-runner-45G3VU7X.js +39 -0
- package/chunks/run-D56MPDKY.js +32716 -0
- package/chunks/runtime-G7YWIO3B.js +43 -0
- package/chunks/runtime-J7PLXZNM.js +12 -0
- package/chunks/scan-stream-FHI2FYZE.js +96 -0
- package/chunks/scope-BI7BF4ZY.js +18 -0
- package/chunks/session-store-GZ4A4KYT.js +28 -0
- package/chunks/source-files-PWRQR6LY.js +12 -0
- package/chunks/specdrift-WCLH6TTQ.js +19 -0
- package/chunks/variant-candidates-YERCYMPD.js +13 -0
- package/chunks/web-recon-prepass-JJGPEOLE.js +1148 -0
- package/dashboard/assets/0sec-icon-66SreztZ.gif +0 -0
- package/dashboard/assets/bot-yILPnRgD.js +1 -0
- package/dashboard/assets/chevron-down-BeDJ-Vka.js +1 -0
- package/dashboard/assets/circle-alert-BEnMBMT-.js +1 -0
- package/dashboard/assets/client-DRYsuOGl.js +9 -0
- package/dashboard/assets/copy-Bi5vzoF9.js +1 -0
- package/dashboard/assets/desktop-CXTQonNm.js +32 -0
- package/dashboard/assets/desktop-NOekk4QS.css +1 -0
- package/dashboard/assets/dist-CmX-h7a4.js +1 -0
- package/dashboard/assets/dist-DAwYQmf_.js +45 -0
- package/dashboard/assets/findings-page-CACW9nw2.js +5 -0
- package/dashboard/assets/format-PoISqsES.js +1 -0
- package/dashboard/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/dashboard/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/dashboard/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/dashboard/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/dashboard/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/dashboard/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
- package/dashboard/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
- package/dashboard/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
- package/dashboard/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
- package/dashboard/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/dashboard/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/dashboard/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
- package/dashboard/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
- package/dashboard/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
- package/dashboard/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
- package/dashboard/assets/jsx-runtime-C7oxC63R.js +1 -0
- package/dashboard/assets/live-page-DrgQqQK5.js +1 -0
- package/dashboard/assets/meta-tile-CcskIV_o.js +1 -0
- package/dashboard/assets/operations-BKE8T-vY.css +2 -0
- package/dashboard/assets/operations-D8nUP5_m.js +4 -0
- package/dashboard/assets/operations-app-CQQhG54n.js +2 -0
- package/dashboard/assets/overview-page-DTGS8dPo.js +1 -0
- package/dashboard/assets/page-header-MFVvEtZW.js +1 -0
- package/dashboard/assets/play-Dwo-9xt6.js +1 -0
- package/dashboard/assets/scans-page-ChVtq8Us.js +1 -0
- package/dashboard/assets/search-CiooTLck.js +1 -0
- package/dashboard/assets/siren-CCBwTSuk.js +1 -0
- package/dashboard/assets/table-BYS-nIVA.js +1 -0
- package/dashboard/assets/tabs-DivxbhQy.js +1 -0
- package/dashboard/desktop.html +21 -0
- package/dashboard/index.html +15 -0
- package/package.json +30 -18
- package/bin/0sec.cjs +0 -361
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __0secCreateRequire } from "node:module";
|
|
3
|
+
const require = __0secCreateRequire(import.meta.url);
|
|
4
|
+
|
|
5
|
+
// packages/core/dist/source-files.js
|
|
6
|
+
import { lstatSync, readdirSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
var DEFAULT_SOURCE_EXTS = /* @__PURE__ */ new Set([
|
|
9
|
+
".js",
|
|
10
|
+
".mjs",
|
|
11
|
+
".cjs",
|
|
12
|
+
".ts",
|
|
13
|
+
".mts",
|
|
14
|
+
".cts",
|
|
15
|
+
".jsx",
|
|
16
|
+
".tsx",
|
|
17
|
+
".json",
|
|
18
|
+
".yml",
|
|
19
|
+
".yaml",
|
|
20
|
+
".py",
|
|
21
|
+
".pyx",
|
|
22
|
+
".pyi",
|
|
23
|
+
".rs",
|
|
24
|
+
".go",
|
|
25
|
+
".rb",
|
|
26
|
+
".php",
|
|
27
|
+
".java",
|
|
28
|
+
".kt",
|
|
29
|
+
".c",
|
|
30
|
+
".h",
|
|
31
|
+
".cc",
|
|
32
|
+
".cpp",
|
|
33
|
+
".cxx",
|
|
34
|
+
".hpp",
|
|
35
|
+
".sol"
|
|
36
|
+
]);
|
|
37
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
38
|
+
"node_modules",
|
|
39
|
+
".git",
|
|
40
|
+
"dist",
|
|
41
|
+
"build",
|
|
42
|
+
"target",
|
|
43
|
+
"__pycache__",
|
|
44
|
+
".venv",
|
|
45
|
+
"venv",
|
|
46
|
+
".tox",
|
|
47
|
+
"vendor",
|
|
48
|
+
"third_party",
|
|
49
|
+
"deps",
|
|
50
|
+
".cache",
|
|
51
|
+
".next",
|
|
52
|
+
".nuxt",
|
|
53
|
+
"coverage"
|
|
54
|
+
]);
|
|
55
|
+
var DEFAULT_MAX_FILES = 50;
|
|
56
|
+
var DEFAULT_MAX_FILE_SIZE = 2e5;
|
|
57
|
+
function collectScopeFiles(dir, opts = {}) {
|
|
58
|
+
const maxFiles = opts.maxFiles ?? DEFAULT_MAX_FILES;
|
|
59
|
+
const maxFileSize = opts.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
|
|
60
|
+
const exts = opts.extensions ?? DEFAULT_SOURCE_EXTS;
|
|
61
|
+
const files = [];
|
|
62
|
+
function walk(d) {
|
|
63
|
+
if (files.length >= maxFiles)
|
|
64
|
+
return;
|
|
65
|
+
let entries;
|
|
66
|
+
try {
|
|
67
|
+
entries = readdirSync(d);
|
|
68
|
+
} catch {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
if (files.length >= maxFiles)
|
|
73
|
+
return;
|
|
74
|
+
if (SKIP_DIRS.has(entry))
|
|
75
|
+
continue;
|
|
76
|
+
const full = join(d, entry);
|
|
77
|
+
try {
|
|
78
|
+
const st = lstatSync(full);
|
|
79
|
+
if (st.isDirectory()) {
|
|
80
|
+
walk(full);
|
|
81
|
+
} else if (st.isFile() && st.nlink === 1 && st.size < maxFileSize) {
|
|
82
|
+
const ext = full.slice(full.lastIndexOf("."));
|
|
83
|
+
if (exts.has(ext)) {
|
|
84
|
+
files.push(full);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
walk(dir);
|
|
92
|
+
return files;
|
|
93
|
+
}
|
|
94
|
+
function countScopeFilesUpTo(dir, limit, opts = {}) {
|
|
95
|
+
const maxFileSize = opts.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
|
|
96
|
+
const exts = opts.extensions ?? DEFAULT_SOURCE_EXTS;
|
|
97
|
+
let count = 0;
|
|
98
|
+
function walk(d) {
|
|
99
|
+
if (count > limit)
|
|
100
|
+
return;
|
|
101
|
+
let entries;
|
|
102
|
+
try {
|
|
103
|
+
entries = readdirSync(d);
|
|
104
|
+
} catch {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
for (const entry of entries) {
|
|
108
|
+
if (count > limit)
|
|
109
|
+
return;
|
|
110
|
+
if (SKIP_DIRS.has(entry))
|
|
111
|
+
continue;
|
|
112
|
+
const full = join(d, entry);
|
|
113
|
+
try {
|
|
114
|
+
const st = lstatSync(full);
|
|
115
|
+
if (st.isDirectory()) {
|
|
116
|
+
walk(full);
|
|
117
|
+
} else if (st.isFile() && st.nlink === 1 && st.size < maxFileSize) {
|
|
118
|
+
const ext = full.slice(full.lastIndexOf("."));
|
|
119
|
+
if (exts.has(ext)) {
|
|
120
|
+
count += 1;
|
|
121
|
+
if (count > limit)
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} catch {
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
walk(dir);
|
|
130
|
+
return count;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export {
|
|
134
|
+
collectScopeFiles,
|
|
135
|
+
countScopeFilesUpTo
|
|
136
|
+
};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __0secCreateRequire } from "node:module";
|
|
3
|
+
const require = __0secCreateRequire(import.meta.url);
|
|
4
|
+
|
|
5
|
+
// packages/core/dist/scope/scope.js
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import { isIP } from "node:net";
|
|
8
|
+
var ScopePolicy = class _ScopePolicy {
|
|
9
|
+
inScope;
|
|
10
|
+
outOfScope;
|
|
11
|
+
/**
|
|
12
|
+
* Original JSON the policy was constructed from. Exposed read-only so
|
|
13
|
+
* downstream features (0sec#216 attribution headers) can pull their
|
|
14
|
+
* own optional blocks out of the same file without re-reading it.
|
|
15
|
+
*/
|
|
16
|
+
raw;
|
|
17
|
+
constructor(json) {
|
|
18
|
+
this.inScope = (json.in_scope ?? []).map(parseRule);
|
|
19
|
+
this.outOfScope = (json.out_of_scope ?? []).map(parseRule);
|
|
20
|
+
this.raw = json;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Load a policy from a JSON file on disk.
|
|
24
|
+
*
|
|
25
|
+
* Bubbles up the underlying ENOENT/JSON-parse error to the caller; the
|
|
26
|
+
* scan CLI is responsible for translating these into a user-facing
|
|
27
|
+
* "scope file not found / malformed" message and exiting non-zero before
|
|
28
|
+
* the agent ever boots.
|
|
29
|
+
*/
|
|
30
|
+
static fromJsonFile(path) {
|
|
31
|
+
const raw = readFileSync(path, "utf-8");
|
|
32
|
+
let parsed;
|
|
33
|
+
try {
|
|
34
|
+
parsed = JSON.parse(raw);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
37
|
+
throw new Error(`Scope file ${path} is not valid JSON: ${msg}`);
|
|
38
|
+
}
|
|
39
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
40
|
+
throw new Error(`Scope file ${path} must be a JSON object with in_scope/out_of_scope arrays`);
|
|
41
|
+
}
|
|
42
|
+
return _ScopePolicy.fromJson(parsed);
|
|
43
|
+
}
|
|
44
|
+
static fromJson(obj) {
|
|
45
|
+
return new _ScopePolicy(obj);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Decide whether a URL is allowed by this policy.
|
|
49
|
+
*
|
|
50
|
+
* The contract used by every call site:
|
|
51
|
+
* - allowed=true → URL is in scope, callers may proceed.
|
|
52
|
+
* - allowed=false → URL is out of scope; callers MUST surface this as
|
|
53
|
+
* a hard error (`ToolResult.error`), not a warning.
|
|
54
|
+
*
|
|
55
|
+
* `reason` is human-readable and meant to be embedded in the error
|
|
56
|
+
* message returned to the agent so it can self-correct.
|
|
57
|
+
*/
|
|
58
|
+
match(url) {
|
|
59
|
+
let host;
|
|
60
|
+
try {
|
|
61
|
+
host = normalizeScopeHostname(new URL(url).hostname);
|
|
62
|
+
} catch {
|
|
63
|
+
return { allowed: false, reason: `out-of-scope: not a valid URL (${url})` };
|
|
64
|
+
}
|
|
65
|
+
const denyHit = this.outOfScope.find((rule) => matches(rule, host));
|
|
66
|
+
if (denyHit) {
|
|
67
|
+
return { allowed: false, reason: `out-of-scope: host '${host}' matches deny rule '${denyHit.raw}'` };
|
|
68
|
+
}
|
|
69
|
+
if (this.inScope.length === 0) {
|
|
70
|
+
return { allowed: false, reason: `out-of-scope: host '${host}' (no in_scope rules defined)` };
|
|
71
|
+
}
|
|
72
|
+
const allowHit = this.inScope.find((rule) => matches(rule, host));
|
|
73
|
+
if (allowHit) {
|
|
74
|
+
return { allowed: true, reason: `in-scope: host '${host}' matches '${allowHit.raw}'` };
|
|
75
|
+
}
|
|
76
|
+
return { allowed: false, reason: `out-of-scope: host '${host}' did not match any in_scope rule` };
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
function extractUrls(text) {
|
|
80
|
+
const re = /https?:\/\/[^\s'"`<>|]+/gi;
|
|
81
|
+
const out = [];
|
|
82
|
+
let m;
|
|
83
|
+
while ((m = re.exec(text)) !== null) {
|
|
84
|
+
let url = m[0];
|
|
85
|
+
while (url.length > 0 && /[.,;:)\]}]$/.test(url)) {
|
|
86
|
+
url = url.slice(0, -1);
|
|
87
|
+
}
|
|
88
|
+
if (url.length > 0)
|
|
89
|
+
out.push(url);
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
function loadScope(path) {
|
|
94
|
+
return ScopePolicy.fromJsonFile(path);
|
|
95
|
+
}
|
|
96
|
+
function matchUrl(url, policy) {
|
|
97
|
+
return policy.match(url);
|
|
98
|
+
}
|
|
99
|
+
function normalizeScopeHostname(hostname) {
|
|
100
|
+
const normalized = hostname.toLowerCase().replace(/\.+$/, "");
|
|
101
|
+
const address = normalized.startsWith("[") && normalized.endsWith("]") ? normalized.slice(1, -1) : normalized;
|
|
102
|
+
return isIP(address) === 6 ? new URL(`http://[${address}]/`).hostname : normalized;
|
|
103
|
+
}
|
|
104
|
+
function parseRule(raw) {
|
|
105
|
+
if (typeof raw !== "string" || raw.trim().length === 0) {
|
|
106
|
+
throw new Error(`Invalid scope rule: ${JSON.stringify(raw)} (must be a non-empty string)`);
|
|
107
|
+
}
|
|
108
|
+
const rule = raw.trim().toLowerCase();
|
|
109
|
+
if (rule.includes("/")) {
|
|
110
|
+
const parts = rule.split("/");
|
|
111
|
+
if (parts.length !== 2) {
|
|
112
|
+
throw new Error(`Invalid CIDR '${raw}': expected exactly one '/'`);
|
|
113
|
+
}
|
|
114
|
+
const [ip, prefixStr] = parts;
|
|
115
|
+
if (!/^\d+$/.test(prefixStr)) {
|
|
116
|
+
throw new Error(`Invalid CIDR prefix in '${raw}': must be a non-negative integer 0-32`);
|
|
117
|
+
}
|
|
118
|
+
const prefix = Number(prefixStr);
|
|
119
|
+
if (prefix < 0 || prefix > 32) {
|
|
120
|
+
throw new Error(`Invalid CIDR prefix in '${raw}': must be 0-32`);
|
|
121
|
+
}
|
|
122
|
+
if (isIP(ip) !== 4) {
|
|
123
|
+
throw new Error(`Invalid CIDR '${raw}': only IPv4 CIDR is supported`);
|
|
124
|
+
}
|
|
125
|
+
return { raw, kind: "cidr", cidr: { network: ipv4ToInt(ip), prefix } };
|
|
126
|
+
}
|
|
127
|
+
if (rule.startsWith("*.")) {
|
|
128
|
+
const suffix = normalizeScopeHostname(rule.slice(2));
|
|
129
|
+
if (suffix.length === 0 || suffix.includes("*")) {
|
|
130
|
+
throw new Error(`Invalid wildcard rule '${raw}': must be of the form '*.domain.tld'`);
|
|
131
|
+
}
|
|
132
|
+
return { raw, kind: "wildcard", suffix };
|
|
133
|
+
}
|
|
134
|
+
if (rule.includes("*")) {
|
|
135
|
+
throw new Error(`Invalid scope rule '${raw}': only leading '*.' wildcards are supported`);
|
|
136
|
+
}
|
|
137
|
+
return { raw, kind: "exact", host: normalizeScopeHostname(rule) };
|
|
138
|
+
}
|
|
139
|
+
function matches(rule, host) {
|
|
140
|
+
switch (rule.kind) {
|
|
141
|
+
case "exact":
|
|
142
|
+
return rule.host === host;
|
|
143
|
+
case "wildcard": {
|
|
144
|
+
const suffix = "." + rule.suffix;
|
|
145
|
+
return host !== rule.suffix && host.endsWith(suffix);
|
|
146
|
+
}
|
|
147
|
+
case "cidr": {
|
|
148
|
+
if (isIP(host) !== 4)
|
|
149
|
+
return false;
|
|
150
|
+
const { network, prefix } = rule.cidr;
|
|
151
|
+
if (prefix === 0)
|
|
152
|
+
return true;
|
|
153
|
+
const mask = prefix === 32 ? 4294967295 : ~((1 << 32 - prefix) - 1) >>> 0;
|
|
154
|
+
return (ipv4ToInt(host) & mask) >>> 0 === (network & mask) >>> 0;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function ipv4ToInt(ip) {
|
|
159
|
+
const parts = ip.split(".").map((p) => Number(p));
|
|
160
|
+
return (parts[0] << 24 | parts[1] << 16 | parts[2] << 8 | parts[3]) >>> 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export {
|
|
164
|
+
ScopePolicy,
|
|
165
|
+
extractUrls,
|
|
166
|
+
loadScope,
|
|
167
|
+
matchUrl,
|
|
168
|
+
normalizeScopeHostname
|
|
169
|
+
};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __0secCreateRequire } from "node:module";
|
|
3
|
+
const require = __0secCreateRequire(import.meta.url);
|
|
4
|
+
import {
|
|
5
|
+
getDetectorById,
|
|
6
|
+
resolveDetectors,
|
|
7
|
+
runDetectorOnPackage
|
|
8
|
+
} from "./chunk-RWONANDA.js";
|
|
9
|
+
|
|
10
|
+
// packages/core/dist/stages/npm-dynamic-discovery.js
|
|
11
|
+
import { randomUUID } from "node:crypto";
|
|
12
|
+
function leadToFinding(pkg, detector, lead) {
|
|
13
|
+
const conf = lead.confirmation;
|
|
14
|
+
const severity = conf.severity ?? detector.severityFloor;
|
|
15
|
+
const novel = lead.dedup.novel;
|
|
16
|
+
const versionLabel = pkg.version ? `@${pkg.version}` : "";
|
|
17
|
+
const escalation = conf.evidence.escalation;
|
|
18
|
+
const description = [
|
|
19
|
+
`${detector.title} confirmed in npm package ${pkg.name}${versionLabel}.`,
|
|
20
|
+
`Class: ${detector.cwe} (${detector.category}).`,
|
|
21
|
+
`Observed consequence: ${conf.evidence.observation}`,
|
|
22
|
+
escalation ? `Escalation ${escalation.kind}: ${escalation.achieved ? "ACHIEVED" : "not achieved"}${escalation.note ? ` (${escalation.note})` : ""}.` : "",
|
|
23
|
+
novel ? "Novelty: no live advisory / fork-twin / prior report matched (disclosure-eligible)." : `Novelty: NON-novel \u2014 ${lead.dedup.source} (${lead.dedup.advisories.join("; ")}).`
|
|
24
|
+
].filter(Boolean).join("\n");
|
|
25
|
+
const analysis = [
|
|
26
|
+
`Detector: ${detector.id}`,
|
|
27
|
+
`Candidate/source: ${conf.source ?? lead.candidateId}`,
|
|
28
|
+
`Severity: ${severity}`,
|
|
29
|
+
pkg.weeklyDownloads !== void 0 ? `Weekly downloads: ${pkg.weeklyDownloads}` : "",
|
|
30
|
+
"",
|
|
31
|
+
"---observed consequence (assume-FP: this is the runtime fact)---",
|
|
32
|
+
conf.evidence.observation,
|
|
33
|
+
conf.evidence.analysis ? `
|
|
34
|
+
${conf.evidence.analysis}` : "",
|
|
35
|
+
"",
|
|
36
|
+
"---dedup---",
|
|
37
|
+
`novel=${novel} source=${lead.dedup.source}`,
|
|
38
|
+
...lead.dedup.advisories.map((a) => ` ${a}`)
|
|
39
|
+
].filter(Boolean).join("\n");
|
|
40
|
+
return {
|
|
41
|
+
id: randomUUID(),
|
|
42
|
+
templateId: `npm-dynamic-${detector.id}`,
|
|
43
|
+
title: `${detector.title} in ${pkg.name}${versionLabel}`,
|
|
44
|
+
description,
|
|
45
|
+
severity,
|
|
46
|
+
category: detector.category,
|
|
47
|
+
status: "discovered",
|
|
48
|
+
evidence: {
|
|
49
|
+
request: conf.evidence.payload ?? `${detector.id} candidate ${lead.candidateId}`,
|
|
50
|
+
response: conf.evidence.observation,
|
|
51
|
+
analysis
|
|
52
|
+
},
|
|
53
|
+
fingerprint: `npm-dynamic:${detector.id}:${pkg.name}:${conf.source ?? lead.candidateId}`,
|
|
54
|
+
// A runtime-observed consequence is a high-confidence lead; the shared
|
|
55
|
+
// adversarial-verify stage re-runs it at the publication bar regardless.
|
|
56
|
+
confidence: 0.85,
|
|
57
|
+
noveltyVerdict: novel ? "novel" : "possibly-known",
|
|
58
|
+
dedupRefs: lead.dedup.advisories,
|
|
59
|
+
timestamp: Date.now()
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async function pool(items, limit, fn) {
|
|
63
|
+
let next = 0;
|
|
64
|
+
const n = Math.max(1, Math.min(limit, items.length || 1));
|
|
65
|
+
const workers = Array.from({ length: n }, async () => {
|
|
66
|
+
for (; ; ) {
|
|
67
|
+
const i = next++;
|
|
68
|
+
if (i >= items.length)
|
|
69
|
+
return;
|
|
70
|
+
await fn(items[i], i);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
await Promise.all(workers);
|
|
74
|
+
}
|
|
75
|
+
async function runNpmDynamicDiscovery(opts) {
|
|
76
|
+
const log = opts.log ?? (() => {
|
|
77
|
+
});
|
|
78
|
+
if (!opts.packageRunner && !opts.probeFactory) {
|
|
79
|
+
throw new Error("runNpmDynamicDiscovery: provide a packageRunner (sandbox) or a probeFactory (in-process)");
|
|
80
|
+
}
|
|
81
|
+
const { detectors, unknown: unknownDetectors } = resolveDetectors(opts.detectorIds);
|
|
82
|
+
for (const u of unknownDetectors)
|
|
83
|
+
log(`WARN unknown detector id "${u}" \u2014 skipped`);
|
|
84
|
+
const findings = [];
|
|
85
|
+
const novel = [];
|
|
86
|
+
const known = [];
|
|
87
|
+
const unpreparable = [];
|
|
88
|
+
const warnings = [];
|
|
89
|
+
const statById = /* @__PURE__ */ new Map();
|
|
90
|
+
for (const d of detectors)
|
|
91
|
+
statById.set(d.id, { detectorId: d.id, packagesRun: 0, candidates: 0, confirmed: 0, novel: 0 });
|
|
92
|
+
const promote = async (pkg, detector, outcome) => {
|
|
93
|
+
for (const w of outcome.warnings)
|
|
94
|
+
warnings.push(`${pkg.name}/${detector.id}: ${w}`);
|
|
95
|
+
const stat = statById.get(detector.id);
|
|
96
|
+
if (!stat)
|
|
97
|
+
return;
|
|
98
|
+
if (outcome.ran)
|
|
99
|
+
stat.packagesRun += 1;
|
|
100
|
+
stat.candidates += outcome.candidates;
|
|
101
|
+
for (const lead of outcome.leads) {
|
|
102
|
+
stat.confirmed += 1;
|
|
103
|
+
const finding = leadToFinding(pkg, detector, lead);
|
|
104
|
+
findings.push(finding);
|
|
105
|
+
if (lead.dedup.novel) {
|
|
106
|
+
stat.novel += 1;
|
|
107
|
+
novel.push(finding);
|
|
108
|
+
if (opts.onConfirmed) {
|
|
109
|
+
try {
|
|
110
|
+
await opts.onConfirmed(finding);
|
|
111
|
+
} catch (e) {
|
|
112
|
+
warnings.push(`onConfirmed(${finding.id}) threw: ${e instanceof Error ? e.message : String(e)}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
known.push(finding);
|
|
117
|
+
}
|
|
118
|
+
log(`${lead.dedup.novel ? "NOVEL" : "known"} ${detector.id} ${pkg.name}: ${lead.confirmation.evidence.observation}`);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
await pool(opts.worklist, opts.concurrency ?? 1, async (pkg) => {
|
|
122
|
+
if (opts.packageRunner) {
|
|
123
|
+
let ran;
|
|
124
|
+
try {
|
|
125
|
+
ran = await opts.packageRunner(pkg, detectors.map((d) => d.id));
|
|
126
|
+
} catch (e) {
|
|
127
|
+
ran = void 0;
|
|
128
|
+
warnings.push(`${pkg.name}: packageRunner threw: ${e instanceof Error ? e.message : String(e)}`);
|
|
129
|
+
}
|
|
130
|
+
if (!ran) {
|
|
131
|
+
unpreparable.push(pkg.name);
|
|
132
|
+
log(`SKIP ${pkg.name}: sandbox could not prepare/execute (skipped, not confirmed)`);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
for (const w of ran.warnings)
|
|
136
|
+
warnings.push(`${pkg.name}: ${w}`);
|
|
137
|
+
for (const outcome of ran.outcomes) {
|
|
138
|
+
const detector = getDetectorById(outcome.detectorId);
|
|
139
|
+
if (!detector) {
|
|
140
|
+
warnings.push(`${pkg.name}: sandbox returned unknown detector "${outcome.detectorId}"`);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
await promote(pkg, detector, outcome);
|
|
144
|
+
}
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
let probe;
|
|
148
|
+
try {
|
|
149
|
+
probe = await opts.probeFactory(pkg);
|
|
150
|
+
} catch (e) {
|
|
151
|
+
probe = void 0;
|
|
152
|
+
warnings.push(`${pkg.name}: probeFactory threw: ${e instanceof Error ? e.message : String(e)}`);
|
|
153
|
+
}
|
|
154
|
+
if (!probe) {
|
|
155
|
+
unpreparable.push(pkg.name);
|
|
156
|
+
log(`SKIP ${pkg.name}: could not prepare execution probe`);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
for (const detector of detectors) {
|
|
160
|
+
const outcome = await runDetectorOnPackage(detector, probe, {
|
|
161
|
+
guards: opts.guards,
|
|
162
|
+
advisoryLookup: opts.advisoryLookup
|
|
163
|
+
});
|
|
164
|
+
await promote(pkg, detector, outcome);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
return {
|
|
168
|
+
findings,
|
|
169
|
+
novel,
|
|
170
|
+
known,
|
|
171
|
+
unpreparable,
|
|
172
|
+
unknownDetectors,
|
|
173
|
+
scannedPackages: opts.worklist.length,
|
|
174
|
+
perDetector: [...statById.values()],
|
|
175
|
+
warnings
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export {
|
|
180
|
+
leadToFinding,
|
|
181
|
+
runNpmDynamicDiscovery
|
|
182
|
+
};
|