@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/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
- mergedPolicy.smartRules = [...defaultBlocks, ...p.smartRules, ...defaultNonBlocks];
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) => e.tool === toolName || matchesPattern(toolName, e.tool));
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((e) => e.tool !== toolName && e.expiry > now);
1922
- trust.entries.push({ tool: toolName, expiry: now + durationMs });
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
- mergedPolicy.smartRules = [...defaultBlocks, ...p.smartRules, ...defaultNonBlocks];
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) => e.tool === toolName || matchesPattern(toolName, e.tool));
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((e) => e.tool !== toolName && e.expiry > now);
1892
- trust.entries.push({ tool: toolName, expiry: now + durationMs });
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
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node9/proxy",
3
- "version": "1.10.2",
3
+ "version": "1.11.0",
4
4
  "description": "The Sudo Command for AI Agents. Execution Security for Claude Code & MCP.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",