@node9/proxy 1.0.1 → 1.0.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/README.md +28 -4
- package/dist/cli.js +207 -143
- package/dist/cli.mjs +207 -143
- package/dist/index.js +190 -71
- package/dist/index.mjs +190 -71
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -115,21 +115,47 @@ function sendDesktopNotification(title, body) {
|
|
|
115
115
|
} catch {
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
+
function escapePango(text) {
|
|
119
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
120
|
+
}
|
|
121
|
+
function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked) {
|
|
122
|
+
const lines = [];
|
|
123
|
+
if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
|
|
124
|
+
lines.push(`\u{1F916} ${agent || "AI Agent"} | \u{1F527} ${toolName}`);
|
|
125
|
+
lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
|
|
126
|
+
lines.push("");
|
|
127
|
+
lines.push(formattedArgs);
|
|
128
|
+
if (!locked) {
|
|
129
|
+
lines.push("");
|
|
130
|
+
lines.push('\u21B5 Enter = Allow \u21B5 | \u238B Esc = Block \u238B | "Always Allow" = never ask again');
|
|
131
|
+
}
|
|
132
|
+
return lines.join("\n");
|
|
133
|
+
}
|
|
134
|
+
function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked) {
|
|
135
|
+
const lines = [];
|
|
136
|
+
if (locked) {
|
|
137
|
+
lines.push('<span foreground="red" weight="bold">\u26A0\uFE0F LOCKED BY ADMIN POLICY</span>');
|
|
138
|
+
lines.push("");
|
|
139
|
+
}
|
|
140
|
+
lines.push(
|
|
141
|
+
`<b>\u{1F916} ${escapePango(agent || "AI Agent")}</b> | <b>\u{1F527} <tt>${escapePango(toolName)}</tt></b>`
|
|
142
|
+
);
|
|
143
|
+
lines.push(`<i>\u{1F6E1}\uFE0F ${escapePango(explainableLabel || "Security Policy")}</i>`);
|
|
144
|
+
lines.push("");
|
|
145
|
+
lines.push(`<tt>${escapePango(formattedArgs)}</tt>`);
|
|
146
|
+
if (!locked) {
|
|
147
|
+
lines.push("");
|
|
148
|
+
lines.push(
|
|
149
|
+
'<small>\u21B5 Enter = <b>Allow \u21B5</b> | \u238B Esc = <b>Block \u238B</b> | "Always Allow" = never ask again</small>'
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
return lines.join("\n");
|
|
153
|
+
}
|
|
118
154
|
async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal) {
|
|
119
155
|
if (isTestEnv()) return "deny";
|
|
120
156
|
const formattedArgs = formatArgs(args);
|
|
121
157
|
const title = locked ? `\u26A1 Node9 \u2014 Locked` : `\u{1F6E1}\uFE0F Node9 \u2014 Action Approval`;
|
|
122
|
-
|
|
123
|
-
if (locked) message += `\u26A0\uFE0F LOCKED BY ADMIN POLICY
|
|
124
|
-
`;
|
|
125
|
-
message += `Tool: ${toolName}
|
|
126
|
-
`;
|
|
127
|
-
message += `Agent: ${agent || "AI Agent"}
|
|
128
|
-
`;
|
|
129
|
-
message += `Rule: ${explainableLabel || "Security Policy"}
|
|
130
|
-
|
|
131
|
-
`;
|
|
132
|
-
message += `${formattedArgs}`;
|
|
158
|
+
const message = buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked);
|
|
133
159
|
process.stderr.write(import_chalk.default.yellow(`
|
|
134
160
|
\u{1F6E1}\uFE0F Node9: Intercepted "${toolName}" \u2014 awaiting user...
|
|
135
161
|
`));
|
|
@@ -150,7 +176,7 @@ async function askNativePopup(toolName, args, agent, explainableLabel, locked =
|
|
|
150
176
|
}
|
|
151
177
|
try {
|
|
152
178
|
if (process.platform === "darwin") {
|
|
153
|
-
const buttons = locked ? `buttons {"Waiting\u2026"} default button "Waiting\u2026"` : `buttons {"Block", "Always Allow", "Allow"} default button "Allow" cancel button "Block"`;
|
|
179
|
+
const buttons = locked ? `buttons {"Waiting\u2026"} default button "Waiting\u2026"` : `buttons {"Block \u238B", "Always Allow", "Allow \u21B5"} default button "Allow \u21B5" cancel button "Block \u238B"`;
|
|
154
180
|
const script = `on run argv
|
|
155
181
|
tell application "System Events"
|
|
156
182
|
activate
|
|
@@ -159,21 +185,28 @@ end tell
|
|
|
159
185
|
end run`;
|
|
160
186
|
childProcess = (0, import_child_process.spawn)("osascript", ["-e", script, "--", message, title]);
|
|
161
187
|
} else if (process.platform === "linux") {
|
|
188
|
+
const pangoMessage = buildPangoMessage(
|
|
189
|
+
toolName,
|
|
190
|
+
formattedArgs,
|
|
191
|
+
agent,
|
|
192
|
+
explainableLabel,
|
|
193
|
+
locked
|
|
194
|
+
);
|
|
162
195
|
const argsList = [
|
|
163
196
|
locked ? "--info" : "--question",
|
|
164
197
|
"--modal",
|
|
165
|
-
"--width=
|
|
198
|
+
"--width=480",
|
|
166
199
|
"--title",
|
|
167
200
|
title,
|
|
168
201
|
"--text",
|
|
169
|
-
|
|
202
|
+
pangoMessage,
|
|
170
203
|
"--ok-label",
|
|
171
|
-
locked ? "Waiting..." : "Allow",
|
|
204
|
+
locked ? "Waiting..." : "Allow \u21B5",
|
|
172
205
|
"--timeout",
|
|
173
206
|
"300"
|
|
174
207
|
];
|
|
175
208
|
if (!locked) {
|
|
176
|
-
argsList.push("--cancel-label", "Block");
|
|
209
|
+
argsList.push("--cancel-label", "Block \u238B");
|
|
177
210
|
argsList.push("--extra-button", "Always Allow");
|
|
178
211
|
}
|
|
179
212
|
childProcess = (0, import_child_process.spawn)("zenity", argsList);
|
|
@@ -201,6 +234,8 @@ end run`;
|
|
|
201
234
|
// src/core.ts
|
|
202
235
|
var PAUSED_FILE = import_path.default.join(import_os.default.homedir(), ".node9", "PAUSED");
|
|
203
236
|
var TRUST_FILE = import_path.default.join(import_os.default.homedir(), ".node9", "trust.json");
|
|
237
|
+
var LOCAL_AUDIT_LOG = import_path.default.join(import_os.default.homedir(), ".node9", "audit.log");
|
|
238
|
+
var HOOK_DEBUG_LOG = import_path.default.join(import_os.default.homedir(), ".node9", "hook-debug.log");
|
|
204
239
|
function checkPause() {
|
|
205
240
|
try {
|
|
206
241
|
if (!import_fs.default.existsSync(PAUSED_FILE)) return { paused: false };
|
|
@@ -257,36 +292,39 @@ function writeTrustSession(toolName, durationMs) {
|
|
|
257
292
|
}
|
|
258
293
|
}
|
|
259
294
|
}
|
|
260
|
-
function
|
|
295
|
+
function appendToLog(logPath, entry) {
|
|
261
296
|
try {
|
|
262
|
-
const entry = JSON.stringify({
|
|
263
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
264
|
-
tool: toolName,
|
|
265
|
-
args,
|
|
266
|
-
decision: "would-have-blocked",
|
|
267
|
-
source: "audit-mode"
|
|
268
|
-
});
|
|
269
|
-
const logPath = import_path.default.join(import_os.default.homedir(), ".node9", "audit.log");
|
|
270
297
|
const dir = import_path.default.dirname(logPath);
|
|
271
298
|
if (!import_fs.default.existsSync(dir)) import_fs.default.mkdirSync(dir, { recursive: true });
|
|
272
|
-
import_fs.default.appendFileSync(logPath, entry + "\n");
|
|
299
|
+
import_fs.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
|
273
300
|
} catch {
|
|
274
301
|
}
|
|
275
302
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
303
|
+
function appendHookDebug(toolName, args, meta) {
|
|
304
|
+
const safeArgs = args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {};
|
|
305
|
+
appendToLog(HOOK_DEBUG_LOG, {
|
|
306
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
307
|
+
tool: toolName,
|
|
308
|
+
args: safeArgs,
|
|
309
|
+
agent: meta?.agent,
|
|
310
|
+
mcpServer: meta?.mcpServer,
|
|
311
|
+
hostname: import_os.default.hostname(),
|
|
312
|
+
cwd: process.cwd()
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
function appendLocalAudit(toolName, args, decision, checkedBy, meta) {
|
|
316
|
+
const safeArgs = args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {};
|
|
317
|
+
appendToLog(LOCAL_AUDIT_LOG, {
|
|
318
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
319
|
+
tool: toolName,
|
|
320
|
+
args: safeArgs,
|
|
321
|
+
decision,
|
|
322
|
+
checkedBy,
|
|
323
|
+
agent: meta?.agent,
|
|
324
|
+
mcpServer: meta?.mcpServer,
|
|
325
|
+
hostname: import_os.default.hostname()
|
|
326
|
+
});
|
|
327
|
+
}
|
|
290
328
|
function tokenize(toolName) {
|
|
291
329
|
return toolName.toLowerCase().split(/[_.\-\s]+/).filter(Boolean);
|
|
292
330
|
}
|
|
@@ -380,16 +418,41 @@ async function analyzeShellCommand(command) {
|
|
|
380
418
|
}
|
|
381
419
|
return { actions, paths, allTokens };
|
|
382
420
|
}
|
|
421
|
+
function redactSecrets(text) {
|
|
422
|
+
if (!text) return text;
|
|
423
|
+
let redacted = text;
|
|
424
|
+
redacted = redacted.replace(
|
|
425
|
+
/(authorization:\s*(?:bearer|basic)\s+)[a-zA-Z0-9._\-\/\\=]+/gi,
|
|
426
|
+
"$1********"
|
|
427
|
+
);
|
|
428
|
+
redacted = redacted.replace(
|
|
429
|
+
/(api[_-]?key|secret|password|token)([:=]\s*['"]?)[a-zA-Z0-9._\-]{8,}/gi,
|
|
430
|
+
"$1$2********"
|
|
431
|
+
);
|
|
432
|
+
return redacted;
|
|
433
|
+
}
|
|
434
|
+
var DANGEROUS_WORDS = [
|
|
435
|
+
"drop",
|
|
436
|
+
"truncate",
|
|
437
|
+
"purge",
|
|
438
|
+
"format",
|
|
439
|
+
"destroy",
|
|
440
|
+
"terminate",
|
|
441
|
+
"revoke",
|
|
442
|
+
"docker",
|
|
443
|
+
"psql"
|
|
444
|
+
];
|
|
383
445
|
var DEFAULT_CONFIG = {
|
|
384
446
|
settings: {
|
|
385
447
|
mode: "standard",
|
|
386
448
|
autoStartDaemon: true,
|
|
387
|
-
enableUndo:
|
|
449
|
+
enableUndo: true,
|
|
450
|
+
// 🔥 ALWAYS TRUE BY DEFAULT for the safety net
|
|
388
451
|
enableHookLogDebug: false,
|
|
389
452
|
approvers: { native: true, browser: true, cloud: true, terminal: true }
|
|
390
453
|
},
|
|
391
454
|
policy: {
|
|
392
|
-
sandboxPaths: [],
|
|
455
|
+
sandboxPaths: ["/tmp/**", "**/sandbox/**", "**/test-results/**"],
|
|
393
456
|
dangerousWords: DANGEROUS_WORDS,
|
|
394
457
|
ignoredTools: [
|
|
395
458
|
"list_*",
|
|
@@ -397,12 +460,44 @@ var DEFAULT_CONFIG = {
|
|
|
397
460
|
"read_*",
|
|
398
461
|
"describe_*",
|
|
399
462
|
"read",
|
|
463
|
+
"glob",
|
|
400
464
|
"grep",
|
|
401
465
|
"ls",
|
|
402
|
-
"
|
|
466
|
+
"notebookread",
|
|
467
|
+
"notebookedit",
|
|
468
|
+
"webfetch",
|
|
469
|
+
"websearch",
|
|
470
|
+
"exitplanmode",
|
|
471
|
+
"askuserquestion",
|
|
472
|
+
"agent",
|
|
473
|
+
"task*",
|
|
474
|
+
"toolsearch",
|
|
475
|
+
"mcp__ide__*",
|
|
476
|
+
"getDiagnostics"
|
|
403
477
|
],
|
|
404
|
-
toolInspection: {
|
|
405
|
-
|
|
478
|
+
toolInspection: {
|
|
479
|
+
bash: "command",
|
|
480
|
+
shell: "command",
|
|
481
|
+
run_shell_command: "command",
|
|
482
|
+
"terminal.execute": "command",
|
|
483
|
+
"postgres:query": "sql"
|
|
484
|
+
},
|
|
485
|
+
rules: [
|
|
486
|
+
{
|
|
487
|
+
action: "rm",
|
|
488
|
+
allowPaths: [
|
|
489
|
+
"**/node_modules/**",
|
|
490
|
+
"dist/**",
|
|
491
|
+
"build/**",
|
|
492
|
+
".next/**",
|
|
493
|
+
"coverage/**",
|
|
494
|
+
".cache/**",
|
|
495
|
+
"tmp/**",
|
|
496
|
+
"temp/**",
|
|
497
|
+
".DS_Store"
|
|
498
|
+
]
|
|
499
|
+
}
|
|
500
|
+
]
|
|
406
501
|
},
|
|
407
502
|
environments: {}
|
|
408
503
|
};
|
|
@@ -440,20 +535,15 @@ async function evaluatePolicy(toolName, args, agent) {
|
|
|
440
535
|
}
|
|
441
536
|
const isManual = agent === "Terminal";
|
|
442
537
|
if (isManual) {
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
"
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
"revoke",
|
|
453
|
-
"docker"
|
|
454
|
-
];
|
|
455
|
-
const hasNuclear = allTokens.some((t) => NUCLEAR_COMMANDS.includes(t.toLowerCase()));
|
|
456
|
-
if (!hasNuclear) return { decision: "allow" };
|
|
538
|
+
const SYSTEM_DISASTER_COMMANDS = ["mkfs", "shred", "dd", "drop", "truncate", "purge"];
|
|
539
|
+
const hasSystemDisaster = allTokens.some(
|
|
540
|
+
(t) => SYSTEM_DISASTER_COMMANDS.includes(t.toLowerCase())
|
|
541
|
+
);
|
|
542
|
+
const isRootWipe = allTokens.includes("rm") && (allTokens.includes("/") || allTokens.includes("/*"));
|
|
543
|
+
if (hasSystemDisaster || isRootWipe) {
|
|
544
|
+
return { decision: "review", blockedByLabel: "Manual Nuclear Protection" };
|
|
545
|
+
}
|
|
546
|
+
return { decision: "allow" };
|
|
457
547
|
}
|
|
458
548
|
if (pathTokens.length > 0 && config.policy.sandboxPaths.length > 0) {
|
|
459
549
|
const allInSandbox = pathTokens.every((p) => matchesPattern(p, config.policy.sandboxPaths));
|
|
@@ -467,27 +557,39 @@ async function evaluatePolicy(toolName, args, agent) {
|
|
|
467
557
|
if (pathTokens.length > 0) {
|
|
468
558
|
const anyBlocked = pathTokens.some((p) => matchesPattern(p, rule.blockPaths || []));
|
|
469
559
|
if (anyBlocked)
|
|
470
|
-
return {
|
|
560
|
+
return {
|
|
561
|
+
decision: "review",
|
|
562
|
+
blockedByLabel: `Project/Global Config \u2014 rule "${rule.action}" (path blocked)`
|
|
563
|
+
};
|
|
471
564
|
const allAllowed = pathTokens.every((p) => matchesPattern(p, rule.allowPaths || []));
|
|
472
565
|
if (allAllowed) return { decision: "allow" };
|
|
473
566
|
}
|
|
474
|
-
return {
|
|
567
|
+
return {
|
|
568
|
+
decision: "review",
|
|
569
|
+
blockedByLabel: `Project/Global Config \u2014 rule "${rule.action}" (default block)`
|
|
570
|
+
};
|
|
475
571
|
}
|
|
476
572
|
}
|
|
573
|
+
let matchedDangerousWord;
|
|
477
574
|
const isDangerous = allTokens.some(
|
|
478
575
|
(token) => config.policy.dangerousWords.some((word) => {
|
|
479
576
|
const w = word.toLowerCase();
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
577
|
+
const hit = token === w || (() => {
|
|
578
|
+
try {
|
|
579
|
+
return new RegExp(`\\b${w}\\b`, "i").test(token);
|
|
580
|
+
} catch {
|
|
581
|
+
return false;
|
|
582
|
+
}
|
|
583
|
+
})();
|
|
584
|
+
if (hit && !matchedDangerousWord) matchedDangerousWord = word;
|
|
585
|
+
return hit;
|
|
486
586
|
})
|
|
487
587
|
);
|
|
488
588
|
if (isDangerous) {
|
|
489
|
-
|
|
490
|
-
|
|
589
|
+
return {
|
|
590
|
+
decision: "review",
|
|
591
|
+
blockedByLabel: `Project/Global Config \u2014 dangerous word: "${matchedDangerousWord}"`
|
|
592
|
+
};
|
|
491
593
|
}
|
|
492
594
|
if (config.settings.mode === "strict") {
|
|
493
595
|
const envConfig = getActiveEnvironment(config);
|
|
@@ -602,13 +704,16 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
|
|
|
602
704
|
approvers.browser = false;
|
|
603
705
|
approvers.terminal = false;
|
|
604
706
|
}
|
|
707
|
+
if (config.settings.enableHookLogDebug && !isTestEnv2) {
|
|
708
|
+
appendHookDebug(toolName, args, meta);
|
|
709
|
+
}
|
|
605
710
|
const isManual = meta?.agent === "Terminal";
|
|
606
711
|
let explainableLabel = "Local Config";
|
|
607
712
|
if (config.settings.mode === "audit") {
|
|
608
713
|
if (!isIgnoredTool(toolName)) {
|
|
609
714
|
const policyResult = await evaluatePolicy(toolName, args, meta?.agent);
|
|
610
715
|
if (policyResult.decision === "review") {
|
|
611
|
-
|
|
716
|
+
appendLocalAudit(toolName, args, "allow", "audit-mode", meta);
|
|
612
717
|
sendDesktopNotification(
|
|
613
718
|
"Node9 Audit Mode",
|
|
614
719
|
`Would have blocked "${toolName}" (${policyResult.blockedByLabel || "Local Config"}) \u2014 running in audit mode`
|
|
@@ -620,20 +725,24 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
|
|
|
620
725
|
if (!isIgnoredTool(toolName)) {
|
|
621
726
|
if (getActiveTrustSession(toolName)) {
|
|
622
727
|
if (creds?.apiKey) auditLocalAllow(toolName, args, "trust", creds, meta);
|
|
728
|
+
if (!isManual) appendLocalAudit(toolName, args, "allow", "trust", meta);
|
|
623
729
|
return { approved: true, checkedBy: "trust" };
|
|
624
730
|
}
|
|
625
731
|
const policyResult = await evaluatePolicy(toolName, args, meta?.agent);
|
|
626
732
|
if (policyResult.decision === "allow") {
|
|
627
733
|
if (creds?.apiKey) auditLocalAllow(toolName, args, "local-policy", creds, meta);
|
|
734
|
+
if (!isManual) appendLocalAudit(toolName, args, "allow", "local-policy", meta);
|
|
628
735
|
return { approved: true, checkedBy: "local-policy" };
|
|
629
736
|
}
|
|
630
737
|
explainableLabel = policyResult.blockedByLabel || "Local Config";
|
|
631
738
|
const persistent = getPersistentDecision(toolName);
|
|
632
739
|
if (persistent === "allow") {
|
|
633
740
|
if (creds?.apiKey) auditLocalAllow(toolName, args, "persistent", creds, meta);
|
|
741
|
+
if (!isManual) appendLocalAudit(toolName, args, "allow", "persistent", meta);
|
|
634
742
|
return { approved: true, checkedBy: "persistent" };
|
|
635
743
|
}
|
|
636
744
|
if (persistent === "deny") {
|
|
745
|
+
if (!isManual) appendLocalAudit(toolName, args, "deny", "persistent-deny", meta);
|
|
637
746
|
return {
|
|
638
747
|
approved: false,
|
|
639
748
|
reason: `This tool ("${toolName}") is explicitly listed in your 'Always Deny' list.`,
|
|
@@ -643,6 +752,7 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
|
|
|
643
752
|
}
|
|
644
753
|
} else {
|
|
645
754
|
if (creds?.apiKey) auditLocalAllow(toolName, args, "ignoredTools", creds, meta);
|
|
755
|
+
if (!isManual) appendLocalAudit(toolName, args, "allow", "ignored", meta);
|
|
646
756
|
return { approved: true };
|
|
647
757
|
}
|
|
648
758
|
let cloudRequestId = null;
|
|
@@ -867,6 +977,15 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
|
|
|
867
977
|
if (cloudRequestId && creds && finalResult.checkedBy !== "cloud") {
|
|
868
978
|
await resolveNode9SaaS(cloudRequestId, creds, finalResult.approved);
|
|
869
979
|
}
|
|
980
|
+
if (!isManual) {
|
|
981
|
+
appendLocalAudit(
|
|
982
|
+
toolName,
|
|
983
|
+
args,
|
|
984
|
+
finalResult.approved ? "allow" : "deny",
|
|
985
|
+
finalResult.checkedBy || finalResult.blockedBy || "unknown",
|
|
986
|
+
meta
|
|
987
|
+
);
|
|
988
|
+
}
|
|
870
989
|
return finalResult;
|
|
871
990
|
}
|
|
872
991
|
function getConfig() {
|
|
@@ -897,8 +1016,8 @@ function getConfig() {
|
|
|
897
1016
|
mergedSettings.enableHookLogDebug = s.enableHookLogDebug;
|
|
898
1017
|
if (s.approvers) mergedSettings.approvers = { ...mergedSettings.approvers, ...s.approvers };
|
|
899
1018
|
if (p.sandboxPaths) mergedPolicy.sandboxPaths.push(...p.sandboxPaths);
|
|
900
|
-
if (p.dangerousWords) mergedPolicy.dangerousWords = [...p.dangerousWords];
|
|
901
1019
|
if (p.ignoredTools) mergedPolicy.ignoredTools.push(...p.ignoredTools);
|
|
1020
|
+
if (p.dangerousWords) mergedPolicy.dangerousWords = [...p.dangerousWords];
|
|
902
1021
|
if (p.toolInspection)
|
|
903
1022
|
mergedPolicy.toolInspection = { ...mergedPolicy.toolInspection, ...p.toolInspection };
|
|
904
1023
|
if (p.rules) mergedPolicy.rules.push(...p.rules);
|