@node9/proxy 1.17.0 → 1.18.0
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 +3291 -6458
- package/dist/cli.mjs +3251 -6417
- package/dist/index.js +93 -27
- package/dist/index.mjs +93 -27
- package/package.json +1 -1
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
|
-
|
|
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(
|
|
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(
|
|
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 (
|
|
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 &&
|
|
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
|
|
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:
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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 (
|
|
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 &&
|
|
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
|
|
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:
|
|
4626
|
+
decisionSource: "terminal"
|
|
4561
4627
|
};
|
|
4562
4628
|
})()
|
|
4563
4629
|
);
|