@node9/proxy 1.10.2 → 1.11.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/README.md +110 -4
- package/dist/cli.js +2812 -584
- package/dist/cli.mjs +2775 -546
- package/dist/index.js +80 -17
- package/dist/index.mjs +80 -17
- package/dist/shields/builtin/bash-safe.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -239,7 +239,8 @@ var ConfigFileSchema = import_zod.z.object({
|
|
|
239
239
|
enableTrustSessions: import_zod.z.boolean().optional(),
|
|
240
240
|
allowGlobalPause: import_zod.z.boolean().optional(),
|
|
241
241
|
auditHashArgs: import_zod.z.boolean().optional(),
|
|
242
|
-
agentPolicy: import_zod.z.enum(["require_approval", "block_on_rules"]).optional()
|
|
242
|
+
agentPolicy: import_zod.z.enum(["require_approval", "block_on_rules"]).optional(),
|
|
243
|
+
cloudSyncIntervalHours: import_zod.z.number().positive().optional()
|
|
243
244
|
}).optional(),
|
|
244
245
|
policy: import_zod.z.object({
|
|
245
246
|
sandboxPaths: import_zod.z.array(import_zod.z.string()).optional(),
|
|
@@ -260,6 +261,11 @@ var ConfigFileSchema = import_zod.z.object({
|
|
|
260
261
|
enabled: import_zod.z.boolean().optional(),
|
|
261
262
|
threshold: import_zod.z.number().min(2).optional(),
|
|
262
263
|
windowSeconds: import_zod.z.number().min(10).optional()
|
|
264
|
+
}).optional(),
|
|
265
|
+
skillPinning: import_zod.z.object({
|
|
266
|
+
enabled: import_zod.z.boolean().optional(),
|
|
267
|
+
mode: import_zod.z.enum(["warn", "block"]).optional(),
|
|
268
|
+
roots: import_zod.z.array(import_zod.z.string()).optional()
|
|
263
269
|
}).optional()
|
|
264
270
|
}).optional(),
|
|
265
271
|
environments: import_zod.z.record(import_zod.z.object({ requireApproval: import_zod.z.boolean().optional() })).optional()
|
|
@@ -441,7 +447,8 @@ var DEFAULT_CONFIG = {
|
|
|
441
447
|
// 120-second auto-deny timeout
|
|
442
448
|
flightRecorder: true,
|
|
443
449
|
auditHashArgs: true,
|
|
444
|
-
approvers: { native: true, browser: true, cloud: false, terminal: true }
|
|
450
|
+
approvers: { native: true, browser: true, cloud: false, terminal: true },
|
|
451
|
+
cloudSyncIntervalHours: 5
|
|
445
452
|
},
|
|
446
453
|
policy: {
|
|
447
454
|
sandboxPaths: ["/tmp/**", "**/sandbox/**", "**/test-results/**"],
|
|
@@ -616,7 +623,8 @@ var DEFAULT_CONFIG = {
|
|
|
616
623
|
}
|
|
617
624
|
],
|
|
618
625
|
dlp: { enabled: true, scanIgnoredTools: true },
|
|
619
|
-
loopDetection: { enabled: true, threshold: 5, windowSeconds: 120 }
|
|
626
|
+
loopDetection: { enabled: true, threshold: 5, windowSeconds: 120 },
|
|
627
|
+
skillPinning: { enabled: false, mode: "warn", roots: [] }
|
|
620
628
|
},
|
|
621
629
|
environments: {}
|
|
622
630
|
};
|
|
@@ -739,7 +747,11 @@ function getConfig(cwd) {
|
|
|
739
747
|
ignorePaths: [...DEFAULT_CONFIG.policy.snapshot.ignorePaths]
|
|
740
748
|
},
|
|
741
749
|
dlp: { ...DEFAULT_CONFIG.policy.dlp },
|
|
742
|
-
loopDetection: { ...DEFAULT_CONFIG.policy.loopDetection }
|
|
750
|
+
loopDetection: { ...DEFAULT_CONFIG.policy.loopDetection },
|
|
751
|
+
skillPinning: {
|
|
752
|
+
...DEFAULT_CONFIG.policy.skillPinning,
|
|
753
|
+
roots: [...DEFAULT_CONFIG.policy.skillPinning.roots]
|
|
754
|
+
}
|
|
743
755
|
};
|
|
744
756
|
const mergedEnvironments = { ...DEFAULT_CONFIG.environments };
|
|
745
757
|
const applyLayer = (source) => {
|
|
@@ -756,6 +768,8 @@ function getConfig(cwd) {
|
|
|
756
768
|
if (s.approvalTimeoutSeconds !== void 0 && s.approvalTimeoutMs === void 0)
|
|
757
769
|
mergedSettings.approvalTimeoutMs = s.approvalTimeoutSeconds * 1e3;
|
|
758
770
|
if (s.environment !== void 0) mergedSettings.environment = s.environment;
|
|
771
|
+
if (s.cloudSyncIntervalHours !== void 0)
|
|
772
|
+
mergedSettings.cloudSyncIntervalHours = s.cloudSyncIntervalHours;
|
|
759
773
|
if (s.hud !== void 0) mergedSettings.hud = { ...mergedSettings.hud, ...s.hud };
|
|
760
774
|
if (p.sandboxPaths) mergedPolicy.sandboxPaths.push(...p.sandboxPaths);
|
|
761
775
|
if (p.ignoredTools) mergedPolicy.ignoredTools.push(...p.ignoredTools);
|
|
@@ -765,7 +779,12 @@ function getConfig(cwd) {
|
|
|
765
779
|
if (p.smartRules) {
|
|
766
780
|
const defaultBlocks = mergedPolicy.smartRules.filter((r) => r.verdict === "block");
|
|
767
781
|
const defaultNonBlocks = mergedPolicy.smartRules.filter((r) => r.verdict !== "block");
|
|
768
|
-
|
|
782
|
+
const userRuleNames = new Set(p.smartRules.filter((r) => r.name).map((r) => r.name));
|
|
783
|
+
const filteredBlocks = defaultBlocks.filter((r) => !r.name || !userRuleNames.has(r.name));
|
|
784
|
+
const filteredNonBlocks = defaultNonBlocks.filter(
|
|
785
|
+
(r) => !r.name || !userRuleNames.has(r.name)
|
|
786
|
+
);
|
|
787
|
+
mergedPolicy.smartRules = [...filteredBlocks, ...p.smartRules, ...filteredNonBlocks];
|
|
769
788
|
}
|
|
770
789
|
if (p.snapshot) {
|
|
771
790
|
const s2 = p.snapshot;
|
|
@@ -785,6 +804,16 @@ function getConfig(cwd) {
|
|
|
785
804
|
if (ld.windowSeconds !== void 0)
|
|
786
805
|
mergedPolicy.loopDetection.windowSeconds = ld.windowSeconds;
|
|
787
806
|
}
|
|
807
|
+
if (p.skillPinning && typeof p.skillPinning === "object") {
|
|
808
|
+
const sp = p.skillPinning;
|
|
809
|
+
if (sp.enabled !== void 0) mergedPolicy.skillPinning.enabled = sp.enabled;
|
|
810
|
+
if (sp.mode !== void 0) mergedPolicy.skillPinning.mode = sp.mode;
|
|
811
|
+
if (Array.isArray(sp.roots)) {
|
|
812
|
+
for (const r of sp.roots) {
|
|
813
|
+
if (typeof r === "string" && r.length > 0) mergedPolicy.skillPinning.roots.push(r);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
788
817
|
const envs = source.environments || {};
|
|
789
818
|
for (const [envName, envConfig] of Object.entries(envs)) {
|
|
790
819
|
if (envConfig && typeof envConfig === "object") {
|
|
@@ -799,6 +828,16 @@ function getConfig(cwd) {
|
|
|
799
828
|
};
|
|
800
829
|
applyLayer(globalConfig);
|
|
801
830
|
applyLayer(projectConfig);
|
|
831
|
+
{
|
|
832
|
+
const cacheFile = import_path3.default.join(import_os3.default.homedir(), ".node9", "rules-cache.json");
|
|
833
|
+
try {
|
|
834
|
+
const raw = JSON.parse(import_fs3.default.readFileSync(cacheFile, "utf-8"));
|
|
835
|
+
if (Array.isArray(raw.rules) && raw.rules.length > 0) {
|
|
836
|
+
applyLayer({ policy: { smartRules: raw.rules } });
|
|
837
|
+
}
|
|
838
|
+
} catch {
|
|
839
|
+
}
|
|
840
|
+
}
|
|
802
841
|
const shieldOverrides = readShieldOverrides();
|
|
803
842
|
for (const shieldName of readActiveShields()) {
|
|
804
843
|
const shield = getShield(shieldName);
|
|
@@ -826,6 +865,7 @@ function getConfig(cwd) {
|
|
|
826
865
|
mergedPolicy.sandboxPaths = [...new Set(mergedPolicy.sandboxPaths)];
|
|
827
866
|
mergedPolicy.dangerousWords = [...new Set(mergedPolicy.dangerousWords)];
|
|
828
867
|
mergedPolicy.ignoredTools = [...new Set(mergedPolicy.ignoredTools)];
|
|
868
|
+
mergedPolicy.skillPinning.roots = [...new Set(mergedPolicy.skillPinning.roots)];
|
|
829
869
|
mergedPolicy.snapshot.tools = [...new Set(mergedPolicy.snapshot.tools)];
|
|
830
870
|
mergedPolicy.snapshot.onlyPaths = [...new Set(mergedPolicy.snapshot.onlyPaths)];
|
|
831
871
|
mergedPolicy.snapshot.ignorePaths = [...new Set(mergedPolicy.snapshot.ignorePaths)];
|
|
@@ -1871,6 +1911,15 @@ var import_path9 = __toESM(require("path"));
|
|
|
1871
1911
|
var import_os6 = __toESM(require("os"));
|
|
1872
1912
|
var PAUSED_FILE = import_path9.default.join(import_os6.default.homedir(), ".node9", "PAUSED");
|
|
1873
1913
|
var TRUST_FILE = import_path9.default.join(import_os6.default.homedir(), ".node9", "trust.json");
|
|
1914
|
+
function extractCommandPattern(toolName, args) {
|
|
1915
|
+
const lower = toolName.toLowerCase();
|
|
1916
|
+
if (lower !== "bash" && lower !== "execute_bash" && lower !== "shell") return void 0;
|
|
1917
|
+
const a = args;
|
|
1918
|
+
const cmd = typeof a?.["command"] === "string" ? a["command"].trim() : "";
|
|
1919
|
+
if (!cmd) return void 0;
|
|
1920
|
+
const words = cmd.split(/\s+/);
|
|
1921
|
+
return words.slice(0, 2).join(" ");
|
|
1922
|
+
}
|
|
1874
1923
|
function checkPause() {
|
|
1875
1924
|
try {
|
|
1876
1925
|
if (!import_fs7.default.existsSync(PAUSED_FILE)) return { paused: false };
|
|
@@ -1894,7 +1943,7 @@ function atomicWriteSync(filePath, data, options) {
|
|
|
1894
1943
|
import_fs7.default.writeFileSync(tmpPath, data, options);
|
|
1895
1944
|
import_fs7.default.renameSync(tmpPath, filePath);
|
|
1896
1945
|
}
|
|
1897
|
-
function getActiveTrustSession(toolName) {
|
|
1946
|
+
function getActiveTrustSession(toolName, args) {
|
|
1898
1947
|
try {
|
|
1899
1948
|
if (!import_fs7.default.existsSync(TRUST_FILE)) return false;
|
|
1900
1949
|
const trust = JSON.parse(import_fs7.default.readFileSync(TRUST_FILE, "utf-8"));
|
|
@@ -1903,12 +1952,20 @@ function getActiveTrustSession(toolName) {
|
|
|
1903
1952
|
if (active.length !== trust.entries.length) {
|
|
1904
1953
|
import_fs7.default.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
|
|
1905
1954
|
}
|
|
1906
|
-
return active.some((e) =>
|
|
1955
|
+
return active.some((e) => {
|
|
1956
|
+
if (!(e.tool === toolName || matchesPattern(toolName, e.tool))) return false;
|
|
1957
|
+
if (e.commandPattern) {
|
|
1958
|
+
const actual = extractCommandPattern(toolName, args) ?? "";
|
|
1959
|
+
return actual === e.commandPattern || actual.startsWith(e.commandPattern + " ");
|
|
1960
|
+
}
|
|
1961
|
+
return true;
|
|
1962
|
+
});
|
|
1907
1963
|
} catch {
|
|
1908
1964
|
return false;
|
|
1909
1965
|
}
|
|
1910
1966
|
}
|
|
1911
|
-
function writeTrustSession(toolName, durationMs) {
|
|
1967
|
+
function writeTrustSession(toolName, durationMs, args) {
|
|
1968
|
+
const commandPattern = extractCommandPattern(toolName, args);
|
|
1912
1969
|
try {
|
|
1913
1970
|
let trust = { entries: [] };
|
|
1914
1971
|
try {
|
|
@@ -1918,8 +1975,14 @@ function writeTrustSession(toolName, durationMs) {
|
|
|
1918
1975
|
} catch {
|
|
1919
1976
|
}
|
|
1920
1977
|
const now = Date.now();
|
|
1921
|
-
trust.entries = trust.entries.filter(
|
|
1922
|
-
|
|
1978
|
+
trust.entries = trust.entries.filter(
|
|
1979
|
+
(e) => !(e.tool === toolName && e.commandPattern === commandPattern) && e.expiry > now
|
|
1980
|
+
);
|
|
1981
|
+
trust.entries.push({
|
|
1982
|
+
tool: toolName,
|
|
1983
|
+
...commandPattern && { commandPattern },
|
|
1984
|
+
expiry: now + durationMs
|
|
1985
|
+
});
|
|
1923
1986
|
atomicWriteSync(TRUST_FILE, JSON.stringify(trust, null, 2));
|
|
1924
1987
|
} catch (err) {
|
|
1925
1988
|
if (process.env.NODE9_DEBUG === "1") {
|
|
@@ -2881,12 +2944,6 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2881
2944
|
};
|
|
2882
2945
|
}
|
|
2883
2946
|
}
|
|
2884
|
-
if (getActiveTrustSession(toolName)) {
|
|
2885
|
-
if (approvers.cloud && creds?.apiKey)
|
|
2886
|
-
await auditLocalAllow(toolName, args, "trust", creds, meta);
|
|
2887
|
-
if (!isManual) appendLocalAudit(toolName, args, "allow", "trust", meta, hashAuditArgs);
|
|
2888
|
-
return { approved: true, checkedBy: "trust" };
|
|
2889
|
-
}
|
|
2890
2947
|
const policyResult = await evaluatePolicy(toolName, args, meta?.agent);
|
|
2891
2948
|
if (policyResult.decision === "allow") {
|
|
2892
2949
|
if (approvers.cloud && creds?.apiKey)
|
|
@@ -2968,6 +3025,12 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2968
3025
|
if (!isManual) appendLocalAudit(toolName, args, "allow", "ignored", meta, hashAuditArgs);
|
|
2969
3026
|
return { approved: true };
|
|
2970
3027
|
}
|
|
3028
|
+
if (!taintWarning && getActiveTrustSession(toolName, args)) {
|
|
3029
|
+
if (approvers.cloud && creds?.apiKey)
|
|
3030
|
+
await auditLocalAllow(toolName, args, "trust", creds, meta);
|
|
3031
|
+
if (!isManual) appendLocalAudit(toolName, args, "allow", "trust", meta, hashAuditArgs);
|
|
3032
|
+
return { approved: true, checkedBy: "trust" };
|
|
3033
|
+
}
|
|
2971
3034
|
if (taintWarning) {
|
|
2972
3035
|
explainableLabel = "\u{1F534} Node9 Taint (Exfiltration Prevention)";
|
|
2973
3036
|
riskMetadata = computeRiskMetadata(
|
|
@@ -3100,7 +3163,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3100
3163
|
riskMetadata?.ruleDescription
|
|
3101
3164
|
);
|
|
3102
3165
|
if (decision === "always_allow") {
|
|
3103
|
-
writeTrustSession(toolName, 36e5);
|
|
3166
|
+
writeTrustSession(toolName, 36e5, args);
|
|
3104
3167
|
return { approved: true, checkedBy: "trust" };
|
|
3105
3168
|
}
|
|
3106
3169
|
const isApproved = decision === "allow";
|
package/dist/index.mjs
CHANGED
|
@@ -209,7 +209,8 @@ var ConfigFileSchema = z.object({
|
|
|
209
209
|
enableTrustSessions: z.boolean().optional(),
|
|
210
210
|
allowGlobalPause: z.boolean().optional(),
|
|
211
211
|
auditHashArgs: z.boolean().optional(),
|
|
212
|
-
agentPolicy: z.enum(["require_approval", "block_on_rules"]).optional()
|
|
212
|
+
agentPolicy: z.enum(["require_approval", "block_on_rules"]).optional(),
|
|
213
|
+
cloudSyncIntervalHours: z.number().positive().optional()
|
|
213
214
|
}).optional(),
|
|
214
215
|
policy: z.object({
|
|
215
216
|
sandboxPaths: z.array(z.string()).optional(),
|
|
@@ -230,6 +231,11 @@ var ConfigFileSchema = z.object({
|
|
|
230
231
|
enabled: z.boolean().optional(),
|
|
231
232
|
threshold: z.number().min(2).optional(),
|
|
232
233
|
windowSeconds: z.number().min(10).optional()
|
|
234
|
+
}).optional(),
|
|
235
|
+
skillPinning: z.object({
|
|
236
|
+
enabled: z.boolean().optional(),
|
|
237
|
+
mode: z.enum(["warn", "block"]).optional(),
|
|
238
|
+
roots: z.array(z.string()).optional()
|
|
233
239
|
}).optional()
|
|
234
240
|
}).optional(),
|
|
235
241
|
environments: z.record(z.object({ requireApproval: z.boolean().optional() })).optional()
|
|
@@ -411,7 +417,8 @@ var DEFAULT_CONFIG = {
|
|
|
411
417
|
// 120-second auto-deny timeout
|
|
412
418
|
flightRecorder: true,
|
|
413
419
|
auditHashArgs: true,
|
|
414
|
-
approvers: { native: true, browser: true, cloud: false, terminal: true }
|
|
420
|
+
approvers: { native: true, browser: true, cloud: false, terminal: true },
|
|
421
|
+
cloudSyncIntervalHours: 5
|
|
415
422
|
},
|
|
416
423
|
policy: {
|
|
417
424
|
sandboxPaths: ["/tmp/**", "**/sandbox/**", "**/test-results/**"],
|
|
@@ -586,7 +593,8 @@ var DEFAULT_CONFIG = {
|
|
|
586
593
|
}
|
|
587
594
|
],
|
|
588
595
|
dlp: { enabled: true, scanIgnoredTools: true },
|
|
589
|
-
loopDetection: { enabled: true, threshold: 5, windowSeconds: 120 }
|
|
596
|
+
loopDetection: { enabled: true, threshold: 5, windowSeconds: 120 },
|
|
597
|
+
skillPinning: { enabled: false, mode: "warn", roots: [] }
|
|
590
598
|
},
|
|
591
599
|
environments: {}
|
|
592
600
|
};
|
|
@@ -709,7 +717,11 @@ function getConfig(cwd) {
|
|
|
709
717
|
ignorePaths: [...DEFAULT_CONFIG.policy.snapshot.ignorePaths]
|
|
710
718
|
},
|
|
711
719
|
dlp: { ...DEFAULT_CONFIG.policy.dlp },
|
|
712
|
-
loopDetection: { ...DEFAULT_CONFIG.policy.loopDetection }
|
|
720
|
+
loopDetection: { ...DEFAULT_CONFIG.policy.loopDetection },
|
|
721
|
+
skillPinning: {
|
|
722
|
+
...DEFAULT_CONFIG.policy.skillPinning,
|
|
723
|
+
roots: [...DEFAULT_CONFIG.policy.skillPinning.roots]
|
|
724
|
+
}
|
|
713
725
|
};
|
|
714
726
|
const mergedEnvironments = { ...DEFAULT_CONFIG.environments };
|
|
715
727
|
const applyLayer = (source) => {
|
|
@@ -726,6 +738,8 @@ function getConfig(cwd) {
|
|
|
726
738
|
if (s.approvalTimeoutSeconds !== void 0 && s.approvalTimeoutMs === void 0)
|
|
727
739
|
mergedSettings.approvalTimeoutMs = s.approvalTimeoutSeconds * 1e3;
|
|
728
740
|
if (s.environment !== void 0) mergedSettings.environment = s.environment;
|
|
741
|
+
if (s.cloudSyncIntervalHours !== void 0)
|
|
742
|
+
mergedSettings.cloudSyncIntervalHours = s.cloudSyncIntervalHours;
|
|
729
743
|
if (s.hud !== void 0) mergedSettings.hud = { ...mergedSettings.hud, ...s.hud };
|
|
730
744
|
if (p.sandboxPaths) mergedPolicy.sandboxPaths.push(...p.sandboxPaths);
|
|
731
745
|
if (p.ignoredTools) mergedPolicy.ignoredTools.push(...p.ignoredTools);
|
|
@@ -735,7 +749,12 @@ function getConfig(cwd) {
|
|
|
735
749
|
if (p.smartRules) {
|
|
736
750
|
const defaultBlocks = mergedPolicy.smartRules.filter((r) => r.verdict === "block");
|
|
737
751
|
const defaultNonBlocks = mergedPolicy.smartRules.filter((r) => r.verdict !== "block");
|
|
738
|
-
|
|
752
|
+
const userRuleNames = new Set(p.smartRules.filter((r) => r.name).map((r) => r.name));
|
|
753
|
+
const filteredBlocks = defaultBlocks.filter((r) => !r.name || !userRuleNames.has(r.name));
|
|
754
|
+
const filteredNonBlocks = defaultNonBlocks.filter(
|
|
755
|
+
(r) => !r.name || !userRuleNames.has(r.name)
|
|
756
|
+
);
|
|
757
|
+
mergedPolicy.smartRules = [...filteredBlocks, ...p.smartRules, ...filteredNonBlocks];
|
|
739
758
|
}
|
|
740
759
|
if (p.snapshot) {
|
|
741
760
|
const s2 = p.snapshot;
|
|
@@ -755,6 +774,16 @@ function getConfig(cwd) {
|
|
|
755
774
|
if (ld.windowSeconds !== void 0)
|
|
756
775
|
mergedPolicy.loopDetection.windowSeconds = ld.windowSeconds;
|
|
757
776
|
}
|
|
777
|
+
if (p.skillPinning && typeof p.skillPinning === "object") {
|
|
778
|
+
const sp = p.skillPinning;
|
|
779
|
+
if (sp.enabled !== void 0) mergedPolicy.skillPinning.enabled = sp.enabled;
|
|
780
|
+
if (sp.mode !== void 0) mergedPolicy.skillPinning.mode = sp.mode;
|
|
781
|
+
if (Array.isArray(sp.roots)) {
|
|
782
|
+
for (const r of sp.roots) {
|
|
783
|
+
if (typeof r === "string" && r.length > 0) mergedPolicy.skillPinning.roots.push(r);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
758
787
|
const envs = source.environments || {};
|
|
759
788
|
for (const [envName, envConfig] of Object.entries(envs)) {
|
|
760
789
|
if (envConfig && typeof envConfig === "object") {
|
|
@@ -769,6 +798,16 @@ function getConfig(cwd) {
|
|
|
769
798
|
};
|
|
770
799
|
applyLayer(globalConfig);
|
|
771
800
|
applyLayer(projectConfig);
|
|
801
|
+
{
|
|
802
|
+
const cacheFile = path3.join(os3.homedir(), ".node9", "rules-cache.json");
|
|
803
|
+
try {
|
|
804
|
+
const raw = JSON.parse(fs3.readFileSync(cacheFile, "utf-8"));
|
|
805
|
+
if (Array.isArray(raw.rules) && raw.rules.length > 0) {
|
|
806
|
+
applyLayer({ policy: { smartRules: raw.rules } });
|
|
807
|
+
}
|
|
808
|
+
} catch {
|
|
809
|
+
}
|
|
810
|
+
}
|
|
772
811
|
const shieldOverrides = readShieldOverrides();
|
|
773
812
|
for (const shieldName of readActiveShields()) {
|
|
774
813
|
const shield = getShield(shieldName);
|
|
@@ -796,6 +835,7 @@ function getConfig(cwd) {
|
|
|
796
835
|
mergedPolicy.sandboxPaths = [...new Set(mergedPolicy.sandboxPaths)];
|
|
797
836
|
mergedPolicy.dangerousWords = [...new Set(mergedPolicy.dangerousWords)];
|
|
798
837
|
mergedPolicy.ignoredTools = [...new Set(mergedPolicy.ignoredTools)];
|
|
838
|
+
mergedPolicy.skillPinning.roots = [...new Set(mergedPolicy.skillPinning.roots)];
|
|
799
839
|
mergedPolicy.snapshot.tools = [...new Set(mergedPolicy.snapshot.tools)];
|
|
800
840
|
mergedPolicy.snapshot.onlyPaths = [...new Set(mergedPolicy.snapshot.onlyPaths)];
|
|
801
841
|
mergedPolicy.snapshot.ignorePaths = [...new Set(mergedPolicy.snapshot.ignorePaths)];
|
|
@@ -1841,6 +1881,15 @@ import path9 from "path";
|
|
|
1841
1881
|
import os6 from "os";
|
|
1842
1882
|
var PAUSED_FILE = path9.join(os6.homedir(), ".node9", "PAUSED");
|
|
1843
1883
|
var TRUST_FILE = path9.join(os6.homedir(), ".node9", "trust.json");
|
|
1884
|
+
function extractCommandPattern(toolName, args) {
|
|
1885
|
+
const lower = toolName.toLowerCase();
|
|
1886
|
+
if (lower !== "bash" && lower !== "execute_bash" && lower !== "shell") return void 0;
|
|
1887
|
+
const a = args;
|
|
1888
|
+
const cmd = typeof a?.["command"] === "string" ? a["command"].trim() : "";
|
|
1889
|
+
if (!cmd) return void 0;
|
|
1890
|
+
const words = cmd.split(/\s+/);
|
|
1891
|
+
return words.slice(0, 2).join(" ");
|
|
1892
|
+
}
|
|
1844
1893
|
function checkPause() {
|
|
1845
1894
|
try {
|
|
1846
1895
|
if (!fs7.existsSync(PAUSED_FILE)) return { paused: false };
|
|
@@ -1864,7 +1913,7 @@ function atomicWriteSync(filePath, data, options) {
|
|
|
1864
1913
|
fs7.writeFileSync(tmpPath, data, options);
|
|
1865
1914
|
fs7.renameSync(tmpPath, filePath);
|
|
1866
1915
|
}
|
|
1867
|
-
function getActiveTrustSession(toolName) {
|
|
1916
|
+
function getActiveTrustSession(toolName, args) {
|
|
1868
1917
|
try {
|
|
1869
1918
|
if (!fs7.existsSync(TRUST_FILE)) return false;
|
|
1870
1919
|
const trust = JSON.parse(fs7.readFileSync(TRUST_FILE, "utf-8"));
|
|
@@ -1873,12 +1922,20 @@ function getActiveTrustSession(toolName) {
|
|
|
1873
1922
|
if (active.length !== trust.entries.length) {
|
|
1874
1923
|
fs7.writeFileSync(TRUST_FILE, JSON.stringify({ entries: active }, null, 2));
|
|
1875
1924
|
}
|
|
1876
|
-
return active.some((e) =>
|
|
1925
|
+
return active.some((e) => {
|
|
1926
|
+
if (!(e.tool === toolName || matchesPattern(toolName, e.tool))) return false;
|
|
1927
|
+
if (e.commandPattern) {
|
|
1928
|
+
const actual = extractCommandPattern(toolName, args) ?? "";
|
|
1929
|
+
return actual === e.commandPattern || actual.startsWith(e.commandPattern + " ");
|
|
1930
|
+
}
|
|
1931
|
+
return true;
|
|
1932
|
+
});
|
|
1877
1933
|
} catch {
|
|
1878
1934
|
return false;
|
|
1879
1935
|
}
|
|
1880
1936
|
}
|
|
1881
|
-
function writeTrustSession(toolName, durationMs) {
|
|
1937
|
+
function writeTrustSession(toolName, durationMs, args) {
|
|
1938
|
+
const commandPattern = extractCommandPattern(toolName, args);
|
|
1882
1939
|
try {
|
|
1883
1940
|
let trust = { entries: [] };
|
|
1884
1941
|
try {
|
|
@@ -1888,8 +1945,14 @@ function writeTrustSession(toolName, durationMs) {
|
|
|
1888
1945
|
} catch {
|
|
1889
1946
|
}
|
|
1890
1947
|
const now = Date.now();
|
|
1891
|
-
trust.entries = trust.entries.filter(
|
|
1892
|
-
|
|
1948
|
+
trust.entries = trust.entries.filter(
|
|
1949
|
+
(e) => !(e.tool === toolName && e.commandPattern === commandPattern) && e.expiry > now
|
|
1950
|
+
);
|
|
1951
|
+
trust.entries.push({
|
|
1952
|
+
tool: toolName,
|
|
1953
|
+
...commandPattern && { commandPattern },
|
|
1954
|
+
expiry: now + durationMs
|
|
1955
|
+
});
|
|
1893
1956
|
atomicWriteSync(TRUST_FILE, JSON.stringify(trust, null, 2));
|
|
1894
1957
|
} catch (err) {
|
|
1895
1958
|
if (process.env.NODE9_DEBUG === "1") {
|
|
@@ -2851,12 +2914,6 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2851
2914
|
};
|
|
2852
2915
|
}
|
|
2853
2916
|
}
|
|
2854
|
-
if (getActiveTrustSession(toolName)) {
|
|
2855
|
-
if (approvers.cloud && creds?.apiKey)
|
|
2856
|
-
await auditLocalAllow(toolName, args, "trust", creds, meta);
|
|
2857
|
-
if (!isManual) appendLocalAudit(toolName, args, "allow", "trust", meta, hashAuditArgs);
|
|
2858
|
-
return { approved: true, checkedBy: "trust" };
|
|
2859
|
-
}
|
|
2860
2917
|
const policyResult = await evaluatePolicy(toolName, args, meta?.agent);
|
|
2861
2918
|
if (policyResult.decision === "allow") {
|
|
2862
2919
|
if (approvers.cloud && creds?.apiKey)
|
|
@@ -2938,6 +2995,12 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
2938
2995
|
if (!isManual) appendLocalAudit(toolName, args, "allow", "ignored", meta, hashAuditArgs);
|
|
2939
2996
|
return { approved: true };
|
|
2940
2997
|
}
|
|
2998
|
+
if (!taintWarning && getActiveTrustSession(toolName, args)) {
|
|
2999
|
+
if (approvers.cloud && creds?.apiKey)
|
|
3000
|
+
await auditLocalAllow(toolName, args, "trust", creds, meta);
|
|
3001
|
+
if (!isManual) appendLocalAudit(toolName, args, "allow", "trust", meta, hashAuditArgs);
|
|
3002
|
+
return { approved: true, checkedBy: "trust" };
|
|
3003
|
+
}
|
|
2941
3004
|
if (taintWarning) {
|
|
2942
3005
|
explainableLabel = "\u{1F534} Node9 Taint (Exfiltration Prevention)";
|
|
2943
3006
|
riskMetadata = computeRiskMetadata(
|
|
@@ -3070,7 +3133,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
3070
3133
|
riskMetadata?.ruleDescription
|
|
3071
3134
|
);
|
|
3072
3135
|
if (decision === "always_allow") {
|
|
3073
|
-
writeTrustSession(toolName, 36e5);
|
|
3136
|
+
writeTrustSession(toolName, 36e5, args);
|
|
3074
3137
|
return { approved: true, checkedBy: "trust" };
|
|
3075
3138
|
}
|
|
3076
3139
|
const isApproved = decision === "allow";
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
{
|
|
11
11
|
"field": "command",
|
|
12
12
|
"op": "matches",
|
|
13
|
-
"value": "(curl|wget)\\s+[^|]*\\|\\s*(bash|sh|zsh|fish|python3?|ruby|perl|node)",
|
|
13
|
+
"value": "(curl|wget)\\s+[^|]*\\|\\s*(?:(bash|sh|zsh|fish)|(python3?|ruby|perl|node)\\b(?!\\s+-[cem]\\b))",
|
|
14
14
|
"flags": "i"
|
|
15
15
|
}
|
|
16
16
|
],
|