@node9/proxy 1.1.0 → 1.1.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/dist/cli.js CHANGED
@@ -790,7 +790,9 @@ var init_dlp = __esm({
790
790
  DLP_PATTERNS = [
791
791
  { name: "AWS Access Key ID", regex: /\bAKIA[0-9A-Z]{16}\b/, severity: "block" },
792
792
  { name: "GitHub Token", regex: /\bgh[pous]_[A-Za-z0-9]{36}\b/, severity: "block" },
793
- { name: "Slack Bot Token", regex: /\bxoxb-[0-9A-Za-z-]+\b/, severity: "block" },
793
+ // Slack bot tokens: xoxb- + variable segment. Real tokens are ~50–80 chars;
794
+ // lower bound 20 avoids false negatives on partial tokens, upper 100 caps scan cost.
795
+ { name: "Slack Bot Token", regex: /\bxoxb-[0-9A-Za-z-]{20,100}\b/, severity: "block" },
794
796
  { name: "OpenAI API Key", regex: /\bsk-[a-zA-Z0-9_-]{20,}\b/, severity: "block" },
795
797
  { name: "Stripe Secret Key", regex: /\bsk_(?:live|test)_[0-9a-zA-Z]{24}\b/, severity: "block" },
796
798
  {
@@ -878,40 +880,13 @@ function resumeNode9() {
878
880
  function validateRegex(pattern) {
879
881
  if (!pattern) return "Pattern is required";
880
882
  if (pattern.length > MAX_REGEX_LENGTH) return `Pattern exceeds max length of ${MAX_REGEX_LENGTH}`;
881
- let parens = 0, brackets = 0, isEscaped = false, inCharClass = false;
882
- for (let i = 0; i < pattern.length; i++) {
883
- const char = pattern[i];
884
- if (isEscaped) {
885
- isEscaped = false;
886
- continue;
887
- }
888
- if (char === "\\") {
889
- isEscaped = true;
890
- continue;
891
- }
892
- if (char === "[" && !inCharClass) {
893
- inCharClass = true;
894
- brackets++;
895
- continue;
896
- }
897
- if (char === "]" && inCharClass) {
898
- inCharClass = false;
899
- brackets--;
900
- continue;
901
- }
902
- if (inCharClass) continue;
903
- if (char === "(") parens++;
904
- else if (char === ")") parens--;
905
- }
906
- if (parens !== 0) return "Unbalanced parentheses";
907
- if (brackets !== 0) return "Unbalanced brackets";
908
- if (/\\\d+[*+{]/.test(pattern)) return "Quantified backreferences are forbidden (ReDoS risk)";
909
- if (!(0, import_safe_regex2.default)(pattern)) return "Pattern rejected: potential ReDoS vulnerability detected";
910
883
  try {
911
884
  new RegExp(pattern);
912
885
  } catch (e) {
913
886
  return `Invalid regex syntax: ${e.message}`;
914
887
  }
888
+ if (/\\\d+[*+{]/.test(pattern)) return "Quantified backreferences are forbidden (ReDoS risk)";
889
+ if (!(0, import_safe_regex2.default)(pattern)) return "Pattern rejected: potential ReDoS vulnerability detected";
915
890
  return null;
916
891
  }
917
892
  function getCompiledRegex(pattern, flags = "") {
@@ -2125,10 +2100,10 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
2125
2100
  }
2126
2101
  return finalResult;
2127
2102
  }
2128
- function getConfig() {
2129
- if (cachedConfig) return cachedConfig;
2103
+ function getConfig(cwd) {
2104
+ if (!cwd && cachedConfig) return cachedConfig;
2130
2105
  const globalPath = import_path5.default.join(import_os2.default.homedir(), ".node9", "config.json");
2131
- const projectPath = import_path5.default.join(process.cwd(), "node9.config.json");
2106
+ const projectPath = import_path5.default.join(cwd ?? process.cwd(), "node9.config.json");
2132
2107
  const globalConfig = tryLoadConfig(globalPath);
2133
2108
  const projectConfig = tryLoadConfig(projectPath);
2134
2109
  const mergedSettings = {
@@ -2215,12 +2190,13 @@ function getConfig() {
2215
2190
  mergedPolicy.snapshot.tools = [...new Set(mergedPolicy.snapshot.tools)];
2216
2191
  mergedPolicy.snapshot.onlyPaths = [...new Set(mergedPolicy.snapshot.onlyPaths)];
2217
2192
  mergedPolicy.snapshot.ignorePaths = [...new Set(mergedPolicy.snapshot.ignorePaths)];
2218
- cachedConfig = {
2193
+ const result = {
2219
2194
  settings: mergedSettings,
2220
2195
  policy: mergedPolicy,
2221
2196
  environments: mergedEnvironments
2222
2197
  };
2223
- return cachedConfig;
2198
+ if (!cwd) cachedConfig = result;
2199
+ return result;
2224
2200
  }
2225
2201
  function tryLoadConfig(filePath) {
2226
2202
  if (!import_fs3.default.existsSync(filePath)) return null;
@@ -5683,9 +5659,19 @@ function applyUndo(hash, cwd) {
5683
5659
  env,
5684
5660
  timeout: GIT_TIMEOUT
5685
5661
  });
5662
+ if (lsTree.status !== 0) {
5663
+ process.stderr.write(`[Node9] applyUndo: git ls-tree failed for hash ${hash}
5664
+ `);
5665
+ return false;
5666
+ }
5686
5667
  const snapshotFiles = new Set(
5687
5668
  lsTree.stdout?.toString().trim().split("\n").filter(Boolean) ?? []
5688
5669
  );
5670
+ if (snapshotFiles.size === 0) {
5671
+ process.stderr.write(`[Node9] applyUndo: ls-tree returned no files for hash ${hash}
5672
+ `);
5673
+ return false;
5674
+ }
5689
5675
  const tracked = (0, import_child_process4.spawnSync)("git", ["ls-files"], { cwd: dir, env, timeout: GIT_TIMEOUT }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
5690
5676
  const untracked = (0, import_child_process4.spawnSync)("git", ["ls-files", "--others", "--exclude-standard"], {
5691
5677
  cwd: dir,
@@ -5840,7 +5826,7 @@ async function runProxy(targetCommand) {
5840
5826
  if (stdout) executable = stdout.trim();
5841
5827
  } catch {
5842
5828
  }
5843
- console.log(import_chalk6.default.green(`\u{1F680} Node9 Proxy Active: Monitoring [${targetCommand}]`));
5829
+ console.error(import_chalk6.default.green(`\u{1F680} Node9 Proxy Active: Monitoring [${targetCommand}]`));
5844
5830
  const child = (0, import_child_process6.spawn)(executable, args, {
5845
5831
  stdio: ["pipe", "pipe", "inherit"],
5846
5832
  // We control STDIN and STDOUT
@@ -6511,14 +6497,7 @@ RAW: ${raw}
6511
6497
  }
6512
6498
  process.exit(0);
6513
6499
  }
6514
- if (payload.cwd) {
6515
- try {
6516
- process.chdir(payload.cwd);
6517
- _resetConfigCache();
6518
- } catch {
6519
- }
6520
- }
6521
- const config = getConfig();
6500
+ const config = getConfig(payload.cwd || void 0);
6522
6501
  if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
6523
6502
  const logPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "hook-debug.log");
6524
6503
  if (!import_fs8.default.existsSync(import_path10.default.dirname(logPath)))
@@ -6651,11 +6630,21 @@ program.command("log").description("PostToolUse hook \u2014 records executed too
6651
6630
  if (!import_fs8.default.existsSync(import_path10.default.dirname(logPath)))
6652
6631
  import_fs8.default.mkdirSync(import_path10.default.dirname(logPath), { recursive: true });
6653
6632
  import_fs8.default.appendFileSync(logPath, JSON.stringify(entry) + "\n");
6654
- const config = getConfig();
6633
+ const safeCwd = typeof payload.cwd === "string" && import_path10.default.isAbsolute(payload.cwd) ? payload.cwd : void 0;
6634
+ const config = getConfig(safeCwd);
6655
6635
  if (shouldSnapshot(tool, {}, config)) {
6656
6636
  await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
6657
6637
  }
6658
- } catch {
6638
+ } catch (err) {
6639
+ const msg = err instanceof Error ? err.message : String(err);
6640
+ process.stderr.write(`[Node9] audit log error: ${msg}
6641
+ `);
6642
+ const debugPath = import_path10.default.join(import_os7.default.homedir(), ".node9", "hook-debug.log");
6643
+ try {
6644
+ import_fs8.default.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
6645
+ `);
6646
+ } catch {
6647
+ }
6659
6648
  }
6660
6649
  process.exit(0);
6661
6650
  };
package/dist/cli.mjs CHANGED
@@ -769,7 +769,9 @@ var init_dlp = __esm({
769
769
  DLP_PATTERNS = [
770
770
  { name: "AWS Access Key ID", regex: /\bAKIA[0-9A-Z]{16}\b/, severity: "block" },
771
771
  { name: "GitHub Token", regex: /\bgh[pous]_[A-Za-z0-9]{36}\b/, severity: "block" },
772
- { name: "Slack Bot Token", regex: /\bxoxb-[0-9A-Za-z-]+\b/, severity: "block" },
772
+ // Slack bot tokens: xoxb- + variable segment. Real tokens are ~50–80 chars;
773
+ // lower bound 20 avoids false negatives on partial tokens, upper 100 caps scan cost.
774
+ { name: "Slack Bot Token", regex: /\bxoxb-[0-9A-Za-z-]{20,100}\b/, severity: "block" },
773
775
  { name: "OpenAI API Key", regex: /\bsk-[a-zA-Z0-9_-]{20,}\b/, severity: "block" },
774
776
  { name: "Stripe Secret Key", regex: /\bsk_(?:live|test)_[0-9a-zA-Z]{24}\b/, severity: "block" },
775
777
  {
@@ -868,40 +870,13 @@ function resumeNode9() {
868
870
  function validateRegex(pattern) {
869
871
  if (!pattern) return "Pattern is required";
870
872
  if (pattern.length > MAX_REGEX_LENGTH) return `Pattern exceeds max length of ${MAX_REGEX_LENGTH}`;
871
- let parens = 0, brackets = 0, isEscaped = false, inCharClass = false;
872
- for (let i = 0; i < pattern.length; i++) {
873
- const char = pattern[i];
874
- if (isEscaped) {
875
- isEscaped = false;
876
- continue;
877
- }
878
- if (char === "\\") {
879
- isEscaped = true;
880
- continue;
881
- }
882
- if (char === "[" && !inCharClass) {
883
- inCharClass = true;
884
- brackets++;
885
- continue;
886
- }
887
- if (char === "]" && inCharClass) {
888
- inCharClass = false;
889
- brackets--;
890
- continue;
891
- }
892
- if (inCharClass) continue;
893
- if (char === "(") parens++;
894
- else if (char === ")") parens--;
895
- }
896
- if (parens !== 0) return "Unbalanced parentheses";
897
- if (brackets !== 0) return "Unbalanced brackets";
898
- if (/\\\d+[*+{]/.test(pattern)) return "Quantified backreferences are forbidden (ReDoS risk)";
899
- if (!safeRegex(pattern)) return "Pattern rejected: potential ReDoS vulnerability detected";
900
873
  try {
901
874
  new RegExp(pattern);
902
875
  } catch (e) {
903
876
  return `Invalid regex syntax: ${e.message}`;
904
877
  }
878
+ if (/\\\d+[*+{]/.test(pattern)) return "Quantified backreferences are forbidden (ReDoS risk)";
879
+ if (!safeRegex(pattern)) return "Pattern rejected: potential ReDoS vulnerability detected";
905
880
  return null;
906
881
  }
907
882
  function getCompiledRegex(pattern, flags = "") {
@@ -2115,10 +2090,10 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
2115
2090
  }
2116
2091
  return finalResult;
2117
2092
  }
2118
- function getConfig() {
2119
- if (cachedConfig) return cachedConfig;
2093
+ function getConfig(cwd) {
2094
+ if (!cwd && cachedConfig) return cachedConfig;
2120
2095
  const globalPath = path5.join(os2.homedir(), ".node9", "config.json");
2121
- const projectPath = path5.join(process.cwd(), "node9.config.json");
2096
+ const projectPath = path5.join(cwd ?? process.cwd(), "node9.config.json");
2122
2097
  const globalConfig = tryLoadConfig(globalPath);
2123
2098
  const projectConfig = tryLoadConfig(projectPath);
2124
2099
  const mergedSettings = {
@@ -2205,12 +2180,13 @@ function getConfig() {
2205
2180
  mergedPolicy.snapshot.tools = [...new Set(mergedPolicy.snapshot.tools)];
2206
2181
  mergedPolicy.snapshot.onlyPaths = [...new Set(mergedPolicy.snapshot.onlyPaths)];
2207
2182
  mergedPolicy.snapshot.ignorePaths = [...new Set(mergedPolicy.snapshot.ignorePaths)];
2208
- cachedConfig = {
2183
+ const result = {
2209
2184
  settings: mergedSettings,
2210
2185
  policy: mergedPolicy,
2211
2186
  environments: mergedEnvironments
2212
2187
  };
2213
- return cachedConfig;
2188
+ if (!cwd) cachedConfig = result;
2189
+ return result;
2214
2190
  }
2215
2191
  function tryLoadConfig(filePath) {
2216
2192
  if (!fs3.existsSync(filePath)) return null;
@@ -5662,9 +5638,19 @@ function applyUndo(hash, cwd) {
5662
5638
  env,
5663
5639
  timeout: GIT_TIMEOUT
5664
5640
  });
5641
+ if (lsTree.status !== 0) {
5642
+ process.stderr.write(`[Node9] applyUndo: git ls-tree failed for hash ${hash}
5643
+ `);
5644
+ return false;
5645
+ }
5665
5646
  const snapshotFiles = new Set(
5666
5647
  lsTree.stdout?.toString().trim().split("\n").filter(Boolean) ?? []
5667
5648
  );
5649
+ if (snapshotFiles.size === 0) {
5650
+ process.stderr.write(`[Node9] applyUndo: ls-tree returned no files for hash ${hash}
5651
+ `);
5652
+ return false;
5653
+ }
5668
5654
  const tracked = spawnSync3("git", ["ls-files"], { cwd: dir, env, timeout: GIT_TIMEOUT }).stdout?.toString().trim().split("\n").filter(Boolean) ?? [];
5669
5655
  const untracked = spawnSync3("git", ["ls-files", "--others", "--exclude-standard"], {
5670
5656
  cwd: dir,
@@ -5819,7 +5805,7 @@ async function runProxy(targetCommand) {
5819
5805
  if (stdout) executable = stdout.trim();
5820
5806
  } catch {
5821
5807
  }
5822
- console.log(chalk6.green(`\u{1F680} Node9 Proxy Active: Monitoring [${targetCommand}]`));
5808
+ console.error(chalk6.green(`\u{1F680} Node9 Proxy Active: Monitoring [${targetCommand}]`));
5823
5809
  const child = spawn5(executable, args, {
5824
5810
  stdio: ["pipe", "pipe", "inherit"],
5825
5811
  // We control STDIN and STDOUT
@@ -6490,14 +6476,7 @@ RAW: ${raw}
6490
6476
  }
6491
6477
  process.exit(0);
6492
6478
  }
6493
- if (payload.cwd) {
6494
- try {
6495
- process.chdir(payload.cwd);
6496
- _resetConfigCache();
6497
- } catch {
6498
- }
6499
- }
6500
- const config = getConfig();
6479
+ const config = getConfig(payload.cwd || void 0);
6501
6480
  if (process.env.NODE9_DEBUG === "1" || config.settings.enableHookLogDebug) {
6502
6481
  const logPath = path10.join(os7.homedir(), ".node9", "hook-debug.log");
6503
6482
  if (!fs8.existsSync(path10.dirname(logPath)))
@@ -6630,11 +6609,21 @@ program.command("log").description("PostToolUse hook \u2014 records executed too
6630
6609
  if (!fs8.existsSync(path10.dirname(logPath)))
6631
6610
  fs8.mkdirSync(path10.dirname(logPath), { recursive: true });
6632
6611
  fs8.appendFileSync(logPath, JSON.stringify(entry) + "\n");
6633
- const config = getConfig();
6612
+ const safeCwd = typeof payload.cwd === "string" && path10.isAbsolute(payload.cwd) ? payload.cwd : void 0;
6613
+ const config = getConfig(safeCwd);
6634
6614
  if (shouldSnapshot(tool, {}, config)) {
6635
6615
  await createShadowSnapshot("unknown", {}, config.policy.snapshot.ignorePaths);
6636
6616
  }
6637
- } catch {
6617
+ } catch (err) {
6618
+ const msg = err instanceof Error ? err.message : String(err);
6619
+ process.stderr.write(`[Node9] audit log error: ${msg}
6620
+ `);
6621
+ const debugPath = path10.join(os7.homedir(), ".node9", "hook-debug.log");
6622
+ try {
6623
+ fs8.appendFileSync(debugPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] LOG_ERROR: ${msg}
6624
+ `);
6625
+ } catch {
6626
+ }
6638
6627
  }
6639
6628
  process.exit(0);
6640
6629
  };
package/dist/index.js CHANGED
@@ -685,7 +685,9 @@ var import_path4 = __toESM(require("path"));
685
685
  var DLP_PATTERNS = [
686
686
  { name: "AWS Access Key ID", regex: /\bAKIA[0-9A-Z]{16}\b/, severity: "block" },
687
687
  { name: "GitHub Token", regex: /\bgh[pous]_[A-Za-z0-9]{36}\b/, severity: "block" },
688
- { name: "Slack Bot Token", regex: /\bxoxb-[0-9A-Za-z-]+\b/, severity: "block" },
688
+ // Slack bot tokens: xoxb- + variable segment. Real tokens are ~50–80 chars;
689
+ // lower bound 20 avoids false negatives on partial tokens, upper 100 caps scan cost.
690
+ { name: "Slack Bot Token", regex: /\bxoxb-[0-9A-Za-z-]{20,100}\b/, severity: "block" },
689
691
  { name: "OpenAI API Key", regex: /\bsk-[a-zA-Z0-9_-]{20,}\b/, severity: "block" },
690
692
  { name: "Stripe Secret Key", regex: /\bsk_(?:live|test)_[0-9a-zA-Z]{24}\b/, severity: "block" },
691
693
  {
@@ -853,40 +855,13 @@ var regexCache = /* @__PURE__ */ new Map();
853
855
  function validateRegex(pattern) {
854
856
  if (!pattern) return "Pattern is required";
855
857
  if (pattern.length > MAX_REGEX_LENGTH) return `Pattern exceeds max length of ${MAX_REGEX_LENGTH}`;
856
- let parens = 0, brackets = 0, isEscaped = false, inCharClass = false;
857
- for (let i = 0; i < pattern.length; i++) {
858
- const char = pattern[i];
859
- if (isEscaped) {
860
- isEscaped = false;
861
- continue;
862
- }
863
- if (char === "\\") {
864
- isEscaped = true;
865
- continue;
866
- }
867
- if (char === "[" && !inCharClass) {
868
- inCharClass = true;
869
- brackets++;
870
- continue;
871
- }
872
- if (char === "]" && inCharClass) {
873
- inCharClass = false;
874
- brackets--;
875
- continue;
876
- }
877
- if (inCharClass) continue;
878
- if (char === "(") parens++;
879
- else if (char === ")") parens--;
880
- }
881
- if (parens !== 0) return "Unbalanced parentheses";
882
- if (brackets !== 0) return "Unbalanced brackets";
883
- if (/\\\d+[*+{]/.test(pattern)) return "Quantified backreferences are forbidden (ReDoS risk)";
884
- if (!(0, import_safe_regex2.default)(pattern)) return "Pattern rejected: potential ReDoS vulnerability detected";
885
858
  try {
886
859
  new RegExp(pattern);
887
860
  } catch (e) {
888
861
  return `Invalid regex syntax: ${e.message}`;
889
862
  }
863
+ if (/\\\d+[*+{]/.test(pattern)) return "Quantified backreferences are forbidden (ReDoS risk)";
864
+ if (!(0, import_safe_regex2.default)(pattern)) return "Pattern rejected: potential ReDoS vulnerability detected";
890
865
  return null;
891
866
  }
892
867
  function getCompiledRegex(pattern, flags = "") {
@@ -2016,10 +1991,10 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
2016
1991
  }
2017
1992
  return finalResult;
2018
1993
  }
2019
- function getConfig() {
2020
- if (cachedConfig) return cachedConfig;
1994
+ function getConfig(cwd) {
1995
+ if (!cwd && cachedConfig) return cachedConfig;
2021
1996
  const globalPath = import_path5.default.join(import_os2.default.homedir(), ".node9", "config.json");
2022
- const projectPath = import_path5.default.join(process.cwd(), "node9.config.json");
1997
+ const projectPath = import_path5.default.join(cwd ?? process.cwd(), "node9.config.json");
2023
1998
  const globalConfig = tryLoadConfig(globalPath);
2024
1999
  const projectConfig = tryLoadConfig(projectPath);
2025
2000
  const mergedSettings = {
@@ -2106,12 +2081,13 @@ function getConfig() {
2106
2081
  mergedPolicy.snapshot.tools = [...new Set(mergedPolicy.snapshot.tools)];
2107
2082
  mergedPolicy.snapshot.onlyPaths = [...new Set(mergedPolicy.snapshot.onlyPaths)];
2108
2083
  mergedPolicy.snapshot.ignorePaths = [...new Set(mergedPolicy.snapshot.ignorePaths)];
2109
- cachedConfig = {
2084
+ const result = {
2110
2085
  settings: mergedSettings,
2111
2086
  policy: mergedPolicy,
2112
2087
  environments: mergedEnvironments
2113
2088
  };
2114
- return cachedConfig;
2089
+ if (!cwd) cachedConfig = result;
2090
+ return result;
2115
2091
  }
2116
2092
  function tryLoadConfig(filePath) {
2117
2093
  if (!import_fs3.default.existsSync(filePath)) return null;
package/dist/index.mjs CHANGED
@@ -649,7 +649,9 @@ import path4 from "path";
649
649
  var DLP_PATTERNS = [
650
650
  { name: "AWS Access Key ID", regex: /\bAKIA[0-9A-Z]{16}\b/, severity: "block" },
651
651
  { name: "GitHub Token", regex: /\bgh[pous]_[A-Za-z0-9]{36}\b/, severity: "block" },
652
- { name: "Slack Bot Token", regex: /\bxoxb-[0-9A-Za-z-]+\b/, severity: "block" },
652
+ // Slack bot tokens: xoxb- + variable segment. Real tokens are ~50–80 chars;
653
+ // lower bound 20 avoids false negatives on partial tokens, upper 100 caps scan cost.
654
+ { name: "Slack Bot Token", regex: /\bxoxb-[0-9A-Za-z-]{20,100}\b/, severity: "block" },
653
655
  { name: "OpenAI API Key", regex: /\bsk-[a-zA-Z0-9_-]{20,}\b/, severity: "block" },
654
656
  { name: "Stripe Secret Key", regex: /\bsk_(?:live|test)_[0-9a-zA-Z]{24}\b/, severity: "block" },
655
657
  {
@@ -817,40 +819,13 @@ var regexCache = /* @__PURE__ */ new Map();
817
819
  function validateRegex(pattern) {
818
820
  if (!pattern) return "Pattern is required";
819
821
  if (pattern.length > MAX_REGEX_LENGTH) return `Pattern exceeds max length of ${MAX_REGEX_LENGTH}`;
820
- let parens = 0, brackets = 0, isEscaped = false, inCharClass = false;
821
- for (let i = 0; i < pattern.length; i++) {
822
- const char = pattern[i];
823
- if (isEscaped) {
824
- isEscaped = false;
825
- continue;
826
- }
827
- if (char === "\\") {
828
- isEscaped = true;
829
- continue;
830
- }
831
- if (char === "[" && !inCharClass) {
832
- inCharClass = true;
833
- brackets++;
834
- continue;
835
- }
836
- if (char === "]" && inCharClass) {
837
- inCharClass = false;
838
- brackets--;
839
- continue;
840
- }
841
- if (inCharClass) continue;
842
- if (char === "(") parens++;
843
- else if (char === ")") parens--;
844
- }
845
- if (parens !== 0) return "Unbalanced parentheses";
846
- if (brackets !== 0) return "Unbalanced brackets";
847
- if (/\\\d+[*+{]/.test(pattern)) return "Quantified backreferences are forbidden (ReDoS risk)";
848
- if (!safeRegex(pattern)) return "Pattern rejected: potential ReDoS vulnerability detected";
849
822
  try {
850
823
  new RegExp(pattern);
851
824
  } catch (e) {
852
825
  return `Invalid regex syntax: ${e.message}`;
853
826
  }
827
+ if (/\\\d+[*+{]/.test(pattern)) return "Quantified backreferences are forbidden (ReDoS risk)";
828
+ if (!safeRegex(pattern)) return "Pattern rejected: potential ReDoS vulnerability detected";
854
829
  return null;
855
830
  }
856
831
  function getCompiledRegex(pattern, flags = "") {
@@ -1980,10 +1955,10 @@ REASON: Action blocked because no approval channels are available. (Native/Brows
1980
1955
  }
1981
1956
  return finalResult;
1982
1957
  }
1983
- function getConfig() {
1984
- if (cachedConfig) return cachedConfig;
1958
+ function getConfig(cwd) {
1959
+ if (!cwd && cachedConfig) return cachedConfig;
1985
1960
  const globalPath = path5.join(os2.homedir(), ".node9", "config.json");
1986
- const projectPath = path5.join(process.cwd(), "node9.config.json");
1961
+ const projectPath = path5.join(cwd ?? process.cwd(), "node9.config.json");
1987
1962
  const globalConfig = tryLoadConfig(globalPath);
1988
1963
  const projectConfig = tryLoadConfig(projectPath);
1989
1964
  const mergedSettings = {
@@ -2070,12 +2045,13 @@ function getConfig() {
2070
2045
  mergedPolicy.snapshot.tools = [...new Set(mergedPolicy.snapshot.tools)];
2071
2046
  mergedPolicy.snapshot.onlyPaths = [...new Set(mergedPolicy.snapshot.onlyPaths)];
2072
2047
  mergedPolicy.snapshot.ignorePaths = [...new Set(mergedPolicy.snapshot.ignorePaths)];
2073
- cachedConfig = {
2048
+ const result = {
2074
2049
  settings: mergedSettings,
2075
2050
  policy: mergedPolicy,
2076
2051
  environments: mergedEnvironments
2077
2052
  };
2078
- return cachedConfig;
2053
+ if (!cwd) cachedConfig = result;
2054
+ return result;
2079
2055
  }
2080
2056
  function tryLoadConfig(filePath) {
2081
2057
  if (!fs3.existsSync(filePath)) return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node9/proxy",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
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",