@node9/proxy 1.65.1 → 1.67.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 +290 -51
- package/dist/cli.mjs +290 -51
- package/dist/dashboard.mjs +44 -21
- package/dist/index.js +167 -24
- package/dist/index.mjs +167 -24
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -72,7 +72,7 @@ function redactSecrets(text) {
|
|
|
72
72
|
if (!text) return text;
|
|
73
73
|
let redacted = text;
|
|
74
74
|
redacted = redacted.replace(
|
|
75
|
-
/(authorization:\s*(?:bearer|basic)\s+)[a-zA-Z0-9._
|
|
75
|
+
/(authorization:\s*(?:bearer|basic)\s+)[a-zA-Z0-9._\-\/=]+/gi,
|
|
76
76
|
"$1********"
|
|
77
77
|
);
|
|
78
78
|
redacted = redacted.replace(
|
|
@@ -133,7 +133,7 @@ function filePathFromArgs(args) {
|
|
|
133
133
|
return void 0;
|
|
134
134
|
}
|
|
135
135
|
function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashArgsEnabled) {
|
|
136
|
-
const isDlpRow = checkedBy.toLowerCase().includes("dlp") || Boolean(meta?.dlpPattern);
|
|
136
|
+
const isDlpRow = checkedBy.toLowerCase().includes("dlp") || Boolean(meta?.dlpPattern) || /dlp|taint/i.test(String(meta?.ruleName ?? ""));
|
|
137
137
|
const preview = auditHashArgsEnabled && !isDlpRow ? buildArgsPreview(args) : void 0;
|
|
138
138
|
const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args), ...preview ? { argsPreview: preview } : {} } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
|
|
139
139
|
const testRun = isTestCall(toolName, args) || process.env.NODE9_TESTING === "1" ? { testRun: true } : {};
|
|
@@ -284,8 +284,10 @@ var ConfigFileSchema = z.object({
|
|
|
284
284
|
agentPolicy: z.enum(["require_approval", "block_on_rules"]).optional(),
|
|
285
285
|
// Where a `review` verdict's prompt is rendered: 'ask' = the agent's own
|
|
286
286
|
// inline approve/deny prompt (Claude Code / GitHub Copilot); 'approver' =
|
|
287
|
-
// node9's own approver (terminal/native/cloud). Unset →
|
|
288
|
-
//
|
|
287
|
+
// node9's own approver (terminal/native/cloud). Unset → default ASK for
|
|
288
|
+
// ask-capable agents (v2: cloud no longer disables inline — the outcome
|
|
289
|
+
// ships to the dashboard as audit; admins force routing via managed
|
|
290
|
+
// reviewChannel, which outranks the local --ask flag).
|
|
289
291
|
reviewChannel: z.enum(["ask", "approver"]).optional(),
|
|
290
292
|
// When true, agents may call WEAKENING node9 MCP tools (shield_disable,
|
|
291
293
|
// approver_set). Default (unset/false): those tools refuse over MCP — a human
|
|
@@ -323,7 +325,18 @@ var ConfigFileSchema = z.object({
|
|
|
323
325
|
dlp: z.object({
|
|
324
326
|
enabled: z.boolean().optional(),
|
|
325
327
|
scanIgnoredTools: z.boolean().optional(),
|
|
326
|
-
pii: z.enum(["off", "block"]).optional()
|
|
328
|
+
pii: z.enum(["off", "block"]).optional(),
|
|
329
|
+
reviewAction: z.enum(["review", "block"]).optional()
|
|
330
|
+
}).optional(),
|
|
331
|
+
// Command-checks governance. Class-B keys (evalDynamic, pipeChainHigh)
|
|
332
|
+
// deliberately exclude 'off' — tighten-only.
|
|
333
|
+
commandChecks: z.object({
|
|
334
|
+
inlineExec: z.enum(["off", "review", "block"]).optional(),
|
|
335
|
+
rmAdvisory: z.enum(["off", "review", "block"]).optional(),
|
|
336
|
+
chmod: z.enum(["off", "review", "block"]).optional(),
|
|
337
|
+
sqlDdl: z.enum(["off", "review", "block"]).optional(),
|
|
338
|
+
evalDynamic: z.enum(["review", "block"]).optional(),
|
|
339
|
+
pipeChainHigh: z.enum(["review", "block"]).optional()
|
|
327
340
|
}).optional(),
|
|
328
341
|
egress: z.object({
|
|
329
342
|
enabled: z.boolean().optional(),
|
|
@@ -2335,6 +2348,47 @@ function evaluateSmartConditions(args, rule) {
|
|
|
2335
2348
|
});
|
|
2336
2349
|
return mode === "any" ? results.some((r) => r) : results.every((r) => r);
|
|
2337
2350
|
}
|
|
2351
|
+
function resolveCheck(v) {
|
|
2352
|
+
return v === "off" || v === "block" ? v : "review";
|
|
2353
|
+
}
|
|
2354
|
+
function resolveCheckTight(v) {
|
|
2355
|
+
return v === "block" ? "block" : "review";
|
|
2356
|
+
}
|
|
2357
|
+
var INLINE_INTERP = /^(python[\d.]*|bash|sh|zsh|perl|ruby|node|php|lua)$/i;
|
|
2358
|
+
var INLINE_SHELL = /^(bash|sh|zsh)$/i;
|
|
2359
|
+
function detectInlineExec(command) {
|
|
2360
|
+
const pipeFed = command.includes("|");
|
|
2361
|
+
const segments = command.split(/\|\||&&|;|\||\n|\$\(|`|\(/);
|
|
2362
|
+
for (const rawSeg of segments) {
|
|
2363
|
+
const tokens = rawSeg.trim().split(/\s+/).filter(Boolean);
|
|
2364
|
+
let i = 0;
|
|
2365
|
+
while (i < tokens.length && /^[A-Za-z_]\w*=/.test(tokens[i])) i++;
|
|
2366
|
+
if (i >= tokens.length) continue;
|
|
2367
|
+
const base = tokens[i].split("/").pop() ?? tokens[i];
|
|
2368
|
+
if (!INLINE_INTERP.test(base)) continue;
|
|
2369
|
+
const args = tokens.slice(i + 1);
|
|
2370
|
+
let hadRedirect = false;
|
|
2371
|
+
const positionals = [];
|
|
2372
|
+
for (let j = 0; j < args.length; j++) {
|
|
2373
|
+
const a = args[j];
|
|
2374
|
+
if (a === "-") return true;
|
|
2375
|
+
if (a.startsWith("<")) {
|
|
2376
|
+
hadRedirect = true;
|
|
2377
|
+
if (a === "<" || a === "<<") j++;
|
|
2378
|
+
continue;
|
|
2379
|
+
}
|
|
2380
|
+
if (a.startsWith("-")) {
|
|
2381
|
+
if (/^-(c|e|eval)$/i.test(a)) return true;
|
|
2382
|
+
continue;
|
|
2383
|
+
}
|
|
2384
|
+
positionals.push(a);
|
|
2385
|
+
}
|
|
2386
|
+
if (positionals.length === 0 && (hadRedirect || pipeFed) && !INLINE_SHELL.test(base)) {
|
|
2387
|
+
return true;
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
return false;
|
|
2391
|
+
}
|
|
2338
2392
|
var VERDICT_RANK = {
|
|
2339
2393
|
allow: 0,
|
|
2340
2394
|
review: 1,
|
|
@@ -2367,7 +2421,7 @@ function isSqlTool(toolName, toolInspection) {
|
|
|
2367
2421
|
return fieldName === "sql" || fieldName === "query";
|
|
2368
2422
|
}
|
|
2369
2423
|
var SQL_DML_KEYWORDS = /* @__PURE__ */ new Set(["select", "insert", "update", "delete", "merge", "upsert"]);
|
|
2370
|
-
function pipeChainVerdict(command, isTrustedHost2) {
|
|
2424
|
+
function pipeChainVerdict(command, isTrustedHost2, highAction = "review") {
|
|
2371
2425
|
const pipeAnalysis = analyzePipeChain(command);
|
|
2372
2426
|
if (!pipeAnalysis.isPipeline) return null;
|
|
2373
2427
|
if (pipeAnalysis.risk !== "critical" && pipeAnalysis.risk !== "high") return null;
|
|
@@ -2398,7 +2452,7 @@ function pipeChainVerdict(command, isTrustedHost2) {
|
|
|
2398
2452
|
};
|
|
2399
2453
|
}
|
|
2400
2454
|
return {
|
|
2401
|
-
decision:
|
|
2455
|
+
decision: highAction,
|
|
2402
2456
|
blockedByLabel: "Node9: Pipe-Chain Exfiltration (high)",
|
|
2403
2457
|
reason: `Sensitive file piped to network sink: ${pipeAnalysis.sourceFiles.join(", ")} \u2192 ${sinks.join(", ")}`,
|
|
2404
2458
|
tier: 3
|
|
@@ -2412,7 +2466,10 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2412
2466
|
const dlpMatch = args !== void 0 ? scanArgs(args) : null;
|
|
2413
2467
|
if (dlpMatch) {
|
|
2414
2468
|
return {
|
|
2415
|
-
|
|
2469
|
+
// reviewAction:'block' (inline-ask v2): the admin upgraded
|
|
2470
|
+
// review-severity matches to a hard block — every evaluatePolicy
|
|
2471
|
+
// caller (orchestrator, explain, gateway) must agree with the gate.
|
|
2472
|
+
decision: dlpMatch.severity === "block" || config.policy.dlp.reviewAction === "block" ? "block" : "review",
|
|
2416
2473
|
blockedByLabel: `DLP: ${dlpMatch.patternName}`,
|
|
2417
2474
|
reason: `${dlpMatch.patternName} detected in ${dlpMatch.fieldPath}`
|
|
2418
2475
|
};
|
|
@@ -2421,7 +2478,11 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2421
2478
|
if (wouldBeIgnored) return { decision: "allow" };
|
|
2422
2479
|
const bashCommand = agent !== "Terminal" && isBashTool(toolName) && args && typeof args === "object" ? typeof args.command === "string" ? args.command : null : null;
|
|
2423
2480
|
if (bashCommand !== null) {
|
|
2424
|
-
const pipeVerdict = pipeChainVerdict(
|
|
2481
|
+
const pipeVerdict = pipeChainVerdict(
|
|
2482
|
+
bashCommand,
|
|
2483
|
+
isTrustedHost2,
|
|
2484
|
+
resolveCheckTight(config.policy.commandChecks?.pipeChainHigh)
|
|
2485
|
+
);
|
|
2425
2486
|
if (pipeVerdict) return pipeVerdict;
|
|
2426
2487
|
const fsVerdict = analyzeFsOperation(bashCommand);
|
|
2427
2488
|
if (fsVerdict) {
|
|
@@ -2436,10 +2497,12 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2436
2497
|
ruleDescription: fsVerdict.reason
|
|
2437
2498
|
};
|
|
2438
2499
|
}
|
|
2439
|
-
const
|
|
2500
|
+
const sqlAction = resolveCheck(config.policy.commandChecks?.sqlDdl);
|
|
2501
|
+
const sqlVerdict = sqlAction === "off" ? null : analyzeSqlDestructive(bashCommand);
|
|
2440
2502
|
if (sqlVerdict) {
|
|
2441
2503
|
return {
|
|
2442
|
-
|
|
2504
|
+
// analyzeSqlDestructive is typed review-only, so the knob maps 1:1.
|
|
2505
|
+
decision: sqlAction === "block" ? "block" : "review",
|
|
2443
2506
|
blockedByLabel: `Node9 (AST): ${sqlVerdict.ruleName}`,
|
|
2444
2507
|
reason: sqlVerdict.reason,
|
|
2445
2508
|
tier: 2,
|
|
@@ -2447,10 +2510,12 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2447
2510
|
ruleDescription: sqlVerdict.description
|
|
2448
2511
|
};
|
|
2449
2512
|
}
|
|
2450
|
-
const
|
|
2513
|
+
const chmodAction = resolveCheck(config.policy.commandChecks?.chmod);
|
|
2514
|
+
const chmodVerdict = chmodAction === "off" ? null : analyzeChmod777(bashCommand);
|
|
2451
2515
|
if (chmodVerdict) {
|
|
2452
2516
|
return {
|
|
2453
|
-
|
|
2517
|
+
// analyzeChmod777 is typed review-only, so the knob maps 1:1.
|
|
2518
|
+
decision: chmodAction === "block" ? "block" : "review",
|
|
2454
2519
|
blockedByLabel: `project-jail (AST): ${chmodVerdict.ruleName}`,
|
|
2455
2520
|
reason: chmodVerdict.reason,
|
|
2456
2521
|
tier: 2,
|
|
@@ -2493,10 +2558,10 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2493
2558
|
const analyzed = analyzeShellCommand(shellCommand);
|
|
2494
2559
|
allTokens = analyzed.allTokens;
|
|
2495
2560
|
pathTokens = analyzed.paths;
|
|
2496
|
-
const
|
|
2497
|
-
if (
|
|
2561
|
+
const inlineAction = resolveCheck(config.policy.commandChecks?.inlineExec);
|
|
2562
|
+
if (inlineAction !== "off" && detectInlineExec(shellCommand)) {
|
|
2498
2563
|
return {
|
|
2499
|
-
decision: "review",
|
|
2564
|
+
decision: inlineAction === "block" ? "block" : "review",
|
|
2500
2565
|
blockedByLabel: "Node9 Standard (Inline Execution)",
|
|
2501
2566
|
ruleDescription: "The AI is running code directly from the command line. Review the full script below before allowing it to execute.",
|
|
2502
2567
|
tier: 3
|
|
@@ -2514,14 +2579,21 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2514
2579
|
}
|
|
2515
2580
|
if (evalVerdict === "review") {
|
|
2516
2581
|
return {
|
|
2517
|
-
|
|
2582
|
+
// Class B tighten-only: commandChecks.evalDynamic may upgrade to
|
|
2583
|
+
// block but can never turn this off (eval-remote above is Class A —
|
|
2584
|
+
// no knob at all).
|
|
2585
|
+
decision: resolveCheckTight(config.policy.commandChecks?.evalDynamic),
|
|
2518
2586
|
blockedByLabel: "Node9: Eval Dynamic Content",
|
|
2519
2587
|
reason: "eval of dynamic content (variable or subshell expansion) requires approval",
|
|
2520
2588
|
ruleDescription: "The AI is running a command that includes a variable or subshell expansion. The actual command executed at runtime may differ from what is shown here.",
|
|
2521
2589
|
tier: 3
|
|
2522
2590
|
};
|
|
2523
2591
|
}
|
|
2524
|
-
const ptVerdict = pipeChainVerdict(
|
|
2592
|
+
const ptVerdict = pipeChainVerdict(
|
|
2593
|
+
shellCommand,
|
|
2594
|
+
isTrustedHost2,
|
|
2595
|
+
resolveCheckTight(config.policy.commandChecks?.pipeChainHigh)
|
|
2596
|
+
);
|
|
2525
2597
|
if (ptVerdict) return ptVerdict;
|
|
2526
2598
|
if (config.policy.egress?.enabled) {
|
|
2527
2599
|
const dests = extractShellDestinations(shellCommand);
|
|
@@ -3608,6 +3680,7 @@ function applyManagedEgress(local, managed, locked) {
|
|
|
3608
3680
|
return next;
|
|
3609
3681
|
}
|
|
3610
3682
|
var DLP_PII_ORDER = ["off", "block"];
|
|
3683
|
+
var DLP_REVIEW_ACTION_ORDER = ["review", "block"];
|
|
3611
3684
|
function applyManagedDlp(local, managed, locked) {
|
|
3612
3685
|
const next = { ...local };
|
|
3613
3686
|
if (typeof managed.enabled === "boolean") {
|
|
@@ -3621,6 +3694,40 @@ function applyManagedDlp(local, managed, locked) {
|
|
|
3621
3694
|
locked.includes("dlpPii")
|
|
3622
3695
|
);
|
|
3623
3696
|
}
|
|
3697
|
+
if (typeof managed.reviewAction === "string") {
|
|
3698
|
+
next.reviewAction = resolveByOrder(
|
|
3699
|
+
DLP_REVIEW_ACTION_ORDER,
|
|
3700
|
+
local.reviewAction ?? "review",
|
|
3701
|
+
managed.reviewAction,
|
|
3702
|
+
locked.includes("dlpReviewAction")
|
|
3703
|
+
);
|
|
3704
|
+
}
|
|
3705
|
+
return next;
|
|
3706
|
+
}
|
|
3707
|
+
var COMMAND_CHECK_ORDER = ["off", "review", "block"];
|
|
3708
|
+
var COMMAND_CHECK_KEYS = [
|
|
3709
|
+
"inlineExec",
|
|
3710
|
+
"rmAdvisory",
|
|
3711
|
+
"chmod",
|
|
3712
|
+
"sqlDdl",
|
|
3713
|
+
"evalDynamic",
|
|
3714
|
+
"pipeChainHigh"
|
|
3715
|
+
];
|
|
3716
|
+
function applyManagedCommandChecks(local, managed, locked) {
|
|
3717
|
+
const next = { ...local };
|
|
3718
|
+
for (const key of COMMAND_CHECK_KEYS) {
|
|
3719
|
+
const m = managed[key];
|
|
3720
|
+
if (typeof m !== "string") continue;
|
|
3721
|
+
const lockKey = `commandChecks${key[0].toUpperCase()}${key.slice(1)}`;
|
|
3722
|
+
const resolved = resolveByOrder(
|
|
3723
|
+
COMMAND_CHECK_ORDER,
|
|
3724
|
+
local[key] ?? "review",
|
|
3725
|
+
m,
|
|
3726
|
+
locked.includes(lockKey)
|
|
3727
|
+
);
|
|
3728
|
+
if ((key === "evalDynamic" || key === "pipeChainHigh") && resolved === "off") continue;
|
|
3729
|
+
next[key] = resolved;
|
|
3730
|
+
}
|
|
3624
3731
|
return next;
|
|
3625
3732
|
}
|
|
3626
3733
|
function applyManagedApprovers(local, managed) {
|
|
@@ -4191,6 +4298,22 @@ function getConfig(cwd) {
|
|
|
4191
4298
|
if (d.enabled !== void 0) mergedPolicy.dlp.enabled = d.enabled;
|
|
4192
4299
|
if (d.scanIgnoredTools !== void 0) mergedPolicy.dlp.scanIgnoredTools = d.scanIgnoredTools;
|
|
4193
4300
|
if (d.pii !== void 0) mergedPolicy.dlp.pii = d.pii;
|
|
4301
|
+
if (d.reviewAction !== void 0) mergedPolicy.dlp.reviewAction = d.reviewAction;
|
|
4302
|
+
}
|
|
4303
|
+
if (p.commandChecks && typeof p.commandChecks === "object") {
|
|
4304
|
+
const src = p.commandChecks;
|
|
4305
|
+
const cc2 = {
|
|
4306
|
+
...mergedPolicy.commandChecks
|
|
4307
|
+
};
|
|
4308
|
+
for (const k of ["inlineExec", "rmAdvisory", "chmod", "sqlDdl"]) {
|
|
4309
|
+
const v = src[k];
|
|
4310
|
+
if (v === "off" || v === "review" || v === "block") cc2[k] = v;
|
|
4311
|
+
}
|
|
4312
|
+
for (const k of ["evalDynamic", "pipeChainHigh"]) {
|
|
4313
|
+
const v = src[k];
|
|
4314
|
+
if (v === "review" || v === "block") cc2[k] = v;
|
|
4315
|
+
}
|
|
4316
|
+
if (Object.keys(cc2).length > 0) mergedPolicy.commandChecks = cc2;
|
|
4194
4317
|
}
|
|
4195
4318
|
if (p.egress) {
|
|
4196
4319
|
const e = p.egress;
|
|
@@ -4287,11 +4410,19 @@ function getConfig(cwd) {
|
|
|
4287
4410
|
mergedPolicy.dlp,
|
|
4288
4411
|
{
|
|
4289
4412
|
enabled: typeof mc.dlp.enabled === "boolean" ? mc.dlp.enabled : void 0,
|
|
4290
|
-
pii: typeof mc.dlp.pii === "string" ? mc.dlp.pii : void 0
|
|
4413
|
+
pii: typeof mc.dlp.pii === "string" ? mc.dlp.pii : void 0,
|
|
4414
|
+
reviewAction: mc.dlp.reviewAction === "review" || mc.dlp.reviewAction === "block" ? mc.dlp.reviewAction : void 0
|
|
4291
4415
|
},
|
|
4292
4416
|
locked
|
|
4293
4417
|
);
|
|
4294
4418
|
}
|
|
4419
|
+
if (mc.commandChecks && typeof mc.commandChecks === "object") {
|
|
4420
|
+
mergedPolicy.commandChecks = applyManagedCommandChecks(
|
|
4421
|
+
mergedPolicy.commandChecks ?? {},
|
|
4422
|
+
mc.commandChecks,
|
|
4423
|
+
locked
|
|
4424
|
+
);
|
|
4425
|
+
}
|
|
4295
4426
|
if (mc.approvers && typeof mc.approvers === "object") {
|
|
4296
4427
|
const bool = (v) => typeof v === "boolean" ? v : void 0;
|
|
4297
4428
|
mergedSettings.approvers = applyManagedApprovers(mergedSettings.approvers, {
|
|
@@ -4303,6 +4434,7 @@ function getConfig(cwd) {
|
|
|
4303
4434
|
}
|
|
4304
4435
|
if (mc.reviewChannel === "ask" || mc.reviewChannel === "approver") {
|
|
4305
4436
|
mergedSettings.reviewChannel = mc.reviewChannel;
|
|
4437
|
+
mergedSettings.reviewChannelManaged = true;
|
|
4306
4438
|
}
|
|
4307
4439
|
if (typeof mc.approvalTimeoutMs === "number" && mc.approvalTimeoutMs > 0) {
|
|
4308
4440
|
mergedSettings.approvalTimeoutMs = mc.approvalTimeoutMs;
|
|
@@ -4400,8 +4532,17 @@ function getConfig(cwd) {
|
|
|
4400
4532
|
}
|
|
4401
4533
|
}
|
|
4402
4534
|
const existingAdvisoryNames = new Set(mergedPolicy.smartRules.map((r) => r.name));
|
|
4535
|
+
const cc = mergedPolicy.commandChecks ?? {};
|
|
4536
|
+
const advisoryKnob = (name) => {
|
|
4537
|
+
if (name === "review-rm") return cc.rmAdvisory;
|
|
4538
|
+
if (name?.endsWith("-sql")) return cc.sqlDdl;
|
|
4539
|
+
return void 0;
|
|
4540
|
+
};
|
|
4403
4541
|
for (const rule of ADVISORY_SMART_RULES) {
|
|
4404
|
-
if (
|
|
4542
|
+
if (existingAdvisoryNames.has(rule.name)) continue;
|
|
4543
|
+
const knob = rule.verdict === "review" ? advisoryKnob(rule.name) : void 0;
|
|
4544
|
+
if (knob === "off") continue;
|
|
4545
|
+
mergedPolicy.smartRules.push(knob === "block" ? { ...rule, verdict: "block" } : rule);
|
|
4405
4546
|
}
|
|
4406
4547
|
const envMode = process.env.NODE9_MODE;
|
|
4407
4548
|
if (envMode && !modeCloudControlled && ["observe", "audit", "standard", "strict"].includes(envMode)) {
|
|
@@ -5661,7 +5802,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
|
|
|
5661
5802
|
const dlpMatch = (filePath ? scanFilePath(filePath) : null) ?? scanArgs(args);
|
|
5662
5803
|
if (dlpMatch) {
|
|
5663
5804
|
const dlpReason = `\u{1F6A8} DATA LOSS PREVENTION: ${dlpMatch.patternName} detected in field "${dlpMatch.fieldPath}" (${dlpMatch.redactedSample})`;
|
|
5664
|
-
if (dlpMatch.severity === "block") {
|
|
5805
|
+
if (dlpMatch.severity === "block" || config.policy.dlp.reviewAction === "block") {
|
|
5665
5806
|
let dlpEid;
|
|
5666
5807
|
if (!isManual)
|
|
5667
5808
|
dlpEid = appendLocalAudit(
|
|
@@ -5999,12 +6140,14 @@ ${appPermReview}`
|
|
|
5999
6140
|
);
|
|
6000
6141
|
}
|
|
6001
6142
|
}
|
|
6002
|
-
|
|
6003
|
-
if (options?.deferReview && !hardBlockDowngraded && !taintWarning && !appPermReview && !cloudEnforcedForDefer) {
|
|
6143
|
+
if (options?.deferReview && !hardBlockDowngraded) {
|
|
6004
6144
|
return {
|
|
6005
6145
|
approved: false,
|
|
6006
6146
|
review: true,
|
|
6007
|
-
|
|
6147
|
+
// The prompt must say WHY: the taint sentence beats the bare label so
|
|
6148
|
+
// the dev sees the actual risk context inline. (No appPermReview term:
|
|
6149
|
+
// deferReview and serverKey never co-occur in production — see above.)
|
|
6150
|
+
reason: taintWarning || explainableLabel || "Node9 flagged this action for review.",
|
|
6008
6151
|
ruleDescription: policyRuleDescription,
|
|
6009
6152
|
blockedByLabel: explainableLabel
|
|
6010
6153
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node9/proxy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.67.0",
|
|
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",
|