@node9/proxy 1.0.14 → 1.0.16
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 +99 -19
- package/dist/cli.js +2139 -1194
- package/dist/cli.mjs +2120 -1173
- package/dist/index.js +93 -16
- package/dist/index.mjs +93 -16
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -40,6 +40,9 @@ var import_prompts = require("@inquirer/prompts");
|
|
|
40
40
|
var import_fs2 = __toESM(require("fs"));
|
|
41
41
|
var import_path4 = __toESM(require("path"));
|
|
42
42
|
var import_os2 = __toESM(require("os"));
|
|
43
|
+
var import_net = __toESM(require("net"));
|
|
44
|
+
var import_crypto = require("crypto");
|
|
45
|
+
var import_child_process2 = require("child_process");
|
|
43
46
|
var import_picomatch = __toESM(require("picomatch"));
|
|
44
47
|
var import_sh_syntax = require("sh-syntax");
|
|
45
48
|
|
|
@@ -390,7 +393,13 @@ var SmartConditionSchema = import_zod.z.object({
|
|
|
390
393
|
),
|
|
391
394
|
value: import_zod.z.string().optional(),
|
|
392
395
|
flags: import_zod.z.string().optional()
|
|
393
|
-
})
|
|
396
|
+
}).refine(
|
|
397
|
+
(c) => {
|
|
398
|
+
if (c.op === "matchesGlob" || c.op === "notMatchesGlob") return c.value !== void 0;
|
|
399
|
+
return true;
|
|
400
|
+
},
|
|
401
|
+
{ message: "matchesGlob and notMatchesGlob conditions require a value field" }
|
|
402
|
+
);
|
|
394
403
|
var SmartRuleSchema = import_zod.z.object({
|
|
395
404
|
name: import_zod.z.string().optional(),
|
|
396
405
|
tool: import_zod.z.string().min(1, "Smart rule tool must not be empty"),
|
|
@@ -409,6 +418,7 @@ var ConfigFileSchema = import_zod.z.object({
|
|
|
409
418
|
enableUndo: import_zod.z.boolean().optional(),
|
|
410
419
|
enableHookLogDebug: import_zod.z.boolean().optional(),
|
|
411
420
|
approvalTimeoutMs: import_zod.z.number().nonnegative().optional(),
|
|
421
|
+
flightRecorder: import_zod.z.boolean().optional(),
|
|
412
422
|
approvers: import_zod.z.object({
|
|
413
423
|
native: import_zod.z.boolean().optional(),
|
|
414
424
|
browser: import_zod.z.boolean().optional(),
|
|
@@ -883,7 +893,7 @@ function evaluateSmartConditions(args, rule) {
|
|
|
883
893
|
case "matchesGlob":
|
|
884
894
|
return val !== null && cond.value ? import_picomatch.default.isMatch(val, cond.value) : false;
|
|
885
895
|
case "notMatchesGlob":
|
|
886
|
-
return val !== null && cond.value ? !import_picomatch.default.isMatch(val, cond.value) :
|
|
896
|
+
return val !== null && cond.value ? !import_picomatch.default.isMatch(val, cond.value) : false;
|
|
887
897
|
default:
|
|
888
898
|
return false;
|
|
889
899
|
}
|
|
@@ -994,15 +1004,17 @@ var DANGEROUS_WORDS = [
|
|
|
994
1004
|
// permanently overwrites file contents (unrecoverable)
|
|
995
1005
|
];
|
|
996
1006
|
var DEFAULT_CONFIG = {
|
|
1007
|
+
version: "1.0",
|
|
997
1008
|
settings: {
|
|
998
|
-
mode: "
|
|
1009
|
+
mode: "audit",
|
|
999
1010
|
autoStartDaemon: true,
|
|
1000
1011
|
enableUndo: true,
|
|
1001
1012
|
// 🔥 ALWAYS TRUE BY DEFAULT for the safety net
|
|
1002
|
-
enableHookLogDebug:
|
|
1003
|
-
approvalTimeoutMs:
|
|
1004
|
-
//
|
|
1005
|
-
|
|
1013
|
+
enableHookLogDebug: true,
|
|
1014
|
+
approvalTimeoutMs: 3e4,
|
|
1015
|
+
// 30-second auto-deny timeout
|
|
1016
|
+
flightRecorder: true,
|
|
1017
|
+
approvers: { native: true, browser: true, cloud: false, terminal: true }
|
|
1006
1018
|
},
|
|
1007
1019
|
policy: {
|
|
1008
1020
|
sandboxPaths: ["/tmp/**", "**/sandbox/**", "**/test-results/**"],
|
|
@@ -1313,13 +1325,23 @@ function isIgnoredTool(toolName) {
|
|
|
1313
1325
|
var DAEMON_PORT = 7391;
|
|
1314
1326
|
var DAEMON_HOST = "127.0.0.1";
|
|
1315
1327
|
function isDaemonRunning() {
|
|
1328
|
+
const pidFile = import_path4.default.join(import_os2.default.homedir(), ".node9", "daemon.pid");
|
|
1329
|
+
if (import_fs2.default.existsSync(pidFile)) {
|
|
1330
|
+
try {
|
|
1331
|
+
const { pid, port } = JSON.parse(import_fs2.default.readFileSync(pidFile, "utf-8"));
|
|
1332
|
+
if (port !== DAEMON_PORT) return false;
|
|
1333
|
+
process.kill(pid, 0);
|
|
1334
|
+
return true;
|
|
1335
|
+
} catch {
|
|
1336
|
+
return false;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1316
1339
|
try {
|
|
1317
|
-
const
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
return true;
|
|
1340
|
+
const r = (0, import_child_process2.spawnSync)("ss", ["-Htnp", `sport = :${DAEMON_PORT}`], {
|
|
1341
|
+
encoding: "utf8",
|
|
1342
|
+
timeout: 500
|
|
1343
|
+
});
|
|
1344
|
+
return r.status === 0 && (r.stdout ?? "").includes(`:${DAEMON_PORT}`);
|
|
1323
1345
|
} catch {
|
|
1324
1346
|
return false;
|
|
1325
1347
|
}
|
|
@@ -1335,7 +1357,7 @@ function getPersistentDecision(toolName) {
|
|
|
1335
1357
|
}
|
|
1336
1358
|
return null;
|
|
1337
1359
|
}
|
|
1338
|
-
async function askDaemon(toolName, args, meta, signal, riskMetadata) {
|
|
1360
|
+
async function askDaemon(toolName, args, meta, signal, riskMetadata, activityId) {
|
|
1339
1361
|
const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
1340
1362
|
const checkCtrl = new AbortController();
|
|
1341
1363
|
const checkTimer = setTimeout(() => checkCtrl.abort(), 5e3);
|
|
@@ -1350,6 +1372,12 @@ async function askDaemon(toolName, args, meta, signal, riskMetadata) {
|
|
|
1350
1372
|
args,
|
|
1351
1373
|
agent: meta?.agent,
|
|
1352
1374
|
mcpServer: meta?.mcpServer,
|
|
1375
|
+
fromCLI: true,
|
|
1376
|
+
// Pass the flight-recorder ID so the daemon uses the same UUID for
|
|
1377
|
+
// activity-result as the CLI used for the pending activity event.
|
|
1378
|
+
// Without this, the two UUIDs never match and tail.ts never resolves
|
|
1379
|
+
// the pending item.
|
|
1380
|
+
activityId,
|
|
1353
1381
|
...riskMetadata && { riskMetadata }
|
|
1354
1382
|
}),
|
|
1355
1383
|
signal: checkCtrl.signal
|
|
@@ -1404,7 +1432,45 @@ async function resolveViaDaemon(id, decision, internalToken) {
|
|
|
1404
1432
|
signal: AbortSignal.timeout(3e3)
|
|
1405
1433
|
});
|
|
1406
1434
|
}
|
|
1435
|
+
var ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : import_path4.default.join(import_os2.default.tmpdir(), "node9-activity.sock");
|
|
1436
|
+
function notifyActivity(data) {
|
|
1437
|
+
return new Promise((resolve) => {
|
|
1438
|
+
try {
|
|
1439
|
+
const payload = JSON.stringify(data);
|
|
1440
|
+
const sock = import_net.default.createConnection(ACTIVITY_SOCKET_PATH);
|
|
1441
|
+
sock.on("connect", () => {
|
|
1442
|
+
sock.on("close", resolve);
|
|
1443
|
+
sock.end(payload);
|
|
1444
|
+
});
|
|
1445
|
+
sock.on("error", resolve);
|
|
1446
|
+
} catch {
|
|
1447
|
+
resolve();
|
|
1448
|
+
}
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1407
1451
|
async function authorizeHeadless(toolName, args, allowTerminalFallback = false, meta, options) {
|
|
1452
|
+
if (!options?.calledFromDaemon) {
|
|
1453
|
+
const actId = (0, import_crypto.randomUUID)();
|
|
1454
|
+
const actTs = Date.now();
|
|
1455
|
+
await notifyActivity({ id: actId, ts: actTs, tool: toolName, args, status: "pending" });
|
|
1456
|
+
const result = await _authorizeHeadlessCore(toolName, args, allowTerminalFallback, meta, {
|
|
1457
|
+
...options,
|
|
1458
|
+
activityId: actId
|
|
1459
|
+
});
|
|
1460
|
+
if (!result.noApprovalMechanism) {
|
|
1461
|
+
await notifyActivity({
|
|
1462
|
+
id: actId,
|
|
1463
|
+
tool: toolName,
|
|
1464
|
+
ts: actTs,
|
|
1465
|
+
status: result.approved ? "allow" : result.blockedByLabel?.includes("DLP") ? "dlp" : "block",
|
|
1466
|
+
label: result.blockedByLabel
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
return result;
|
|
1470
|
+
}
|
|
1471
|
+
return _authorizeHeadlessCore(toolName, args, allowTerminalFallback, meta, options);
|
|
1472
|
+
}
|
|
1473
|
+
async function _authorizeHeadlessCore(toolName, args, allowTerminalFallback = false, meta, options) {
|
|
1408
1474
|
if (process.env.NODE9_PAUSED === "1") return { approved: true, checkedBy: "paused" };
|
|
1409
1475
|
const pauseState = checkPause();
|
|
1410
1476
|
if (pauseState.paused) return { approved: true, checkedBy: "paused" };
|
|
@@ -1440,6 +1506,7 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
|
|
|
1440
1506
|
blockedByLabel: "\u{1F6A8} Node9 DLP (Secret Detected)"
|
|
1441
1507
|
};
|
|
1442
1508
|
}
|
|
1509
|
+
if (!isManual) appendLocalAudit(toolName, args, "allow", "dlp-review-flagged", meta);
|
|
1443
1510
|
explainableLabel = "\u{1F6A8} Node9 DLP (Credential Review)";
|
|
1444
1511
|
}
|
|
1445
1512
|
}
|
|
@@ -1662,7 +1729,14 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
|
|
|
1662
1729
|
console.error(import_chalk2.default.cyan(` URL \u2192 http://${DAEMON_HOST}:${DAEMON_PORT}/
|
|
1663
1730
|
`));
|
|
1664
1731
|
}
|
|
1665
|
-
const daemonDecision = await askDaemon(
|
|
1732
|
+
const daemonDecision = await askDaemon(
|
|
1733
|
+
toolName,
|
|
1734
|
+
args,
|
|
1735
|
+
meta,
|
|
1736
|
+
signal,
|
|
1737
|
+
riskMetadata,
|
|
1738
|
+
options?.activityId
|
|
1739
|
+
);
|
|
1666
1740
|
if (daemonDecision === "abandoned") throw new Error("Abandoned");
|
|
1667
1741
|
const isApproved = daemonDecision === "allow";
|
|
1668
1742
|
return {
|
|
@@ -1866,7 +1940,10 @@ function getConfig() {
|
|
|
1866
1940
|
for (const rule of shield.smartRules) {
|
|
1867
1941
|
if (!existingRuleNames.has(rule.name)) mergedPolicy.smartRules.push(rule);
|
|
1868
1942
|
}
|
|
1869
|
-
|
|
1943
|
+
const existingWords = new Set(mergedPolicy.dangerousWords);
|
|
1944
|
+
for (const word of shield.dangerousWords) {
|
|
1945
|
+
if (!existingWords.has(word)) mergedPolicy.dangerousWords.push(word);
|
|
1946
|
+
}
|
|
1870
1947
|
}
|
|
1871
1948
|
const existingAdvisoryNames = new Set(mergedPolicy.smartRules.map((r) => r.name));
|
|
1872
1949
|
for (const rule of ADVISORY_SMART_RULES) {
|
package/dist/index.mjs
CHANGED
|
@@ -4,6 +4,9 @@ import { confirm } from "@inquirer/prompts";
|
|
|
4
4
|
import fs2 from "fs";
|
|
5
5
|
import path4 from "path";
|
|
6
6
|
import os2 from "os";
|
|
7
|
+
import net from "net";
|
|
8
|
+
import { randomUUID } from "crypto";
|
|
9
|
+
import { spawnSync } from "child_process";
|
|
7
10
|
import pm from "picomatch";
|
|
8
11
|
import { parse } from "sh-syntax";
|
|
9
12
|
|
|
@@ -354,7 +357,13 @@ var SmartConditionSchema = z.object({
|
|
|
354
357
|
),
|
|
355
358
|
value: z.string().optional(),
|
|
356
359
|
flags: z.string().optional()
|
|
357
|
-
})
|
|
360
|
+
}).refine(
|
|
361
|
+
(c) => {
|
|
362
|
+
if (c.op === "matchesGlob" || c.op === "notMatchesGlob") return c.value !== void 0;
|
|
363
|
+
return true;
|
|
364
|
+
},
|
|
365
|
+
{ message: "matchesGlob and notMatchesGlob conditions require a value field" }
|
|
366
|
+
);
|
|
358
367
|
var SmartRuleSchema = z.object({
|
|
359
368
|
name: z.string().optional(),
|
|
360
369
|
tool: z.string().min(1, "Smart rule tool must not be empty"),
|
|
@@ -373,6 +382,7 @@ var ConfigFileSchema = z.object({
|
|
|
373
382
|
enableUndo: z.boolean().optional(),
|
|
374
383
|
enableHookLogDebug: z.boolean().optional(),
|
|
375
384
|
approvalTimeoutMs: z.number().nonnegative().optional(),
|
|
385
|
+
flightRecorder: z.boolean().optional(),
|
|
376
386
|
approvers: z.object({
|
|
377
387
|
native: z.boolean().optional(),
|
|
378
388
|
browser: z.boolean().optional(),
|
|
@@ -847,7 +857,7 @@ function evaluateSmartConditions(args, rule) {
|
|
|
847
857
|
case "matchesGlob":
|
|
848
858
|
return val !== null && cond.value ? pm.isMatch(val, cond.value) : false;
|
|
849
859
|
case "notMatchesGlob":
|
|
850
|
-
return val !== null && cond.value ? !pm.isMatch(val, cond.value) :
|
|
860
|
+
return val !== null && cond.value ? !pm.isMatch(val, cond.value) : false;
|
|
851
861
|
default:
|
|
852
862
|
return false;
|
|
853
863
|
}
|
|
@@ -958,15 +968,17 @@ var DANGEROUS_WORDS = [
|
|
|
958
968
|
// permanently overwrites file contents (unrecoverable)
|
|
959
969
|
];
|
|
960
970
|
var DEFAULT_CONFIG = {
|
|
971
|
+
version: "1.0",
|
|
961
972
|
settings: {
|
|
962
|
-
mode: "
|
|
973
|
+
mode: "audit",
|
|
963
974
|
autoStartDaemon: true,
|
|
964
975
|
enableUndo: true,
|
|
965
976
|
// 🔥 ALWAYS TRUE BY DEFAULT for the safety net
|
|
966
|
-
enableHookLogDebug:
|
|
967
|
-
approvalTimeoutMs:
|
|
968
|
-
//
|
|
969
|
-
|
|
977
|
+
enableHookLogDebug: true,
|
|
978
|
+
approvalTimeoutMs: 3e4,
|
|
979
|
+
// 30-second auto-deny timeout
|
|
980
|
+
flightRecorder: true,
|
|
981
|
+
approvers: { native: true, browser: true, cloud: false, terminal: true }
|
|
970
982
|
},
|
|
971
983
|
policy: {
|
|
972
984
|
sandboxPaths: ["/tmp/**", "**/sandbox/**", "**/test-results/**"],
|
|
@@ -1277,13 +1289,23 @@ function isIgnoredTool(toolName) {
|
|
|
1277
1289
|
var DAEMON_PORT = 7391;
|
|
1278
1290
|
var DAEMON_HOST = "127.0.0.1";
|
|
1279
1291
|
function isDaemonRunning() {
|
|
1292
|
+
const pidFile = path4.join(os2.homedir(), ".node9", "daemon.pid");
|
|
1293
|
+
if (fs2.existsSync(pidFile)) {
|
|
1294
|
+
try {
|
|
1295
|
+
const { pid, port } = JSON.parse(fs2.readFileSync(pidFile, "utf-8"));
|
|
1296
|
+
if (port !== DAEMON_PORT) return false;
|
|
1297
|
+
process.kill(pid, 0);
|
|
1298
|
+
return true;
|
|
1299
|
+
} catch {
|
|
1300
|
+
return false;
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1280
1303
|
try {
|
|
1281
|
-
const
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
return true;
|
|
1304
|
+
const r = spawnSync("ss", ["-Htnp", `sport = :${DAEMON_PORT}`], {
|
|
1305
|
+
encoding: "utf8",
|
|
1306
|
+
timeout: 500
|
|
1307
|
+
});
|
|
1308
|
+
return r.status === 0 && (r.stdout ?? "").includes(`:${DAEMON_PORT}`);
|
|
1287
1309
|
} catch {
|
|
1288
1310
|
return false;
|
|
1289
1311
|
}
|
|
@@ -1299,7 +1321,7 @@ function getPersistentDecision(toolName) {
|
|
|
1299
1321
|
}
|
|
1300
1322
|
return null;
|
|
1301
1323
|
}
|
|
1302
|
-
async function askDaemon(toolName, args, meta, signal, riskMetadata) {
|
|
1324
|
+
async function askDaemon(toolName, args, meta, signal, riskMetadata, activityId) {
|
|
1303
1325
|
const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
1304
1326
|
const checkCtrl = new AbortController();
|
|
1305
1327
|
const checkTimer = setTimeout(() => checkCtrl.abort(), 5e3);
|
|
@@ -1314,6 +1336,12 @@ async function askDaemon(toolName, args, meta, signal, riskMetadata) {
|
|
|
1314
1336
|
args,
|
|
1315
1337
|
agent: meta?.agent,
|
|
1316
1338
|
mcpServer: meta?.mcpServer,
|
|
1339
|
+
fromCLI: true,
|
|
1340
|
+
// Pass the flight-recorder ID so the daemon uses the same UUID for
|
|
1341
|
+
// activity-result as the CLI used for the pending activity event.
|
|
1342
|
+
// Without this, the two UUIDs never match and tail.ts never resolves
|
|
1343
|
+
// the pending item.
|
|
1344
|
+
activityId,
|
|
1317
1345
|
...riskMetadata && { riskMetadata }
|
|
1318
1346
|
}),
|
|
1319
1347
|
signal: checkCtrl.signal
|
|
@@ -1368,7 +1396,45 @@ async function resolveViaDaemon(id, decision, internalToken) {
|
|
|
1368
1396
|
signal: AbortSignal.timeout(3e3)
|
|
1369
1397
|
});
|
|
1370
1398
|
}
|
|
1399
|
+
var ACTIVITY_SOCKET_PATH = process.platform === "win32" ? "\\\\.\\pipe\\node9-activity" : path4.join(os2.tmpdir(), "node9-activity.sock");
|
|
1400
|
+
function notifyActivity(data) {
|
|
1401
|
+
return new Promise((resolve) => {
|
|
1402
|
+
try {
|
|
1403
|
+
const payload = JSON.stringify(data);
|
|
1404
|
+
const sock = net.createConnection(ACTIVITY_SOCKET_PATH);
|
|
1405
|
+
sock.on("connect", () => {
|
|
1406
|
+
sock.on("close", resolve);
|
|
1407
|
+
sock.end(payload);
|
|
1408
|
+
});
|
|
1409
|
+
sock.on("error", resolve);
|
|
1410
|
+
} catch {
|
|
1411
|
+
resolve();
|
|
1412
|
+
}
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1371
1415
|
async function authorizeHeadless(toolName, args, allowTerminalFallback = false, meta, options) {
|
|
1416
|
+
if (!options?.calledFromDaemon) {
|
|
1417
|
+
const actId = randomUUID();
|
|
1418
|
+
const actTs = Date.now();
|
|
1419
|
+
await notifyActivity({ id: actId, ts: actTs, tool: toolName, args, status: "pending" });
|
|
1420
|
+
const result = await _authorizeHeadlessCore(toolName, args, allowTerminalFallback, meta, {
|
|
1421
|
+
...options,
|
|
1422
|
+
activityId: actId
|
|
1423
|
+
});
|
|
1424
|
+
if (!result.noApprovalMechanism) {
|
|
1425
|
+
await notifyActivity({
|
|
1426
|
+
id: actId,
|
|
1427
|
+
tool: toolName,
|
|
1428
|
+
ts: actTs,
|
|
1429
|
+
status: result.approved ? "allow" : result.blockedByLabel?.includes("DLP") ? "dlp" : "block",
|
|
1430
|
+
label: result.blockedByLabel
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
return result;
|
|
1434
|
+
}
|
|
1435
|
+
return _authorizeHeadlessCore(toolName, args, allowTerminalFallback, meta, options);
|
|
1436
|
+
}
|
|
1437
|
+
async function _authorizeHeadlessCore(toolName, args, allowTerminalFallback = false, meta, options) {
|
|
1372
1438
|
if (process.env.NODE9_PAUSED === "1") return { approved: true, checkedBy: "paused" };
|
|
1373
1439
|
const pauseState = checkPause();
|
|
1374
1440
|
if (pauseState.paused) return { approved: true, checkedBy: "paused" };
|
|
@@ -1404,6 +1470,7 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
|
|
|
1404
1470
|
blockedByLabel: "\u{1F6A8} Node9 DLP (Secret Detected)"
|
|
1405
1471
|
};
|
|
1406
1472
|
}
|
|
1473
|
+
if (!isManual) appendLocalAudit(toolName, args, "allow", "dlp-review-flagged", meta);
|
|
1407
1474
|
explainableLabel = "\u{1F6A8} Node9 DLP (Credential Review)";
|
|
1408
1475
|
}
|
|
1409
1476
|
}
|
|
@@ -1626,7 +1693,14 @@ async function authorizeHeadless(toolName, args, allowTerminalFallback = false,
|
|
|
1626
1693
|
console.error(chalk2.cyan(` URL \u2192 http://${DAEMON_HOST}:${DAEMON_PORT}/
|
|
1627
1694
|
`));
|
|
1628
1695
|
}
|
|
1629
|
-
const daemonDecision = await askDaemon(
|
|
1696
|
+
const daemonDecision = await askDaemon(
|
|
1697
|
+
toolName,
|
|
1698
|
+
args,
|
|
1699
|
+
meta,
|
|
1700
|
+
signal,
|
|
1701
|
+
riskMetadata,
|
|
1702
|
+
options?.activityId
|
|
1703
|
+
);
|
|
1630
1704
|
if (daemonDecision === "abandoned") throw new Error("Abandoned");
|
|
1631
1705
|
const isApproved = daemonDecision === "allow";
|
|
1632
1706
|
return {
|
|
@@ -1830,7 +1904,10 @@ function getConfig() {
|
|
|
1830
1904
|
for (const rule of shield.smartRules) {
|
|
1831
1905
|
if (!existingRuleNames.has(rule.name)) mergedPolicy.smartRules.push(rule);
|
|
1832
1906
|
}
|
|
1833
|
-
|
|
1907
|
+
const existingWords = new Set(mergedPolicy.dangerousWords);
|
|
1908
|
+
for (const word of shield.dangerousWords) {
|
|
1909
|
+
if (!existingWords.has(word)) mergedPolicy.dangerousWords.push(word);
|
|
1910
|
+
}
|
|
1834
1911
|
}
|
|
1835
1912
|
const existingAdvisoryNames = new Set(mergedPolicy.smartRules.map((r) => r.name));
|
|
1836
1913
|
for (const rule of ADVISORY_SMART_RULES) {
|