@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/index.mjs
CHANGED
|
@@ -364,9 +364,9 @@ function matchesPattern(text, patterns) {
|
|
|
364
364
|
const withoutDotSlash = text.replace(/^\.\//, "");
|
|
365
365
|
return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
|
|
366
366
|
}
|
|
367
|
-
function getNestedValue(obj,
|
|
367
|
+
function getNestedValue(obj, path19) {
|
|
368
368
|
if (!obj || typeof obj !== "object") return null;
|
|
369
|
-
const segments2 =
|
|
369
|
+
const segments2 = path19.split(".");
|
|
370
370
|
for (const seg of segments2) {
|
|
371
371
|
if (FORBIDDEN_PATH_SEGMENTS.has(seg)) return null;
|
|
372
372
|
}
|
|
@@ -2176,14 +2176,54 @@ function pipInstall(words, from, manager, out) {
|
|
|
2176
2176
|
if (v >= 0 && lower(words[v]) === "install")
|
|
2177
2177
|
pushPy(out, manager, operands(words, v + 1, PIP_VALUE_FLAGS));
|
|
2178
2178
|
}
|
|
2179
|
+
function localContexts(file) {
|
|
2180
|
+
const contexts = /* @__PURE__ */ new Map();
|
|
2181
|
+
const sample = parseShared("cd . && true");
|
|
2182
|
+
if (typeof sample === "symbol") return contexts;
|
|
2183
|
+
const andOp = sample.Stmts[0].Cmd.Op;
|
|
2184
|
+
function visit(stmt, steps, depth = 0) {
|
|
2185
|
+
if (depth > 64) return null;
|
|
2186
|
+
if (!stmt || stmt.Background || stmt.Negated || stmt.Redirs?.length) return null;
|
|
2187
|
+
const cmd = stmt.Cmd;
|
|
2188
|
+
const kind = syntax2.NodeType(cmd);
|
|
2189
|
+
if (kind === "BinaryCmd" && cmd.Op === andOp) {
|
|
2190
|
+
const next = visit(cmd.X, steps, depth + 1);
|
|
2191
|
+
return next === null ? null : visit(cmd.Y, next, depth + 1);
|
|
2192
|
+
}
|
|
2193
|
+
if (kind !== "CallExpr" || cmd.Assigns?.length) return null;
|
|
2194
|
+
const words = (cmd.Args ?? []).map((w) => resolveWordLiteral(w));
|
|
2195
|
+
if (words[0] === "cd" && words.length === 2 && typeof words[1] === "string" && words[1] && !/^[-~]/.test(words[1]))
|
|
2196
|
+
return [...steps, words[1]];
|
|
2197
|
+
if (!["npx", "npm", "bunx", "bun"].includes(words[0])) return null;
|
|
2198
|
+
if (words.some(
|
|
2199
|
+
(w) => w === null || /^(?:--(?:prefix|cwd|directory|workspace|workspaces|global|ignore-existing)(?:=|$)|-[Cwg])/.test(
|
|
2200
|
+
w
|
|
2201
|
+
)
|
|
2202
|
+
))
|
|
2203
|
+
return null;
|
|
2204
|
+
contexts.set(cmd.Pos().Offset(), steps);
|
|
2205
|
+
return null;
|
|
2206
|
+
}
|
|
2207
|
+
if (file.Stmts?.length === 1) visit(file.Stmts[0], []);
|
|
2208
|
+
return contexts;
|
|
2209
|
+
}
|
|
2179
2210
|
function collect(command, out, depth) {
|
|
2180
2211
|
if (!command || command.length > 5e4) return;
|
|
2181
2212
|
const f = parseShared(command);
|
|
2182
2213
|
if (typeof f === "symbol") return;
|
|
2214
|
+
const contexts = depth === 0 ? localContexts(f) : /* @__PURE__ */ new Map();
|
|
2183
2215
|
syntax2.Walk(f, (node) => {
|
|
2184
2216
|
if (node && syntax2.NodeType(node) === "CallExpr") {
|
|
2185
2217
|
const words = (node.Args || []).map((a) => resolveWordLiteral(a));
|
|
2218
|
+
const start = out.length;
|
|
2186
2219
|
if (words.length > 0) fromCall(words, out, depth);
|
|
2220
|
+
const steps = contexts.get(node.Pos().Offset());
|
|
2221
|
+
for (let i = start; i < out.length; i++) {
|
|
2222
|
+
if (["npx", "bunx", "npm exec"].includes(out[i].manager)) {
|
|
2223
|
+
if (!steps) out[i].localCwd = null;
|
|
2224
|
+
else if (steps.length) out[i].localCwd = steps;
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2187
2227
|
}
|
|
2188
2228
|
return true;
|
|
2189
2229
|
});
|
|
@@ -2193,7 +2233,7 @@ function extractPackageInstalls(command) {
|
|
|
2193
2233
|
collect(command, out, 0);
|
|
2194
2234
|
const seen = /* @__PURE__ */ new Set();
|
|
2195
2235
|
return out.filter((r) => {
|
|
2196
|
-
const k =
|
|
2236
|
+
const k = JSON.stringify([r.ecosystem, r.manager, r.name, r.raw, r.localCwd]);
|
|
2197
2237
|
if (seen.has(k)) return false;
|
|
2198
2238
|
seen.add(k);
|
|
2199
2239
|
return true;
|
|
@@ -2626,7 +2666,15 @@ function tuningToLegacyPolicy(policy, tuning) {
|
|
|
2626
2666
|
const unknown = [];
|
|
2627
2667
|
for (const [id, fields] of Object.entries(tuning)) {
|
|
2628
2668
|
if (!fields || typeof fields !== "object") continue;
|
|
2629
|
-
|
|
2669
|
+
const normalized = { ...fields };
|
|
2670
|
+
if (id === "loading.malicious-package") {
|
|
2671
|
+
if (normalized.registrySignals === false) {
|
|
2672
|
+
normalized.newPackage ??= "off";
|
|
2673
|
+
normalized.installScript ??= "off";
|
|
2674
|
+
}
|
|
2675
|
+
delete normalized.registrySignals;
|
|
2676
|
+
}
|
|
2677
|
+
for (const [name, value] of Object.entries(normalized)) {
|
|
2630
2678
|
const field = TUNING_FIELDS.find((f) => f.checkId === id && f.name === name);
|
|
2631
2679
|
if (!field) {
|
|
2632
2680
|
unknown.push(`${id}.${name}`);
|
|
@@ -2985,9 +3033,9 @@ function bareToolName(toolName) {
|
|
|
2985
3033
|
const parts = toolName.split("__");
|
|
2986
3034
|
return (parts.length >= 3 ? parts.slice(2).join("__") : toolName).toLowerCase();
|
|
2987
3035
|
}
|
|
2988
|
-
function valuesAt(args,
|
|
3036
|
+
function valuesAt(args, path19) {
|
|
2989
3037
|
let cursors = [args];
|
|
2990
|
-
for (const rawSegment of
|
|
3038
|
+
for (const rawSegment of path19.split(".")) {
|
|
2991
3039
|
const isArray = rawSegment.endsWith("[]");
|
|
2992
3040
|
const key = isArray ? rawSegment.slice(0, -2) : rawSegment;
|
|
2993
3041
|
const next = [];
|
|
@@ -3019,8 +3067,8 @@ function extractToolDestinations(toolName, args) {
|
|
|
3019
3067
|
const paths = DESTINATION_ARGS.get(bareToolName(toolName));
|
|
3020
3068
|
if (!paths) return [];
|
|
3021
3069
|
const out = [];
|
|
3022
|
-
for (const
|
|
3023
|
-
for (const value of valuesAt(args,
|
|
3070
|
+
for (const path19 of paths) {
|
|
3071
|
+
for (const value of valuesAt(args, path19)) {
|
|
3024
3072
|
const host = hostOf(value);
|
|
3025
3073
|
if (host) out.push({ host, binary: toolName, raw: value });
|
|
3026
3074
|
}
|
|
@@ -3034,15 +3082,15 @@ function ssrfDestinationFloor(toolName, args, opts = {}) {
|
|
|
3034
3082
|
try {
|
|
3035
3083
|
const paths = DESTINATION_ARGS.get(bareToolName(toolName));
|
|
3036
3084
|
if (!paths) return null;
|
|
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) continue;
|
|
3041
3089
|
const m = classifySsrf(host);
|
|
3042
3090
|
if (!m) continue;
|
|
3043
3091
|
if (isStrictGatedTier(m.tier) && !opts.ssrfStrict) continue;
|
|
3044
3092
|
if (m.overridable && ssrfExemptMatches(opts.ssrfAllow, m.normalized)) continue;
|
|
3045
|
-
return { ...m, argPath:
|
|
3093
|
+
return { ...m, argPath: path19, host, reason: ssrfReason(m, host) };
|
|
3046
3094
|
}
|
|
3047
3095
|
}
|
|
3048
3096
|
return null;
|
|
@@ -6940,14 +6988,19 @@ var init_dist = __esm({
|
|
|
6940
6988
|
{ checkId: "loading.skill-tamper", name: "roots", legacy: ["skillPinning", "roots"] },
|
|
6941
6989
|
{
|
|
6942
6990
|
checkId: "loading.malicious-package",
|
|
6943
|
-
name: "
|
|
6944
|
-
legacy: ["packageCheck", "
|
|
6991
|
+
name: "newPackage",
|
|
6992
|
+
legacy: ["packageCheck", "newPackage"]
|
|
6945
6993
|
},
|
|
6946
6994
|
{
|
|
6947
6995
|
checkId: "loading.malicious-package",
|
|
6948
6996
|
name: "maxAgeHours",
|
|
6949
6997
|
legacy: ["packageCheck", "maxAgeHours"]
|
|
6950
6998
|
},
|
|
6999
|
+
{
|
|
7000
|
+
checkId: "loading.malicious-package",
|
|
7001
|
+
name: "installScript",
|
|
7002
|
+
legacy: ["packageCheck", "installScript"]
|
|
7003
|
+
},
|
|
6951
7004
|
{
|
|
6952
7005
|
checkId: "loading.malicious-package",
|
|
6953
7006
|
name: "onlineFallback",
|
|
@@ -7230,7 +7283,8 @@ __export(audit_exports, {
|
|
|
7230
7283
|
buildArgsPreview: () => buildArgsPreview,
|
|
7231
7284
|
filePathFromArgs: () => filePathFromArgs,
|
|
7232
7285
|
generateEventId: () => generateEventId,
|
|
7233
|
-
redactSecrets: () => redactSecrets
|
|
7286
|
+
redactSecrets: () => redactSecrets,
|
|
7287
|
+
truncateAuditPreview: () => truncateAuditPreview
|
|
7234
7288
|
});
|
|
7235
7289
|
import fs from "fs";
|
|
7236
7290
|
import path from "path";
|
|
@@ -7263,11 +7317,14 @@ function buildArgsPreview(args) {
|
|
|
7263
7317
|
const primary = o && (o.command ?? o.file_path ?? o.path ?? o.url ?? o.query);
|
|
7264
7318
|
const text = typeof primary === "string" ? primary : args ? JSON.stringify(args) : "";
|
|
7265
7319
|
if (!text) return void 0;
|
|
7266
|
-
return redactSecrets(text)
|
|
7320
|
+
return truncateAuditPreview(redactSecrets(text));
|
|
7267
7321
|
} catch {
|
|
7268
7322
|
return void 0;
|
|
7269
7323
|
}
|
|
7270
7324
|
}
|
|
7325
|
+
function truncateAuditPreview(text) {
|
|
7326
|
+
return text.slice(0, 120).replace(/[\uD800-\uDBFF]$/, "");
|
|
7327
|
+
}
|
|
7271
7328
|
function appendToLog(logPath, entry) {
|
|
7272
7329
|
try {
|
|
7273
7330
|
const dir = path.dirname(logPath);
|
|
@@ -7550,7 +7607,10 @@ var ConfigFileSchema = z.object({
|
|
|
7550
7607
|
packageCheck: z.object({
|
|
7551
7608
|
enabled: z.boolean().optional(),
|
|
7552
7609
|
onMalicious: z.enum(["block", "review"]).optional(),
|
|
7610
|
+
// 2.27.0 shape, still accepted: false = newPackage and installScript off.
|
|
7553
7611
|
registrySignals: z.boolean().optional(),
|
|
7612
|
+
newPackage: z.enum(["review", "off"]).optional(),
|
|
7613
|
+
installScript: z.enum(["review", "off"]).optional(),
|
|
7554
7614
|
maxAgeHours: z.number().min(0).optional(),
|
|
7555
7615
|
onlineFallback: z.boolean().optional(),
|
|
7556
7616
|
allow: z.array(z.string()).optional()
|
|
@@ -7565,8 +7625,8 @@ var ConfigFileSchema = z.object({
|
|
|
7565
7625
|
}).strict();
|
|
7566
7626
|
function formatIssues(issues) {
|
|
7567
7627
|
const lines = issues.map((issue) => {
|
|
7568
|
-
const
|
|
7569
|
-
return ` \u2022 ${
|
|
7628
|
+
const path19 = issue.path.length > 0 ? issue.path.map(String).join(".") : "root";
|
|
7629
|
+
return ` \u2022 ${path19}: ${issue.message}`;
|
|
7570
7630
|
});
|
|
7571
7631
|
return `Invalid config:
|
|
7572
7632
|
${lines.join("\n")}`;
|
|
@@ -7579,9 +7639,9 @@ function prunePaths(root, paths) {
|
|
|
7579
7639
|
const yi = y[y.length - 1];
|
|
7580
7640
|
return typeof xi === "number" && typeof yi === "number" ? yi - xi : 0;
|
|
7581
7641
|
});
|
|
7582
|
-
for (const
|
|
7642
|
+
for (const path19 of ordered) {
|
|
7583
7643
|
let cur = root;
|
|
7584
|
-
for (const key of
|
|
7644
|
+
for (const key of path19.slice(0, -1)) {
|
|
7585
7645
|
if (cur === null || typeof cur !== "object") {
|
|
7586
7646
|
cur = void 0;
|
|
7587
7647
|
break;
|
|
@@ -7589,7 +7649,7 @@ function prunePaths(root, paths) {
|
|
|
7589
7649
|
cur = cur[key];
|
|
7590
7650
|
}
|
|
7591
7651
|
if (cur === null || typeof cur !== "object") continue;
|
|
7592
|
-
const last =
|
|
7652
|
+
const last = path19[path19.length - 1];
|
|
7593
7653
|
if (Array.isArray(cur)) {
|
|
7594
7654
|
const i = Number(last);
|
|
7595
7655
|
if (Number.isInteger(i) && i >= 0 && i < cur.length) {
|
|
@@ -7625,7 +7685,7 @@ function sanitizeConfig(raw) {
|
|
|
7625
7685
|
return issue.keys.map((k) => [...at, k]);
|
|
7626
7686
|
}
|
|
7627
7687
|
return [at.slice(0, -level || void 0)];
|
|
7628
|
-
}).filter((
|
|
7688
|
+
}).filter((path19) => path19.length > 0);
|
|
7629
7689
|
if (paths.length === 0 || !prunePaths(working, paths)) break;
|
|
7630
7690
|
}
|
|
7631
7691
|
if (ConfigFileSchema.safeParse(working).success) break;
|
|
@@ -8361,8 +8421,9 @@ var DEFAULT_CONFIG = {
|
|
|
8361
8421
|
packageCheck: {
|
|
8362
8422
|
enabled: true,
|
|
8363
8423
|
onMalicious: "block",
|
|
8364
|
-
|
|
8424
|
+
newPackage: "review",
|
|
8365
8425
|
maxAgeHours: 48,
|
|
8426
|
+
installScript: "review",
|
|
8366
8427
|
onlineFallback: true,
|
|
8367
8428
|
allow: []
|
|
8368
8429
|
},
|
|
@@ -8724,7 +8785,13 @@ function getConfig(cwd) {
|
|
|
8724
8785
|
if (typeof pc.enabled === "boolean") cur.enabled = pc.enabled;
|
|
8725
8786
|
if (pc.onMalicious === "block" || pc.onMalicious === "review")
|
|
8726
8787
|
cur.onMalicious = pc.onMalicious;
|
|
8727
|
-
if (
|
|
8788
|
+
if (pc.registrySignals === false) {
|
|
8789
|
+
cur.newPackage = "off";
|
|
8790
|
+
cur.installScript = "off";
|
|
8791
|
+
}
|
|
8792
|
+
if (pc.newPackage === "review" || pc.newPackage === "off") cur.newPackage = pc.newPackage;
|
|
8793
|
+
if (pc.installScript === "review" || pc.installScript === "off")
|
|
8794
|
+
cur.installScript = pc.installScript;
|
|
8728
8795
|
if (typeof pc.maxAgeHours === "number" && pc.maxAgeHours >= 0)
|
|
8729
8796
|
cur.maxAgeHours = pc.maxAgeHours;
|
|
8730
8797
|
if (typeof pc.onlineFallback === "boolean") cur.onlineFallback = pc.onlineFallback;
|
|
@@ -8934,7 +9001,10 @@ function getConfig(cwd) {
|
|
|
8934
9001
|
mergedPolicy.packageCheck = {
|
|
8935
9002
|
enabled: typeof pc.enabled === "boolean" ? pc.enabled : cur.enabled,
|
|
8936
9003
|
onMalicious: pc.onMalicious === "block" || pc.onMalicious === "review" ? pc.onMalicious : cur.onMalicious,
|
|
8937
|
-
|
|
9004
|
+
// The 2.27.0 boolean still arrives from older dashboard columns:
|
|
9005
|
+
// false turns both signals off unless the new keys say otherwise.
|
|
9006
|
+
newPackage: pc.newPackage === "review" || pc.newPackage === "off" ? pc.newPackage : pc.registrySignals === false ? "off" : cur.newPackage,
|
|
9007
|
+
installScript: pc.installScript === "review" || pc.installScript === "off" ? pc.installScript : pc.registrySignals === false ? "off" : cur.installScript,
|
|
8938
9008
|
maxAgeHours: typeof pc.maxAgeHours === "number" && Number.isFinite(pc.maxAgeHours) && pc.maxAgeHours >= 0 ? pc.maxAgeHours : cur.maxAgeHours,
|
|
8939
9009
|
onlineFallback: typeof pc.onlineFallback === "boolean" ? pc.onlineFallback : cur.onlineFallback,
|
|
8940
9010
|
allow: Array.isArray(pc.allow) ? pc.allow.filter((a) => typeof a === "string" && a.length > 0) : cur.allow
|
|
@@ -8960,13 +9030,13 @@ function getConfig(cwd) {
|
|
|
8960
9030
|
}
|
|
8961
9031
|
if (Array.isArray(mc.jailPaths)) {
|
|
8962
9032
|
for (const jp of mc.jailPaths) {
|
|
8963
|
-
const
|
|
8964
|
-
if (!
|
|
9033
|
+
const path19 = typeof jp?.path === "string" ? jp.path.trim() : "";
|
|
9034
|
+
if (!path19) continue;
|
|
8965
9035
|
const verdict = jp?.verdict === "review" ? "review" : "block";
|
|
8966
|
-
for (const r of pathRules(
|
|
9036
|
+
for (const r of pathRules(path19, verdict, "org-managed jail")) {
|
|
8967
9037
|
mergedPolicy.smartRules.push({ ...r, name: `org:${r.name}` });
|
|
8968
9038
|
}
|
|
8969
|
-
mergedPolicy.managedJailPaths.push({ path:
|
|
9039
|
+
mergedPolicy.managedJailPaths.push({ path: path19, verdict });
|
|
8970
9040
|
}
|
|
8971
9041
|
}
|
|
8972
9042
|
if (Array.isArray(mc.trustedHosts)) {
|
|
@@ -10363,6 +10433,7 @@ function recordAndCheck(tool, args, threshold = 3, windowMs = 12e4) {
|
|
|
10363
10433
|
// src/supply-chain/check.ts
|
|
10364
10434
|
init_dist();
|
|
10365
10435
|
import picomatch from "picomatch";
|
|
10436
|
+
import path18 from "path";
|
|
10366
10437
|
|
|
10367
10438
|
// src/supply-chain/osv-index.ts
|
|
10368
10439
|
init_dist();
|
|
@@ -10523,30 +10594,58 @@ async function queryOsvMalicious(queries) {
|
|
|
10523
10594
|
}
|
|
10524
10595
|
|
|
10525
10596
|
// src/supply-chain/registry.ts
|
|
10597
|
+
import { maxSatisfying, satisfies, validRange } from "semver";
|
|
10526
10598
|
var TIMEOUT_MS2 = 1500;
|
|
10527
10599
|
var MAX_BYTES = 2 * 1024 * 1024;
|
|
10528
|
-
async function npmInfo(name, version) {
|
|
10600
|
+
async function npmInfo(name, version, freshWindowMs, raw) {
|
|
10529
10601
|
const base = endpoints().npmRegistry;
|
|
10530
10602
|
if (!base) return null;
|
|
10531
|
-
const
|
|
10603
|
+
const url = `${base}/${name.replaceAll("/", "%2F")}`;
|
|
10604
|
+
const doc = await fetchJson(url, {
|
|
10532
10605
|
timeoutMs: TIMEOUT_MS2,
|
|
10533
10606
|
maxBytes: MAX_BYTES,
|
|
10534
10607
|
headers: { Accept: "application/vnd.npm.install-v1+json" }
|
|
10535
10608
|
});
|
|
10536
10609
|
if (!doc) return null;
|
|
10537
10610
|
const latest = doc["dist-tags"]?.latest;
|
|
10538
|
-
const
|
|
10611
|
+
const aliasStart = raw?.indexOf("@npm:") ?? -1;
|
|
10612
|
+
const target = aliasStart > 0 ? raw.slice(aliasStart + 5) : raw?.replace(/^npm:/, "");
|
|
10613
|
+
const specStart = target?.indexOf("@", 1) ?? -1;
|
|
10614
|
+
const spec = specStart > 0 ? target.slice(specStart + 1) : "";
|
|
10615
|
+
let resolved = version;
|
|
10616
|
+
if (!resolved) {
|
|
10617
|
+
if (!spec) resolved = latest;
|
|
10618
|
+
else if (doc["dist-tags"]?.[spec]) resolved = doc["dist-tags"][spec];
|
|
10619
|
+
else if (validRange(spec)) {
|
|
10620
|
+
resolved = latest && satisfies(latest, spec) ? latest : maxSatisfying(Object.keys(doc.versions ?? {}), spec) ?? void 0;
|
|
10621
|
+
}
|
|
10622
|
+
}
|
|
10539
10623
|
if (!resolved) return null;
|
|
10540
10624
|
const v = doc.versions?.[resolved];
|
|
10541
10625
|
const modifiedMs = doc.modified ? Date.parse(doc.modified) : NaN;
|
|
10542
|
-
|
|
10626
|
+
const info = {
|
|
10543
10627
|
version: resolved,
|
|
10544
|
-
// `modified` bounds the publish time of EVERY version from above, but it
|
|
10545
|
-
// only approximates the age of the newest one: an older pinned version
|
|
10546
|
-
// gets no age (it is at least as old as `latest`, so never "new").
|
|
10547
|
-
publishedAtMs: Number.isFinite(modifiedMs) && resolved === latest ? modifiedMs : void 0,
|
|
10548
10628
|
hasInstallScript: v?.hasInstallScript === true
|
|
10549
10629
|
};
|
|
10630
|
+
if (!Number.isFinite(modifiedMs)) return info;
|
|
10631
|
+
if (Date.now() - modifiedMs >= freshWindowMs) {
|
|
10632
|
+
info.publishedAtMs = resolved === latest ? modifiedMs : void 0;
|
|
10633
|
+
return info;
|
|
10634
|
+
}
|
|
10635
|
+
try {
|
|
10636
|
+
const full = await fetchJson(url, { timeoutMs: TIMEOUT_MS2, maxBytes: MAX_BYTES });
|
|
10637
|
+
const t = full?.time?.[resolved];
|
|
10638
|
+
const tMs = t ? Date.parse(t) : NaN;
|
|
10639
|
+
if (Number.isFinite(tMs)) {
|
|
10640
|
+
info.publishedAtMs = tMs;
|
|
10641
|
+
return info;
|
|
10642
|
+
}
|
|
10643
|
+
info.ageFallback = `no publish time for ${resolved} in the full document`;
|
|
10644
|
+
} catch (err) {
|
|
10645
|
+
info.ageFallback = `full document ${err.message}`;
|
|
10646
|
+
}
|
|
10647
|
+
info.publishedAtMs = modifiedMs;
|
|
10648
|
+
return info;
|
|
10550
10649
|
}
|
|
10551
10650
|
async function pypiInfo(name, version) {
|
|
10552
10651
|
const base = endpoints().pypi;
|
|
@@ -10561,8 +10660,36 @@ async function pypiInfo(name, version) {
|
|
|
10561
10660
|
publishedAtMs: times.length > 0 ? Math.min(...times) : void 0
|
|
10562
10661
|
};
|
|
10563
10662
|
}
|
|
10564
|
-
async function registryInfo(eco, name, version) {
|
|
10565
|
-
return eco === "npm" ? npmInfo(name, version) : pypiInfo(name, version);
|
|
10663
|
+
async function registryInfo(eco, name, version, freshWindowMs, raw) {
|
|
10664
|
+
return eco === "npm" ? npmInfo(name, version, freshWindowMs, raw) : pypiInfo(name, version);
|
|
10665
|
+
}
|
|
10666
|
+
|
|
10667
|
+
// src/supply-chain/local-resolve.ts
|
|
10668
|
+
import fs16 from "fs";
|
|
10669
|
+
import path17 from "path";
|
|
10670
|
+
var MAX_LEVELS = 32;
|
|
10671
|
+
var LOCAL_FIRST_MANAGERS = /* @__PURE__ */ new Set(["npx", "bunx", "npm exec"]);
|
|
10672
|
+
function resolveInstalled(name, cwd) {
|
|
10673
|
+
if (!cwd || !path17.isAbsolute(cwd)) return null;
|
|
10674
|
+
if (!/^(?:@[^/\\]+\/)?[^/\\]+$/.test(name)) return null;
|
|
10675
|
+
let dir = path17.resolve(cwd);
|
|
10676
|
+
for (let i = 0; i < MAX_LEVELS; i++) {
|
|
10677
|
+
const pkgDir = path17.join(dir, "node_modules", ...name.split("/"));
|
|
10678
|
+
const manifest = path17.join(pkgDir, "package.json");
|
|
10679
|
+
try {
|
|
10680
|
+
const raw = fs16.readFileSync(manifest, "utf8");
|
|
10681
|
+
const parsed = JSON.parse(raw);
|
|
10682
|
+
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
10683
|
+
return { version: parsed.version, dir: pkgDir };
|
|
10684
|
+
}
|
|
10685
|
+
return null;
|
|
10686
|
+
} catch {
|
|
10687
|
+
}
|
|
10688
|
+
const parent = path17.dirname(dir);
|
|
10689
|
+
if (parent === dir) break;
|
|
10690
|
+
dir = parent;
|
|
10691
|
+
}
|
|
10692
|
+
return null;
|
|
10566
10693
|
}
|
|
10567
10694
|
|
|
10568
10695
|
// src/supply-chain/check.ts
|
|
@@ -10633,13 +10760,45 @@ async function maliciousFor(p, version, cfg, misses, networkCapped) {
|
|
|
10633
10760
|
}
|
|
10634
10761
|
return null;
|
|
10635
10762
|
}
|
|
10636
|
-
|
|
10763
|
+
function checkInstalled(p, version, misses) {
|
|
10764
|
+
const local = lookupIndex(p.ecosystem, p.name);
|
|
10765
|
+
if (local.status === "unavailable") {
|
|
10766
|
+
misses.push(`${label(p, version)}: installed copy, local index unavailable`);
|
|
10767
|
+
return [];
|
|
10768
|
+
}
|
|
10769
|
+
if (local.status !== "hit") return [];
|
|
10770
|
+
const covering = local.entries.filter((e) => entryCovers(e, version) === true).map((e) => e.id);
|
|
10771
|
+
if (covering.length === 0) return [];
|
|
10772
|
+
return [
|
|
10773
|
+
{
|
|
10774
|
+
pkg: p,
|
|
10775
|
+
version,
|
|
10776
|
+
installed: true,
|
|
10777
|
+
kind: "malicious",
|
|
10778
|
+
advisories: covering,
|
|
10779
|
+
detail: `installed ${label(p, version)} (${covering.join(", ")})`
|
|
10780
|
+
}
|
|
10781
|
+
];
|
|
10782
|
+
}
|
|
10783
|
+
async function checkOne(p, cfg, misses, networkCapped, cwd) {
|
|
10784
|
+
if (p.ecosystem === "npm" && LOCAL_FIRST_MANAGERS.has(p.manager) && p.localCwd !== null && (p.raw === p.name || p.version)) {
|
|
10785
|
+
const localCwd = cwd && path18.isAbsolute(cwd) ? (p.localCwd ?? []).reduce((dir, step) => path18.resolve(dir, step), cwd) : void 0;
|
|
10786
|
+
const installed = resolveInstalled(p.name, localCwd);
|
|
10787
|
+
if (installed && (!p.version || p.version === installed.version))
|
|
10788
|
+
return checkInstalled(p, installed.version, misses);
|
|
10789
|
+
}
|
|
10637
10790
|
const findings = [];
|
|
10638
10791
|
let info = null;
|
|
10639
|
-
|
|
10792
|
+
const wantsRegistry = cfg.newPackage !== "off" || cfg.installScript !== "off";
|
|
10793
|
+
if (wantsRegistry && !networkCapped) {
|
|
10640
10794
|
try {
|
|
10641
|
-
|
|
10795
|
+
const windowMs = cfg.newPackage === "off" ? 0 : cfg.maxAgeHours * 36e5;
|
|
10796
|
+
info = await registryInfo(p.ecosystem, p.name, p.version, windowMs, p.raw);
|
|
10642
10797
|
if (!info) misses.push(`${label(p, p.version)}: registry metadata unavailable`);
|
|
10798
|
+
else if (info.ageFallback)
|
|
10799
|
+
misses.push(
|
|
10800
|
+
`${label(p, info.version)}: age from the package modified time (${info.ageFallback})`
|
|
10801
|
+
);
|
|
10643
10802
|
} catch (err) {
|
|
10644
10803
|
misses.push(`${label(p, p.version)}: registry ${err.message}`);
|
|
10645
10804
|
}
|
|
@@ -10647,18 +10806,18 @@ async function checkOne(p, cfg, misses, networkCapped) {
|
|
|
10647
10806
|
const version = p.version ?? info?.version;
|
|
10648
10807
|
const mal = await maliciousFor(p, version, cfg, misses, networkCapped);
|
|
10649
10808
|
if (mal) findings.push(mal);
|
|
10650
|
-
if (info?.publishedAtMs !== void 0) {
|
|
10809
|
+
if (cfg.newPackage !== "off" && info?.publishedAtMs !== void 0) {
|
|
10651
10810
|
const ageH = (Date.now() - info.publishedAtMs) / 36e5;
|
|
10652
10811
|
if (ageH >= 0 && ageH < cfg.maxAgeHours) {
|
|
10653
10812
|
findings.push({
|
|
10654
10813
|
pkg: p,
|
|
10655
10814
|
version,
|
|
10656
10815
|
kind: "new",
|
|
10657
|
-
detail: `${label(p, version)} was published ${Math.max(1, Math.round(ageH))}h ago (under ${cfg.maxAgeHours}h)`
|
|
10816
|
+
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)`
|
|
10658
10817
|
});
|
|
10659
10818
|
}
|
|
10660
10819
|
}
|
|
10661
|
-
if (info?.hasInstallScript) {
|
|
10820
|
+
if (cfg.installScript !== "off" && info?.hasInstallScript) {
|
|
10662
10821
|
findings.push({
|
|
10663
10822
|
pkg: p,
|
|
10664
10823
|
version,
|
|
@@ -10668,7 +10827,7 @@ async function checkOne(p, cfg, misses, networkCapped) {
|
|
|
10668
10827
|
}
|
|
10669
10828
|
return findings;
|
|
10670
10829
|
}
|
|
10671
|
-
async function runPackageCheck(toolName, args, cfg) {
|
|
10830
|
+
async function runPackageCheck(toolName, args, cfg, cwd) {
|
|
10672
10831
|
if (!cfg.enabled) return ALLOW;
|
|
10673
10832
|
try {
|
|
10674
10833
|
const command = commandOf(toolName, args);
|
|
@@ -10679,7 +10838,7 @@ async function runPackageCheck(toolName, args, cfg) {
|
|
|
10679
10838
|
const misses = [];
|
|
10680
10839
|
const checked = requests;
|
|
10681
10840
|
const settled = await Promise.allSettled(
|
|
10682
|
-
checked.map((p, i) => checkOne(p, cfg, misses, i >= MAX_NETWORK_PACKAGES))
|
|
10841
|
+
checked.map((p, i) => checkOne(p, cfg, misses, i >= MAX_NETWORK_PACKAGES, cwd))
|
|
10683
10842
|
);
|
|
10684
10843
|
const findings = [];
|
|
10685
10844
|
settled.forEach((s, i) => {
|
|
@@ -11087,7 +11246,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
11087
11246
|
}
|
|
11088
11247
|
const packageCheck = config.policy.packageCheck;
|
|
11089
11248
|
if (packageCheck?.enabled && !isManual) {
|
|
11090
|
-
const pkg = await runPackageCheck(toolName, args, packageCheck);
|
|
11249
|
+
const pkg = await runPackageCheck(toolName, args, packageCheck, options?.cwd);
|
|
11091
11250
|
if (pkg.misses.length > 0) {
|
|
11092
11251
|
appendToLog(HOOK_DEBUG_LOG, {
|
|
11093
11252
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
package/dist/scan-ink.mjs
CHANGED
|
@@ -196,7 +196,10 @@ var ConfigFileSchema = z.object({
|
|
|
196
196
|
packageCheck: z.object({
|
|
197
197
|
enabled: z.boolean().optional(),
|
|
198
198
|
onMalicious: z.enum(["block", "review"]).optional(),
|
|
199
|
+
// 2.27.0 shape, still accepted: false = newPackage and installScript off.
|
|
199
200
|
registrySignals: z.boolean().optional(),
|
|
201
|
+
newPackage: z.enum(["review", "off"]).optional(),
|
|
202
|
+
installScript: z.enum(["review", "off"]).optional(),
|
|
200
203
|
maxAgeHours: z.number().min(0).optional(),
|
|
201
204
|
onlineFallback: z.boolean().optional(),
|
|
202
205
|
allow: z.array(z.string()).optional()
|
|
@@ -2800,14 +2803,19 @@ var TUNING_FIELDS = [
|
|
|
2800
2803
|
{ checkId: "loading.skill-tamper", name: "roots", legacy: ["skillPinning", "roots"] },
|
|
2801
2804
|
{
|
|
2802
2805
|
checkId: "loading.malicious-package",
|
|
2803
|
-
name: "
|
|
2804
|
-
legacy: ["packageCheck", "
|
|
2806
|
+
name: "newPackage",
|
|
2807
|
+
legacy: ["packageCheck", "newPackage"]
|
|
2805
2808
|
},
|
|
2806
2809
|
{
|
|
2807
2810
|
checkId: "loading.malicious-package",
|
|
2808
2811
|
name: "maxAgeHours",
|
|
2809
2812
|
legacy: ["packageCheck", "maxAgeHours"]
|
|
2810
2813
|
},
|
|
2814
|
+
{
|
|
2815
|
+
checkId: "loading.malicious-package",
|
|
2816
|
+
name: "installScript",
|
|
2817
|
+
legacy: ["packageCheck", "installScript"]
|
|
2818
|
+
},
|
|
2811
2819
|
{
|
|
2812
2820
|
checkId: "loading.malicious-package",
|
|
2813
2821
|
name: "onlineFallback",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node9/proxy",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.28.1",
|
|
4
4
|
"description": "Access control for AI agents. Set what Claude Code, Codex, Gemini, Cursor and any MCP server are allowed to do, review risky actions before they run, and keep every action on the record.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
"picomatch": "^4.0.3",
|
|
100
100
|
"react": "^19.2.6",
|
|
101
101
|
"safe-regex2": "^5.1.0",
|
|
102
|
+
"semver": "^7.8.5",
|
|
102
103
|
"smol-toml": "^1.6.1",
|
|
103
104
|
"string-width": "^8.3.0",
|
|
104
105
|
"yaml": "^2.9.0",
|
|
@@ -118,6 +119,7 @@
|
|
|
118
119
|
"@types/node": "^26.4.1",
|
|
119
120
|
"@types/picomatch": "^4.0.2",
|
|
120
121
|
"@types/react": "^19.2.14",
|
|
122
|
+
"@types/semver": "^7.8.0",
|
|
121
123
|
"@vitest/coverage-v8": "^5.0.0",
|
|
122
124
|
"cross-env": "^10.1.0",
|
|
123
125
|
"ink-testing-library": "^4.0.0",
|