@node9/proxy 2.25.1 → 2.26.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.
@@ -1282,19 +1282,19 @@ function resolvePart(p, inQuotes) {
1282
1282
  }
1283
1283
  function analyzeFsOperation(command) {
1284
1284
  const normalized = normalizeCommandForPolicy(command);
1285
- if (!FS_OP_PRESCREEN_RE.test(normalized)) return null;
1286
- if (fsOpCache.has(normalized)) {
1287
- const hit = fsOpCache.get(normalized) ?? null;
1288
- fsOpCache.delete(normalized);
1289
- fsOpCache.set(normalized, hit);
1285
+ if (!FS_OP_PRESCREEN_RE.test(normalized) && !FS_OP_PRESCREEN_RE.test(command)) return null;
1286
+ if (fsOpCache.has(command)) {
1287
+ const hit = fsOpCache.get(command) ?? null;
1288
+ fsOpCache.delete(command);
1289
+ fsOpCache.set(command, hit);
1290
1290
  return hit;
1291
1291
  }
1292
- const computed = analyzeFsOperationImpl(normalized);
1292
+ const computed = analyzeFsOperationImpl(command);
1293
1293
  if (fsOpCache.size >= FS_OP_CACHE_MAX) {
1294
1294
  const oldest = fsOpCache.keys().next().value;
1295
1295
  if (oldest !== void 0) fsOpCache.delete(oldest);
1296
1296
  }
1297
- fsOpCache.set(normalized, computed);
1297
+ fsOpCache.set(command, computed);
1298
1298
  return computed;
1299
1299
  }
1300
1300
  function deriveRedirOp(sample) {
@@ -5638,6 +5638,9 @@ function tryLoadConfig(filePath) {
5638
5638
  );
5639
5639
  }
5640
5640
  }
5641
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
5642
+ for (const key of RUNTIME_ONLY_CONFIG_KEYS) delete raw[key];
5643
+ }
5641
5644
  const { sanitized, error } = sanitizeConfig(raw);
5642
5645
  if (error) {
5643
5646
  process.stderr.write(
@@ -5651,7 +5654,7 @@ ${error.replace("Invalid config:\n", "")}
5651
5654
  }
5652
5655
  return sanitized;
5653
5656
  }
5654
- var DANGEROUS_WORDS, DEFAULT_CONFIG, RM_SAFE_PATH_PATTERN, VERDICT_ORDER, ADVISORY_SMART_RULES, cachedConfig, rejectedApiUrlsSeen, lastParsedRulesCache, CACHE_LOG_REARM_MS, cacheReadLastLoggedAt;
5657
+ var DANGEROUS_WORDS, RUNTIME_ONLY_CONFIG_KEYS, DEFAULT_CONFIG, RM_SAFE_PATH_PATTERN, VERDICT_ORDER, ADVISORY_SMART_RULES, cachedConfig, rejectedApiUrlsSeen, lastParsedRulesCache, CACHE_LOG_REARM_MS, cacheReadLastLoggedAt;
5655
5658
  var init_config = __esm({
5656
5659
  "src/config/index.ts"() {
5657
5660
  "use strict";
@@ -5669,6 +5672,7 @@ var init_config = __esm({
5669
5672
  "shred"
5670
5673
  // permanently overwrites file contents (unrecoverable)
5671
5674
  ];
5675
+ RUNTIME_ONLY_CONFIG_KEYS = ["policySource", "ssrfStrictSource"];
5672
5676
  DEFAULT_CONFIG = {
5673
5677
  version: "1.0",
5674
5678
  policySource: "local",
package/dist/index.js CHANGED
@@ -3078,19 +3078,19 @@ var FS_OP_CACHE_MAX = 5e3;
3078
3078
  var fsOpCache = /* @__PURE__ */ new Map();
3079
3079
  function analyzeFsOperation(command) {
3080
3080
  const normalized = normalizeCommandForPolicy(command);
3081
- if (!FS_OP_PRESCREEN_RE.test(normalized)) return null;
3082
- if (fsOpCache.has(normalized)) {
3083
- const hit = fsOpCache.get(normalized) ?? null;
3084
- fsOpCache.delete(normalized);
3085
- fsOpCache.set(normalized, hit);
3081
+ if (!FS_OP_PRESCREEN_RE.test(normalized) && !FS_OP_PRESCREEN_RE.test(command)) return null;
3082
+ if (fsOpCache.has(command)) {
3083
+ const hit = fsOpCache.get(command) ?? null;
3084
+ fsOpCache.delete(command);
3085
+ fsOpCache.set(command, hit);
3086
3086
  return hit;
3087
3087
  }
3088
- const computed = analyzeFsOperationImpl(normalized);
3088
+ const computed = analyzeFsOperationImpl(command);
3089
3089
  if (fsOpCache.size >= FS_OP_CACHE_MAX) {
3090
3090
  const oldest = fsOpCache.keys().next().value;
3091
3091
  if (oldest !== void 0) fsOpCache.delete(oldest);
3092
3092
  }
3093
- fsOpCache.set(normalized, computed);
3093
+ fsOpCache.set(command, computed);
3094
3094
  return computed;
3095
3095
  }
3096
3096
  var stripDotSlash = (p) => p.replace(/^\.\//, "");
@@ -6317,6 +6317,7 @@ var DANGEROUS_WORDS = [
6317
6317
  "shred"
6318
6318
  // permanently overwrites file contents (unrecoverable)
6319
6319
  ];
6320
+ var RUNTIME_ONLY_CONFIG_KEYS = ["policySource", "ssrfStrictSource"];
6320
6321
  var DEFAULT_CONFIG = {
6321
6322
  version: "1.0",
6322
6323
  policySource: "local",
@@ -7251,6 +7252,9 @@ function tryLoadConfig(filePath) {
7251
7252
  );
7252
7253
  }
7253
7254
  }
7255
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
7256
+ for (const key of RUNTIME_ONLY_CONFIG_KEYS) delete raw[key];
7257
+ }
7254
7258
  const { sanitized, error } = sanitizeConfig(raw);
7255
7259
  if (error) {
7256
7260
  process.stderr.write(
@@ -8383,6 +8387,37 @@ function recordAndCheck(tool, args, threshold = 3, windowMs = 12e4) {
8383
8387
  }
8384
8388
 
8385
8389
  // src/auth/orchestrator.ts
8390
+ var SENSITIVE_PATH_PATTERN = sensitivePathMatch("").patternName;
8391
+ function isProtectedPathRule(ruleName) {
8392
+ if (!ruleName) return false;
8393
+ return /^(?:org:)?block-path-/.test(ruleName) || /^shield:project-jail:block-read-/.test(ruleName);
8394
+ }
8395
+ function matchedPathField(ruleName) {
8396
+ if (/-anytool-pattern$/.test(ruleName)) return "pattern";
8397
+ if (/-anytool-path$/.test(ruleName)) return "path";
8398
+ if (/-anytool$/.test(ruleName)) return "file_path";
8399
+ return void 0;
8400
+ }
8401
+ function protectedPathOf(ruleName, toolName, args) {
8402
+ const a = args && typeof args === "object" && !Array.isArray(args) ? args : {};
8403
+ if (isBashTool(toolName) && typeof a.command === "string") {
8404
+ return analyzeFsOperation(a.command)?.path || void 0;
8405
+ }
8406
+ const field = matchedPathField(ruleName);
8407
+ const order = field ? [field] : ["file_path", "path", "filename", "pattern"];
8408
+ for (const k of order) {
8409
+ if (typeof a[k] === "string" && a[k]) return a[k];
8410
+ }
8411
+ return void 0;
8412
+ }
8413
+ function safePathForDisplay(p) {
8414
+ return safeMessage(p).replace(/[\u0080-\u009f\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/g, "");
8415
+ }
8416
+ function protectedPathFields(ruleName, toolName, args, credentialInArgs) {
8417
+ if (!ruleName || credentialInArgs || !isProtectedPathRule(ruleName)) return {};
8418
+ const p = protectedPathOf(ruleName, toolName, args);
8419
+ return { blockKind: "protected-path", blockedPath: p ? safePathForDisplay(p) : void 0 };
8420
+ }
8386
8421
  var WRITE_TOOLS = /* @__PURE__ */ new Set([
8387
8422
  "write",
8388
8423
  "write_file",
@@ -8675,7 +8710,17 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
8675
8710
  approved: false,
8676
8711
  reason: dlpReason,
8677
8712
  blockedBy: "local-config",
8678
- blockedByLabel: "\u{1F6A8} Node9 DLP (Secret Detected)"
8713
+ blockedByLabel: "\u{1F6A8} Node9 DLP (Secret Detected)",
8714
+ // A PATH hit is a protected file, not a secret in the arguments: the
8715
+ // agent must not be told to rotate a credential it never saw (MSG-1).
8716
+ // Only when the arguments carry no credential: a path match
8717
+ // short-circuits the argument scan above (`scanFilePath ?? scanArgs`),
8718
+ // so a Write of a real key into .env would otherwise be told "nothing
8719
+ // was exposed" (/code-review).
8720
+ ...dlpMatch.patternName === SENSITIVE_PATH_PATTERN && !scanArgs(args) && {
8721
+ blockKind: "protected-path",
8722
+ blockedPath: safePathForDisplay(dlpMatch.redactedSample)
8723
+ }
8679
8724
  };
8680
8725
  }
8681
8726
  if (!isManual)
@@ -8861,7 +8906,8 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
8861
8906
  blockedByLabel: policyResult.blockedByLabel,
8862
8907
  ruleHit: policyResult.ruleName,
8863
8908
  ...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand },
8864
- ...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription }
8909
+ ...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription },
8910
+ ...protectedPathFields(policyResult.ruleName, toolName, args, dlpReviewFlagged)
8865
8911
  };
8866
8912
  };
8867
8913
  if (policyResult.dependsOnStatePredicates?.length) {
@@ -8965,7 +9011,8 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
8965
9011
  reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
8966
9012
  blockedBy: "local-config",
8967
9013
  blockedByLabel: policyResult.blockedByLabel,
8968
- ruleHit: policyResult.ruleName
9014
+ ruleHit: policyResult.ruleName,
9015
+ ...protectedPathFields(policyResult.ruleName, toolName, args, dlpReviewFlagged)
8969
9016
  };
8970
9017
  }
8971
9018
  } else {
package/dist/index.mjs CHANGED
@@ -3048,19 +3048,19 @@ var FS_OP_CACHE_MAX = 5e3;
3048
3048
  var fsOpCache = /* @__PURE__ */ new Map();
3049
3049
  function analyzeFsOperation(command) {
3050
3050
  const normalized = normalizeCommandForPolicy(command);
3051
- if (!FS_OP_PRESCREEN_RE.test(normalized)) return null;
3052
- if (fsOpCache.has(normalized)) {
3053
- const hit = fsOpCache.get(normalized) ?? null;
3054
- fsOpCache.delete(normalized);
3055
- fsOpCache.set(normalized, hit);
3051
+ if (!FS_OP_PRESCREEN_RE.test(normalized) && !FS_OP_PRESCREEN_RE.test(command)) return null;
3052
+ if (fsOpCache.has(command)) {
3053
+ const hit = fsOpCache.get(command) ?? null;
3054
+ fsOpCache.delete(command);
3055
+ fsOpCache.set(command, hit);
3056
3056
  return hit;
3057
3057
  }
3058
- const computed = analyzeFsOperationImpl(normalized);
3058
+ const computed = analyzeFsOperationImpl(command);
3059
3059
  if (fsOpCache.size >= FS_OP_CACHE_MAX) {
3060
3060
  const oldest = fsOpCache.keys().next().value;
3061
3061
  if (oldest !== void 0) fsOpCache.delete(oldest);
3062
3062
  }
3063
- fsOpCache.set(normalized, computed);
3063
+ fsOpCache.set(command, computed);
3064
3064
  return computed;
3065
3065
  }
3066
3066
  var stripDotSlash = (p) => p.replace(/^\.\//, "");
@@ -6287,6 +6287,7 @@ var DANGEROUS_WORDS = [
6287
6287
  "shred"
6288
6288
  // permanently overwrites file contents (unrecoverable)
6289
6289
  ];
6290
+ var RUNTIME_ONLY_CONFIG_KEYS = ["policySource", "ssrfStrictSource"];
6290
6291
  var DEFAULT_CONFIG = {
6291
6292
  version: "1.0",
6292
6293
  policySource: "local",
@@ -7221,6 +7222,9 @@ function tryLoadConfig(filePath) {
7221
7222
  );
7222
7223
  }
7223
7224
  }
7225
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
7226
+ for (const key of RUNTIME_ONLY_CONFIG_KEYS) delete raw[key];
7227
+ }
7224
7228
  const { sanitized, error } = sanitizeConfig(raw);
7225
7229
  if (error) {
7226
7230
  process.stderr.write(
@@ -8353,6 +8357,37 @@ function recordAndCheck(tool, args, threshold = 3, windowMs = 12e4) {
8353
8357
  }
8354
8358
 
8355
8359
  // src/auth/orchestrator.ts
8360
+ var SENSITIVE_PATH_PATTERN = sensitivePathMatch("").patternName;
8361
+ function isProtectedPathRule(ruleName) {
8362
+ if (!ruleName) return false;
8363
+ return /^(?:org:)?block-path-/.test(ruleName) || /^shield:project-jail:block-read-/.test(ruleName);
8364
+ }
8365
+ function matchedPathField(ruleName) {
8366
+ if (/-anytool-pattern$/.test(ruleName)) return "pattern";
8367
+ if (/-anytool-path$/.test(ruleName)) return "path";
8368
+ if (/-anytool$/.test(ruleName)) return "file_path";
8369
+ return void 0;
8370
+ }
8371
+ function protectedPathOf(ruleName, toolName, args) {
8372
+ const a = args && typeof args === "object" && !Array.isArray(args) ? args : {};
8373
+ if (isBashTool(toolName) && typeof a.command === "string") {
8374
+ return analyzeFsOperation(a.command)?.path || void 0;
8375
+ }
8376
+ const field = matchedPathField(ruleName);
8377
+ const order = field ? [field] : ["file_path", "path", "filename", "pattern"];
8378
+ for (const k of order) {
8379
+ if (typeof a[k] === "string" && a[k]) return a[k];
8380
+ }
8381
+ return void 0;
8382
+ }
8383
+ function safePathForDisplay(p) {
8384
+ return safeMessage(p).replace(/[\u0080-\u009f\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/g, "");
8385
+ }
8386
+ function protectedPathFields(ruleName, toolName, args, credentialInArgs) {
8387
+ if (!ruleName || credentialInArgs || !isProtectedPathRule(ruleName)) return {};
8388
+ const p = protectedPathOf(ruleName, toolName, args);
8389
+ return { blockKind: "protected-path", blockedPath: p ? safePathForDisplay(p) : void 0 };
8390
+ }
8356
8391
  var WRITE_TOOLS = /* @__PURE__ */ new Set([
8357
8392
  "write",
8358
8393
  "write_file",
@@ -8645,7 +8680,17 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
8645
8680
  approved: false,
8646
8681
  reason: dlpReason,
8647
8682
  blockedBy: "local-config",
8648
- blockedByLabel: "\u{1F6A8} Node9 DLP (Secret Detected)"
8683
+ blockedByLabel: "\u{1F6A8} Node9 DLP (Secret Detected)",
8684
+ // A PATH hit is a protected file, not a secret in the arguments: the
8685
+ // agent must not be told to rotate a credential it never saw (MSG-1).
8686
+ // Only when the arguments carry no credential: a path match
8687
+ // short-circuits the argument scan above (`scanFilePath ?? scanArgs`),
8688
+ // so a Write of a real key into .env would otherwise be told "nothing
8689
+ // was exposed" (/code-review).
8690
+ ...dlpMatch.patternName === SENSITIVE_PATH_PATTERN && !scanArgs(args) && {
8691
+ blockKind: "protected-path",
8692
+ blockedPath: safePathForDisplay(dlpMatch.redactedSample)
8693
+ }
8649
8694
  };
8650
8695
  }
8651
8696
  if (!isManual)
@@ -8831,7 +8876,8 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
8831
8876
  blockedByLabel: policyResult.blockedByLabel,
8832
8877
  ruleHit: policyResult.ruleName,
8833
8878
  ...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand },
8834
- ...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription }
8879
+ ...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription },
8880
+ ...protectedPathFields(policyResult.ruleName, toolName, args, dlpReviewFlagged)
8835
8881
  };
8836
8882
  };
8837
8883
  if (policyResult.dependsOnStatePredicates?.length) {
@@ -8935,7 +8981,8 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
8935
8981
  reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
8936
8982
  blockedBy: "local-config",
8937
8983
  blockedByLabel: policyResult.blockedByLabel,
8938
- ruleHit: policyResult.ruleName
8984
+ ruleHit: policyResult.ruleName,
8985
+ ...protectedPathFields(policyResult.ruleName, toolName, args, dlpReviewFlagged)
8939
8986
  };
8940
8987
  }
8941
8988
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node9/proxy",
3
- "version": "2.25.1",
3
+ "version": "2.26.0",
4
4
  "description": "IAM for your AI agents. Set what Claude Code, Codex, Gemini, Cursor and any MCP server are allowed to do, review risky actions before they run, and keep every action on the record.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",