@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/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 === "evalDynamic" || key === "pipeChainHigh") && resolved === "off") continue;
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
- enableHookLogDebug: true,
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 applyLayer = (source, isProject = false) => {
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({ policy: { smartRules: raw.rules } });
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
- mergedSettings.mode = resolveManagedMode(
4713
- mergedSettings.mode,
4714
- mc.mode,
4715
- locked.includes("mode")
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
- mergedPolicy.egress = applyManagedEgress(
4724
- mergedPolicy.egress,
4725
- {
4726
- enabled: typeof mc.egress.enabled === "boolean" ? mc.egress.enabled : void 0,
4727
- mode: typeof mc.egress.mode === "string" ? mc.egress.mode : void 0,
4728
- allow: hosts(mc.egress.allow),
4729
- deny: hosts(mc.egress.deny),
4730
- allowPrivate: typeof mc.egress.allowPrivate === "boolean" ? mc.egress.allowPrivate : void 0
4731
- },
4732
- locked,
4733
- egressModeUserSet
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
- mergedPolicy.dlp = applyManagedDlp(
4738
- mergedPolicy.dlp,
4739
- {
4740
- enabled: typeof mc.dlp.enabled === "boolean" ? mc.dlp.enabled : void 0,
4741
- pii: typeof mc.dlp.pii === "string" ? mc.dlp.pii : void 0,
4742
- reviewAction: mc.dlp.reviewAction === "review" || mc.dlp.reviewAction === "block" ? mc.dlp.reviewAction : void 0
4743
- },
4744
- locked
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
- mergedPolicy.commandChecks = applyManagedCommandChecks(
4749
- mergedPolicy.commandChecks ?? {},
4750
- mc.commandChecks,
4751
- locked
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
- const activeShieldNames = [.../* @__PURE__ */ new Set([...readActiveShields(), ...cloudManagedShields])];
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 && ["observe", "audit", "standard", "strict"].includes(envMode)) {
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 (!localSmartRuleMatched && !options?.localSmartRuleMatched && !appPermReview && !taintWarning) {
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 === "evalDynamic" || key === "pipeChainHigh") && resolved === "off") continue;
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
- enableHookLogDebug: true,
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 applyLayer = (source, isProject = false) => {
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({ policy: { smartRules: raw.rules } });
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
- mergedSettings.mode = resolveManagedMode(
4683
- mergedSettings.mode,
4684
- mc.mode,
4685
- locked.includes("mode")
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
- mergedPolicy.egress = applyManagedEgress(
4694
- mergedPolicy.egress,
4695
- {
4696
- enabled: typeof mc.egress.enabled === "boolean" ? mc.egress.enabled : void 0,
4697
- mode: typeof mc.egress.mode === "string" ? mc.egress.mode : void 0,
4698
- allow: hosts(mc.egress.allow),
4699
- deny: hosts(mc.egress.deny),
4700
- allowPrivate: typeof mc.egress.allowPrivate === "boolean" ? mc.egress.allowPrivate : void 0
4701
- },
4702
- locked,
4703
- egressModeUserSet
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
- mergedPolicy.dlp = applyManagedDlp(
4708
- mergedPolicy.dlp,
4709
- {
4710
- enabled: typeof mc.dlp.enabled === "boolean" ? mc.dlp.enabled : void 0,
4711
- pii: typeof mc.dlp.pii === "string" ? mc.dlp.pii : void 0,
4712
- reviewAction: mc.dlp.reviewAction === "review" || mc.dlp.reviewAction === "block" ? mc.dlp.reviewAction : void 0
4713
- },
4714
- locked
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
- mergedPolicy.commandChecks = applyManagedCommandChecks(
4719
- mergedPolicy.commandChecks ?? {},
4720
- mc.commandChecks,
4721
- locked
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
- const activeShieldNames = [.../* @__PURE__ */ new Set([...readActiveShields(), ...cloudManagedShields])];
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 && ["observe", "audit", "standard", "strict"].includes(envMode)) {
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 (!localSmartRuleMatched && !options?.localSmartRuleMatched && !appPermReview && !taintWarning) {
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.5.0",
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",