@node9/proxy 2.16.0 → 2.16.2
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/dist/cli.js +274 -284
- package/dist/cli.mjs +274 -284
- package/dist/dashboard.mjs +4 -4
- package/dist/index.js +220 -215
- package/dist/index.mjs +220 -215
- package/package.json +1 -1
package/dist/dashboard.mjs
CHANGED
|
@@ -1944,7 +1944,7 @@ function matchCanaryArgs(args, values) {
|
|
|
1944
1944
|
return null;
|
|
1945
1945
|
}
|
|
1946
1946
|
}
|
|
1947
|
-
var B58, B58_INDEX, XPRV_VERSIONS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, GREP_SHAPE, PATTERN_VERBS, PATTERN_VERB_NAMES, GREP_FILE_OPERANDS, READER_VALUE_LETTERS, FILE_OPERAND_FLAGS, SCP_VALUE_FLAGS, TAR_VALUE_LETTERS, ZIP_VALUE_LETTERS, RSYNC_VALUE_LETTERS, RSYNC_SKIP, CP_VALUE_LETTERS, INSTALL_VALUE_LETTERS, COPY_VERBS, TAR_MODE_WORD, COPY_VERB_HEADS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, FS_OP_CACHE_MAX, fsOpCache, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, NONE, UNKNOWN,
|
|
1947
|
+
var B58, B58_INDEX, XPRV_VERSIONS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, GREP_SHAPE, PATTERN_VERBS, PATTERN_VERB_NAMES, GREP_FILE_OPERANDS, READER_VALUE_LETTERS, FILE_OPERAND_FLAGS, SCP_VALUE_FLAGS, TAR_VALUE_LETTERS, ZIP_VALUE_LETTERS, RSYNC_VALUE_LETTERS, RSYNC_SKIP, CP_VALUE_LETTERS, INSTALL_VALUE_LETTERS, COPY_VERBS, TAR_MODE_WORD, COPY_VERB_HEADS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, FS_OP_CACHE_MAX, fsOpCache, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, NONE, UNKNOWN, SSRF_MAX_HOST, METADATA_ADDRESSES, METADATA_HOSTNAMES, v4Octets, SOURCE_COMMANDS, TERMINAL_ESCAPE_RE, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, CANARY_MIN_LENGTH, MAX_TEXT, MAX_DEPTH2, MAX_JSON_PARSE, URL_DEPTH, B64_DEPTH, MIN_SEGMENT, SEPARATORS, stripSeparators, looksText, CANARY_DECODERS, VIEWS, LONG_OUTPUT_THRESHOLD_BYTES;
|
|
1948
1948
|
var init_dist = __esm({
|
|
1949
1949
|
"packages/policy-engine/dist/index.mjs"() {
|
|
1950
1950
|
"use strict";
|
|
@@ -3136,7 +3136,6 @@ var init_dist = __esm({
|
|
|
3136
3136
|
positionalAfter = (words, from, to = words.length) => positionedArgs(words, from, to).map((a) => a.value);
|
|
3137
3137
|
NONE = { kind: "none" };
|
|
3138
3138
|
UNKNOWN = { kind: "unknown" };
|
|
3139
|
-
SOURCE_COMMANDS = /* @__PURE__ */ new Set([...FS_READ_TOOLS, "tee"]);
|
|
3140
3139
|
SSRF_MAX_HOST = 253;
|
|
3141
3140
|
METADATA_ADDRESSES = /* @__PURE__ */ new Set([
|
|
3142
3141
|
"169.254.169.254",
|
|
@@ -3158,6 +3157,7 @@ var init_dist = __esm({
|
|
|
3158
3157
|
const p = a.split(".");
|
|
3159
3158
|
return p.length === 4 ? p.map(Number) : null;
|
|
3160
3159
|
};
|
|
3160
|
+
SOURCE_COMMANDS = /* @__PURE__ */ new Set([...FS_READ_TOOLS, "tee"]);
|
|
3161
3161
|
TERMINAL_ESCAPE_RE = /\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
|
|
3162
3162
|
aws_default = {
|
|
3163
3163
|
name: "aws",
|
|
@@ -4756,14 +4756,14 @@ function getCredentials() {
|
|
|
4756
4756
|
const creds = JSON.parse(fs6.readFileSync(credPath, "utf-8"));
|
|
4757
4757
|
const profileName = process.env.NODE9_PROFILE || "default";
|
|
4758
4758
|
const profile = creds[profileName];
|
|
4759
|
-
if (profile?.apiKey) {
|
|
4759
|
+
if (typeof profile?.apiKey === "string" && profile.apiKey.length > 0) {
|
|
4760
4760
|
return {
|
|
4761
4761
|
apiKey: profile.apiKey,
|
|
4762
4762
|
apiUrl: safeApiUrl(profile.apiUrl || DEFAULT_API_URL, noteRejectedApiUrl),
|
|
4763
4763
|
localOnly: profile.localOnly === true || profileName !== "default"
|
|
4764
4764
|
};
|
|
4765
4765
|
}
|
|
4766
|
-
if (creds.apiKey) {
|
|
4766
|
+
if (typeof creds.apiKey === "string" && creds.apiKey.length > 0) {
|
|
4767
4767
|
return {
|
|
4768
4768
|
apiKey: creds.apiKey,
|
|
4769
4769
|
apiUrl: safeApiUrl(creds.apiUrl || DEFAULT_API_URL, noteRejectedApiUrl),
|
package/dist/index.js
CHANGED
|
@@ -3446,6 +3446,211 @@ function analyzeShellCommand(command) {
|
|
|
3446
3446
|
}
|
|
3447
3447
|
return { actions, paths, allTokens };
|
|
3448
3448
|
}
|
|
3449
|
+
var SSRF_MAX_HOST = 253;
|
|
3450
|
+
function parseComponent(s) {
|
|
3451
|
+
if (!s) return null;
|
|
3452
|
+
if (/^0[xX][0-9a-fA-F]+$/.test(s)) return parseInt(s.slice(2), 16);
|
|
3453
|
+
if (s === "0") return 0;
|
|
3454
|
+
if (/^0[0-7]+$/.test(s)) return parseInt(s.slice(1), 8);
|
|
3455
|
+
if (/^[1-9][0-9]*$/.test(s)) return Number(s);
|
|
3456
|
+
return null;
|
|
3457
|
+
}
|
|
3458
|
+
function parseIpv4(input) {
|
|
3459
|
+
let s = input;
|
|
3460
|
+
if (s.endsWith(".")) s = s.slice(0, -1);
|
|
3461
|
+
if (!s) return null;
|
|
3462
|
+
const parts = s.split(".");
|
|
3463
|
+
if (parts.length > 4) return null;
|
|
3464
|
+
const vals = [];
|
|
3465
|
+
for (const p of parts) {
|
|
3466
|
+
const v = parseComponent(p);
|
|
3467
|
+
if (v === null || !Number.isFinite(v) || v < 0) return null;
|
|
3468
|
+
vals.push(v);
|
|
3469
|
+
}
|
|
3470
|
+
const n = vals.length;
|
|
3471
|
+
for (let i = 0; i < n - 1; i++) if (vals[i] > 255) return null;
|
|
3472
|
+
const last = vals[n - 1];
|
|
3473
|
+
const remainingBytes = 4 - (n - 1);
|
|
3474
|
+
const limit = Math.pow(256, remainingBytes);
|
|
3475
|
+
if (last >= limit) return null;
|
|
3476
|
+
let value = last;
|
|
3477
|
+
for (let i = 0; i < n - 1; i++) value += vals[i] * Math.pow(256, 3 - i);
|
|
3478
|
+
if (value > 4294967295) return null;
|
|
3479
|
+
return [value >>> 24 & 255, value >>> 16 & 255, value >>> 8 & 255, value & 255].join(".");
|
|
3480
|
+
}
|
|
3481
|
+
function expandIpv6(input) {
|
|
3482
|
+
const s = input.toLowerCase();
|
|
3483
|
+
if (!/^[0-9a-f:.]+$/.test(s)) return null;
|
|
3484
|
+
if ((s.match(/::/g) ?? []).length > 1) return null;
|
|
3485
|
+
let head = s;
|
|
3486
|
+
let tailV4 = null;
|
|
3487
|
+
const lastColon = s.lastIndexOf(":");
|
|
3488
|
+
const afterLast = s.slice(lastColon + 1);
|
|
3489
|
+
if (afterLast.includes(".")) {
|
|
3490
|
+
const dotted = parseIpv4(afterLast);
|
|
3491
|
+
if (!dotted) return null;
|
|
3492
|
+
const o = dotted.split(".").map(Number);
|
|
3493
|
+
tailV4 = [o[0] << 8 | o[1], o[2] << 8 | o[3]];
|
|
3494
|
+
head = s.slice(0, lastColon + 1) + "0";
|
|
3495
|
+
}
|
|
3496
|
+
const [lhs, rhs] = head.includes("::") ? head.split("::") : [head, null];
|
|
3497
|
+
const toGroups = (part) => {
|
|
3498
|
+
if (!part) return [];
|
|
3499
|
+
const out = [];
|
|
3500
|
+
for (const g of part.split(":")) {
|
|
3501
|
+
if (!/^[0-9a-f]{1,4}$/.test(g)) return null;
|
|
3502
|
+
out.push(parseInt(g, 16));
|
|
3503
|
+
}
|
|
3504
|
+
return out;
|
|
3505
|
+
};
|
|
3506
|
+
const left = toGroups(lhs);
|
|
3507
|
+
if (left === null) return null;
|
|
3508
|
+
let right = [];
|
|
3509
|
+
if (rhs !== null) {
|
|
3510
|
+
const r = toGroups(rhs);
|
|
3511
|
+
if (r === null) return null;
|
|
3512
|
+
right = r;
|
|
3513
|
+
}
|
|
3514
|
+
if (tailV4) {
|
|
3515
|
+
if (rhs !== null) right = right.slice(0, -1).concat(tailV4);
|
|
3516
|
+
else left.splice(left.length - 1, 1, ...tailV4);
|
|
3517
|
+
}
|
|
3518
|
+
const groups = rhs === null ? left : left.concat(new Array(8 - left.length - right.length).fill(0), right);
|
|
3519
|
+
if (rhs === null && groups.length !== 8) return null;
|
|
3520
|
+
if (rhs !== null && left.length + right.length > 8) return null;
|
|
3521
|
+
if (groups.length !== 8) return null;
|
|
3522
|
+
return groups;
|
|
3523
|
+
}
|
|
3524
|
+
function compressIpv6(g) {
|
|
3525
|
+
let bestStart = -1;
|
|
3526
|
+
let bestLen = 0;
|
|
3527
|
+
let i = 0;
|
|
3528
|
+
while (i < 8) {
|
|
3529
|
+
if (g[i] !== 0) {
|
|
3530
|
+
i++;
|
|
3531
|
+
continue;
|
|
3532
|
+
}
|
|
3533
|
+
let j = i;
|
|
3534
|
+
while (j < 8 && g[j] === 0) j++;
|
|
3535
|
+
if (j - i > bestLen) {
|
|
3536
|
+
bestLen = j - i;
|
|
3537
|
+
bestStart = i;
|
|
3538
|
+
}
|
|
3539
|
+
i = j;
|
|
3540
|
+
}
|
|
3541
|
+
const hex = g.map((x) => x.toString(16));
|
|
3542
|
+
if (bestLen < 2) return hex.join(":");
|
|
3543
|
+
return hex.slice(0, bestStart).join(":") + "::" + hex.slice(bestStart + bestLen).join(":");
|
|
3544
|
+
}
|
|
3545
|
+
function normalizeIpLiteral(host) {
|
|
3546
|
+
try {
|
|
3547
|
+
if (typeof host !== "string") return null;
|
|
3548
|
+
let s = host.trim();
|
|
3549
|
+
if (!s || s.length > SSRF_MAX_HOST) return null;
|
|
3550
|
+
if (s.startsWith("[") && s.endsWith("]")) s = s.slice(1, -1);
|
|
3551
|
+
const zone = s.indexOf("%");
|
|
3552
|
+
if (zone >= 0) s = s.slice(0, zone);
|
|
3553
|
+
if (!s) return null;
|
|
3554
|
+
if (s.includes(":")) {
|
|
3555
|
+
const g = expandIpv6(s);
|
|
3556
|
+
if (!g) return null;
|
|
3557
|
+
const mapped = g.slice(0, 5).every((x) => x === 0) && g[5] === 65535;
|
|
3558
|
+
if (mapped) {
|
|
3559
|
+
return [g[6] >> 8 & 255, g[6] & 255, g[7] >> 8 & 255, g[7] & 255].join(".");
|
|
3560
|
+
}
|
|
3561
|
+
return compressIpv6(g);
|
|
3562
|
+
}
|
|
3563
|
+
return parseIpv4(s);
|
|
3564
|
+
} catch {
|
|
3565
|
+
return null;
|
|
3566
|
+
}
|
|
3567
|
+
}
|
|
3568
|
+
var METADATA_ADDRESSES = /* @__PURE__ */ new Set([
|
|
3569
|
+
"169.254.169.254",
|
|
3570
|
+
// AWS / Azure / DigitalOcean / OpenStack IMDS
|
|
3571
|
+
"169.254.170.2",
|
|
3572
|
+
// AWS ECS task role
|
|
3573
|
+
"168.63.129.16",
|
|
3574
|
+
// Azure WireServer
|
|
3575
|
+
"fd00:ec2::254",
|
|
3576
|
+
// AWS IMDS over IPv6 (inside fc00::/7, which is NOT a tier)
|
|
3577
|
+
// Alibaba Cloud IMDS. It sits INSIDE 100.64/10, so before this line it was
|
|
3578
|
+
// classified as cgnat and therefore exemptable: one ssrfAllow entry opened a
|
|
3579
|
+
// live credential endpoint. It has to be named here, above the range check,
|
|
3580
|
+
// and it is the reason relaxing cgnat is safe.
|
|
3581
|
+
"100.100.100.200"
|
|
3582
|
+
]);
|
|
3583
|
+
var STRICT_TIERS = /* @__PURE__ */ new Set(["private", "unspecified", "cgnat"]);
|
|
3584
|
+
function isStrictGatedTier(tier) {
|
|
3585
|
+
return STRICT_TIERS.has(tier);
|
|
3586
|
+
}
|
|
3587
|
+
function ssrfReason(m, asWritten) {
|
|
3588
|
+
return `Blocked: ${asWritten} is ${TIER_REASON[m.tier]}` + (m.normalized && m.normalized !== asWritten ? ` (${m.normalized})` : "") + (m.overridable ? "." : ". This address cannot be allowlisted.");
|
|
3589
|
+
}
|
|
3590
|
+
var METADATA_HOSTNAMES = /* @__PURE__ */ new Set(["metadata.google.internal", "metadata.goog", "metadata"]);
|
|
3591
|
+
var v4Octets = (a) => {
|
|
3592
|
+
const p = a.split(".");
|
|
3593
|
+
return p.length === 4 ? p.map(Number) : null;
|
|
3594
|
+
};
|
|
3595
|
+
function classifySsrf(host) {
|
|
3596
|
+
try {
|
|
3597
|
+
if (typeof host !== "string" || !host) return null;
|
|
3598
|
+
const lower = host.trim().toLowerCase().replace(/\.$/, "");
|
|
3599
|
+
const ip = normalizeIpLiteral(host);
|
|
3600
|
+
if (ip === null) {
|
|
3601
|
+
return METADATA_HOSTNAMES.has(lower) ? { tier: "metadata", overridable: false, kind: "hostname" } : null;
|
|
3602
|
+
}
|
|
3603
|
+
const hit = (tier, overridable) => ({
|
|
3604
|
+
tier,
|
|
3605
|
+
overridable,
|
|
3606
|
+
kind: "address",
|
|
3607
|
+
normalized: ip
|
|
3608
|
+
});
|
|
3609
|
+
if (METADATA_ADDRESSES.has(ip)) return hit("metadata", false);
|
|
3610
|
+
const o = v4Octets(ip);
|
|
3611
|
+
if (o) {
|
|
3612
|
+
if (o[0] === 0 && o[1] === 0 && o[2] === 0 && o[3] === 0) return hit("unspecified", true);
|
|
3613
|
+
if (o[0] === 169 && o[1] === 254) return hit("link-local", false);
|
|
3614
|
+
if (o[0] >= 224 && o[0] <= 239) return hit("multicast", false);
|
|
3615
|
+
if (o[0] === 100 && o[1] >= 64 && o[1] <= 127) return hit("cgnat", true);
|
|
3616
|
+
if (o[0] === 127) return hit("private", true);
|
|
3617
|
+
if (o[0] === 10) return hit("private", true);
|
|
3618
|
+
if (o[0] === 192 && o[1] === 168) return hit("private", true);
|
|
3619
|
+
if (o[0] === 172 && o[1] >= 16 && o[1] <= 31) return hit("private", true);
|
|
3620
|
+
return null;
|
|
3621
|
+
}
|
|
3622
|
+
const g = expandIpv6(ip);
|
|
3623
|
+
if (!g) return null;
|
|
3624
|
+
if (g.every((x) => x === 0)) return hit("unspecified", true);
|
|
3625
|
+
if ((g[0] & 65472) === 65152) return hit("link-local", false);
|
|
3626
|
+
if ((g[0] & 65280) === 65280) return hit("multicast", false);
|
|
3627
|
+
if (g.slice(0, 7).every((x) => x === 0) && g[7] === 1) return hit("private", true);
|
|
3628
|
+
return null;
|
|
3629
|
+
} catch {
|
|
3630
|
+
return null;
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
var TIER_REASON = {
|
|
3634
|
+
metadata: "a cloud instance-metadata endpoint, the classic credential-theft target",
|
|
3635
|
+
"link-local": "a link-local address",
|
|
3636
|
+
multicast: "a multicast address",
|
|
3637
|
+
unspecified: "the unspecified address, which reaches this host",
|
|
3638
|
+
cgnat: "a carrier-grade NAT address",
|
|
3639
|
+
private: "a loopback or private address"
|
|
3640
|
+
};
|
|
3641
|
+
function ssrfFloor(tokens, opts = {}) {
|
|
3642
|
+
const exempt = new Set(
|
|
3643
|
+
(opts.ssrfAllow ?? []).map((e) => normalizeIpLiteral(e) ?? e.trim().toLowerCase())
|
|
3644
|
+
);
|
|
3645
|
+
for (const { token, binary } of tokens) {
|
|
3646
|
+
const m = classifySsrf(token);
|
|
3647
|
+
if (!m) continue;
|
|
3648
|
+
if (isStrictGatedTier(m.tier) && !opts.ssrfStrict) continue;
|
|
3649
|
+
if (m.overridable && m.normalized && exempt.has(m.normalized)) continue;
|
|
3650
|
+
return { ...m, host: token, binary, reason: ssrfReason(m, token) };
|
|
3651
|
+
}
|
|
3652
|
+
return null;
|
|
3653
|
+
}
|
|
3449
3654
|
var DEFAULT_EGRESS_ALLOWLIST = [
|
|
3450
3655
|
// node9's own control plane (api, app, dev-api, staging and the apex).
|
|
3451
3656
|
// Without it, turning egress on asks the user to approve node9 itself.
|
|
@@ -3484,15 +3689,20 @@ function matchesAny(host, patterns) {
|
|
|
3484
3689
|
for (const p of patterns) if (hostMatches(host, p)) return true;
|
|
3485
3690
|
return false;
|
|
3486
3691
|
}
|
|
3692
|
+
var PRIVATE_HOST_SUFFIXES = [".local", ".internal", ".localhost"];
|
|
3693
|
+
function isUniqueLocalV6(host) {
|
|
3694
|
+
const ip = normalizeIpLiteral(host);
|
|
3695
|
+
if (!ip || !ip.includes(":")) return false;
|
|
3696
|
+
const g = expandIpv6(ip);
|
|
3697
|
+
return g !== null && (g[0] & 65024) === 64512;
|
|
3698
|
+
}
|
|
3487
3699
|
function isPrivateHost(host) {
|
|
3488
|
-
const h = host.toLowerCase();
|
|
3489
|
-
|
|
3490
|
-
if (
|
|
3491
|
-
if (
|
|
3492
|
-
if (
|
|
3493
|
-
|
|
3494
|
-
if (/^172\.(1[6-9]|2\d|3[01])\./.test(h)) return true;
|
|
3495
|
-
return false;
|
|
3700
|
+
const h = host.trim().toLowerCase();
|
|
3701
|
+
const m = classifySsrf(h);
|
|
3702
|
+
if (m) return m.kind === "address" && (m.tier === "private" || m.tier === "unspecified");
|
|
3703
|
+
if (h === "localhost") return true;
|
|
3704
|
+
if (PRIVATE_HOST_SUFFIXES.some((s) => h.endsWith(s))) return true;
|
|
3705
|
+
return isUniqueLocalV6(h);
|
|
3496
3706
|
}
|
|
3497
3707
|
function evaluateEgress(dests, policy) {
|
|
3498
3708
|
if (!policy.enabled) return null;
|
|
@@ -3835,211 +4045,6 @@ function extractAllSshHosts(tokens) {
|
|
|
3835
4045
|
}
|
|
3836
4046
|
return [...hosts].filter(Boolean);
|
|
3837
4047
|
}
|
|
3838
|
-
var SSRF_MAX_HOST = 253;
|
|
3839
|
-
function parseComponent(s) {
|
|
3840
|
-
if (!s) return null;
|
|
3841
|
-
if (/^0[xX][0-9a-fA-F]+$/.test(s)) return parseInt(s.slice(2), 16);
|
|
3842
|
-
if (s === "0") return 0;
|
|
3843
|
-
if (/^0[0-7]+$/.test(s)) return parseInt(s.slice(1), 8);
|
|
3844
|
-
if (/^[1-9][0-9]*$/.test(s)) return Number(s);
|
|
3845
|
-
return null;
|
|
3846
|
-
}
|
|
3847
|
-
function parseIpv4(input) {
|
|
3848
|
-
let s = input;
|
|
3849
|
-
if (s.endsWith(".")) s = s.slice(0, -1);
|
|
3850
|
-
if (!s) return null;
|
|
3851
|
-
const parts = s.split(".");
|
|
3852
|
-
if (parts.length > 4) return null;
|
|
3853
|
-
const vals = [];
|
|
3854
|
-
for (const p of parts) {
|
|
3855
|
-
const v = parseComponent(p);
|
|
3856
|
-
if (v === null || !Number.isFinite(v) || v < 0) return null;
|
|
3857
|
-
vals.push(v);
|
|
3858
|
-
}
|
|
3859
|
-
const n = vals.length;
|
|
3860
|
-
for (let i = 0; i < n - 1; i++) if (vals[i] > 255) return null;
|
|
3861
|
-
const last = vals[n - 1];
|
|
3862
|
-
const remainingBytes = 4 - (n - 1);
|
|
3863
|
-
const limit = Math.pow(256, remainingBytes);
|
|
3864
|
-
if (last >= limit) return null;
|
|
3865
|
-
let value = last;
|
|
3866
|
-
for (let i = 0; i < n - 1; i++) value += vals[i] * Math.pow(256, 3 - i);
|
|
3867
|
-
if (value > 4294967295) return null;
|
|
3868
|
-
return [value >>> 24 & 255, value >>> 16 & 255, value >>> 8 & 255, value & 255].join(".");
|
|
3869
|
-
}
|
|
3870
|
-
function expandIpv6(input) {
|
|
3871
|
-
const s = input.toLowerCase();
|
|
3872
|
-
if (!/^[0-9a-f:.]+$/.test(s)) return null;
|
|
3873
|
-
if ((s.match(/::/g) ?? []).length > 1) return null;
|
|
3874
|
-
let head = s;
|
|
3875
|
-
let tailV4 = null;
|
|
3876
|
-
const lastColon = s.lastIndexOf(":");
|
|
3877
|
-
const afterLast = s.slice(lastColon + 1);
|
|
3878
|
-
if (afterLast.includes(".")) {
|
|
3879
|
-
const dotted = parseIpv4(afterLast);
|
|
3880
|
-
if (!dotted) return null;
|
|
3881
|
-
const o = dotted.split(".").map(Number);
|
|
3882
|
-
tailV4 = [o[0] << 8 | o[1], o[2] << 8 | o[3]];
|
|
3883
|
-
head = s.slice(0, lastColon + 1) + "0";
|
|
3884
|
-
}
|
|
3885
|
-
const [lhs, rhs] = head.includes("::") ? head.split("::") : [head, null];
|
|
3886
|
-
const toGroups = (part) => {
|
|
3887
|
-
if (!part) return [];
|
|
3888
|
-
const out = [];
|
|
3889
|
-
for (const g of part.split(":")) {
|
|
3890
|
-
if (!/^[0-9a-f]{1,4}$/.test(g)) return null;
|
|
3891
|
-
out.push(parseInt(g, 16));
|
|
3892
|
-
}
|
|
3893
|
-
return out;
|
|
3894
|
-
};
|
|
3895
|
-
const left = toGroups(lhs);
|
|
3896
|
-
if (left === null) return null;
|
|
3897
|
-
let right = [];
|
|
3898
|
-
if (rhs !== null) {
|
|
3899
|
-
const r = toGroups(rhs);
|
|
3900
|
-
if (r === null) return null;
|
|
3901
|
-
right = r;
|
|
3902
|
-
}
|
|
3903
|
-
if (tailV4) {
|
|
3904
|
-
if (rhs !== null) right = right.slice(0, -1).concat(tailV4);
|
|
3905
|
-
else left.splice(left.length - 1, 1, ...tailV4);
|
|
3906
|
-
}
|
|
3907
|
-
const groups = rhs === null ? left : left.concat(new Array(8 - left.length - right.length).fill(0), right);
|
|
3908
|
-
if (rhs === null && groups.length !== 8) return null;
|
|
3909
|
-
if (rhs !== null && left.length + right.length > 8) return null;
|
|
3910
|
-
if (groups.length !== 8) return null;
|
|
3911
|
-
return groups;
|
|
3912
|
-
}
|
|
3913
|
-
function compressIpv6(g) {
|
|
3914
|
-
let bestStart = -1;
|
|
3915
|
-
let bestLen = 0;
|
|
3916
|
-
let i = 0;
|
|
3917
|
-
while (i < 8) {
|
|
3918
|
-
if (g[i] !== 0) {
|
|
3919
|
-
i++;
|
|
3920
|
-
continue;
|
|
3921
|
-
}
|
|
3922
|
-
let j = i;
|
|
3923
|
-
while (j < 8 && g[j] === 0) j++;
|
|
3924
|
-
if (j - i > bestLen) {
|
|
3925
|
-
bestLen = j - i;
|
|
3926
|
-
bestStart = i;
|
|
3927
|
-
}
|
|
3928
|
-
i = j;
|
|
3929
|
-
}
|
|
3930
|
-
const hex = g.map((x) => x.toString(16));
|
|
3931
|
-
if (bestLen < 2) return hex.join(":");
|
|
3932
|
-
return hex.slice(0, bestStart).join(":") + "::" + hex.slice(bestStart + bestLen).join(":");
|
|
3933
|
-
}
|
|
3934
|
-
function normalizeIpLiteral(host) {
|
|
3935
|
-
try {
|
|
3936
|
-
if (typeof host !== "string") return null;
|
|
3937
|
-
let s = host.trim();
|
|
3938
|
-
if (!s || s.length > SSRF_MAX_HOST) return null;
|
|
3939
|
-
if (s.startsWith("[") && s.endsWith("]")) s = s.slice(1, -1);
|
|
3940
|
-
const zone = s.indexOf("%");
|
|
3941
|
-
if (zone >= 0) s = s.slice(0, zone);
|
|
3942
|
-
if (!s) return null;
|
|
3943
|
-
if (s.includes(":")) {
|
|
3944
|
-
const g = expandIpv6(s);
|
|
3945
|
-
if (!g) return null;
|
|
3946
|
-
const mapped = g.slice(0, 5).every((x) => x === 0) && g[5] === 65535;
|
|
3947
|
-
if (mapped) {
|
|
3948
|
-
return [g[6] >> 8 & 255, g[6] & 255, g[7] >> 8 & 255, g[7] & 255].join(".");
|
|
3949
|
-
}
|
|
3950
|
-
return compressIpv6(g);
|
|
3951
|
-
}
|
|
3952
|
-
return parseIpv4(s);
|
|
3953
|
-
} catch {
|
|
3954
|
-
return null;
|
|
3955
|
-
}
|
|
3956
|
-
}
|
|
3957
|
-
var METADATA_ADDRESSES = /* @__PURE__ */ new Set([
|
|
3958
|
-
"169.254.169.254",
|
|
3959
|
-
// AWS / Azure / DigitalOcean / OpenStack IMDS
|
|
3960
|
-
"169.254.170.2",
|
|
3961
|
-
// AWS ECS task role
|
|
3962
|
-
"168.63.129.16",
|
|
3963
|
-
// Azure WireServer
|
|
3964
|
-
"fd00:ec2::254",
|
|
3965
|
-
// AWS IMDS over IPv6 (inside fc00::/7, which is NOT a tier)
|
|
3966
|
-
// Alibaba Cloud IMDS. It sits INSIDE 100.64/10, so before this line it was
|
|
3967
|
-
// classified as cgnat and therefore exemptable: one ssrfAllow entry opened a
|
|
3968
|
-
// live credential endpoint. It has to be named here, above the range check,
|
|
3969
|
-
// and it is the reason relaxing cgnat is safe.
|
|
3970
|
-
"100.100.100.200"
|
|
3971
|
-
]);
|
|
3972
|
-
var STRICT_TIERS = /* @__PURE__ */ new Set(["private", "unspecified", "cgnat"]);
|
|
3973
|
-
function isStrictGatedTier(tier) {
|
|
3974
|
-
return STRICT_TIERS.has(tier);
|
|
3975
|
-
}
|
|
3976
|
-
function ssrfReason(m, asWritten) {
|
|
3977
|
-
return `Blocked: ${asWritten} is ${TIER_REASON[m.tier]}` + (m.normalized && m.normalized !== asWritten ? ` (${m.normalized})` : "") + (m.overridable ? "." : ". This address cannot be allowlisted.");
|
|
3978
|
-
}
|
|
3979
|
-
var METADATA_HOSTNAMES = /* @__PURE__ */ new Set(["metadata.google.internal", "metadata.goog", "metadata"]);
|
|
3980
|
-
var v4Octets = (a) => {
|
|
3981
|
-
const p = a.split(".");
|
|
3982
|
-
return p.length === 4 ? p.map(Number) : null;
|
|
3983
|
-
};
|
|
3984
|
-
function classifySsrf(host) {
|
|
3985
|
-
try {
|
|
3986
|
-
if (typeof host !== "string" || !host) return null;
|
|
3987
|
-
const lower = host.trim().toLowerCase().replace(/\.$/, "");
|
|
3988
|
-
const ip = normalizeIpLiteral(host);
|
|
3989
|
-
if (ip === null) {
|
|
3990
|
-
return METADATA_HOSTNAMES.has(lower) ? { tier: "metadata", overridable: false, kind: "hostname" } : null;
|
|
3991
|
-
}
|
|
3992
|
-
const hit = (tier, overridable) => ({
|
|
3993
|
-
tier,
|
|
3994
|
-
overridable,
|
|
3995
|
-
kind: "address",
|
|
3996
|
-
normalized: ip
|
|
3997
|
-
});
|
|
3998
|
-
if (METADATA_ADDRESSES.has(ip)) return hit("metadata", false);
|
|
3999
|
-
const o = v4Octets(ip);
|
|
4000
|
-
if (o) {
|
|
4001
|
-
if (o[0] === 0 && o[1] === 0 && o[2] === 0 && o[3] === 0) return hit("unspecified", true);
|
|
4002
|
-
if (o[0] === 169 && o[1] === 254) return hit("link-local", false);
|
|
4003
|
-
if (o[0] >= 224 && o[0] <= 239) return hit("multicast", false);
|
|
4004
|
-
if (o[0] === 100 && o[1] >= 64 && o[1] <= 127) return hit("cgnat", true);
|
|
4005
|
-
if (o[0] === 127) return hit("private", true);
|
|
4006
|
-
if (o[0] === 10) return hit("private", true);
|
|
4007
|
-
if (o[0] === 192 && o[1] === 168) return hit("private", true);
|
|
4008
|
-
if (o[0] === 172 && o[1] >= 16 && o[1] <= 31) return hit("private", true);
|
|
4009
|
-
return null;
|
|
4010
|
-
}
|
|
4011
|
-
const g = expandIpv6(ip);
|
|
4012
|
-
if (!g) return null;
|
|
4013
|
-
if (g.every((x) => x === 0)) return hit("unspecified", true);
|
|
4014
|
-
if ((g[0] & 65472) === 65152) return hit("link-local", false);
|
|
4015
|
-
if ((g[0] & 65280) === 65280) return hit("multicast", false);
|
|
4016
|
-
if (g.slice(0, 7).every((x) => x === 0) && g[7] === 1) return hit("private", true);
|
|
4017
|
-
return null;
|
|
4018
|
-
} catch {
|
|
4019
|
-
return null;
|
|
4020
|
-
}
|
|
4021
|
-
}
|
|
4022
|
-
var TIER_REASON = {
|
|
4023
|
-
metadata: "a cloud instance-metadata endpoint, the classic credential-theft target",
|
|
4024
|
-
"link-local": "a link-local address",
|
|
4025
|
-
multicast: "a multicast address",
|
|
4026
|
-
unspecified: "the unspecified address, which reaches this host",
|
|
4027
|
-
cgnat: "a carrier-grade NAT address",
|
|
4028
|
-
private: "a loopback or private address"
|
|
4029
|
-
};
|
|
4030
|
-
function ssrfFloor(tokens, opts = {}) {
|
|
4031
|
-
const exempt = new Set(
|
|
4032
|
-
(opts.ssrfAllow ?? []).map((e) => normalizeIpLiteral(e) ?? e.trim().toLowerCase())
|
|
4033
|
-
);
|
|
4034
|
-
for (const { token, binary } of tokens) {
|
|
4035
|
-
const m = classifySsrf(token);
|
|
4036
|
-
if (!m) continue;
|
|
4037
|
-
if (isStrictGatedTier(m.tier) && !opts.ssrfStrict) continue;
|
|
4038
|
-
if (m.overridable && m.normalized && exempt.has(m.normalized)) continue;
|
|
4039
|
-
return { ...m, host: token, binary, reason: ssrfReason(m, token) };
|
|
4040
|
-
}
|
|
4041
|
-
return null;
|
|
4042
|
-
}
|
|
4043
4048
|
var DESTINATION_ARGS = /* @__PURE__ */ new Map([
|
|
4044
4049
|
["webfetch", ["url"]],
|
|
4045
4050
|
["fetch", ["url", "uri"]],
|
|
@@ -6229,14 +6234,14 @@ function getCredentials() {
|
|
|
6229
6234
|
const creds = JSON.parse(import_fs4.default.readFileSync(credPath, "utf-8"));
|
|
6230
6235
|
const profileName = process.env.NODE9_PROFILE || "default";
|
|
6231
6236
|
const profile = creds[profileName];
|
|
6232
|
-
if (profile?.apiKey) {
|
|
6237
|
+
if (typeof profile?.apiKey === "string" && profile.apiKey.length > 0) {
|
|
6233
6238
|
return {
|
|
6234
6239
|
apiKey: profile.apiKey,
|
|
6235
6240
|
apiUrl: safeApiUrl(profile.apiUrl || DEFAULT_API_URL, noteRejectedApiUrl),
|
|
6236
6241
|
localOnly: profile.localOnly === true || profileName !== "default"
|
|
6237
6242
|
};
|
|
6238
6243
|
}
|
|
6239
|
-
if (creds.apiKey) {
|
|
6244
|
+
if (typeof creds.apiKey === "string" && creds.apiKey.length > 0) {
|
|
6240
6245
|
return {
|
|
6241
6246
|
apiKey: creds.apiKey,
|
|
6242
6247
|
apiUrl: safeApiUrl(creds.apiUrl || DEFAULT_API_URL, noteRejectedApiUrl),
|