@node9/proxy 1.63.0 → 1.65.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.mjs CHANGED
@@ -295,6 +295,7 @@ var ConfigFileSchema = z.object({
295
295
  // nudge-only (default), and the scan cadence in minutes.
296
296
  mcpAutoWrap: z.boolean().optional(),
297
297
  mcpReconcileIntervalMinutes: z.number().positive().optional(),
298
+ mcpStaleAfterDays: z.number().min(0).optional(),
298
299
  cloudSyncIntervalHours: z.number().positive().optional(),
299
300
  // Seconds-granular override for the cloud policy sync cadence. Wins over
300
301
  // cloudSyncIntervalHours when set. Lets you opt into fast apply (e.g. 20)
@@ -3305,6 +3306,7 @@ var redis_default = {
3305
3306
  name: "redis",
3306
3307
  description: "Protects Redis instances from destructive AI operations",
3307
3308
  aliases: [],
3309
+ _scopeNote: "Every rule requires Redis to actually be involved: a client in the command (redis-cli / valkey-cli, in either order \u2014 `redis-cli FLUSHALL` and `echo FLUSHALL | redis-cli` are both real), OR the command field holding the bare keyword, which is what the MCP redis_command tool sends ({ command: 'FLUSHALL', args: [] }). Without the client requirement these rules matched the WORD anywhere \u2014 including prose in a heredoc writing a file \u2014 which produced two observed false positives: a test fixture describing the shield, and a commit message naming the rule. Every other builtin shield already scopes this way (aws s3, docker system prune, kubectl delete, gh repo delete). See redis-scope.spec.ts.",
3308
3310
  smartRules: [
3309
3311
  {
3310
3312
  name: "shield:redis:block-flushall",
@@ -3313,7 +3315,7 @@ var redis_default = {
3313
3315
  {
3314
3316
  field: "command",
3315
3317
  op: "matches",
3316
- value: "\\bFLUSHALL\\b",
3318
+ value: "(redis|valkey)-cli.*\\bFLUSHALL\\b|\\bFLUSHALL\\b.*(redis|valkey)-cli|^ ?FLUSHALL\\b|\\.flushall\\s*\\(",
3317
3319
  flags: "i"
3318
3320
  }
3319
3321
  ],
@@ -3327,7 +3329,7 @@ var redis_default = {
3327
3329
  {
3328
3330
  field: "command",
3329
3331
  op: "matches",
3330
- value: "\\bFLUSHDB\\b",
3332
+ value: "(redis|valkey)-cli.*\\bFLUSHDB\\b|\\bFLUSHDB\\b.*(redis|valkey)-cli|^ ?FLUSHDB\\b|\\.flushdb\\s*\\(",
3331
3333
  flags: "i"
3332
3334
  }
3333
3335
  ],
@@ -3341,7 +3343,7 @@ var redis_default = {
3341
3343
  {
3342
3344
  field: "command",
3343
3345
  op: "matches",
3344
- value: "\\bCONFIG\\s+RESETSTAT\\b",
3346
+ value: "(redis|valkey)-cli.*CONFIG\\s+RESETSTAT|^ ?CONFIG\\s+RESETSTAT",
3345
3347
  flags: "i"
3346
3348
  }
3347
3349
  ],
@@ -3355,7 +3357,7 @@ var redis_default = {
3355
3357
  {
3356
3358
  field: "command",
3357
3359
  op: "matches",
3358
- value: "\\bCONFIG\\s+SET\\b",
3360
+ value: "(redis|valkey)-cli.*\\bCONFIG\\s+SET\\b|\\bCONFIG\\s+SET\\b.*(redis|valkey)-cli|^ ?CONFIG\\s+SET\\b",
3359
3361
  flags: "i"
3360
3362
  }
3361
3363
  ],
@@ -3369,7 +3371,7 @@ var redis_default = {
3369
3371
  {
3370
3372
  field: "command",
3371
3373
  op: "matches",
3372
- value: "\\bDEL\\b.*[*?\\[]|redis-cli.*--scan.*\\|.*xargs.*del",
3374
+ value: "(redis|valkey)-cli.*\\bDEL\\b.*[*?\\[]|^ ?DEL\\b.*[*?\\[]|-cli.*--scan.*xargs.*del",
3373
3375
  flags: "i"
3374
3376
  }
3375
3377
  ],
@@ -4038,6 +4040,61 @@ function getActiveEnvironment(config) {
4038
4040
  const env = config.settings.environment || process.env.NODE_ENV || "development";
4039
4041
  return config.environments[env] ?? null;
4040
4042
  }
4043
+ function readRulesCacheResilient(cacheFile) {
4044
+ let existed = false;
4045
+ let sawReadError = false;
4046
+ for (let attempt = 0; attempt < 3; attempt++) {
4047
+ let content;
4048
+ try {
4049
+ content = fs4.readFileSync(cacheFile, "utf-8");
4050
+ existed = true;
4051
+ } catch (err) {
4052
+ if (err.code === "ENOENT") return {};
4053
+ sawReadError = true;
4054
+ continue;
4055
+ }
4056
+ try {
4057
+ const parsed = JSON.parse(content);
4058
+ lastParsedRulesCache = parsed;
4059
+ return parsed;
4060
+ } catch {
4061
+ }
4062
+ }
4063
+ if (existed || sawReadError) {
4064
+ const backup = path4.join(path4.dirname(cacheFile), "rules-cache.last-good.json");
4065
+ if (backup !== cacheFile) {
4066
+ try {
4067
+ const raw = JSON.parse(fs4.readFileSync(backup, "utf-8"));
4068
+ logCacheReadIssue(cacheFile, "RULES_CACHE_CORRUPT_USED_BACKUP");
4069
+ lastParsedRulesCache = raw;
4070
+ return raw;
4071
+ } catch {
4072
+ }
4073
+ }
4074
+ if (lastParsedRulesCache) {
4075
+ logCacheReadIssue(cacheFile, "RULES_CACHE_USED_MEMORY");
4076
+ return lastParsedRulesCache;
4077
+ }
4078
+ logCacheReadIssue(cacheFile, "RULES_CACHE_UNREADABLE");
4079
+ }
4080
+ return {};
4081
+ }
4082
+ var lastParsedRulesCache = null;
4083
+ var CACHE_LOG_REARM_MS = 5 * 60 * 1e3;
4084
+ var cacheReadLastLoggedAt = 0;
4085
+ function logCacheReadIssue(cacheFile, kind) {
4086
+ const now = Date.now();
4087
+ if (now - cacheReadLastLoggedAt < CACHE_LOG_REARM_MS) return;
4088
+ cacheReadLastLoggedAt = now;
4089
+ try {
4090
+ fs4.appendFileSync(
4091
+ path4.join(os4.homedir(), ".node9", "hook-debug.log"),
4092
+ `[${(/* @__PURE__ */ new Date()).toISOString()}] ${kind} ${cacheFile}
4093
+ `
4094
+ );
4095
+ } catch {
4096
+ }
4097
+ }
4041
4098
  function getConfig(cwd) {
4042
4099
  if (!cwd && cachedConfig) return cachedConfig;
4043
4100
  const globalPath = path4.join(os4.homedir(), ".node9", "config.json");
@@ -4105,6 +4162,7 @@ function getConfig(cwd) {
4105
4162
  if (s.mcpAutoWrap !== void 0) mergedSettings.mcpAutoWrap = s.mcpAutoWrap === true;
4106
4163
  if (s.mcpReconcileIntervalMinutes !== void 0)
4107
4164
  mergedSettings.mcpReconcileIntervalMinutes = s.mcpReconcileIntervalMinutes;
4165
+ if (s.mcpStaleAfterDays !== void 0) mergedSettings.mcpStaleAfterDays = s.mcpStaleAfterDays;
4108
4166
  if (s.hud !== void 0) mergedSettings.hud = { ...mergedSettings.hud, ...s.hud };
4109
4167
  if (p.sandboxPaths) mergedPolicy.sandboxPaths.push(...p.sandboxPaths);
4110
4168
  if (p.ignoredTools) mergedPolicy.ignoredTools.push(...p.ignoredTools);
@@ -4114,12 +4172,13 @@ function getConfig(cwd) {
4114
4172
  if (p.smartRules) {
4115
4173
  const defaultBlocks = mergedPolicy.smartRules.filter((r) => r.verdict === "block");
4116
4174
  const defaultNonBlocks = mergedPolicy.smartRules.filter((r) => r.verdict !== "block");
4117
- const userRuleNames = new Set(p.smartRules.filter((r) => r.name).map((r) => r.name));
4175
+ const localRules = p.smartRules.map(({ pinned: _pinned, ...r }) => r);
4176
+ const userRuleNames = new Set(localRules.filter((r) => r.name).map((r) => r.name));
4118
4177
  const filteredBlocks = defaultBlocks.filter((r) => !r.name || !userRuleNames.has(r.name));
4119
4178
  const filteredNonBlocks = defaultNonBlocks.filter(
4120
4179
  (r) => !r.name || !userRuleNames.has(r.name)
4121
4180
  );
4122
- mergedPolicy.smartRules = [...filteredBlocks, ...p.smartRules, ...filteredNonBlocks];
4181
+ mergedPolicy.smartRules = [...filteredBlocks, ...localRules, ...filteredNonBlocks];
4123
4182
  }
4124
4183
  if (p.snapshot) {
4125
4184
  const s2 = p.snapshot;
@@ -4184,10 +4243,12 @@ function getConfig(cwd) {
4184
4243
  applyLayer(globalConfig);
4185
4244
  applyLayer(projectConfig);
4186
4245
  let cloudManagedShields = [];
4246
+ let modeCloudControlled = false;
4247
+ let modeCloudStaged = false;
4187
4248
  {
4188
4249
  const cacheFile = path4.join(os4.homedir(), ".node9", "rules-cache.json");
4189
4250
  try {
4190
- const raw = JSON.parse(fs4.readFileSync(cacheFile, "utf-8"));
4251
+ const raw = readRulesCacheResilient(cacheFile);
4191
4252
  if (Array.isArray(raw.rules) && raw.rules.length > 0) {
4192
4253
  applyLayer({ policy: { smartRules: raw.rules } });
4193
4254
  }
@@ -4204,6 +4265,9 @@ function getConfig(cwd) {
4204
4265
  locked.includes("mode")
4205
4266
  );
4206
4267
  }
4268
+ if (typeof mc.mode === "string" || locked.includes("mode")) {
4269
+ modeCloudControlled = true;
4270
+ }
4207
4271
  if (mc.egress && typeof mc.egress === "object") {
4208
4272
  const hosts = (v) => Array.isArray(v) ? v.filter((h) => typeof h === "string") : void 0;
4209
4273
  mergedPolicy.egress = applyManagedEgress(
@@ -4302,19 +4366,28 @@ function getConfig(cwd) {
4302
4366
  }
4303
4367
  if (raw.shadowMode === true) {
4304
4368
  mergedSettings.mode = "observe";
4369
+ modeCloudStaged = true;
4305
4370
  }
4306
4371
  } catch {
4307
4372
  }
4308
4373
  }
4309
4374
  const shieldOverrides = readShieldOverrides();
4310
4375
  const activeShieldNames = [.../* @__PURE__ */ new Set([...readActiveShields(), ...cloudManagedShields])];
4376
+ const cloudManagedSet = new Set(cloudManagedShields);
4311
4377
  for (const shieldName of activeShieldNames) {
4312
- const shield = getShield(shieldName);
4378
+ const isCloudMandated = cloudManagedSet.has(shieldName);
4379
+ const shield = isCloudMandated ? BUILTIN_SHIELDS[shieldName] : getShield(shieldName);
4313
4380
  if (!shield) continue;
4314
4381
  const existingRuleNames = new Set(mergedPolicy.smartRules.map((r) => r.name));
4315
- const ruleOverrides = shieldOverrides[shieldName] ?? {};
4382
+ const ruleOverrides = isCloudMandated ? {} : shieldOverrides[shieldName] ?? {};
4316
4383
  for (const rule of shield.smartRules) {
4317
- if (!existingRuleNames.has(rule.name)) {
4384
+ const collides = rule.name ? existingRuleNames.has(rule.name) : false;
4385
+ if (isCloudMandated) {
4386
+ if (collides) {
4387
+ mergedPolicy.smartRules = mergedPolicy.smartRules.filter((r) => r.name !== rule.name);
4388
+ }
4389
+ mergedPolicy.smartRules.push({ ...rule, pinned: true });
4390
+ } else if (!collides) {
4318
4391
  const overrideVerdict = rule.name ? ruleOverrides[rule.name] : void 0;
4319
4392
  mergedPolicy.smartRules.push(
4320
4393
  overrideVerdict !== void 0 ? { ...rule, verdict: overrideVerdict } : rule
@@ -4330,7 +4403,27 @@ function getConfig(cwd) {
4330
4403
  for (const rule of ADVISORY_SMART_RULES) {
4331
4404
  if (!existingAdvisoryNames.has(rule.name)) mergedPolicy.smartRules.push(rule);
4332
4405
  }
4333
- if (process.env.NODE9_MODE) mergedSettings.mode = process.env.NODE9_MODE;
4406
+ const envMode = process.env.NODE9_MODE;
4407
+ if (envMode && !modeCloudControlled && ["observe", "audit", "standard", "strict"].includes(envMode)) {
4408
+ mergedSettings.mode = envMode;
4409
+ }
4410
+ if (cloudManagedShields.length > 0 && !modeCloudControlled && !modeCloudStaged && (mergedSettings.mode === "observe" || mergedSettings.mode === "audit")) {
4411
+ mergedSettings.mode = "standard";
4412
+ }
4413
+ const managedFloorActive = cloudManagedShields.length > 0 || modeCloudControlled && mergedSettings.mode === "strict";
4414
+ if (modeCloudControlled && mergedSettings.mode === "strict") {
4415
+ for (const name of Object.keys(mergedEnvironments)) {
4416
+ if (mergedEnvironments[name]?.requireApproval === false) {
4417
+ const cleaned = { ...mergedEnvironments[name] };
4418
+ delete cleaned.requireApproval;
4419
+ mergedEnvironments[name] = cleaned;
4420
+ }
4421
+ }
4422
+ }
4423
+ if (managedFloorActive) {
4424
+ mergedPolicy.ignoredTools = [...DEFAULT_CONFIG.policy.ignoredTools];
4425
+ mergedPolicy.sandboxPaths = [...DEFAULT_CONFIG.policy.sandboxPaths];
4426
+ }
4334
4427
  mergedPolicy.sandboxPaths = [...new Set(mergedPolicy.sandboxPaths)];
4335
4428
  mergedPolicy.dangerousWords = [...new Set(mergedPolicy.dangerousWords)];
4336
4429
  mergedPolicy.ignoredTools = [...new Set(mergedPolicy.ignoredTools)];
@@ -4718,6 +4811,18 @@ function isDaemonRunning() {
4718
4811
  }
4719
4812
  return false;
4720
4813
  }
4814
+ async function daemonHasInteractiveApprover(timeoutMs = 400) {
4815
+ try {
4816
+ const res = await fetch(`http://${DAEMON_HOST}:${DAEMON_PORT}/approver`, {
4817
+ signal: AbortSignal.timeout(timeoutMs)
4818
+ });
4819
+ if (!res.ok) return false;
4820
+ const body = await res.json();
4821
+ return body.interactive === true;
4822
+ } catch {
4823
+ return false;
4824
+ }
4825
+ }
4721
4826
  async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd, recoveryCommand, skipBackgroundAuth, viewOnly, localSmartRuleMatched, socketActivitySent) {
4722
4827
  const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
4723
4828
  const ctrl = new AbortController();
@@ -5414,6 +5519,12 @@ function isNetworkTool(toolName, args) {
5414
5519
  function notifyActivity(data) {
5415
5520
  return notifyActivitySocket(data);
5416
5521
  }
5522
+ async function hasReachableHumanApprover(opts) {
5523
+ const hasDisplay = !!(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
5524
+ const nativeReachable = !opts.calledFromDaemon && opts.approvers.native !== false && hasDisplay;
5525
+ if (nativeReachable) return true;
5526
+ return opts.approvers.terminal !== false && await daemonHasInteractiveApprover();
5527
+ }
5417
5528
  async function authorizeHeadless(toolName, args, meta, options) {
5418
5529
  if (!options?.calledFromDaemon) {
5419
5530
  const actId = randomUUID();
@@ -5487,6 +5598,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
5487
5598
  let riskMetadata;
5488
5599
  let statefulRecoveryCommand;
5489
5600
  let localSmartRuleMatched = false;
5601
+ let hardBlockDowngraded = false;
5490
5602
  let taintWarning = null;
5491
5603
  if (isNetworkTool(toolName, args)) {
5492
5604
  const filePaths = extractFilePaths(toolName, args);
@@ -5728,6 +5840,36 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
5728
5840
  return { approved: true, checkedBy: "local-policy" };
5729
5841
  }
5730
5842
  if (policyResult.decision === "block") {
5843
+ hardBlockDowngraded = true;
5844
+ const daemonUp = isDaemonRunning();
5845
+ let humanApproverReachable = false;
5846
+ if (!policyResult.dependsOnStatePredicates?.length && daemonUp && !isTestEnv2) {
5847
+ humanApproverReachable = await hasReachableHumanApprover({
5848
+ approvers,
5849
+ calledFromDaemon: options?.calledFromDaemon
5850
+ });
5851
+ }
5852
+ const mayDowngrade = daemonUp && !isTestEnv2 && humanApproverReachable;
5853
+ const hardBlock = () => {
5854
+ if (!isManual)
5855
+ appendLocalAudit(
5856
+ toolName,
5857
+ args,
5858
+ "deny",
5859
+ "smart-rule-block",
5860
+ { ...meta, ruleName: policyResult.ruleName },
5861
+ hashAuditArgs
5862
+ );
5863
+ return {
5864
+ approved: false,
5865
+ reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
5866
+ blockedBy: "local-config",
5867
+ blockedByLabel: policyResult.blockedByLabel,
5868
+ ruleHit: policyResult.ruleName,
5869
+ ...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand },
5870
+ ...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription }
5871
+ };
5872
+ };
5731
5873
  if (policyResult.dependsOnStatePredicates?.length) {
5732
5874
  const stateResults = await checkStatePredicates(policyResult.dependsOnStatePredicates);
5733
5875
  const predicatesMet = stateResults !== null && policyResult.dependsOnStatePredicates.every((p) => stateResults[p] === true);
@@ -5744,7 +5886,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
5744
5886
  if (predicatesMet && policyResult.recoveryCommand) {
5745
5887
  statefulRecoveryCommand = policyResult.recoveryCommand;
5746
5888
  }
5747
- } else if (isDaemonRunning() && !isTestEnv2) {
5889
+ } else if (mayDowngrade) {
5748
5890
  if (!isManual)
5749
5891
  appendLocalAudit(
5750
5892
  toolName,
@@ -5766,36 +5908,14 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
5766
5908
  };
5767
5909
  }
5768
5910
  } else {
5769
- if (!isManual)
5770
- appendLocalAudit(
5771
- toolName,
5772
- args,
5773
- "deny",
5774
- "smart-rule-block",
5775
- // Include policyResult.ruleName so the [2] Report SHIELDS
5776
- // panel can attribute this block to its specific shield
5777
- // (e.g. `shield:project-jail:block-read-ssh`) via the
5778
- // rule→shield map. checkedBy stays as the generic
5779
- // `smart-rule-block` for backward compat with existing
5780
- // log readers.
5781
- { ...meta, ruleName: policyResult.ruleName },
5782
- hashAuditArgs
5783
- );
5784
- return {
5785
- approved: false,
5786
- reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
5787
- blockedBy: "local-config",
5788
- blockedByLabel: policyResult.blockedByLabel,
5789
- ruleHit: policyResult.ruleName,
5790
- ...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand },
5791
- ...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription }
5792
- };
5911
+ return hardBlock();
5793
5912
  }
5794
5913
  }
5795
5914
  explainableLabel = policyResult.blockedByLabel || "Local Config";
5796
5915
  policyMatchedField = policyResult.matchedField;
5797
5916
  policyMatchedWord = policyResult.matchedWord;
5798
- if (policyResult.ruleName) localSmartRuleMatched = true;
5917
+ if (policyResult.ruleName || policyResult.tier === 7 || hardBlockDowngraded)
5918
+ localSmartRuleMatched = true;
5799
5919
  if (policyResult.ruleDescription) policyRuleDescription = policyResult.ruleDescription;
5800
5920
  else if (policyResult.reason) policyRuleDescription = policyResult.reason;
5801
5921
  riskMetadata = computeRiskMetadata(
@@ -5807,7 +5927,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
5807
5927
  policyResult.ruleName
5808
5928
  );
5809
5929
  if (policyRuleDescription) riskMetadata.ruleDescription = policyRuleDescription.slice(0, 200);
5810
- const persistent = policyResult.ruleName ? null : getPersistentDecision(toolName);
5930
+ const persistent = policyResult.ruleName || policyResult.tier === 7 || hardBlockDowngraded ? null : getPersistentDecision(toolName);
5811
5931
  if (persistent === "allow" && !appPermReview) {
5812
5932
  if (!isManual) appendLocalAudit(toolName, args, "allow", "persistent", meta, hashAuditArgs);
5813
5933
  return { approved: true, checkedBy: "persistent" };
@@ -5840,7 +5960,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
5840
5960
  return { approved: true };
5841
5961
  }
5842
5962
  }
5843
- if (!taintWarning && !appPermReview && getActiveTrustSession(toolName, args)) {
5963
+ if (!taintWarning && !appPermReview && !hardBlockDowngraded && getActiveTrustSession(toolName, args)) {
5844
5964
  if (!isManual) appendLocalAudit(toolName, args, "allow", "trust", meta, hashAuditArgs);
5845
5965
  return { approved: true, checkedBy: "trust" };
5846
5966
  }
@@ -5904,7 +6024,7 @@ ${appPermReview}`
5904
6024
  forceReview
5905
6025
  );
5906
6026
  if (!initResult.pending) {
5907
- if (initResult.shadowMode && !appPermReview) {
6027
+ if (initResult.shadowMode && !localSmartRuleMatched && !options?.localSmartRuleMatched && !appPermReview) {
5908
6028
  return { approved: true, checkedBy: "cloud" };
5909
6029
  }
5910
6030
  if (!localSmartRuleMatched && !options?.localSmartRuleMatched && !appPermReview) {
package/dist/scan-ink.mjs CHANGED
@@ -1500,6 +1500,7 @@ var redis_default = {
1500
1500
  name: "redis",
1501
1501
  description: "Protects Redis instances from destructive AI operations",
1502
1502
  aliases: [],
1503
+ _scopeNote: "Every rule requires Redis to actually be involved: a client in the command (redis-cli / valkey-cli, in either order \u2014 `redis-cli FLUSHALL` and `echo FLUSHALL | redis-cli` are both real), OR the command field holding the bare keyword, which is what the MCP redis_command tool sends ({ command: 'FLUSHALL', args: [] }). Without the client requirement these rules matched the WORD anywhere \u2014 including prose in a heredoc writing a file \u2014 which produced two observed false positives: a test fixture describing the shield, and a commit message naming the rule. Every other builtin shield already scopes this way (aws s3, docker system prune, kubectl delete, gh repo delete). See redis-scope.spec.ts.",
1503
1504
  smartRules: [
1504
1505
  {
1505
1506
  name: "shield:redis:block-flushall",
@@ -1508,7 +1509,7 @@ var redis_default = {
1508
1509
  {
1509
1510
  field: "command",
1510
1511
  op: "matches",
1511
- value: "\\bFLUSHALL\\b",
1512
+ value: "(redis|valkey)-cli.*\\bFLUSHALL\\b|\\bFLUSHALL\\b.*(redis|valkey)-cli|^ ?FLUSHALL\\b|\\.flushall\\s*\\(",
1512
1513
  flags: "i"
1513
1514
  }
1514
1515
  ],
@@ -1522,7 +1523,7 @@ var redis_default = {
1522
1523
  {
1523
1524
  field: "command",
1524
1525
  op: "matches",
1525
- value: "\\bFLUSHDB\\b",
1526
+ value: "(redis|valkey)-cli.*\\bFLUSHDB\\b|\\bFLUSHDB\\b.*(redis|valkey)-cli|^ ?FLUSHDB\\b|\\.flushdb\\s*\\(",
1526
1527
  flags: "i"
1527
1528
  }
1528
1529
  ],
@@ -1536,7 +1537,7 @@ var redis_default = {
1536
1537
  {
1537
1538
  field: "command",
1538
1539
  op: "matches",
1539
- value: "\\bCONFIG\\s+RESETSTAT\\b",
1540
+ value: "(redis|valkey)-cli.*CONFIG\\s+RESETSTAT|^ ?CONFIG\\s+RESETSTAT",
1540
1541
  flags: "i"
1541
1542
  }
1542
1543
  ],
@@ -1550,7 +1551,7 @@ var redis_default = {
1550
1551
  {
1551
1552
  field: "command",
1552
1553
  op: "matches",
1553
- value: "\\bCONFIG\\s+SET\\b",
1554
+ value: "(redis|valkey)-cli.*\\bCONFIG\\s+SET\\b|\\bCONFIG\\s+SET\\b.*(redis|valkey)-cli|^ ?CONFIG\\s+SET\\b",
1554
1555
  flags: "i"
1555
1556
  }
1556
1557
  ],
@@ -1564,7 +1565,7 @@ var redis_default = {
1564
1565
  {
1565
1566
  field: "command",
1566
1567
  op: "matches",
1567
- value: "\\bDEL\\b.*[*?\\[]|redis-cli.*--scan.*\\|.*xargs.*del",
1568
+ value: "(redis|valkey)-cli.*\\bDEL\\b.*[*?\\[]|^ ?DEL\\b.*[*?\\[]|-cli.*--scan.*xargs.*del",
1568
1569
  flags: "i"
1569
1570
  }
1570
1571
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node9/proxy",
3
- "version": "1.63.0",
3
+ "version": "1.65.0",
4
4
  "description": "The Sudo Command for AI Agents. Execution Security for Claude Code, Codex, Gemini, Cursor, Opencode, Pi, and any MCP server.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",