@node9/proxy 1.17.0 → 1.18.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/dist/index.js CHANGED
@@ -110,6 +110,7 @@ function appendHookDebug(toolName, args, meta, auditHashArgsEnabled) {
110
110
  ...argsField,
111
111
  agent: meta?.agent,
112
112
  mcpServer: meta?.mcpServer,
113
+ sessionId: meta?.sessionId,
113
114
  hostname: import_os.default.hostname(),
114
115
  cwd: process.cwd()
115
116
  });
@@ -2088,13 +2089,6 @@ var k8s_default = {
2088
2089
  ],
2089
2090
  dangerousWords: []
2090
2091
  };
2091
- var mcp_tool_gating_default = {
2092
- name: "mcp-tool-gating",
2093
- description: "Intercept MCP tool lists and require user approval before the agent can use any tools from a new server",
2094
- aliases: ["mcp-gating", "mcp-tools"],
2095
- smartRules: [],
2096
- dangerousWords: []
2097
- };
2098
2092
  var mongodb_default = {
2099
2093
  name: "mongodb",
2100
2094
  description: "Protects MongoDB databases from destructive AI operations",
@@ -2400,7 +2394,6 @@ var BUILTIN_SHIELDS = {
2400
2394
  [filesystem_default.name]: filesystem_default,
2401
2395
  [github_default.name]: github_default,
2402
2396
  [k8s_default.name]: k8s_default,
2403
- [mcp_tool_gating_default.name]: mcp_tool_gating_default,
2404
2397
  [mongodb_default.name]: mongodb_default,
2405
2398
  [postgres_default.name]: postgres_default,
2406
2399
  [project_jail_default.name]: project_jail_default,
@@ -2929,6 +2922,12 @@ function getConfig(cwd) {
2929
2922
  if (Array.isArray(raw.rules) && raw.rules.length > 0) {
2930
2923
  applyLayer({ policy: { smartRules: raw.rules } });
2931
2924
  }
2925
+ if (raw.panicMode === true) {
2926
+ mergedSettings.panicMode = true;
2927
+ }
2928
+ if (raw.shadowMode === true) {
2929
+ mergedSettings.mode = "observe";
2930
+ }
2932
2931
  } catch {
2933
2932
  }
2934
2933
  }
@@ -3866,6 +3865,12 @@ var KNOWN_CHECKED_BY = /* @__PURE__ */ new Set([
3866
3865
  "audit-mode",
3867
3866
  "local-policy",
3868
3867
  "smart-rule-block",
3868
+ // Smart-rule block was downgraded to review because the daemon was
3869
+ // running and we're not in CI. The block attempt is still recorded;
3870
+ // the user got a popup. Distinct from 'smart-rule-block' so the
3871
+ // dashboard can show "block rule overridden" separately from a hard
3872
+ // block that fired with no human in the loop.
3873
+ "smart-rule-block-override",
3869
3874
  "persistent",
3870
3875
  "trust",
3871
3876
  "observe-mode",
@@ -3886,7 +3891,7 @@ function validateApiUrl(raw) {
3886
3891
  }
3887
3892
  return null;
3888
3893
  }
3889
- function auditLocalAllow(toolName, args, checkedBy, creds, meta, dlpInfo, containsSensitiveArgs = false) {
3894
+ function auditLocalAllow(toolName, args, checkedBy, creds, meta, dlpInfo, containsSensitiveArgs = false, riskMetadata) {
3890
3895
  const validated = validateApiUrl(creds.apiUrl);
3891
3896
  if (!validated) {
3892
3897
  try {
@@ -3903,6 +3908,10 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta, dlpInfo, contai
3903
3908
  const dlpSample = dlpInfo && typeof dlpInfo.redactedSample === "string" ? dlpInfo.redactedSample.slice(0, DLP_SAMPLE_MAX_LEN) : void 0;
3904
3909
  const dlpPattern = dlpInfo && typeof dlpInfo.pattern === "string" ? dlpInfo.pattern.slice(0, DLP_PATTERN_MAX_LEN) : void 0;
3905
3910
  const safeCheckedBy = KNOWN_CHECKED_BY.has(checkedBy) ? checkedBy : "unknown";
3911
+ const cleanedRiskMetadata = riskMetadata ? Object.fromEntries(
3912
+ Object.entries(riskMetadata).filter(([, v]) => typeof v === "string" && v.length > 0)
3913
+ ) : void 0;
3914
+ const hasRiskMetadata = cleanedRiskMetadata && Object.keys(cleanedRiskMetadata).length > 0;
3906
3915
  return fetch(`${validated.toString().replace(/\/$/, "")}/audit`, {
3907
3916
  method: "POST",
3908
3917
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${creds.apiKey}` },
@@ -3911,6 +3920,13 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta, dlpInfo, contai
3911
3920
  args: safeArgs,
3912
3921
  checkedBy: safeCheckedBy,
3913
3922
  ...dlpInfo && { dlpPattern, dlpSample },
3923
+ ...hasRiskMetadata && { riskMetadata: cleanedRiskMetadata },
3924
+ // session_id (Claude Code + Gemini CLI) groups all audit rows from one
3925
+ // agent run; transcript_path is the authoritative pointer to the
3926
+ // session log (survives Gemini resume drift). Both optional —
3927
+ // unsupported agents (MCP-mediated) leave them undefined.
3928
+ ...meta?.sessionId && { runId: meta.sessionId },
3929
+ ...meta?.transcriptPath && { transcriptPath: meta.transcriptPath },
3914
3930
  context: {
3915
3931
  agent: meta?.agent,
3916
3932
  mcpServer: meta?.mcpServer,
@@ -3961,6 +3977,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPol
3961
3977
  body: JSON.stringify({
3962
3978
  toolName,
3963
3979
  args,
3980
+ // See auditLocalAllow above for the rationale on these two fields.
3981
+ ...meta?.sessionId && { runId: meta.sessionId },
3982
+ ...meta?.transcriptPath && { transcriptPath: meta.transcriptPath },
3964
3983
  context: {
3965
3984
  agent: meta?.agent,
3966
3985
  mcpServer: meta?.mcpServer,
@@ -4143,7 +4162,8 @@ async function authorizeHeadless(toolName, args, meta, options) {
4143
4162
  args,
4144
4163
  status: "pending",
4145
4164
  agent: sanitizedAgent,
4146
- mcpServer: sanitizedMcpServer
4165
+ mcpServer: sanitizedMcpServer,
4166
+ sessionId: meta?.sessionId
4147
4167
  });
4148
4168
  const result = await _authorizeHeadlessCore(toolName, args, meta, {
4149
4169
  ...options,
@@ -4163,7 +4183,8 @@ async function authorizeHeadless(toolName, args, meta, options) {
4163
4183
  ruleHit: result.ruleHit,
4164
4184
  observeWouldBlock: result.observeWouldBlock,
4165
4185
  agent: sanitizedAgent,
4166
- mcpServer: sanitizedMcpServer
4186
+ mcpServer: sanitizedMcpServer,
4187
+ sessionId: meta?.sessionId
4167
4188
  });
4168
4189
  }
4169
4190
  return result;
@@ -4183,9 +4204,9 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
4183
4204
  };
4184
4205
  if (isTestEnv2) {
4185
4206
  approvers.native = false;
4186
- approvers.browser = false;
4187
4207
  approvers.terminal = false;
4188
4208
  }
4209
+ approvers.browser = false;
4189
4210
  if (config.settings.enableHookLogDebug && !isTestEnv2) {
4190
4211
  appendHookDebug(toolName, args, meta, hashAuditArgs);
4191
4212
  }
@@ -4315,10 +4336,24 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
4315
4336
  };
4316
4337
  }
4317
4338
  }
4318
- const policyResult = await evaluatePolicy2(toolName, args, meta?.agent);
4339
+ let policyResult = await evaluatePolicy2(toolName, args, meta?.agent);
4340
+ if (config.settings.panicMode && policyResult.decision === "review") {
4341
+ policyResult = {
4342
+ ...policyResult,
4343
+ decision: "block",
4344
+ blockedByLabel: "\u{1F6A8} Panic mode (org policy)",
4345
+ reason: "Workspace is in panic mode \u2014 all review-verdict actions are blocked. Contact your admin to disable panic mode in the Node9 dashboard."
4346
+ };
4347
+ }
4319
4348
  if (policyResult.decision === "allow") {
4320
4349
  if (approvers.cloud && creds?.apiKey)
4321
- await auditLocalAllow(toolName, args, "local-policy", creds, meta);
4350
+ await auditLocalAllow(toolName, args, "local-policy", creds, meta, void 0, false, {
4351
+ ruleName: policyResult.ruleName,
4352
+ ruleDescription: policyResult.ruleDescription,
4353
+ blockedByLabel: policyResult.blockedByLabel,
4354
+ matchedField: policyResult.matchedField,
4355
+ matchedWord: policyResult.matchedWord
4356
+ });
4322
4357
  if (!isManual) appendLocalAudit(toolName, args, "allow", "local-policy", meta, hashAuditArgs);
4323
4358
  return { approved: true, checkedBy: "local-policy" };
4324
4359
  }
@@ -4341,14 +4376,50 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
4341
4376
  }
4342
4377
  } else if (isDaemonRunning() && !isTestEnv2) {
4343
4378
  if (!isManual)
4344
- appendLocalAudit(toolName, args, "deny", "smart-rule-block", meta, hashAuditArgs);
4379
+ appendLocalAudit(
4380
+ toolName,
4381
+ args,
4382
+ "deny",
4383
+ "smart-rule-block-override",
4384
+ meta,
4385
+ hashAuditArgs
4386
+ );
4345
4387
  if (approvers.cloud && creds?.apiKey)
4346
- auditLocalAllow(toolName, args, "smart-rule-block", creds, meta);
4388
+ auditLocalAllow(
4389
+ toolName,
4390
+ args,
4391
+ "smart-rule-block-override",
4392
+ creds,
4393
+ meta,
4394
+ void 0,
4395
+ false,
4396
+ {
4397
+ ruleName: policyResult.ruleName,
4398
+ ruleDescription: policyResult.ruleDescription,
4399
+ blockedByLabel: policyResult.blockedByLabel,
4400
+ matchedField: policyResult.matchedField,
4401
+ matchedWord: policyResult.matchedWord
4402
+ }
4403
+ );
4404
+ const baseLabel = policyResult.blockedByLabel || "Smart Rule";
4405
+ const OVERRIDE_PREFIX = "\u26A0\uFE0F Override block rule: ";
4406
+ if (!baseLabel.startsWith(OVERRIDE_PREFIX)) {
4407
+ policyResult = {
4408
+ ...policyResult,
4409
+ blockedByLabel: `${OVERRIDE_PREFIX}${baseLabel}`
4410
+ };
4411
+ }
4347
4412
  } else {
4348
4413
  if (!isManual)
4349
4414
  appendLocalAudit(toolName, args, "deny", "smart-rule-block", meta, hashAuditArgs);
4350
4415
  if (approvers.cloud && creds?.apiKey)
4351
- auditLocalAllow(toolName, args, "smart-rule-block", creds, meta);
4416
+ auditLocalAllow(toolName, args, "smart-rule-block", creds, meta, void 0, false, {
4417
+ ruleName: policyResult.ruleName,
4418
+ ruleDescription: policyResult.ruleDescription,
4419
+ blockedByLabel: policyResult.blockedByLabel,
4420
+ matchedField: policyResult.matchedField,
4421
+ matchedWord: policyResult.matchedWord
4422
+ });
4352
4423
  return {
4353
4424
  approved: false,
4354
4425
  reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
@@ -4486,7 +4557,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
4486
4557
  const internalToken = getInternalToken();
4487
4558
  let daemonEntryId = null;
4488
4559
  let daemonAllowCount = 1;
4489
- if ((approvers.browser || approvers.terminal) && isDaemonRunning() && !options?.calledFromDaemon) {
4560
+ if (approvers.terminal && isDaemonRunning() && !options?.calledFromDaemon) {
4490
4561
  if (cloudEnforced && cloudRequestId) {
4491
4562
  const viewer = await notifyDaemonViewer(toolName, args, meta, riskMetadata).catch(() => null);
4492
4563
  viewerId = viewer?.id ?? null;
@@ -4564,7 +4635,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
4564
4635
  })()
4565
4636
  );
4566
4637
  }
4567
- if (daemonEntryId && (approvers.browser || approvers.terminal)) {
4638
+ if (daemonEntryId && approvers.terminal) {
4568
4639
  racePromises.push(
4569
4640
  (async () => {
4570
4641
  const {
@@ -4575,19 +4646,14 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
4575
4646
  if (daemonDecision === "abandoned") throw new Error("Abandoned");
4576
4647
  const isApproved = daemonDecision === "allow";
4577
4648
  const isRedirect = decisionSource === "terminal-redirect";
4578
- const src = decisionSource === "terminal" || decisionSource === "terminal-redirect" || decisionSource === "browser" ? decisionSource === "browser" ? "browser" : "terminal" : approvers.browser ? "browser" : "terminal";
4579
- const via = src === "terminal" ? "Terminal (node9 tail)" : "Browser Dashboard";
4649
+ const via = "Terminal (node9 tail)";
4580
4650
  return {
4581
4651
  approved: isApproved,
4582
- reason: isApproved ? void 0 : (
4583
- // Use the redirect reason from the tail when choice [2] was selected;
4584
- // otherwise fall back to the generic rejection message.
4585
- isRedirect && daemonReason || `The human user rejected this action via the Node9 ${via}.`
4586
- ),
4652
+ reason: isApproved ? void 0 : isRedirect && daemonReason || `The human user rejected this action via the Node9 ${via}.`,
4587
4653
  checkedBy: isApproved ? "daemon" : void 0,
4588
4654
  blockedBy: isApproved ? void 0 : "local-decision",
4589
4655
  blockedByLabel: isRedirect ? "Steered Redirect (Terminal)" : `User Decision (${via})`,
4590
- decisionSource: src
4656
+ decisionSource: "terminal"
4591
4657
  };
4592
4658
  })()
4593
4659
  );
package/dist/index.mjs CHANGED
@@ -90,6 +90,7 @@ function appendHookDebug(toolName, args, meta, auditHashArgsEnabled) {
90
90
  ...argsField,
91
91
  agent: meta?.agent,
92
92
  mcpServer: meta?.mcpServer,
93
+ sessionId: meta?.sessionId,
93
94
  hostname: os.hostname(),
94
95
  cwd: process.cwd()
95
96
  });
@@ -2058,13 +2059,6 @@ var k8s_default = {
2058
2059
  ],
2059
2060
  dangerousWords: []
2060
2061
  };
2061
- var mcp_tool_gating_default = {
2062
- name: "mcp-tool-gating",
2063
- description: "Intercept MCP tool lists and require user approval before the agent can use any tools from a new server",
2064
- aliases: ["mcp-gating", "mcp-tools"],
2065
- smartRules: [],
2066
- dangerousWords: []
2067
- };
2068
2062
  var mongodb_default = {
2069
2063
  name: "mongodb",
2070
2064
  description: "Protects MongoDB databases from destructive AI operations",
@@ -2370,7 +2364,6 @@ var BUILTIN_SHIELDS = {
2370
2364
  [filesystem_default.name]: filesystem_default,
2371
2365
  [github_default.name]: github_default,
2372
2366
  [k8s_default.name]: k8s_default,
2373
- [mcp_tool_gating_default.name]: mcp_tool_gating_default,
2374
2367
  [mongodb_default.name]: mongodb_default,
2375
2368
  [postgres_default.name]: postgres_default,
2376
2369
  [project_jail_default.name]: project_jail_default,
@@ -2899,6 +2892,12 @@ function getConfig(cwd) {
2899
2892
  if (Array.isArray(raw.rules) && raw.rules.length > 0) {
2900
2893
  applyLayer({ policy: { smartRules: raw.rules } });
2901
2894
  }
2895
+ if (raw.panicMode === true) {
2896
+ mergedSettings.panicMode = true;
2897
+ }
2898
+ if (raw.shadowMode === true) {
2899
+ mergedSettings.mode = "observe";
2900
+ }
2902
2901
  } catch {
2903
2902
  }
2904
2903
  }
@@ -3836,6 +3835,12 @@ var KNOWN_CHECKED_BY = /* @__PURE__ */ new Set([
3836
3835
  "audit-mode",
3837
3836
  "local-policy",
3838
3837
  "smart-rule-block",
3838
+ // Smart-rule block was downgraded to review because the daemon was
3839
+ // running and we're not in CI. The block attempt is still recorded;
3840
+ // the user got a popup. Distinct from 'smart-rule-block' so the
3841
+ // dashboard can show "block rule overridden" separately from a hard
3842
+ // block that fired with no human in the loop.
3843
+ "smart-rule-block-override",
3839
3844
  "persistent",
3840
3845
  "trust",
3841
3846
  "observe-mode",
@@ -3856,7 +3861,7 @@ function validateApiUrl(raw) {
3856
3861
  }
3857
3862
  return null;
3858
3863
  }
3859
- function auditLocalAllow(toolName, args, checkedBy, creds, meta, dlpInfo, containsSensitiveArgs = false) {
3864
+ function auditLocalAllow(toolName, args, checkedBy, creds, meta, dlpInfo, containsSensitiveArgs = false, riskMetadata) {
3860
3865
  const validated = validateApiUrl(creds.apiUrl);
3861
3866
  if (!validated) {
3862
3867
  try {
@@ -3873,6 +3878,10 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta, dlpInfo, contai
3873
3878
  const dlpSample = dlpInfo && typeof dlpInfo.redactedSample === "string" ? dlpInfo.redactedSample.slice(0, DLP_SAMPLE_MAX_LEN) : void 0;
3874
3879
  const dlpPattern = dlpInfo && typeof dlpInfo.pattern === "string" ? dlpInfo.pattern.slice(0, DLP_PATTERN_MAX_LEN) : void 0;
3875
3880
  const safeCheckedBy = KNOWN_CHECKED_BY.has(checkedBy) ? checkedBy : "unknown";
3881
+ const cleanedRiskMetadata = riskMetadata ? Object.fromEntries(
3882
+ Object.entries(riskMetadata).filter(([, v]) => typeof v === "string" && v.length > 0)
3883
+ ) : void 0;
3884
+ const hasRiskMetadata = cleanedRiskMetadata && Object.keys(cleanedRiskMetadata).length > 0;
3876
3885
  return fetch(`${validated.toString().replace(/\/$/, "")}/audit`, {
3877
3886
  method: "POST",
3878
3887
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${creds.apiKey}` },
@@ -3881,6 +3890,13 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta, dlpInfo, contai
3881
3890
  args: safeArgs,
3882
3891
  checkedBy: safeCheckedBy,
3883
3892
  ...dlpInfo && { dlpPattern, dlpSample },
3893
+ ...hasRiskMetadata && { riskMetadata: cleanedRiskMetadata },
3894
+ // session_id (Claude Code + Gemini CLI) groups all audit rows from one
3895
+ // agent run; transcript_path is the authoritative pointer to the
3896
+ // session log (survives Gemini resume drift). Both optional —
3897
+ // unsupported agents (MCP-mediated) leave them undefined.
3898
+ ...meta?.sessionId && { runId: meta.sessionId },
3899
+ ...meta?.transcriptPath && { transcriptPath: meta.transcriptPath },
3884
3900
  context: {
3885
3901
  agent: meta?.agent,
3886
3902
  mcpServer: meta?.mcpServer,
@@ -3931,6 +3947,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPol
3931
3947
  body: JSON.stringify({
3932
3948
  toolName,
3933
3949
  args,
3950
+ // See auditLocalAllow above for the rationale on these two fields.
3951
+ ...meta?.sessionId && { runId: meta.sessionId },
3952
+ ...meta?.transcriptPath && { transcriptPath: meta.transcriptPath },
3934
3953
  context: {
3935
3954
  agent: meta?.agent,
3936
3955
  mcpServer: meta?.mcpServer,
@@ -4113,7 +4132,8 @@ async function authorizeHeadless(toolName, args, meta, options) {
4113
4132
  args,
4114
4133
  status: "pending",
4115
4134
  agent: sanitizedAgent,
4116
- mcpServer: sanitizedMcpServer
4135
+ mcpServer: sanitizedMcpServer,
4136
+ sessionId: meta?.sessionId
4117
4137
  });
4118
4138
  const result = await _authorizeHeadlessCore(toolName, args, meta, {
4119
4139
  ...options,
@@ -4133,7 +4153,8 @@ async function authorizeHeadless(toolName, args, meta, options) {
4133
4153
  ruleHit: result.ruleHit,
4134
4154
  observeWouldBlock: result.observeWouldBlock,
4135
4155
  agent: sanitizedAgent,
4136
- mcpServer: sanitizedMcpServer
4156
+ mcpServer: sanitizedMcpServer,
4157
+ sessionId: meta?.sessionId
4137
4158
  });
4138
4159
  }
4139
4160
  return result;
@@ -4153,9 +4174,9 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
4153
4174
  };
4154
4175
  if (isTestEnv2) {
4155
4176
  approvers.native = false;
4156
- approvers.browser = false;
4157
4177
  approvers.terminal = false;
4158
4178
  }
4179
+ approvers.browser = false;
4159
4180
  if (config.settings.enableHookLogDebug && !isTestEnv2) {
4160
4181
  appendHookDebug(toolName, args, meta, hashAuditArgs);
4161
4182
  }
@@ -4285,10 +4306,24 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
4285
4306
  };
4286
4307
  }
4287
4308
  }
4288
- const policyResult = await evaluatePolicy2(toolName, args, meta?.agent);
4309
+ let policyResult = await evaluatePolicy2(toolName, args, meta?.agent);
4310
+ if (config.settings.panicMode && policyResult.decision === "review") {
4311
+ policyResult = {
4312
+ ...policyResult,
4313
+ decision: "block",
4314
+ blockedByLabel: "\u{1F6A8} Panic mode (org policy)",
4315
+ reason: "Workspace is in panic mode \u2014 all review-verdict actions are blocked. Contact your admin to disable panic mode in the Node9 dashboard."
4316
+ };
4317
+ }
4289
4318
  if (policyResult.decision === "allow") {
4290
4319
  if (approvers.cloud && creds?.apiKey)
4291
- await auditLocalAllow(toolName, args, "local-policy", creds, meta);
4320
+ await auditLocalAllow(toolName, args, "local-policy", creds, meta, void 0, false, {
4321
+ ruleName: policyResult.ruleName,
4322
+ ruleDescription: policyResult.ruleDescription,
4323
+ blockedByLabel: policyResult.blockedByLabel,
4324
+ matchedField: policyResult.matchedField,
4325
+ matchedWord: policyResult.matchedWord
4326
+ });
4292
4327
  if (!isManual) appendLocalAudit(toolName, args, "allow", "local-policy", meta, hashAuditArgs);
4293
4328
  return { approved: true, checkedBy: "local-policy" };
4294
4329
  }
@@ -4311,14 +4346,50 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
4311
4346
  }
4312
4347
  } else if (isDaemonRunning() && !isTestEnv2) {
4313
4348
  if (!isManual)
4314
- appendLocalAudit(toolName, args, "deny", "smart-rule-block", meta, hashAuditArgs);
4349
+ appendLocalAudit(
4350
+ toolName,
4351
+ args,
4352
+ "deny",
4353
+ "smart-rule-block-override",
4354
+ meta,
4355
+ hashAuditArgs
4356
+ );
4315
4357
  if (approvers.cloud && creds?.apiKey)
4316
- auditLocalAllow(toolName, args, "smart-rule-block", creds, meta);
4358
+ auditLocalAllow(
4359
+ toolName,
4360
+ args,
4361
+ "smart-rule-block-override",
4362
+ creds,
4363
+ meta,
4364
+ void 0,
4365
+ false,
4366
+ {
4367
+ ruleName: policyResult.ruleName,
4368
+ ruleDescription: policyResult.ruleDescription,
4369
+ blockedByLabel: policyResult.blockedByLabel,
4370
+ matchedField: policyResult.matchedField,
4371
+ matchedWord: policyResult.matchedWord
4372
+ }
4373
+ );
4374
+ const baseLabel = policyResult.blockedByLabel || "Smart Rule";
4375
+ const OVERRIDE_PREFIX = "\u26A0\uFE0F Override block rule: ";
4376
+ if (!baseLabel.startsWith(OVERRIDE_PREFIX)) {
4377
+ policyResult = {
4378
+ ...policyResult,
4379
+ blockedByLabel: `${OVERRIDE_PREFIX}${baseLabel}`
4380
+ };
4381
+ }
4317
4382
  } else {
4318
4383
  if (!isManual)
4319
4384
  appendLocalAudit(toolName, args, "deny", "smart-rule-block", meta, hashAuditArgs);
4320
4385
  if (approvers.cloud && creds?.apiKey)
4321
- auditLocalAllow(toolName, args, "smart-rule-block", creds, meta);
4386
+ auditLocalAllow(toolName, args, "smart-rule-block", creds, meta, void 0, false, {
4387
+ ruleName: policyResult.ruleName,
4388
+ ruleDescription: policyResult.ruleDescription,
4389
+ blockedByLabel: policyResult.blockedByLabel,
4390
+ matchedField: policyResult.matchedField,
4391
+ matchedWord: policyResult.matchedWord
4392
+ });
4322
4393
  return {
4323
4394
  approved: false,
4324
4395
  reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
@@ -4456,7 +4527,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
4456
4527
  const internalToken = getInternalToken();
4457
4528
  let daemonEntryId = null;
4458
4529
  let daemonAllowCount = 1;
4459
- if ((approvers.browser || approvers.terminal) && isDaemonRunning() && !options?.calledFromDaemon) {
4530
+ if (approvers.terminal && isDaemonRunning() && !options?.calledFromDaemon) {
4460
4531
  if (cloudEnforced && cloudRequestId) {
4461
4532
  const viewer = await notifyDaemonViewer(toolName, args, meta, riskMetadata).catch(() => null);
4462
4533
  viewerId = viewer?.id ?? null;
@@ -4534,7 +4605,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
4534
4605
  })()
4535
4606
  );
4536
4607
  }
4537
- if (daemonEntryId && (approvers.browser || approvers.terminal)) {
4608
+ if (daemonEntryId && approvers.terminal) {
4538
4609
  racePromises.push(
4539
4610
  (async () => {
4540
4611
  const {
@@ -4545,19 +4616,14 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
4545
4616
  if (daemonDecision === "abandoned") throw new Error("Abandoned");
4546
4617
  const isApproved = daemonDecision === "allow";
4547
4618
  const isRedirect = decisionSource === "terminal-redirect";
4548
- const src = decisionSource === "terminal" || decisionSource === "terminal-redirect" || decisionSource === "browser" ? decisionSource === "browser" ? "browser" : "terminal" : approvers.browser ? "browser" : "terminal";
4549
- const via = src === "terminal" ? "Terminal (node9 tail)" : "Browser Dashboard";
4619
+ const via = "Terminal (node9 tail)";
4550
4620
  return {
4551
4621
  approved: isApproved,
4552
- reason: isApproved ? void 0 : (
4553
- // Use the redirect reason from the tail when choice [2] was selected;
4554
- // otherwise fall back to the generic rejection message.
4555
- isRedirect && daemonReason || `The human user rejected this action via the Node9 ${via}.`
4556
- ),
4622
+ reason: isApproved ? void 0 : isRedirect && daemonReason || `The human user rejected this action via the Node9 ${via}.`,
4557
4623
  checkedBy: isApproved ? "daemon" : void 0,
4558
4624
  blockedBy: isApproved ? void 0 : "local-decision",
4559
4625
  blockedByLabel: isRedirect ? "Steered Redirect (Terminal)" : `User Decision (${via})`,
4560
- decisionSource: src
4626
+ decisionSource: "terminal"
4561
4627
  };
4562
4628
  })()
4563
4629
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node9/proxy",
3
- "version": "1.17.0",
3
+ "version": "1.18.1",
4
4
  "description": "The Sudo Command for AI Agents. Execution Security for Claude Code & MCP.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",