@node9/proxy 2.27.1 → 2.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/dist/cli.js +2494 -1768
- package/dist/cli.mjs +2441 -1715
- package/dist/dashboard.mjs +32 -6
- package/dist/index.js +206 -47
- package/dist/index.mjs +206 -47
- package/dist/scan-ink.mjs +10 -2
- package/package.json +3 -1
package/dist/dashboard.mjs
CHANGED
|
@@ -1748,7 +1748,15 @@ function tuningToLegacyPolicy(policy, tuning) {
|
|
|
1748
1748
|
const unknown = [];
|
|
1749
1749
|
for (const [id, fields] of Object.entries(tuning)) {
|
|
1750
1750
|
if (!fields || typeof fields !== "object") continue;
|
|
1751
|
-
|
|
1751
|
+
const normalized = { ...fields };
|
|
1752
|
+
if (id === "loading.malicious-package") {
|
|
1753
|
+
if (normalized.registrySignals === false) {
|
|
1754
|
+
normalized.newPackage ??= "off";
|
|
1755
|
+
normalized.installScript ??= "off";
|
|
1756
|
+
}
|
|
1757
|
+
delete normalized.registrySignals;
|
|
1758
|
+
}
|
|
1759
|
+
for (const [name, value] of Object.entries(normalized)) {
|
|
1752
1760
|
const field = TUNING_FIELDS.find((f) => f.checkId === id && f.name === name);
|
|
1753
1761
|
if (!field) {
|
|
1754
1762
|
unknown.push(`${id}.${name}`);
|
|
@@ -4685,14 +4693,19 @@ var init_dist = __esm({
|
|
|
4685
4693
|
{ checkId: "loading.skill-tamper", name: "roots", legacy: ["skillPinning", "roots"] },
|
|
4686
4694
|
{
|
|
4687
4695
|
checkId: "loading.malicious-package",
|
|
4688
|
-
name: "
|
|
4689
|
-
legacy: ["packageCheck", "
|
|
4696
|
+
name: "newPackage",
|
|
4697
|
+
legacy: ["packageCheck", "newPackage"]
|
|
4690
4698
|
},
|
|
4691
4699
|
{
|
|
4692
4700
|
checkId: "loading.malicious-package",
|
|
4693
4701
|
name: "maxAgeHours",
|
|
4694
4702
|
legacy: ["packageCheck", "maxAgeHours"]
|
|
4695
4703
|
},
|
|
4704
|
+
{
|
|
4705
|
+
checkId: "loading.malicious-package",
|
|
4706
|
+
name: "installScript",
|
|
4707
|
+
legacy: ["packageCheck", "installScript"]
|
|
4708
|
+
},
|
|
4696
4709
|
{
|
|
4697
4710
|
checkId: "loading.malicious-package",
|
|
4698
4711
|
name: "onlineFallback",
|
|
@@ -5628,7 +5641,10 @@ var init_config_schema = __esm({
|
|
|
5628
5641
|
packageCheck: z.object({
|
|
5629
5642
|
enabled: z.boolean().optional(),
|
|
5630
5643
|
onMalicious: z.enum(["block", "review"]).optional(),
|
|
5644
|
+
// 2.27.0 shape, still accepted: false = newPackage and installScript off.
|
|
5631
5645
|
registrySignals: z.boolean().optional(),
|
|
5646
|
+
newPackage: z.enum(["review", "off"]).optional(),
|
|
5647
|
+
installScript: z.enum(["review", "off"]).optional(),
|
|
5632
5648
|
maxAgeHours: z.number().min(0).optional(),
|
|
5633
5649
|
onlineFallback: z.boolean().optional(),
|
|
5634
5650
|
allow: z.array(z.string()).optional()
|
|
@@ -6398,7 +6414,13 @@ function getConfig(cwd) {
|
|
|
6398
6414
|
if (typeof pc.enabled === "boolean") cur.enabled = pc.enabled;
|
|
6399
6415
|
if (pc.onMalicious === "block" || pc.onMalicious === "review")
|
|
6400
6416
|
cur.onMalicious = pc.onMalicious;
|
|
6401
|
-
if (
|
|
6417
|
+
if (pc.registrySignals === false) {
|
|
6418
|
+
cur.newPackage = "off";
|
|
6419
|
+
cur.installScript = "off";
|
|
6420
|
+
}
|
|
6421
|
+
if (pc.newPackage === "review" || pc.newPackage === "off") cur.newPackage = pc.newPackage;
|
|
6422
|
+
if (pc.installScript === "review" || pc.installScript === "off")
|
|
6423
|
+
cur.installScript = pc.installScript;
|
|
6402
6424
|
if (typeof pc.maxAgeHours === "number" && pc.maxAgeHours >= 0)
|
|
6403
6425
|
cur.maxAgeHours = pc.maxAgeHours;
|
|
6404
6426
|
if (typeof pc.onlineFallback === "boolean") cur.onlineFallback = pc.onlineFallback;
|
|
@@ -6608,7 +6630,10 @@ function getConfig(cwd) {
|
|
|
6608
6630
|
mergedPolicy.packageCheck = {
|
|
6609
6631
|
enabled: typeof pc.enabled === "boolean" ? pc.enabled : cur.enabled,
|
|
6610
6632
|
onMalicious: pc.onMalicious === "block" || pc.onMalicious === "review" ? pc.onMalicious : cur.onMalicious,
|
|
6611
|
-
|
|
6633
|
+
// The 2.27.0 boolean still arrives from older dashboard columns:
|
|
6634
|
+
// false turns both signals off unless the new keys say otherwise.
|
|
6635
|
+
newPackage: pc.newPackage === "review" || pc.newPackage === "off" ? pc.newPackage : pc.registrySignals === false ? "off" : cur.newPackage,
|
|
6636
|
+
installScript: pc.installScript === "review" || pc.installScript === "off" ? pc.installScript : pc.registrySignals === false ? "off" : cur.installScript,
|
|
6612
6637
|
maxAgeHours: typeof pc.maxAgeHours === "number" && Number.isFinite(pc.maxAgeHours) && pc.maxAgeHours >= 0 ? pc.maxAgeHours : cur.maxAgeHours,
|
|
6613
6638
|
onlineFallback: typeof pc.onlineFallback === "boolean" ? pc.onlineFallback : cur.onlineFallback,
|
|
6614
6639
|
allow: Array.isArray(pc.allow) ? pc.allow.filter((a) => typeof a === "string" && a.length > 0) : cur.allow
|
|
@@ -7122,8 +7147,9 @@ var init_config = __esm({
|
|
|
7122
7147
|
packageCheck: {
|
|
7123
7148
|
enabled: true,
|
|
7124
7149
|
onMalicious: "block",
|
|
7125
|
-
|
|
7150
|
+
newPackage: "review",
|
|
7126
7151
|
maxAgeHours: 48,
|
|
7152
|
+
installScript: "review",
|
|
7127
7153
|
onlineFallback: true,
|
|
7128
7154
|
allow: []
|
|
7129
7155
|
},
|
package/dist/index.js
CHANGED
|
@@ -379,9 +379,9 @@ function matchesPattern(text, patterns) {
|
|
|
379
379
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
380
380
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
381
381
|
}
|
|
382
|
-
function getNestedValue(obj,
|
|
382
|
+
function getNestedValue(obj, path19) {
|
|
383
383
|
if (!obj || typeof obj !== "object") return null;
|
|
384
|
-
const segments2 =
|
|
384
|
+
const segments2 = path19.split(".");
|
|
385
385
|
for (const seg of segments2) {
|
|
386
386
|
if (FORBIDDEN_PATH_SEGMENTS.has(seg)) return null;
|
|
387
387
|
}
|
|
@@ -2191,14 +2191,54 @@ function pipInstall(words, from, manager, out) {
|
|
|
2191
2191
|
if (v >= 0 && lower(words[v]) === "install")
|
|
2192
2192
|
pushPy(out, manager, operands(words, v + 1, PIP_VALUE_FLAGS));
|
|
2193
2193
|
}
|
|
2194
|
+
function localContexts(file) {
|
|
2195
|
+
const contexts = /* @__PURE__ */ new Map();
|
|
2196
|
+
const sample = parseShared("cd . && true");
|
|
2197
|
+
if (typeof sample === "symbol") return contexts;
|
|
2198
|
+
const andOp = sample.Stmts[0].Cmd.Op;
|
|
2199
|
+
function visit(stmt, steps, depth = 0) {
|
|
2200
|
+
if (depth > 64) return null;
|
|
2201
|
+
if (!stmt || stmt.Background || stmt.Negated || stmt.Redirs?.length) return null;
|
|
2202
|
+
const cmd = stmt.Cmd;
|
|
2203
|
+
const kind = syntax2.NodeType(cmd);
|
|
2204
|
+
if (kind === "BinaryCmd" && cmd.Op === andOp) {
|
|
2205
|
+
const next = visit(cmd.X, steps, depth + 1);
|
|
2206
|
+
return next === null ? null : visit(cmd.Y, next, depth + 1);
|
|
2207
|
+
}
|
|
2208
|
+
if (kind !== "CallExpr" || cmd.Assigns?.length) return null;
|
|
2209
|
+
const words = (cmd.Args ?? []).map((w) => resolveWordLiteral(w));
|
|
2210
|
+
if (words[0] === "cd" && words.length === 2 && typeof words[1] === "string" && words[1] && !/^[-~]/.test(words[1]))
|
|
2211
|
+
return [...steps, words[1]];
|
|
2212
|
+
if (!["npx", "npm", "bunx", "bun"].includes(words[0])) return null;
|
|
2213
|
+
if (words.some(
|
|
2214
|
+
(w) => w === null || /^(?:--(?:prefix|cwd|directory|workspace|workspaces|global|ignore-existing)(?:=|$)|-[Cwg])/.test(
|
|
2215
|
+
w
|
|
2216
|
+
)
|
|
2217
|
+
))
|
|
2218
|
+
return null;
|
|
2219
|
+
contexts.set(cmd.Pos().Offset(), steps);
|
|
2220
|
+
return null;
|
|
2221
|
+
}
|
|
2222
|
+
if (file.Stmts?.length === 1) visit(file.Stmts[0], []);
|
|
2223
|
+
return contexts;
|
|
2224
|
+
}
|
|
2194
2225
|
function collect(command, out, depth) {
|
|
2195
2226
|
if (!command || command.length > 5e4) return;
|
|
2196
2227
|
const f = parseShared(command);
|
|
2197
2228
|
if (typeof f === "symbol") return;
|
|
2229
|
+
const contexts = depth === 0 ? localContexts(f) : /* @__PURE__ */ new Map();
|
|
2198
2230
|
syntax2.Walk(f, (node) => {
|
|
2199
2231
|
if (node && syntax2.NodeType(node) === "CallExpr") {
|
|
2200
2232
|
const words = (node.Args || []).map((a) => resolveWordLiteral(a));
|
|
2233
|
+
const start = out.length;
|
|
2201
2234
|
if (words.length > 0) fromCall(words, out, depth);
|
|
2235
|
+
const steps = contexts.get(node.Pos().Offset());
|
|
2236
|
+
for (let i = start; i < out.length; i++) {
|
|
2237
|
+
if (["npx", "bunx", "npm exec"].includes(out[i].manager)) {
|
|
2238
|
+
if (!steps) out[i].localCwd = null;
|
|
2239
|
+
else if (steps.length) out[i].localCwd = steps;
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2202
2242
|
}
|
|
2203
2243
|
return true;
|
|
2204
2244
|
});
|
|
@@ -2208,7 +2248,7 @@ function extractPackageInstalls(command) {
|
|
|
2208
2248
|
collect(command, out, 0);
|
|
2209
2249
|
const seen = /* @__PURE__ */ new Set();
|
|
2210
2250
|
return out.filter((r) => {
|
|
2211
|
-
const k =
|
|
2251
|
+
const k = JSON.stringify([r.ecosystem, r.manager, r.name, r.raw, r.localCwd]);
|
|
2212
2252
|
if (seen.has(k)) return false;
|
|
2213
2253
|
seen.add(k);
|
|
2214
2254
|
return true;
|
|
@@ -2641,7 +2681,15 @@ function tuningToLegacyPolicy(policy, tuning) {
|
|
|
2641
2681
|
const unknown = [];
|
|
2642
2682
|
for (const [id, fields] of Object.entries(tuning)) {
|
|
2643
2683
|
if (!fields || typeof fields !== "object") continue;
|
|
2644
|
-
|
|
2684
|
+
const normalized = { ...fields };
|
|
2685
|
+
if (id === "loading.malicious-package") {
|
|
2686
|
+
if (normalized.registrySignals === false) {
|
|
2687
|
+
normalized.newPackage ??= "off";
|
|
2688
|
+
normalized.installScript ??= "off";
|
|
2689
|
+
}
|
|
2690
|
+
delete normalized.registrySignals;
|
|
2691
|
+
}
|
|
2692
|
+
for (const [name, value] of Object.entries(normalized)) {
|
|
2645
2693
|
const field = TUNING_FIELDS.find((f) => f.checkId === id && f.name === name);
|
|
2646
2694
|
if (!field) {
|
|
2647
2695
|
unknown.push(`${id}.${name}`);
|
|
@@ -3000,9 +3048,9 @@ function bareToolName(toolName) {
|
|
|
3000
3048
|
const parts = toolName.split("__");
|
|
3001
3049
|
return (parts.length >= 3 ? parts.slice(2).join("__") : toolName).toLowerCase();
|
|
3002
3050
|
}
|
|
3003
|
-
function valuesAt(args,
|
|
3051
|
+
function valuesAt(args, path19) {
|
|
3004
3052
|
let cursors = [args];
|
|
3005
|
-
for (const rawSegment of
|
|
3053
|
+
for (const rawSegment of path19.split(".")) {
|
|
3006
3054
|
const isArray = rawSegment.endsWith("[]");
|
|
3007
3055
|
const key = isArray ? rawSegment.slice(0, -2) : rawSegment;
|
|
3008
3056
|
const next = [];
|
|
@@ -3034,8 +3082,8 @@ function extractToolDestinations(toolName, args) {
|
|
|
3034
3082
|
const paths = DESTINATION_ARGS.get(bareToolName(toolName));
|
|
3035
3083
|
if (!paths) return [];
|
|
3036
3084
|
const out = [];
|
|
3037
|
-
for (const
|
|
3038
|
-
for (const value of valuesAt(args,
|
|
3085
|
+
for (const path19 of paths) {
|
|
3086
|
+
for (const value of valuesAt(args, path19)) {
|
|
3039
3087
|
const host = hostOf(value);
|
|
3040
3088
|
if (host) out.push({ host, binary: toolName, raw: value });
|
|
3041
3089
|
}
|
|
@@ -3049,15 +3097,15 @@ function ssrfDestinationFloor(toolName, args, opts = {}) {
|
|
|
3049
3097
|
try {
|
|
3050
3098
|
const paths = DESTINATION_ARGS.get(bareToolName(toolName));
|
|
3051
3099
|
if (!paths) return null;
|
|
3052
|
-
for (const
|
|
3053
|
-
for (const value of valuesAt(args,
|
|
3100
|
+
for (const path19 of paths) {
|
|
3101
|
+
for (const value of valuesAt(args, path19)) {
|
|
3054
3102
|
const host = hostOf(value);
|
|
3055
3103
|
if (!host) continue;
|
|
3056
3104
|
const m = classifySsrf(host);
|
|
3057
3105
|
if (!m) continue;
|
|
3058
3106
|
if (isStrictGatedTier(m.tier) && !opts.ssrfStrict) continue;
|
|
3059
3107
|
if (m.overridable && ssrfExemptMatches(opts.ssrfAllow, m.normalized)) continue;
|
|
3060
|
-
return { ...m, argPath:
|
|
3108
|
+
return { ...m, argPath: path19, host, reason: ssrfReason(m, host) };
|
|
3061
3109
|
}
|
|
3062
3110
|
}
|
|
3063
3111
|
return null;
|
|
@@ -6963,14 +7011,19 @@ var init_dist = __esm({
|
|
|
6963
7011
|
{ checkId: "loading.skill-tamper", name: "roots", legacy: ["skillPinning", "roots"] },
|
|
6964
7012
|
{
|
|
6965
7013
|
checkId: "loading.malicious-package",
|
|
6966
|
-
name: "
|
|
6967
|
-
legacy: ["packageCheck", "
|
|
7014
|
+
name: "newPackage",
|
|
7015
|
+
legacy: ["packageCheck", "newPackage"]
|
|
6968
7016
|
},
|
|
6969
7017
|
{
|
|
6970
7018
|
checkId: "loading.malicious-package",
|
|
6971
7019
|
name: "maxAgeHours",
|
|
6972
7020
|
legacy: ["packageCheck", "maxAgeHours"]
|
|
6973
7021
|
},
|
|
7022
|
+
{
|
|
7023
|
+
checkId: "loading.malicious-package",
|
|
7024
|
+
name: "installScript",
|
|
7025
|
+
legacy: ["packageCheck", "installScript"]
|
|
7026
|
+
},
|
|
6974
7027
|
{
|
|
6975
7028
|
checkId: "loading.malicious-package",
|
|
6976
7029
|
name: "onlineFallback",
|
|
@@ -7253,7 +7306,8 @@ __export(audit_exports, {
|
|
|
7253
7306
|
buildArgsPreview: () => buildArgsPreview,
|
|
7254
7307
|
filePathFromArgs: () => filePathFromArgs,
|
|
7255
7308
|
generateEventId: () => generateEventId,
|
|
7256
|
-
redactSecrets: () => redactSecrets
|
|
7309
|
+
redactSecrets: () => redactSecrets,
|
|
7310
|
+
truncateAuditPreview: () => truncateAuditPreview
|
|
7257
7311
|
});
|
|
7258
7312
|
function generateEventId() {
|
|
7259
7313
|
return `${Date.now().toString(36)}-${import_crypto4.default.randomBytes(6).toString("hex")}`;
|
|
@@ -7282,11 +7336,14 @@ function buildArgsPreview(args) {
|
|
|
7282
7336
|
const primary = o && (o.command ?? o.file_path ?? o.path ?? o.url ?? o.query);
|
|
7283
7337
|
const text = typeof primary === "string" ? primary : args ? JSON.stringify(args) : "";
|
|
7284
7338
|
if (!text) return void 0;
|
|
7285
|
-
return redactSecrets(text)
|
|
7339
|
+
return truncateAuditPreview(redactSecrets(text));
|
|
7286
7340
|
} catch {
|
|
7287
7341
|
return void 0;
|
|
7288
7342
|
}
|
|
7289
7343
|
}
|
|
7344
|
+
function truncateAuditPreview(text) {
|
|
7345
|
+
return text.slice(0, 120).replace(/[\uD800-\uDBFF]$/, "");
|
|
7346
|
+
}
|
|
7290
7347
|
function appendToLog(logPath, entry) {
|
|
7291
7348
|
try {
|
|
7292
7349
|
const dir = import_path.default.dirname(logPath);
|
|
@@ -7580,7 +7637,10 @@ var ConfigFileSchema = import_zod.z.object({
|
|
|
7580
7637
|
packageCheck: import_zod.z.object({
|
|
7581
7638
|
enabled: import_zod.z.boolean().optional(),
|
|
7582
7639
|
onMalicious: import_zod.z.enum(["block", "review"]).optional(),
|
|
7640
|
+
// 2.27.0 shape, still accepted: false = newPackage and installScript off.
|
|
7583
7641
|
registrySignals: import_zod.z.boolean().optional(),
|
|
7642
|
+
newPackage: import_zod.z.enum(["review", "off"]).optional(),
|
|
7643
|
+
installScript: import_zod.z.enum(["review", "off"]).optional(),
|
|
7584
7644
|
maxAgeHours: import_zod.z.number().min(0).optional(),
|
|
7585
7645
|
onlineFallback: import_zod.z.boolean().optional(),
|
|
7586
7646
|
allow: import_zod.z.array(import_zod.z.string()).optional()
|
|
@@ -7595,8 +7655,8 @@ var ConfigFileSchema = import_zod.z.object({
|
|
|
7595
7655
|
}).strict();
|
|
7596
7656
|
function formatIssues(issues) {
|
|
7597
7657
|
const lines = issues.map((issue) => {
|
|
7598
|
-
const
|
|
7599
|
-
return ` \u2022 ${
|
|
7658
|
+
const path19 = issue.path.length > 0 ? issue.path.map(String).join(".") : "root";
|
|
7659
|
+
return ` \u2022 ${path19}: ${issue.message}`;
|
|
7600
7660
|
});
|
|
7601
7661
|
return `Invalid config:
|
|
7602
7662
|
${lines.join("\n")}`;
|
|
@@ -7609,9 +7669,9 @@ function prunePaths(root, paths) {
|
|
|
7609
7669
|
const yi = y[y.length - 1];
|
|
7610
7670
|
return typeof xi === "number" && typeof yi === "number" ? yi - xi : 0;
|
|
7611
7671
|
});
|
|
7612
|
-
for (const
|
|
7672
|
+
for (const path19 of ordered) {
|
|
7613
7673
|
let cur = root;
|
|
7614
|
-
for (const key of
|
|
7674
|
+
for (const key of path19.slice(0, -1)) {
|
|
7615
7675
|
if (cur === null || typeof cur !== "object") {
|
|
7616
7676
|
cur = void 0;
|
|
7617
7677
|
break;
|
|
@@ -7619,7 +7679,7 @@ function prunePaths(root, paths) {
|
|
|
7619
7679
|
cur = cur[key];
|
|
7620
7680
|
}
|
|
7621
7681
|
if (cur === null || typeof cur !== "object") continue;
|
|
7622
|
-
const last =
|
|
7682
|
+
const last = path19[path19.length - 1];
|
|
7623
7683
|
if (Array.isArray(cur)) {
|
|
7624
7684
|
const i = Number(last);
|
|
7625
7685
|
if (Number.isInteger(i) && i >= 0 && i < cur.length) {
|
|
@@ -7655,7 +7715,7 @@ function sanitizeConfig(raw) {
|
|
|
7655
7715
|
return issue.keys.map((k) => [...at, k]);
|
|
7656
7716
|
}
|
|
7657
7717
|
return [at.slice(0, -level || void 0)];
|
|
7658
|
-
}).filter((
|
|
7718
|
+
}).filter((path19) => path19.length > 0);
|
|
7659
7719
|
if (paths.length === 0 || !prunePaths(working, paths)) break;
|
|
7660
7720
|
}
|
|
7661
7721
|
if (ConfigFileSchema.safeParse(working).success) break;
|
|
@@ -8391,8 +8451,9 @@ var DEFAULT_CONFIG = {
|
|
|
8391
8451
|
packageCheck: {
|
|
8392
8452
|
enabled: true,
|
|
8393
8453
|
onMalicious: "block",
|
|
8394
|
-
|
|
8454
|
+
newPackage: "review",
|
|
8395
8455
|
maxAgeHours: 48,
|
|
8456
|
+
installScript: "review",
|
|
8396
8457
|
onlineFallback: true,
|
|
8397
8458
|
allow: []
|
|
8398
8459
|
},
|
|
@@ -8754,7 +8815,13 @@ function getConfig(cwd) {
|
|
|
8754
8815
|
if (typeof pc.enabled === "boolean") cur.enabled = pc.enabled;
|
|
8755
8816
|
if (pc.onMalicious === "block" || pc.onMalicious === "review")
|
|
8756
8817
|
cur.onMalicious = pc.onMalicious;
|
|
8757
|
-
if (
|
|
8818
|
+
if (pc.registrySignals === false) {
|
|
8819
|
+
cur.newPackage = "off";
|
|
8820
|
+
cur.installScript = "off";
|
|
8821
|
+
}
|
|
8822
|
+
if (pc.newPackage === "review" || pc.newPackage === "off") cur.newPackage = pc.newPackage;
|
|
8823
|
+
if (pc.installScript === "review" || pc.installScript === "off")
|
|
8824
|
+
cur.installScript = pc.installScript;
|
|
8758
8825
|
if (typeof pc.maxAgeHours === "number" && pc.maxAgeHours >= 0)
|
|
8759
8826
|
cur.maxAgeHours = pc.maxAgeHours;
|
|
8760
8827
|
if (typeof pc.onlineFallback === "boolean") cur.onlineFallback = pc.onlineFallback;
|
|
@@ -8964,7 +9031,10 @@ function getConfig(cwd) {
|
|
|
8964
9031
|
mergedPolicy.packageCheck = {
|
|
8965
9032
|
enabled: typeof pc.enabled === "boolean" ? pc.enabled : cur.enabled,
|
|
8966
9033
|
onMalicious: pc.onMalicious === "block" || pc.onMalicious === "review" ? pc.onMalicious : cur.onMalicious,
|
|
8967
|
-
|
|
9034
|
+
// The 2.27.0 boolean still arrives from older dashboard columns:
|
|
9035
|
+
// false turns both signals off unless the new keys say otherwise.
|
|
9036
|
+
newPackage: pc.newPackage === "review" || pc.newPackage === "off" ? pc.newPackage : pc.registrySignals === false ? "off" : cur.newPackage,
|
|
9037
|
+
installScript: pc.installScript === "review" || pc.installScript === "off" ? pc.installScript : pc.registrySignals === false ? "off" : cur.installScript,
|
|
8968
9038
|
maxAgeHours: typeof pc.maxAgeHours === "number" && Number.isFinite(pc.maxAgeHours) && pc.maxAgeHours >= 0 ? pc.maxAgeHours : cur.maxAgeHours,
|
|
8969
9039
|
onlineFallback: typeof pc.onlineFallback === "boolean" ? pc.onlineFallback : cur.onlineFallback,
|
|
8970
9040
|
allow: Array.isArray(pc.allow) ? pc.allow.filter((a) => typeof a === "string" && a.length > 0) : cur.allow
|
|
@@ -8990,13 +9060,13 @@ function getConfig(cwd) {
|
|
|
8990
9060
|
}
|
|
8991
9061
|
if (Array.isArray(mc.jailPaths)) {
|
|
8992
9062
|
for (const jp of mc.jailPaths) {
|
|
8993
|
-
const
|
|
8994
|
-
if (!
|
|
9063
|
+
const path19 = typeof jp?.path === "string" ? jp.path.trim() : "";
|
|
9064
|
+
if (!path19) continue;
|
|
8995
9065
|
const verdict = jp?.verdict === "review" ? "review" : "block";
|
|
8996
|
-
for (const r of pathRules(
|
|
9066
|
+
for (const r of pathRules(path19, verdict, "org-managed jail")) {
|
|
8997
9067
|
mergedPolicy.smartRules.push({ ...r, name: `org:${r.name}` });
|
|
8998
9068
|
}
|
|
8999
|
-
mergedPolicy.managedJailPaths.push({ path:
|
|
9069
|
+
mergedPolicy.managedJailPaths.push({ path: path19, verdict });
|
|
9000
9070
|
}
|
|
9001
9071
|
}
|
|
9002
9072
|
if (Array.isArray(mc.trustedHosts)) {
|
|
@@ -10392,6 +10462,7 @@ function recordAndCheck(tool, args, threshold = 3, windowMs = 12e4) {
|
|
|
10392
10462
|
|
|
10393
10463
|
// src/supply-chain/check.ts
|
|
10394
10464
|
var import_picomatch2 = __toESM(require("picomatch"));
|
|
10465
|
+
var import_path18 = __toESM(require("path"));
|
|
10395
10466
|
init_dist();
|
|
10396
10467
|
|
|
10397
10468
|
// src/supply-chain/osv-index.ts
|
|
@@ -10553,30 +10624,58 @@ async function queryOsvMalicious(queries) {
|
|
|
10553
10624
|
}
|
|
10554
10625
|
|
|
10555
10626
|
// src/supply-chain/registry.ts
|
|
10627
|
+
var import_semver = require("semver");
|
|
10556
10628
|
var TIMEOUT_MS2 = 1500;
|
|
10557
10629
|
var MAX_BYTES = 2 * 1024 * 1024;
|
|
10558
|
-
async function npmInfo(name, version) {
|
|
10630
|
+
async function npmInfo(name, version, freshWindowMs, raw) {
|
|
10559
10631
|
const base = endpoints().npmRegistry;
|
|
10560
10632
|
if (!base) return null;
|
|
10561
|
-
const
|
|
10633
|
+
const url = `${base}/${name.replaceAll("/", "%2F")}`;
|
|
10634
|
+
const doc = await fetchJson(url, {
|
|
10562
10635
|
timeoutMs: TIMEOUT_MS2,
|
|
10563
10636
|
maxBytes: MAX_BYTES,
|
|
10564
10637
|
headers: { Accept: "application/vnd.npm.install-v1+json" }
|
|
10565
10638
|
});
|
|
10566
10639
|
if (!doc) return null;
|
|
10567
10640
|
const latest = doc["dist-tags"]?.latest;
|
|
10568
|
-
const
|
|
10641
|
+
const aliasStart = raw?.indexOf("@npm:") ?? -1;
|
|
10642
|
+
const target = aliasStart > 0 ? raw.slice(aliasStart + 5) : raw?.replace(/^npm:/, "");
|
|
10643
|
+
const specStart = target?.indexOf("@", 1) ?? -1;
|
|
10644
|
+
const spec = specStart > 0 ? target.slice(specStart + 1) : "";
|
|
10645
|
+
let resolved = version;
|
|
10646
|
+
if (!resolved) {
|
|
10647
|
+
if (!spec) resolved = latest;
|
|
10648
|
+
else if (doc["dist-tags"]?.[spec]) resolved = doc["dist-tags"][spec];
|
|
10649
|
+
else if ((0, import_semver.validRange)(spec)) {
|
|
10650
|
+
resolved = latest && (0, import_semver.satisfies)(latest, spec) ? latest : (0, import_semver.maxSatisfying)(Object.keys(doc.versions ?? {}), spec) ?? void 0;
|
|
10651
|
+
}
|
|
10652
|
+
}
|
|
10569
10653
|
if (!resolved) return null;
|
|
10570
10654
|
const v = doc.versions?.[resolved];
|
|
10571
10655
|
const modifiedMs = doc.modified ? Date.parse(doc.modified) : NaN;
|
|
10572
|
-
|
|
10656
|
+
const info = {
|
|
10573
10657
|
version: resolved,
|
|
10574
|
-
// `modified` bounds the publish time of EVERY version from above, but it
|
|
10575
|
-
// only approximates the age of the newest one: an older pinned version
|
|
10576
|
-
// gets no age (it is at least as old as `latest`, so never "new").
|
|
10577
|
-
publishedAtMs: Number.isFinite(modifiedMs) && resolved === latest ? modifiedMs : void 0,
|
|
10578
10658
|
hasInstallScript: v?.hasInstallScript === true
|
|
10579
10659
|
};
|
|
10660
|
+
if (!Number.isFinite(modifiedMs)) return info;
|
|
10661
|
+
if (Date.now() - modifiedMs >= freshWindowMs) {
|
|
10662
|
+
info.publishedAtMs = resolved === latest ? modifiedMs : void 0;
|
|
10663
|
+
return info;
|
|
10664
|
+
}
|
|
10665
|
+
try {
|
|
10666
|
+
const full = await fetchJson(url, { timeoutMs: TIMEOUT_MS2, maxBytes: MAX_BYTES });
|
|
10667
|
+
const t = full?.time?.[resolved];
|
|
10668
|
+
const tMs = t ? Date.parse(t) : NaN;
|
|
10669
|
+
if (Number.isFinite(tMs)) {
|
|
10670
|
+
info.publishedAtMs = tMs;
|
|
10671
|
+
return info;
|
|
10672
|
+
}
|
|
10673
|
+
info.ageFallback = `no publish time for ${resolved} in the full document`;
|
|
10674
|
+
} catch (err) {
|
|
10675
|
+
info.ageFallback = `full document ${err.message}`;
|
|
10676
|
+
}
|
|
10677
|
+
info.publishedAtMs = modifiedMs;
|
|
10678
|
+
return info;
|
|
10580
10679
|
}
|
|
10581
10680
|
async function pypiInfo(name, version) {
|
|
10582
10681
|
const base = endpoints().pypi;
|
|
@@ -10591,8 +10690,36 @@ async function pypiInfo(name, version) {
|
|
|
10591
10690
|
publishedAtMs: times.length > 0 ? Math.min(...times) : void 0
|
|
10592
10691
|
};
|
|
10593
10692
|
}
|
|
10594
|
-
async function registryInfo(eco, name, version) {
|
|
10595
|
-
return eco === "npm" ? npmInfo(name, version) : pypiInfo(name, version);
|
|
10693
|
+
async function registryInfo(eco, name, version, freshWindowMs, raw) {
|
|
10694
|
+
return eco === "npm" ? npmInfo(name, version, freshWindowMs, raw) : pypiInfo(name, version);
|
|
10695
|
+
}
|
|
10696
|
+
|
|
10697
|
+
// src/supply-chain/local-resolve.ts
|
|
10698
|
+
var import_fs16 = __toESM(require("fs"));
|
|
10699
|
+
var import_path17 = __toESM(require("path"));
|
|
10700
|
+
var MAX_LEVELS = 32;
|
|
10701
|
+
var LOCAL_FIRST_MANAGERS = /* @__PURE__ */ new Set(["npx", "bunx", "npm exec"]);
|
|
10702
|
+
function resolveInstalled(name, cwd) {
|
|
10703
|
+
if (!cwd || !import_path17.default.isAbsolute(cwd)) return null;
|
|
10704
|
+
if (!/^(?:@[^/\\]+\/)?[^/\\]+$/.test(name)) return null;
|
|
10705
|
+
let dir = import_path17.default.resolve(cwd);
|
|
10706
|
+
for (let i = 0; i < MAX_LEVELS; i++) {
|
|
10707
|
+
const pkgDir = import_path17.default.join(dir, "node_modules", ...name.split("/"));
|
|
10708
|
+
const manifest = import_path17.default.join(pkgDir, "package.json");
|
|
10709
|
+
try {
|
|
10710
|
+
const raw = import_fs16.default.readFileSync(manifest, "utf8");
|
|
10711
|
+
const parsed = JSON.parse(raw);
|
|
10712
|
+
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
10713
|
+
return { version: parsed.version, dir: pkgDir };
|
|
10714
|
+
}
|
|
10715
|
+
return null;
|
|
10716
|
+
} catch {
|
|
10717
|
+
}
|
|
10718
|
+
const parent = import_path17.default.dirname(dir);
|
|
10719
|
+
if (parent === dir) break;
|
|
10720
|
+
dir = parent;
|
|
10721
|
+
}
|
|
10722
|
+
return null;
|
|
10596
10723
|
}
|
|
10597
10724
|
|
|
10598
10725
|
// src/supply-chain/check.ts
|
|
@@ -10663,13 +10790,45 @@ async function maliciousFor(p, version, cfg, misses, networkCapped) {
|
|
|
10663
10790
|
}
|
|
10664
10791
|
return null;
|
|
10665
10792
|
}
|
|
10666
|
-
|
|
10793
|
+
function checkInstalled(p, version, misses) {
|
|
10794
|
+
const local = lookupIndex(p.ecosystem, p.name);
|
|
10795
|
+
if (local.status === "unavailable") {
|
|
10796
|
+
misses.push(`${label(p, version)}: installed copy, local index unavailable`);
|
|
10797
|
+
return [];
|
|
10798
|
+
}
|
|
10799
|
+
if (local.status !== "hit") return [];
|
|
10800
|
+
const covering = local.entries.filter((e) => entryCovers(e, version) === true).map((e) => e.id);
|
|
10801
|
+
if (covering.length === 0) return [];
|
|
10802
|
+
return [
|
|
10803
|
+
{
|
|
10804
|
+
pkg: p,
|
|
10805
|
+
version,
|
|
10806
|
+
installed: true,
|
|
10807
|
+
kind: "malicious",
|
|
10808
|
+
advisories: covering,
|
|
10809
|
+
detail: `installed ${label(p, version)} (${covering.join(", ")})`
|
|
10810
|
+
}
|
|
10811
|
+
];
|
|
10812
|
+
}
|
|
10813
|
+
async function checkOne(p, cfg, misses, networkCapped, cwd) {
|
|
10814
|
+
if (p.ecosystem === "npm" && LOCAL_FIRST_MANAGERS.has(p.manager) && p.localCwd !== null && (p.raw === p.name || p.version)) {
|
|
10815
|
+
const localCwd = cwd && import_path18.default.isAbsolute(cwd) ? (p.localCwd ?? []).reduce((dir, step) => import_path18.default.resolve(dir, step), cwd) : void 0;
|
|
10816
|
+
const installed = resolveInstalled(p.name, localCwd);
|
|
10817
|
+
if (installed && (!p.version || p.version === installed.version))
|
|
10818
|
+
return checkInstalled(p, installed.version, misses);
|
|
10819
|
+
}
|
|
10667
10820
|
const findings = [];
|
|
10668
10821
|
let info = null;
|
|
10669
|
-
|
|
10822
|
+
const wantsRegistry = cfg.newPackage !== "off" || cfg.installScript !== "off";
|
|
10823
|
+
if (wantsRegistry && !networkCapped) {
|
|
10670
10824
|
try {
|
|
10671
|
-
|
|
10825
|
+
const windowMs = cfg.newPackage === "off" ? 0 : cfg.maxAgeHours * 36e5;
|
|
10826
|
+
info = await registryInfo(p.ecosystem, p.name, p.version, windowMs, p.raw);
|
|
10672
10827
|
if (!info) misses.push(`${label(p, p.version)}: registry metadata unavailable`);
|
|
10828
|
+
else if (info.ageFallback)
|
|
10829
|
+
misses.push(
|
|
10830
|
+
`${label(p, info.version)}: age from the package modified time (${info.ageFallback})`
|
|
10831
|
+
);
|
|
10673
10832
|
} catch (err) {
|
|
10674
10833
|
misses.push(`${label(p, p.version)}: registry ${err.message}`);
|
|
10675
10834
|
}
|
|
@@ -10677,18 +10836,18 @@ async function checkOne(p, cfg, misses, networkCapped) {
|
|
|
10677
10836
|
const version = p.version ?? info?.version;
|
|
10678
10837
|
const mal = await maliciousFor(p, version, cfg, misses, networkCapped);
|
|
10679
10838
|
if (mal) findings.push(mal);
|
|
10680
|
-
if (info?.publishedAtMs !== void 0) {
|
|
10839
|
+
if (cfg.newPackage !== "off" && info?.publishedAtMs !== void 0) {
|
|
10681
10840
|
const ageH = (Date.now() - info.publishedAtMs) / 36e5;
|
|
10682
10841
|
if (ageH >= 0 && ageH < cfg.maxAgeHours) {
|
|
10683
10842
|
findings.push({
|
|
10684
10843
|
pkg: p,
|
|
10685
10844
|
version,
|
|
10686
10845
|
kind: "new",
|
|
10687
|
-
detail: `${label(p, version)} was published ${Math.max(1, Math.round(ageH))}h ago (under ${cfg.maxAgeHours}h)`
|
|
10846
|
+
detail: info.ageFallback ? `${label(p, version)} has package metadata updated ${Math.max(1, Math.round(ageH))}h ago (under ${cfg.maxAgeHours}h); version publish time unavailable` : `${label(p, version)} was published ${Math.max(1, Math.round(ageH))}h ago (under ${cfg.maxAgeHours}h)`
|
|
10688
10847
|
});
|
|
10689
10848
|
}
|
|
10690
10849
|
}
|
|
10691
|
-
if (info?.hasInstallScript) {
|
|
10850
|
+
if (cfg.installScript !== "off" && info?.hasInstallScript) {
|
|
10692
10851
|
findings.push({
|
|
10693
10852
|
pkg: p,
|
|
10694
10853
|
version,
|
|
@@ -10698,7 +10857,7 @@ async function checkOne(p, cfg, misses, networkCapped) {
|
|
|
10698
10857
|
}
|
|
10699
10858
|
return findings;
|
|
10700
10859
|
}
|
|
10701
|
-
async function runPackageCheck(toolName, args, cfg) {
|
|
10860
|
+
async function runPackageCheck(toolName, args, cfg, cwd) {
|
|
10702
10861
|
if (!cfg.enabled) return ALLOW;
|
|
10703
10862
|
try {
|
|
10704
10863
|
const command = commandOf(toolName, args);
|
|
@@ -10709,7 +10868,7 @@ async function runPackageCheck(toolName, args, cfg) {
|
|
|
10709
10868
|
const misses = [];
|
|
10710
10869
|
const checked = requests;
|
|
10711
10870
|
const settled = await Promise.allSettled(
|
|
10712
|
-
checked.map((p, i) => checkOne(p, cfg, misses, i >= MAX_NETWORK_PACKAGES))
|
|
10871
|
+
checked.map((p, i) => checkOne(p, cfg, misses, i >= MAX_NETWORK_PACKAGES, cwd))
|
|
10713
10872
|
);
|
|
10714
10873
|
const findings = [];
|
|
10715
10874
|
settled.forEach((s, i) => {
|
|
@@ -11117,7 +11276,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
11117
11276
|
}
|
|
11118
11277
|
const packageCheck = config.policy.packageCheck;
|
|
11119
11278
|
if (packageCheck?.enabled && !isManual) {
|
|
11120
|
-
const pkg = await runPackageCheck(toolName, args, packageCheck);
|
|
11279
|
+
const pkg = await runPackageCheck(toolName, args, packageCheck, options?.cwd);
|
|
11121
11280
|
if (pkg.misses.length > 0) {
|
|
11122
11281
|
appendToLog(HOOK_DEBUG_LOG, {
|
|
11123
11282
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|