@node9/proxy 2.5.0 → 2.6.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/LICENSE +78 -59
- package/README.md +14 -1
- package/dist/cli.js +1384 -1149
- package/dist/cli.mjs +1377 -1142
- package/dist/dashboard.mjs +1039 -128
- package/dist/index.js +122 -51
- package/dist/index.mjs +122 -51
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -4027,6 +4027,10 @@ var COMMAND_CHECK_KEYS = [
|
|
|
4027
4027
|
"evalDynamic",
|
|
4028
4028
|
"pipeChainHigh"
|
|
4029
4029
|
];
|
|
4030
|
+
var CLASS_B_COMMAND_CHECKS = /* @__PURE__ */ new Set([
|
|
4031
|
+
"evalDynamic",
|
|
4032
|
+
"pipeChainHigh"
|
|
4033
|
+
]);
|
|
4030
4034
|
function applyManagedCommandChecks(local, managed, locked) {
|
|
4031
4035
|
const next = { ...local };
|
|
4032
4036
|
for (const key of COMMAND_CHECK_KEYS) {
|
|
@@ -4036,7 +4040,7 @@ function applyManagedCommandChecks(local, managed, locked) {
|
|
|
4036
4040
|
const resolved = floorValue(COMMAND_CHECK_ORDER, local[key], m, {
|
|
4037
4041
|
locked: locked.includes(lockKey)
|
|
4038
4042
|
});
|
|
4039
|
-
if ((key
|
|
4043
|
+
if (CLASS_B_COMMAND_CHECKS.has(key) && resolved === "off") continue;
|
|
4040
4044
|
next[key] = resolved;
|
|
4041
4045
|
}
|
|
4042
4046
|
return next;
|
|
@@ -4177,10 +4181,18 @@ var DANGEROUS_WORDS = [
|
|
|
4177
4181
|
];
|
|
4178
4182
|
var DEFAULT_CONFIG = {
|
|
4179
4183
|
version: "1.0",
|
|
4184
|
+
policySource: "local",
|
|
4180
4185
|
settings: {
|
|
4181
4186
|
mode: "standard",
|
|
4182
4187
|
autoStartDaemon: true,
|
|
4183
|
-
|
|
4188
|
+
// Off by default: this is a DEBUG facility, not part of the product's job.
|
|
4189
|
+
// When on, every tool call appends a row to ~/.node9/hook-debug.log, which
|
|
4190
|
+
// has no production reader and no size ceiling. Turn it on deliberately
|
|
4191
|
+
// (this setting, or NODE9_DEBUG=1) while investigating something.
|
|
4192
|
+
// Error breadcrumbs written from catch blocks are NOT gated by this and
|
|
4193
|
+
// keep working when it is off — see CLAUDE.md "Always write to
|
|
4194
|
+
// hook-debug.log in catch blocks that guard audit trail".
|
|
4195
|
+
enableHookLogDebug: false,
|
|
4184
4196
|
approvalTimeoutMs: 12e4,
|
|
4185
4197
|
// 120-second auto-deny timeout
|
|
4186
4198
|
flightRecorder: true,
|
|
@@ -4440,13 +4452,15 @@ function getCredentials() {
|
|
|
4440
4452
|
if (profile?.apiKey) {
|
|
4441
4453
|
return {
|
|
4442
4454
|
apiKey: profile.apiKey,
|
|
4443
|
-
apiUrl: profile.apiUrl || DEFAULT_API_URL
|
|
4455
|
+
apiUrl: profile.apiUrl || DEFAULT_API_URL,
|
|
4456
|
+
localOnly: profile.localOnly === true || profileName !== "default"
|
|
4444
4457
|
};
|
|
4445
4458
|
}
|
|
4446
4459
|
if (creds.apiKey) {
|
|
4447
4460
|
return {
|
|
4448
4461
|
apiKey: creds.apiKey,
|
|
4449
|
-
apiUrl: creds.apiUrl || DEFAULT_API_URL
|
|
4462
|
+
apiUrl: creds.apiUrl || DEFAULT_API_URL,
|
|
4463
|
+
localOnly: creds.localOnly === true
|
|
4450
4464
|
};
|
|
4451
4465
|
}
|
|
4452
4466
|
}
|
|
@@ -4558,22 +4572,16 @@ function getConfig(cwd) {
|
|
|
4558
4572
|
const i = COMMAND_CHECK_ORDER.indexOf(v ?? "");
|
|
4559
4573
|
return i === -1 ? 1 : i;
|
|
4560
4574
|
};
|
|
4561
|
-
const
|
|
4575
|
+
const pr2Creds = getCredentials();
|
|
4576
|
+
const keyed = !!pr2Creds?.apiKey && pr2Creds.localOnly !== true;
|
|
4577
|
+
const applyLayer = (source, isProject = false, isCloud = false) => {
|
|
4562
4578
|
if (!source) return;
|
|
4563
4579
|
const s = source.settings || {};
|
|
4564
4580
|
const p = source.policy || {};
|
|
4565
|
-
if (s.mode !== void 0) mergedSettings.mode = s.mode;
|
|
4566
4581
|
if (s.autoStartDaemon !== void 0) mergedSettings.autoStartDaemon = s.autoStartDaemon;
|
|
4567
4582
|
if (s.enableHookLogDebug !== void 0)
|
|
4568
4583
|
mergedSettings.enableHookLogDebug = s.enableHookLogDebug;
|
|
4569
|
-
if (s.approvers) mergedSettings.approvers = { ...mergedSettings.approvers, ...s.approvers };
|
|
4570
4584
|
if (s.shipper) mergedSettings.shipper = { ...mergedSettings.shipper, ...s.shipper };
|
|
4571
|
-
if (s.approvalTimeoutMs !== void 0) mergedSettings.approvalTimeoutMs = s.approvalTimeoutMs;
|
|
4572
|
-
if (s.approvalTimeoutSeconds !== void 0 && s.approvalTimeoutMs === void 0)
|
|
4573
|
-
mergedSettings.approvalTimeoutMs = s.approvalTimeoutSeconds * 1e3;
|
|
4574
|
-
if (s.environment !== void 0) mergedSettings.environment = s.environment;
|
|
4575
|
-
if (s.reviewChannel !== void 0) mergedSettings.reviewChannel = s.reviewChannel;
|
|
4576
|
-
if (s.mcpAllowWeakening !== void 0) mergedSettings.mcpAllowWeakening = s.mcpAllowWeakening;
|
|
4577
4585
|
if (s.cloudSyncIntervalHours !== void 0)
|
|
4578
4586
|
mergedSettings.cloudSyncIntervalHours = s.cloudSyncIntervalHours;
|
|
4579
4587
|
if (s.mcpAutoWrap !== void 0) mergedSettings.mcpAutoWrap = s.mcpAutoWrap === true;
|
|
@@ -4581,6 +4589,15 @@ function getConfig(cwd) {
|
|
|
4581
4589
|
mergedSettings.mcpReconcileIntervalMinutes = s.mcpReconcileIntervalMinutes;
|
|
4582
4590
|
if (s.mcpStaleAfterDays !== void 0) mergedSettings.mcpStaleAfterDays = s.mcpStaleAfterDays;
|
|
4583
4591
|
if (s.hud !== void 0) mergedSettings.hud = { ...mergedSettings.hud, ...s.hud };
|
|
4592
|
+
if (keyed && !isCloud) return;
|
|
4593
|
+
if (s.mode !== void 0) mergedSettings.mode = s.mode;
|
|
4594
|
+
if (s.approvers) mergedSettings.approvers = { ...mergedSettings.approvers, ...s.approvers };
|
|
4595
|
+
if (s.approvalTimeoutMs !== void 0) mergedSettings.approvalTimeoutMs = s.approvalTimeoutMs;
|
|
4596
|
+
if (s.approvalTimeoutSeconds !== void 0 && s.approvalTimeoutMs === void 0)
|
|
4597
|
+
mergedSettings.approvalTimeoutMs = s.approvalTimeoutSeconds * 1e3;
|
|
4598
|
+
if (s.environment !== void 0) mergedSettings.environment = s.environment;
|
|
4599
|
+
if (s.reviewChannel !== void 0) mergedSettings.reviewChannel = s.reviewChannel;
|
|
4600
|
+
if (s.mcpAllowWeakening !== void 0) mergedSettings.mcpAllowWeakening = s.mcpAllowWeakening;
|
|
4584
4601
|
if (p.sandboxPaths) mergedPolicy.sandboxPaths.push(...p.sandboxPaths);
|
|
4585
4602
|
if (p.ignoredTools) mergedPolicy.ignoredTools.push(...p.ignoredTools);
|
|
4586
4603
|
if (p.dangerousWords) mergedPolicy.dangerousWords = [...p.dangerousWords];
|
|
@@ -4688,6 +4705,9 @@ function getConfig(cwd) {
|
|
|
4688
4705
|
/* isProject */
|
|
4689
4706
|
true
|
|
4690
4707
|
);
|
|
4708
|
+
if (keyed) {
|
|
4709
|
+
mergedSettings.approvers = { ...mergedSettings.approvers, cloud: true };
|
|
4710
|
+
}
|
|
4691
4711
|
let cloudManagedShields = [];
|
|
4692
4712
|
const managedCommandCheckKeys = /* @__PURE__ */ new Set();
|
|
4693
4713
|
const lockedCommandCheckKeys = /* @__PURE__ */ new Set();
|
|
@@ -4700,7 +4720,12 @@ function getConfig(cwd) {
|
|
|
4700
4720
|
try {
|
|
4701
4721
|
const raw = readRulesCacheResilient(cacheFile);
|
|
4702
4722
|
if (Array.isArray(raw.rules) && raw.rules.length > 0) {
|
|
4703
|
-
applyLayer(
|
|
4723
|
+
applyLayer(
|
|
4724
|
+
{ policy: { smartRules: raw.rules } },
|
|
4725
|
+
false,
|
|
4726
|
+
/* isCloud */
|
|
4727
|
+
true
|
|
4728
|
+
);
|
|
4704
4729
|
}
|
|
4705
4730
|
if (Array.isArray(raw.shields)) {
|
|
4706
4731
|
cloudManagedShields = raw.shields.filter((s) => typeof s === "string");
|
|
@@ -4709,47 +4734,85 @@ function getConfig(cwd) {
|
|
|
4709
4734
|
const mc = raw.managedConfig;
|
|
4710
4735
|
const locked = Array.isArray(mc.locked) ? mc.locked.filter((f) => typeof f === "string") : [];
|
|
4711
4736
|
if (typeof mc.mode === "string") {
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4737
|
+
if (keyed) {
|
|
4738
|
+
if (["observe", "audit", "standard", "strict"].includes(mc.mode)) {
|
|
4739
|
+
mergedSettings.mode = mc.mode;
|
|
4740
|
+
}
|
|
4741
|
+
} else {
|
|
4742
|
+
mergedSettings.mode = resolveManagedMode(
|
|
4743
|
+
mergedSettings.mode,
|
|
4744
|
+
mc.mode,
|
|
4745
|
+
locked.includes("mode")
|
|
4746
|
+
);
|
|
4747
|
+
}
|
|
4717
4748
|
}
|
|
4718
4749
|
if (typeof mc.mode === "string" || locked.includes("mode")) {
|
|
4719
4750
|
modeCloudControlled = true;
|
|
4720
4751
|
}
|
|
4721
4752
|
if (mc.egress && typeof mc.egress === "object") {
|
|
4722
4753
|
const hosts = (v) => Array.isArray(v) ? v.filter((h) => typeof h === "string") : void 0;
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4754
|
+
if (keyed) {
|
|
4755
|
+
const e = mc.egress;
|
|
4756
|
+
if (typeof e.enabled === "boolean") mergedPolicy.egress.enabled = e.enabled;
|
|
4757
|
+
if (e.mode === "off" || e.mode === "review" || e.mode === "block")
|
|
4758
|
+
mergedPolicy.egress.mode = e.mode;
|
|
4759
|
+
const allow = hosts(e.allow);
|
|
4760
|
+
if (allow) mergedPolicy.egress.allow = allow;
|
|
4761
|
+
const deny = hosts(e.deny);
|
|
4762
|
+
if (deny) mergedPolicy.egress.deny = deny;
|
|
4763
|
+
if (typeof e.allowPrivate === "boolean")
|
|
4764
|
+
mergedPolicy.egress.allowPrivate = e.allowPrivate;
|
|
4765
|
+
} else {
|
|
4766
|
+
mergedPolicy.egress = applyManagedEgress(
|
|
4767
|
+
mergedPolicy.egress,
|
|
4768
|
+
{
|
|
4769
|
+
enabled: typeof mc.egress.enabled === "boolean" ? mc.egress.enabled : void 0,
|
|
4770
|
+
mode: typeof mc.egress.mode === "string" ? mc.egress.mode : void 0,
|
|
4771
|
+
allow: hosts(mc.egress.allow),
|
|
4772
|
+
deny: hosts(mc.egress.deny),
|
|
4773
|
+
allowPrivate: typeof mc.egress.allowPrivate === "boolean" ? mc.egress.allowPrivate : void 0
|
|
4774
|
+
},
|
|
4775
|
+
locked,
|
|
4776
|
+
egressModeUserSet
|
|
4777
|
+
);
|
|
4778
|
+
}
|
|
4735
4779
|
}
|
|
4736
4780
|
if (mc.dlp && typeof mc.dlp === "object") {
|
|
4737
|
-
|
|
4738
|
-
mergedPolicy.dlp
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4781
|
+
if (keyed) {
|
|
4782
|
+
if (typeof mc.dlp.enabled === "boolean") mergedPolicy.dlp.enabled = mc.dlp.enabled;
|
|
4783
|
+
if (mc.dlp.pii === "off" || mc.dlp.pii === "block") mergedPolicy.dlp.pii = mc.dlp.pii;
|
|
4784
|
+
if (mc.dlp.reviewAction === "review" || mc.dlp.reviewAction === "block")
|
|
4785
|
+
mergedPolicy.dlp.reviewAction = mc.dlp.reviewAction;
|
|
4786
|
+
} else {
|
|
4787
|
+
mergedPolicy.dlp = applyManagedDlp(
|
|
4788
|
+
mergedPolicy.dlp,
|
|
4789
|
+
{
|
|
4790
|
+
enabled: typeof mc.dlp.enabled === "boolean" ? mc.dlp.enabled : void 0,
|
|
4791
|
+
pii: typeof mc.dlp.pii === "string" ? mc.dlp.pii : void 0,
|
|
4792
|
+
reviewAction: mc.dlp.reviewAction === "review" || mc.dlp.reviewAction === "block" ? mc.dlp.reviewAction : void 0
|
|
4793
|
+
},
|
|
4794
|
+
locked
|
|
4795
|
+
);
|
|
4796
|
+
}
|
|
4746
4797
|
}
|
|
4747
4798
|
if (mc.commandChecks && typeof mc.commandChecks === "object") {
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4799
|
+
if (keyed) {
|
|
4800
|
+
const src = mc.commandChecks;
|
|
4801
|
+
const next = { ...mergedPolicy.commandChecks ?? {} };
|
|
4802
|
+
for (const key of COMMAND_CHECK_KEYS) {
|
|
4803
|
+
const val = src[key];
|
|
4804
|
+
if (val !== "off" && val !== "review" && val !== "block") continue;
|
|
4805
|
+
if (val === "off" && CLASS_B_COMMAND_CHECKS.has(key)) continue;
|
|
4806
|
+
next[key] = val;
|
|
4807
|
+
}
|
|
4808
|
+
mergedPolicy.commandChecks = next;
|
|
4809
|
+
} else {
|
|
4810
|
+
mergedPolicy.commandChecks = applyManagedCommandChecks(
|
|
4811
|
+
mergedPolicy.commandChecks ?? {},
|
|
4812
|
+
mc.commandChecks,
|
|
4813
|
+
locked
|
|
4814
|
+
);
|
|
4815
|
+
}
|
|
4753
4816
|
for (const [key, val] of Object.entries(mc.commandChecks)) {
|
|
4754
4817
|
if (typeof val !== "string") continue;
|
|
4755
4818
|
managedCommandCheckKeys.add(key);
|
|
@@ -4846,7 +4909,8 @@ function getConfig(cwd) {
|
|
|
4846
4909
|
}
|
|
4847
4910
|
}
|
|
4848
4911
|
const shieldOverrides = readShieldOverrides();
|
|
4849
|
-
|
|
4912
|
+
if (keyed) mergedPolicy.trustedHostsManaged = true;
|
|
4913
|
+
const activeShieldNames = keyed ? [...new Set(cloudManagedShields)] : [.../* @__PURE__ */ new Set([...readActiveShields(), ...cloudManagedShields])];
|
|
4850
4914
|
const appliedShields = [];
|
|
4851
4915
|
const cloudManagedSet = new Set(cloudManagedShields);
|
|
4852
4916
|
for (const shieldName of activeShieldNames) {
|
|
@@ -4910,7 +4974,9 @@ function getConfig(cwd) {
|
|
|
4910
4974
|
mergedPolicy.smartRules.push(injected);
|
|
4911
4975
|
}
|
|
4912
4976
|
const envMode = process.env.NODE9_MODE;
|
|
4913
|
-
if (envMode && !modeCloudControlled &&
|
|
4977
|
+
if (envMode && !modeCloudControlled && // PR-2: one listening point includes the env var — a keyed machine's
|
|
4978
|
+
// mode comes from the workspace (or the shipped default), never the shell.
|
|
4979
|
+
!keyed && ["observe", "audit", "standard", "strict"].includes(envMode)) {
|
|
4914
4980
|
mergedSettings.mode = envMode;
|
|
4915
4981
|
}
|
|
4916
4982
|
if ((cloudManagedShields.length > 0 || cloudMandatesEnforcement) && !modeCloudControlled && !modeCloudStaged && (mergedSettings.mode === "observe" || mergedSettings.mode === "audit")) {
|
|
@@ -4937,7 +5003,11 @@ function getConfig(cwd) {
|
|
|
4937
5003
|
const result = {
|
|
4938
5004
|
settings: mergedSettings,
|
|
4939
5005
|
policy: mergedPolicy,
|
|
4940
|
-
environments: mergedEnvironments
|
|
5006
|
+
environments: mergedEnvironments,
|
|
5007
|
+
// PR-2 — the one truth introspection reads: which of the two working
|
|
5008
|
+
// modes this machine is in. 'workspace' = keyed, policy from the cloud;
|
|
5009
|
+
// 'local' = the local stack (incl. --local / named-profile keys).
|
|
5010
|
+
policySource: keyed ? "workspace" : "local"
|
|
4941
5011
|
};
|
|
4942
5012
|
if (!cwd) cachedConfig = result;
|
|
4943
5013
|
return result;
|
|
@@ -6483,7 +6553,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
6483
6553
|
} else if (!taintWarning && !appPermReview) {
|
|
6484
6554
|
const toolLower = toolName.toLowerCase();
|
|
6485
6555
|
const isFileTool = toolLower === "read" || toolLower === "grep" || toolLower === "glob" || toolLower === "read_file" || toolLower === "grep_search" || toolLower === "list_files";
|
|
6486
|
-
const activeShields = isFileTool ? readActiveShields() : [];
|
|
6556
|
+
const activeShields = isFileTool ? config.policy.appliedShields ?? readActiveShields() : [];
|
|
6487
6557
|
const managedJail = isFileTool ? config.policy.managedJailPaths ?? [] : [];
|
|
6488
6558
|
if (isFileTool && (activeShields.includes("project-jail") || activeShields.includes(USER_JAIL_SHIELD) || managedJail.length > 0)) {
|
|
6489
6559
|
const argsObj = args && typeof args === "object" && !Array.isArray(args) ? args : {};
|
|
@@ -6580,6 +6650,7 @@ ${appPermReview}`
|
|
|
6580
6650
|
// not an approval of an exfiltration risk. Measured against the live BE:
|
|
6581
6651
|
// {approved:true} without this flag, {pending:true} with it.
|
|
6582
6652
|
!!taintWarning || void 0;
|
|
6653
|
+
const cloudMayVote = !hardBlockDowngraded;
|
|
6583
6654
|
if (cloudEnforced) {
|
|
6584
6655
|
try {
|
|
6585
6656
|
const initResult = await initNode9SaaS(
|
|
@@ -6595,7 +6666,7 @@ ${appPermReview}`
|
|
|
6595
6666
|
if (initResult.shadowMode && !localSmartRuleMatched && !options?.localSmartRuleMatched && !appPermReview && !taintWarning) {
|
|
6596
6667
|
return { approved: true, checkedBy: "cloud" };
|
|
6597
6668
|
}
|
|
6598
|
-
if (!
|
|
6669
|
+
if (!forceReview) {
|
|
6599
6670
|
return {
|
|
6600
6671
|
approved: !!initResult.approved,
|
|
6601
6672
|
reason: initResult.reason || (initResult.approved ? void 0 : "Action rejected by organization policy."),
|
|
@@ -6676,7 +6747,7 @@ ${appPermReview}`
|
|
|
6676
6747
|
}
|
|
6677
6748
|
}
|
|
6678
6749
|
}
|
|
6679
|
-
if (cloudEnforced && cloudRequestId) {
|
|
6750
|
+
if (cloudEnforced && cloudRequestId && cloudMayVote) {
|
|
6680
6751
|
racePromises.push(
|
|
6681
6752
|
(async () => {
|
|
6682
6753
|
try {
|
package/dist/index.mjs
CHANGED
|
@@ -3997,6 +3997,10 @@ var COMMAND_CHECK_KEYS = [
|
|
|
3997
3997
|
"evalDynamic",
|
|
3998
3998
|
"pipeChainHigh"
|
|
3999
3999
|
];
|
|
4000
|
+
var CLASS_B_COMMAND_CHECKS = /* @__PURE__ */ new Set([
|
|
4001
|
+
"evalDynamic",
|
|
4002
|
+
"pipeChainHigh"
|
|
4003
|
+
]);
|
|
4000
4004
|
function applyManagedCommandChecks(local, managed, locked) {
|
|
4001
4005
|
const next = { ...local };
|
|
4002
4006
|
for (const key of COMMAND_CHECK_KEYS) {
|
|
@@ -4006,7 +4010,7 @@ function applyManagedCommandChecks(local, managed, locked) {
|
|
|
4006
4010
|
const resolved = floorValue(COMMAND_CHECK_ORDER, local[key], m, {
|
|
4007
4011
|
locked: locked.includes(lockKey)
|
|
4008
4012
|
});
|
|
4009
|
-
if ((key
|
|
4013
|
+
if (CLASS_B_COMMAND_CHECKS.has(key) && resolved === "off") continue;
|
|
4010
4014
|
next[key] = resolved;
|
|
4011
4015
|
}
|
|
4012
4016
|
return next;
|
|
@@ -4147,10 +4151,18 @@ var DANGEROUS_WORDS = [
|
|
|
4147
4151
|
];
|
|
4148
4152
|
var DEFAULT_CONFIG = {
|
|
4149
4153
|
version: "1.0",
|
|
4154
|
+
policySource: "local",
|
|
4150
4155
|
settings: {
|
|
4151
4156
|
mode: "standard",
|
|
4152
4157
|
autoStartDaemon: true,
|
|
4153
|
-
|
|
4158
|
+
// Off by default: this is a DEBUG facility, not part of the product's job.
|
|
4159
|
+
// When on, every tool call appends a row to ~/.node9/hook-debug.log, which
|
|
4160
|
+
// has no production reader and no size ceiling. Turn it on deliberately
|
|
4161
|
+
// (this setting, or NODE9_DEBUG=1) while investigating something.
|
|
4162
|
+
// Error breadcrumbs written from catch blocks are NOT gated by this and
|
|
4163
|
+
// keep working when it is off — see CLAUDE.md "Always write to
|
|
4164
|
+
// hook-debug.log in catch blocks that guard audit trail".
|
|
4165
|
+
enableHookLogDebug: false,
|
|
4154
4166
|
approvalTimeoutMs: 12e4,
|
|
4155
4167
|
// 120-second auto-deny timeout
|
|
4156
4168
|
flightRecorder: true,
|
|
@@ -4410,13 +4422,15 @@ function getCredentials() {
|
|
|
4410
4422
|
if (profile?.apiKey) {
|
|
4411
4423
|
return {
|
|
4412
4424
|
apiKey: profile.apiKey,
|
|
4413
|
-
apiUrl: profile.apiUrl || DEFAULT_API_URL
|
|
4425
|
+
apiUrl: profile.apiUrl || DEFAULT_API_URL,
|
|
4426
|
+
localOnly: profile.localOnly === true || profileName !== "default"
|
|
4414
4427
|
};
|
|
4415
4428
|
}
|
|
4416
4429
|
if (creds.apiKey) {
|
|
4417
4430
|
return {
|
|
4418
4431
|
apiKey: creds.apiKey,
|
|
4419
|
-
apiUrl: creds.apiUrl || DEFAULT_API_URL
|
|
4432
|
+
apiUrl: creds.apiUrl || DEFAULT_API_URL,
|
|
4433
|
+
localOnly: creds.localOnly === true
|
|
4420
4434
|
};
|
|
4421
4435
|
}
|
|
4422
4436
|
}
|
|
@@ -4528,22 +4542,16 @@ function getConfig(cwd) {
|
|
|
4528
4542
|
const i = COMMAND_CHECK_ORDER.indexOf(v ?? "");
|
|
4529
4543
|
return i === -1 ? 1 : i;
|
|
4530
4544
|
};
|
|
4531
|
-
const
|
|
4545
|
+
const pr2Creds = getCredentials();
|
|
4546
|
+
const keyed = !!pr2Creds?.apiKey && pr2Creds.localOnly !== true;
|
|
4547
|
+
const applyLayer = (source, isProject = false, isCloud = false) => {
|
|
4532
4548
|
if (!source) return;
|
|
4533
4549
|
const s = source.settings || {};
|
|
4534
4550
|
const p = source.policy || {};
|
|
4535
|
-
if (s.mode !== void 0) mergedSettings.mode = s.mode;
|
|
4536
4551
|
if (s.autoStartDaemon !== void 0) mergedSettings.autoStartDaemon = s.autoStartDaemon;
|
|
4537
4552
|
if (s.enableHookLogDebug !== void 0)
|
|
4538
4553
|
mergedSettings.enableHookLogDebug = s.enableHookLogDebug;
|
|
4539
|
-
if (s.approvers) mergedSettings.approvers = { ...mergedSettings.approvers, ...s.approvers };
|
|
4540
4554
|
if (s.shipper) mergedSettings.shipper = { ...mergedSettings.shipper, ...s.shipper };
|
|
4541
|
-
if (s.approvalTimeoutMs !== void 0) mergedSettings.approvalTimeoutMs = s.approvalTimeoutMs;
|
|
4542
|
-
if (s.approvalTimeoutSeconds !== void 0 && s.approvalTimeoutMs === void 0)
|
|
4543
|
-
mergedSettings.approvalTimeoutMs = s.approvalTimeoutSeconds * 1e3;
|
|
4544
|
-
if (s.environment !== void 0) mergedSettings.environment = s.environment;
|
|
4545
|
-
if (s.reviewChannel !== void 0) mergedSettings.reviewChannel = s.reviewChannel;
|
|
4546
|
-
if (s.mcpAllowWeakening !== void 0) mergedSettings.mcpAllowWeakening = s.mcpAllowWeakening;
|
|
4547
4555
|
if (s.cloudSyncIntervalHours !== void 0)
|
|
4548
4556
|
mergedSettings.cloudSyncIntervalHours = s.cloudSyncIntervalHours;
|
|
4549
4557
|
if (s.mcpAutoWrap !== void 0) mergedSettings.mcpAutoWrap = s.mcpAutoWrap === true;
|
|
@@ -4551,6 +4559,15 @@ function getConfig(cwd) {
|
|
|
4551
4559
|
mergedSettings.mcpReconcileIntervalMinutes = s.mcpReconcileIntervalMinutes;
|
|
4552
4560
|
if (s.mcpStaleAfterDays !== void 0) mergedSettings.mcpStaleAfterDays = s.mcpStaleAfterDays;
|
|
4553
4561
|
if (s.hud !== void 0) mergedSettings.hud = { ...mergedSettings.hud, ...s.hud };
|
|
4562
|
+
if (keyed && !isCloud) return;
|
|
4563
|
+
if (s.mode !== void 0) mergedSettings.mode = s.mode;
|
|
4564
|
+
if (s.approvers) mergedSettings.approvers = { ...mergedSettings.approvers, ...s.approvers };
|
|
4565
|
+
if (s.approvalTimeoutMs !== void 0) mergedSettings.approvalTimeoutMs = s.approvalTimeoutMs;
|
|
4566
|
+
if (s.approvalTimeoutSeconds !== void 0 && s.approvalTimeoutMs === void 0)
|
|
4567
|
+
mergedSettings.approvalTimeoutMs = s.approvalTimeoutSeconds * 1e3;
|
|
4568
|
+
if (s.environment !== void 0) mergedSettings.environment = s.environment;
|
|
4569
|
+
if (s.reviewChannel !== void 0) mergedSettings.reviewChannel = s.reviewChannel;
|
|
4570
|
+
if (s.mcpAllowWeakening !== void 0) mergedSettings.mcpAllowWeakening = s.mcpAllowWeakening;
|
|
4554
4571
|
if (p.sandboxPaths) mergedPolicy.sandboxPaths.push(...p.sandboxPaths);
|
|
4555
4572
|
if (p.ignoredTools) mergedPolicy.ignoredTools.push(...p.ignoredTools);
|
|
4556
4573
|
if (p.dangerousWords) mergedPolicy.dangerousWords = [...p.dangerousWords];
|
|
@@ -4658,6 +4675,9 @@ function getConfig(cwd) {
|
|
|
4658
4675
|
/* isProject */
|
|
4659
4676
|
true
|
|
4660
4677
|
);
|
|
4678
|
+
if (keyed) {
|
|
4679
|
+
mergedSettings.approvers = { ...mergedSettings.approvers, cloud: true };
|
|
4680
|
+
}
|
|
4661
4681
|
let cloudManagedShields = [];
|
|
4662
4682
|
const managedCommandCheckKeys = /* @__PURE__ */ new Set();
|
|
4663
4683
|
const lockedCommandCheckKeys = /* @__PURE__ */ new Set();
|
|
@@ -4670,7 +4690,12 @@ function getConfig(cwd) {
|
|
|
4670
4690
|
try {
|
|
4671
4691
|
const raw = readRulesCacheResilient(cacheFile);
|
|
4672
4692
|
if (Array.isArray(raw.rules) && raw.rules.length > 0) {
|
|
4673
|
-
applyLayer(
|
|
4693
|
+
applyLayer(
|
|
4694
|
+
{ policy: { smartRules: raw.rules } },
|
|
4695
|
+
false,
|
|
4696
|
+
/* isCloud */
|
|
4697
|
+
true
|
|
4698
|
+
);
|
|
4674
4699
|
}
|
|
4675
4700
|
if (Array.isArray(raw.shields)) {
|
|
4676
4701
|
cloudManagedShields = raw.shields.filter((s) => typeof s === "string");
|
|
@@ -4679,47 +4704,85 @@ function getConfig(cwd) {
|
|
|
4679
4704
|
const mc = raw.managedConfig;
|
|
4680
4705
|
const locked = Array.isArray(mc.locked) ? mc.locked.filter((f) => typeof f === "string") : [];
|
|
4681
4706
|
if (typeof mc.mode === "string") {
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4707
|
+
if (keyed) {
|
|
4708
|
+
if (["observe", "audit", "standard", "strict"].includes(mc.mode)) {
|
|
4709
|
+
mergedSettings.mode = mc.mode;
|
|
4710
|
+
}
|
|
4711
|
+
} else {
|
|
4712
|
+
mergedSettings.mode = resolveManagedMode(
|
|
4713
|
+
mergedSettings.mode,
|
|
4714
|
+
mc.mode,
|
|
4715
|
+
locked.includes("mode")
|
|
4716
|
+
);
|
|
4717
|
+
}
|
|
4687
4718
|
}
|
|
4688
4719
|
if (typeof mc.mode === "string" || locked.includes("mode")) {
|
|
4689
4720
|
modeCloudControlled = true;
|
|
4690
4721
|
}
|
|
4691
4722
|
if (mc.egress && typeof mc.egress === "object") {
|
|
4692
4723
|
const hosts = (v) => Array.isArray(v) ? v.filter((h) => typeof h === "string") : void 0;
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4724
|
+
if (keyed) {
|
|
4725
|
+
const e = mc.egress;
|
|
4726
|
+
if (typeof e.enabled === "boolean") mergedPolicy.egress.enabled = e.enabled;
|
|
4727
|
+
if (e.mode === "off" || e.mode === "review" || e.mode === "block")
|
|
4728
|
+
mergedPolicy.egress.mode = e.mode;
|
|
4729
|
+
const allow = hosts(e.allow);
|
|
4730
|
+
if (allow) mergedPolicy.egress.allow = allow;
|
|
4731
|
+
const deny = hosts(e.deny);
|
|
4732
|
+
if (deny) mergedPolicy.egress.deny = deny;
|
|
4733
|
+
if (typeof e.allowPrivate === "boolean")
|
|
4734
|
+
mergedPolicy.egress.allowPrivate = e.allowPrivate;
|
|
4735
|
+
} else {
|
|
4736
|
+
mergedPolicy.egress = applyManagedEgress(
|
|
4737
|
+
mergedPolicy.egress,
|
|
4738
|
+
{
|
|
4739
|
+
enabled: typeof mc.egress.enabled === "boolean" ? mc.egress.enabled : void 0,
|
|
4740
|
+
mode: typeof mc.egress.mode === "string" ? mc.egress.mode : void 0,
|
|
4741
|
+
allow: hosts(mc.egress.allow),
|
|
4742
|
+
deny: hosts(mc.egress.deny),
|
|
4743
|
+
allowPrivate: typeof mc.egress.allowPrivate === "boolean" ? mc.egress.allowPrivate : void 0
|
|
4744
|
+
},
|
|
4745
|
+
locked,
|
|
4746
|
+
egressModeUserSet
|
|
4747
|
+
);
|
|
4748
|
+
}
|
|
4705
4749
|
}
|
|
4706
4750
|
if (mc.dlp && typeof mc.dlp === "object") {
|
|
4707
|
-
|
|
4708
|
-
mergedPolicy.dlp
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4751
|
+
if (keyed) {
|
|
4752
|
+
if (typeof mc.dlp.enabled === "boolean") mergedPolicy.dlp.enabled = mc.dlp.enabled;
|
|
4753
|
+
if (mc.dlp.pii === "off" || mc.dlp.pii === "block") mergedPolicy.dlp.pii = mc.dlp.pii;
|
|
4754
|
+
if (mc.dlp.reviewAction === "review" || mc.dlp.reviewAction === "block")
|
|
4755
|
+
mergedPolicy.dlp.reviewAction = mc.dlp.reviewAction;
|
|
4756
|
+
} else {
|
|
4757
|
+
mergedPolicy.dlp = applyManagedDlp(
|
|
4758
|
+
mergedPolicy.dlp,
|
|
4759
|
+
{
|
|
4760
|
+
enabled: typeof mc.dlp.enabled === "boolean" ? mc.dlp.enabled : void 0,
|
|
4761
|
+
pii: typeof mc.dlp.pii === "string" ? mc.dlp.pii : void 0,
|
|
4762
|
+
reviewAction: mc.dlp.reviewAction === "review" || mc.dlp.reviewAction === "block" ? mc.dlp.reviewAction : void 0
|
|
4763
|
+
},
|
|
4764
|
+
locked
|
|
4765
|
+
);
|
|
4766
|
+
}
|
|
4716
4767
|
}
|
|
4717
4768
|
if (mc.commandChecks && typeof mc.commandChecks === "object") {
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4769
|
+
if (keyed) {
|
|
4770
|
+
const src = mc.commandChecks;
|
|
4771
|
+
const next = { ...mergedPolicy.commandChecks ?? {} };
|
|
4772
|
+
for (const key of COMMAND_CHECK_KEYS) {
|
|
4773
|
+
const val = src[key];
|
|
4774
|
+
if (val !== "off" && val !== "review" && val !== "block") continue;
|
|
4775
|
+
if (val === "off" && CLASS_B_COMMAND_CHECKS.has(key)) continue;
|
|
4776
|
+
next[key] = val;
|
|
4777
|
+
}
|
|
4778
|
+
mergedPolicy.commandChecks = next;
|
|
4779
|
+
} else {
|
|
4780
|
+
mergedPolicy.commandChecks = applyManagedCommandChecks(
|
|
4781
|
+
mergedPolicy.commandChecks ?? {},
|
|
4782
|
+
mc.commandChecks,
|
|
4783
|
+
locked
|
|
4784
|
+
);
|
|
4785
|
+
}
|
|
4723
4786
|
for (const [key, val] of Object.entries(mc.commandChecks)) {
|
|
4724
4787
|
if (typeof val !== "string") continue;
|
|
4725
4788
|
managedCommandCheckKeys.add(key);
|
|
@@ -4816,7 +4879,8 @@ function getConfig(cwd) {
|
|
|
4816
4879
|
}
|
|
4817
4880
|
}
|
|
4818
4881
|
const shieldOverrides = readShieldOverrides();
|
|
4819
|
-
|
|
4882
|
+
if (keyed) mergedPolicy.trustedHostsManaged = true;
|
|
4883
|
+
const activeShieldNames = keyed ? [...new Set(cloudManagedShields)] : [.../* @__PURE__ */ new Set([...readActiveShields(), ...cloudManagedShields])];
|
|
4820
4884
|
const appliedShields = [];
|
|
4821
4885
|
const cloudManagedSet = new Set(cloudManagedShields);
|
|
4822
4886
|
for (const shieldName of activeShieldNames) {
|
|
@@ -4880,7 +4944,9 @@ function getConfig(cwd) {
|
|
|
4880
4944
|
mergedPolicy.smartRules.push(injected);
|
|
4881
4945
|
}
|
|
4882
4946
|
const envMode = process.env.NODE9_MODE;
|
|
4883
|
-
if (envMode && !modeCloudControlled &&
|
|
4947
|
+
if (envMode && !modeCloudControlled && // PR-2: one listening point includes the env var — a keyed machine's
|
|
4948
|
+
// mode comes from the workspace (or the shipped default), never the shell.
|
|
4949
|
+
!keyed && ["observe", "audit", "standard", "strict"].includes(envMode)) {
|
|
4884
4950
|
mergedSettings.mode = envMode;
|
|
4885
4951
|
}
|
|
4886
4952
|
if ((cloudManagedShields.length > 0 || cloudMandatesEnforcement) && !modeCloudControlled && !modeCloudStaged && (mergedSettings.mode === "observe" || mergedSettings.mode === "audit")) {
|
|
@@ -4907,7 +4973,11 @@ function getConfig(cwd) {
|
|
|
4907
4973
|
const result = {
|
|
4908
4974
|
settings: mergedSettings,
|
|
4909
4975
|
policy: mergedPolicy,
|
|
4910
|
-
environments: mergedEnvironments
|
|
4976
|
+
environments: mergedEnvironments,
|
|
4977
|
+
// PR-2 — the one truth introspection reads: which of the two working
|
|
4978
|
+
// modes this machine is in. 'workspace' = keyed, policy from the cloud;
|
|
4979
|
+
// 'local' = the local stack (incl. --local / named-profile keys).
|
|
4980
|
+
policySource: keyed ? "workspace" : "local"
|
|
4911
4981
|
};
|
|
4912
4982
|
if (!cwd) cachedConfig = result;
|
|
4913
4983
|
return result;
|
|
@@ -6453,7 +6523,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
6453
6523
|
} else if (!taintWarning && !appPermReview) {
|
|
6454
6524
|
const toolLower = toolName.toLowerCase();
|
|
6455
6525
|
const isFileTool = toolLower === "read" || toolLower === "grep" || toolLower === "glob" || toolLower === "read_file" || toolLower === "grep_search" || toolLower === "list_files";
|
|
6456
|
-
const activeShields = isFileTool ? readActiveShields() : [];
|
|
6526
|
+
const activeShields = isFileTool ? config.policy.appliedShields ?? readActiveShields() : [];
|
|
6457
6527
|
const managedJail = isFileTool ? config.policy.managedJailPaths ?? [] : [];
|
|
6458
6528
|
if (isFileTool && (activeShields.includes("project-jail") || activeShields.includes(USER_JAIL_SHIELD) || managedJail.length > 0)) {
|
|
6459
6529
|
const argsObj = args && typeof args === "object" && !Array.isArray(args) ? args : {};
|
|
@@ -6550,6 +6620,7 @@ ${appPermReview}`
|
|
|
6550
6620
|
// not an approval of an exfiltration risk. Measured against the live BE:
|
|
6551
6621
|
// {approved:true} without this flag, {pending:true} with it.
|
|
6552
6622
|
!!taintWarning || void 0;
|
|
6623
|
+
const cloudMayVote = !hardBlockDowngraded;
|
|
6553
6624
|
if (cloudEnforced) {
|
|
6554
6625
|
try {
|
|
6555
6626
|
const initResult = await initNode9SaaS(
|
|
@@ -6565,7 +6636,7 @@ ${appPermReview}`
|
|
|
6565
6636
|
if (initResult.shadowMode && !localSmartRuleMatched && !options?.localSmartRuleMatched && !appPermReview && !taintWarning) {
|
|
6566
6637
|
return { approved: true, checkedBy: "cloud" };
|
|
6567
6638
|
}
|
|
6568
|
-
if (!
|
|
6639
|
+
if (!forceReview) {
|
|
6569
6640
|
return {
|
|
6570
6641
|
approved: !!initResult.approved,
|
|
6571
6642
|
reason: initResult.reason || (initResult.approved ? void 0 : "Action rejected by organization policy."),
|
|
@@ -6646,7 +6717,7 @@ ${appPermReview}`
|
|
|
6646
6717
|
}
|
|
6647
6718
|
}
|
|
6648
6719
|
}
|
|
6649
|
-
if (cloudEnforced && cloudRequestId) {
|
|
6720
|
+
if (cloudEnforced && cloudRequestId && cloudMayVote) {
|
|
6650
6721
|
racePromises.push(
|
|
6651
6722
|
(async () => {
|
|
6652
6723
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node9/proxy",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.2",
|
|
4
4
|
"description": "The Sudo Command for AI Agents. Execution Security for Claude Code, Codex, Gemini, Cursor, Opencode, Pi, and any MCP server.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -70,7 +70,9 @@
|
|
|
70
70
|
"bump-extractor-version": "node scripts/check-extractor-version.mjs --bump",
|
|
71
71
|
"fix": "npm run format && npm run lint:fix",
|
|
72
72
|
"validate": "npm run format && npm run lint && npm run typecheck && npm run test && npm run test:e2e && npm run build",
|
|
73
|
+
"pretest:e2e": "npm run build",
|
|
73
74
|
"test:e2e": "cross-env NODE9_TESTING=1 bash scripts/e2e.sh",
|
|
75
|
+
"pretest": "npm run build",
|
|
74
76
|
"test": "cross-env NODE9_TESTING=1 vitest --run",
|
|
75
77
|
"test:coverage": "cross-env NODE9_TESTING=1 vitest --run --coverage",
|
|
76
78
|
"test:watch": "cross-env NODE9_TESTING=1 vitest",
|