@node9/proxy 1.63.0 → 1.64.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,49 @@ 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
+ var cacheReadFailureLogged = false;
4044
+ function readRulesCacheResilient(cacheFile) {
4045
+ let existed = 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
+ continue;
4054
+ }
4055
+ try {
4056
+ return JSON.parse(content);
4057
+ } catch {
4058
+ }
4059
+ }
4060
+ if (existed) {
4061
+ const backup = path4.join(path4.dirname(cacheFile), "rules-cache.last-good.json");
4062
+ if (backup !== cacheFile) {
4063
+ try {
4064
+ const raw = JSON.parse(fs4.readFileSync(backup, "utf-8"));
4065
+ logCacheReadIssue(cacheFile, "RULES_CACHE_CORRUPT_USED_BACKUP");
4066
+ return raw;
4067
+ } catch {
4068
+ }
4069
+ }
4070
+ logCacheReadIssue(cacheFile, "RULES_CACHE_UNREADABLE");
4071
+ }
4072
+ return {};
4073
+ }
4074
+ function logCacheReadIssue(cacheFile, kind) {
4075
+ if (cacheReadFailureLogged) return;
4076
+ cacheReadFailureLogged = true;
4077
+ try {
4078
+ fs4.appendFileSync(
4079
+ path4.join(os4.homedir(), ".node9", "hook-debug.log"),
4080
+ `[${(/* @__PURE__ */ new Date()).toISOString()}] ${kind} ${cacheFile}
4081
+ `
4082
+ );
4083
+ } catch {
4084
+ }
4085
+ }
4041
4086
  function getConfig(cwd) {
4042
4087
  if (!cwd && cachedConfig) return cachedConfig;
4043
4088
  const globalPath = path4.join(os4.homedir(), ".node9", "config.json");
@@ -4105,6 +4150,7 @@ function getConfig(cwd) {
4105
4150
  if (s.mcpAutoWrap !== void 0) mergedSettings.mcpAutoWrap = s.mcpAutoWrap === true;
4106
4151
  if (s.mcpReconcileIntervalMinutes !== void 0)
4107
4152
  mergedSettings.mcpReconcileIntervalMinutes = s.mcpReconcileIntervalMinutes;
4153
+ if (s.mcpStaleAfterDays !== void 0) mergedSettings.mcpStaleAfterDays = s.mcpStaleAfterDays;
4108
4154
  if (s.hud !== void 0) mergedSettings.hud = { ...mergedSettings.hud, ...s.hud };
4109
4155
  if (p.sandboxPaths) mergedPolicy.sandboxPaths.push(...p.sandboxPaths);
4110
4156
  if (p.ignoredTools) mergedPolicy.ignoredTools.push(...p.ignoredTools);
@@ -4114,12 +4160,13 @@ function getConfig(cwd) {
4114
4160
  if (p.smartRules) {
4115
4161
  const defaultBlocks = mergedPolicy.smartRules.filter((r) => r.verdict === "block");
4116
4162
  const defaultNonBlocks = mergedPolicy.smartRules.filter((r) => r.verdict !== "block");
4117
- const userRuleNames = new Set(p.smartRules.filter((r) => r.name).map((r) => r.name));
4163
+ const localRules = p.smartRules.map(({ pinned: _pinned, ...r }) => r);
4164
+ const userRuleNames = new Set(localRules.filter((r) => r.name).map((r) => r.name));
4118
4165
  const filteredBlocks = defaultBlocks.filter((r) => !r.name || !userRuleNames.has(r.name));
4119
4166
  const filteredNonBlocks = defaultNonBlocks.filter(
4120
4167
  (r) => !r.name || !userRuleNames.has(r.name)
4121
4168
  );
4122
- mergedPolicy.smartRules = [...filteredBlocks, ...p.smartRules, ...filteredNonBlocks];
4169
+ mergedPolicy.smartRules = [...filteredBlocks, ...localRules, ...filteredNonBlocks];
4123
4170
  }
4124
4171
  if (p.snapshot) {
4125
4172
  const s2 = p.snapshot;
@@ -4184,10 +4231,12 @@ function getConfig(cwd) {
4184
4231
  applyLayer(globalConfig);
4185
4232
  applyLayer(projectConfig);
4186
4233
  let cloudManagedShields = [];
4234
+ let modeCloudControlled = false;
4235
+ let modeCloudStaged = false;
4187
4236
  {
4188
4237
  const cacheFile = path4.join(os4.homedir(), ".node9", "rules-cache.json");
4189
4238
  try {
4190
- const raw = JSON.parse(fs4.readFileSync(cacheFile, "utf-8"));
4239
+ const raw = readRulesCacheResilient(cacheFile);
4191
4240
  if (Array.isArray(raw.rules) && raw.rules.length > 0) {
4192
4241
  applyLayer({ policy: { smartRules: raw.rules } });
4193
4242
  }
@@ -4204,6 +4253,9 @@ function getConfig(cwd) {
4204
4253
  locked.includes("mode")
4205
4254
  );
4206
4255
  }
4256
+ if (typeof mc.mode === "string" || locked.includes("mode")) {
4257
+ modeCloudControlled = true;
4258
+ }
4207
4259
  if (mc.egress && typeof mc.egress === "object") {
4208
4260
  const hosts = (v) => Array.isArray(v) ? v.filter((h) => typeof h === "string") : void 0;
4209
4261
  mergedPolicy.egress = applyManagedEgress(
@@ -4302,19 +4354,28 @@ function getConfig(cwd) {
4302
4354
  }
4303
4355
  if (raw.shadowMode === true) {
4304
4356
  mergedSettings.mode = "observe";
4357
+ modeCloudStaged = true;
4305
4358
  }
4306
4359
  } catch {
4307
4360
  }
4308
4361
  }
4309
4362
  const shieldOverrides = readShieldOverrides();
4310
4363
  const activeShieldNames = [.../* @__PURE__ */ new Set([...readActiveShields(), ...cloudManagedShields])];
4364
+ const cloudManagedSet = new Set(cloudManagedShields);
4311
4365
  for (const shieldName of activeShieldNames) {
4312
- const shield = getShield(shieldName);
4366
+ const isCloudMandated = cloudManagedSet.has(shieldName);
4367
+ const shield = isCloudMandated ? BUILTIN_SHIELDS[shieldName] : getShield(shieldName);
4313
4368
  if (!shield) continue;
4314
4369
  const existingRuleNames = new Set(mergedPolicy.smartRules.map((r) => r.name));
4315
- const ruleOverrides = shieldOverrides[shieldName] ?? {};
4370
+ const ruleOverrides = isCloudMandated ? {} : shieldOverrides[shieldName] ?? {};
4316
4371
  for (const rule of shield.smartRules) {
4317
- if (!existingRuleNames.has(rule.name)) {
4372
+ const collides = rule.name ? existingRuleNames.has(rule.name) : false;
4373
+ if (isCloudMandated) {
4374
+ if (collides) {
4375
+ mergedPolicy.smartRules = mergedPolicy.smartRules.filter((r) => r.name !== rule.name);
4376
+ }
4377
+ mergedPolicy.smartRules.push({ ...rule, pinned: true });
4378
+ } else if (!collides) {
4318
4379
  const overrideVerdict = rule.name ? ruleOverrides[rule.name] : void 0;
4319
4380
  mergedPolicy.smartRules.push(
4320
4381
  overrideVerdict !== void 0 ? { ...rule, verdict: overrideVerdict } : rule
@@ -4330,7 +4391,27 @@ function getConfig(cwd) {
4330
4391
  for (const rule of ADVISORY_SMART_RULES) {
4331
4392
  if (!existingAdvisoryNames.has(rule.name)) mergedPolicy.smartRules.push(rule);
4332
4393
  }
4333
- if (process.env.NODE9_MODE) mergedSettings.mode = process.env.NODE9_MODE;
4394
+ const envMode = process.env.NODE9_MODE;
4395
+ if (envMode && !modeCloudControlled && ["observe", "audit", "standard", "strict"].includes(envMode)) {
4396
+ mergedSettings.mode = envMode;
4397
+ }
4398
+ if (cloudManagedShields.length > 0 && !modeCloudControlled && !modeCloudStaged && (mergedSettings.mode === "observe" || mergedSettings.mode === "audit")) {
4399
+ mergedSettings.mode = "standard";
4400
+ }
4401
+ const managedFloorActive = cloudManagedShields.length > 0 || modeCloudControlled && mergedSettings.mode === "strict";
4402
+ if (modeCloudControlled && mergedSettings.mode === "strict") {
4403
+ for (const name of Object.keys(mergedEnvironments)) {
4404
+ if (mergedEnvironments[name]?.requireApproval === false) {
4405
+ const cleaned = { ...mergedEnvironments[name] };
4406
+ delete cleaned.requireApproval;
4407
+ mergedEnvironments[name] = cleaned;
4408
+ }
4409
+ }
4410
+ }
4411
+ if (managedFloorActive) {
4412
+ mergedPolicy.ignoredTools = [...DEFAULT_CONFIG.policy.ignoredTools];
4413
+ mergedPolicy.sandboxPaths = [...DEFAULT_CONFIG.policy.sandboxPaths];
4414
+ }
4334
4415
  mergedPolicy.sandboxPaths = [...new Set(mergedPolicy.sandboxPaths)];
4335
4416
  mergedPolicy.dangerousWords = [...new Set(mergedPolicy.dangerousWords)];
4336
4417
  mergedPolicy.ignoredTools = [...new Set(mergedPolicy.ignoredTools)];
@@ -4718,6 +4799,18 @@ function isDaemonRunning() {
4718
4799
  }
4719
4800
  return false;
4720
4801
  }
4802
+ async function daemonHasInteractiveApprover(timeoutMs = 400) {
4803
+ try {
4804
+ const res = await fetch(`http://${DAEMON_HOST}:${DAEMON_PORT}/approver`, {
4805
+ signal: AbortSignal.timeout(timeoutMs)
4806
+ });
4807
+ if (!res.ok) return false;
4808
+ const body = await res.json();
4809
+ return body.interactive === true;
4810
+ } catch {
4811
+ return false;
4812
+ }
4813
+ }
4721
4814
  async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd, recoveryCommand, skipBackgroundAuth, viewOnly, localSmartRuleMatched, socketActivitySent) {
4722
4815
  const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
4723
4816
  const ctrl = new AbortController();
@@ -5414,6 +5507,12 @@ function isNetworkTool(toolName, args) {
5414
5507
  function notifyActivity(data) {
5415
5508
  return notifyActivitySocket(data);
5416
5509
  }
5510
+ async function hasReachableHumanApprover(opts) {
5511
+ const hasDisplay = !!(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
5512
+ const nativeReachable = !opts.calledFromDaemon && opts.approvers.native !== false && hasDisplay;
5513
+ if (nativeReachable) return true;
5514
+ return opts.approvers.terminal !== false && await daemonHasInteractiveApprover();
5515
+ }
5417
5516
  async function authorizeHeadless(toolName, args, meta, options) {
5418
5517
  if (!options?.calledFromDaemon) {
5419
5518
  const actId = randomUUID();
@@ -5728,6 +5827,35 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
5728
5827
  return { approved: true, checkedBy: "local-policy" };
5729
5828
  }
5730
5829
  if (policyResult.decision === "block") {
5830
+ const daemonUp = isDaemonRunning();
5831
+ let humanApproverReachable = false;
5832
+ if (!policyResult.dependsOnStatePredicates?.length && daemonUp && !isTestEnv2) {
5833
+ humanApproverReachable = await hasReachableHumanApprover({
5834
+ approvers,
5835
+ calledFromDaemon: options?.calledFromDaemon
5836
+ });
5837
+ }
5838
+ const mayDowngrade = daemonUp && !isTestEnv2 && humanApproverReachable;
5839
+ const hardBlock = () => {
5840
+ if (!isManual)
5841
+ appendLocalAudit(
5842
+ toolName,
5843
+ args,
5844
+ "deny",
5845
+ "smart-rule-block",
5846
+ { ...meta, ruleName: policyResult.ruleName },
5847
+ hashAuditArgs
5848
+ );
5849
+ return {
5850
+ approved: false,
5851
+ reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
5852
+ blockedBy: "local-config",
5853
+ blockedByLabel: policyResult.blockedByLabel,
5854
+ ruleHit: policyResult.ruleName,
5855
+ ...policyResult.recoveryCommand && { recoveryCommand: policyResult.recoveryCommand },
5856
+ ...policyResult.ruleDescription && { ruleDescription: policyResult.ruleDescription }
5857
+ };
5858
+ };
5731
5859
  if (policyResult.dependsOnStatePredicates?.length) {
5732
5860
  const stateResults = await checkStatePredicates(policyResult.dependsOnStatePredicates);
5733
5861
  const predicatesMet = stateResults !== null && policyResult.dependsOnStatePredicates.every((p) => stateResults[p] === true);
@@ -5744,7 +5872,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
5744
5872
  if (predicatesMet && policyResult.recoveryCommand) {
5745
5873
  statefulRecoveryCommand = policyResult.recoveryCommand;
5746
5874
  }
5747
- } else if (isDaemonRunning() && !isTestEnv2) {
5875
+ } else if (mayDowngrade) {
5748
5876
  if (!isManual)
5749
5877
  appendLocalAudit(
5750
5878
  toolName,
@@ -5766,36 +5894,13 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
5766
5894
  };
5767
5895
  }
5768
5896
  } 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
- };
5897
+ return hardBlock();
5793
5898
  }
5794
5899
  }
5795
5900
  explainableLabel = policyResult.blockedByLabel || "Local Config";
5796
5901
  policyMatchedField = policyResult.matchedField;
5797
5902
  policyMatchedWord = policyResult.matchedWord;
5798
- if (policyResult.ruleName) localSmartRuleMatched = true;
5903
+ if (policyResult.ruleName || policyResult.tier === 7) localSmartRuleMatched = true;
5799
5904
  if (policyResult.ruleDescription) policyRuleDescription = policyResult.ruleDescription;
5800
5905
  else if (policyResult.reason) policyRuleDescription = policyResult.reason;
5801
5906
  riskMetadata = computeRiskMetadata(
@@ -5807,7 +5912,7 @@ async function _authorizeHeadlessCore(toolName, args, metaArg, options) {
5807
5912
  policyResult.ruleName
5808
5913
  );
5809
5914
  if (policyRuleDescription) riskMetadata.ruleDescription = policyRuleDescription.slice(0, 200);
5810
- const persistent = policyResult.ruleName ? null : getPersistentDecision(toolName);
5915
+ const persistent = policyResult.ruleName || policyResult.tier === 7 ? null : getPersistentDecision(toolName);
5811
5916
  if (persistent === "allow" && !appPermReview) {
5812
5917
  if (!isManual) appendLocalAudit(toolName, args, "allow", "persistent", meta, hashAuditArgs);
5813
5918
  return { approved: true, checkedBy: "persistent" };
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.64.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",