@node9/proxy 1.5.3 → 1.5.4

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
@@ -266,8 +266,8 @@ function sanitizeConfig(raw) {
266
266
  }
267
267
  }
268
268
  const lines = result.error.issues.map((issue) => {
269
- const path13 = issue.path.length > 0 ? issue.path.join(".") : "root";
270
- return ` \u2022 ${path13}: ${issue.message}`;
269
+ const path14 = issue.path.length > 0 ? issue.path.join(".") : "root";
270
+ return ` \u2022 ${path14}: ${issue.message}`;
271
271
  });
272
272
  return {
273
273
  sanitized,
@@ -581,7 +581,9 @@ var DEFAULT_CONFIG = {
581
581
  {
582
582
  field: "command",
583
583
  op: "matches",
584
- value: "rm\\b.*(-[rRfF]*[rR][rRfF]*|--recursive)"
584
+ // Require the recursive flag to be preceded by whitespace so that
585
+ // filenames containing "-r" (e.g. "ai-review.yml") don't false-positive.
586
+ value: "rm\\b.*\\s(-[rRfF]*[rR][rRfF]*|--recursive)(\\s|$)"
585
587
  },
586
588
  {
587
589
  field: "command",
@@ -1621,9 +1623,9 @@ function matchesPattern(text, patterns) {
1621
1623
  const withoutDotSlash = text.replace(/^\.\//, "");
1622
1624
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1623
1625
  }
1624
- function getNestedValue(obj, path13) {
1626
+ function getNestedValue(obj, path14) {
1625
1627
  if (!obj || typeof obj !== "object") return null;
1626
- return path13.split(".").reduce((prev, curr) => prev?.[curr], obj);
1628
+ return path14.split(".").reduce((prev, curr) => prev?.[curr], obj);
1627
1629
  }
1628
1630
  function evaluateSmartConditions(args, rule) {
1629
1631
  if (!rule.conditions || rule.conditions.length === 0) return true;
@@ -2515,6 +2517,7 @@ init_audit();
2515
2517
  // src/auth/cloud.ts
2516
2518
  var import_fs9 = __toESM(require("fs"));
2517
2519
  var import_os8 = __toESM(require("os"));
2520
+ var import_path13 = __toESM(require("path"));
2518
2521
  init_audit();
2519
2522
  function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
2520
2523
  return fetch(`${creds.apiUrl}/audit`, {
@@ -2540,6 +2543,33 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
2540
2543
  async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
2541
2544
  const controller = new AbortController();
2542
2545
  const timeout = setTimeout(() => controller.abort(), 1e4);
2546
+ if (!creds.apiKey) throw new Error("Node9 API Key is missing");
2547
+ let ciContext;
2548
+ if (process.env.CI) {
2549
+ try {
2550
+ const ciContextPath = import_path13.default.join(import_os8.default.homedir(), ".node9", "ci-context.json");
2551
+ const stats = import_fs9.default.statSync(ciContextPath);
2552
+ if (stats.size > 1e4) throw new Error("ci-context.json exceeds 10 KB");
2553
+ const raw = import_fs9.default.readFileSync(ciContextPath, "utf8");
2554
+ const parsed = JSON.parse(raw);
2555
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
2556
+ throw new Error("ci-context.json is not a plain object");
2557
+ }
2558
+ const p = parsed;
2559
+ ciContext = {
2560
+ tests_after: p["tests_after"],
2561
+ files_changed: p["files_changed"],
2562
+ issues_found: p["issues_found"],
2563
+ issues_fixed: p["issues_fixed"],
2564
+ github_repository: p["github_repository"],
2565
+ github_head_ref: p["github_head_ref"],
2566
+ iteration: p["iteration"],
2567
+ draft_pr_number: p["draft_pr_number"],
2568
+ draft_pr_url: p["draft_pr_url"]
2569
+ };
2570
+ } catch {
2571
+ }
2572
+ }
2543
2573
  try {
2544
2574
  const response = await fetch(creds.apiUrl, {
2545
2575
  method: "POST",
@@ -2554,7 +2584,8 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
2554
2584
  cwd: process.cwd(),
2555
2585
  platform: import_os8.default.platform()
2556
2586
  },
2557
- ...riskMetadata && { riskMetadata }
2587
+ ...riskMetadata && { riskMetadata },
2588
+ ...ciContext && { ciContext }
2558
2589
  }),
2559
2590
  signal: controller.signal
2560
2591
  });
@@ -2580,12 +2611,17 @@ async function pollNode9SaaS(requestId, creds, signal) {
2580
2611
  });
2581
2612
  clearTimeout(pollTimer);
2582
2613
  if (!statusRes.ok) continue;
2583
- const { status, reason } = await statusRes.json();
2614
+ const statusBody = await statusRes.json();
2615
+ const { status } = statusBody;
2584
2616
  if (status === "APPROVED") {
2585
- return { approved: true, reason };
2617
+ return { approved: true, reason: statusBody.reason };
2586
2618
  }
2587
2619
  if (status === "DENIED" || status === "AUTO_BLOCKED" || status === "TIMED_OUT") {
2588
- return { approved: false, reason };
2620
+ return { approved: false, reason: statusBody.reason };
2621
+ }
2622
+ if (status === "FIX") {
2623
+ const feedbackText = statusBody.feedbackText ?? statusBody.reason ?? "Run again with feedback.";
2624
+ return { approved: false, reason: feedbackText };
2589
2625
  }
2590
2626
  } catch {
2591
2627
  }
@@ -2821,7 +2857,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2821
2857
  const policyResult = await evaluatePolicy(toolName, args, meta?.agent);
2822
2858
  if (policyResult.decision === "allow") {
2823
2859
  if (approvers.cloud && creds?.apiKey)
2824
- auditLocalAllow(toolName, args, "local-policy", creds, meta);
2860
+ await auditLocalAllow(toolName, args, "local-policy", creds, meta);
2825
2861
  if (!isManual) appendLocalAudit(toolName, args, "allow", "local-policy", meta, hashAuditArgs);
2826
2862
  return { approved: true, checkedBy: "local-policy" };
2827
2863
  }
@@ -2842,9 +2878,16 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2842
2878
  if (predicatesMet && policyResult.recoveryCommand) {
2843
2879
  statefulRecoveryCommand = policyResult.recoveryCommand;
2844
2880
  }
2881
+ } else if (isDaemonRunning() && !isTestEnv2) {
2882
+ if (!isManual)
2883
+ appendLocalAudit(toolName, args, "deny", "smart-rule-block", meta, hashAuditArgs);
2884
+ if (approvers.cloud && creds?.apiKey)
2885
+ auditLocalAllow(toolName, args, "smart-rule-block", creds, meta);
2845
2886
  } else {
2846
2887
  if (!isManual)
2847
2888
  appendLocalAudit(toolName, args, "deny", "smart-rule-block", meta, hashAuditArgs);
2889
+ if (approvers.cloud && creds?.apiKey)
2890
+ auditLocalAllow(toolName, args, "smart-rule-block", creds, meta);
2848
2891
  return {
2849
2892
  approved: false,
2850
2893
  reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
package/dist/index.mjs CHANGED
@@ -236,8 +236,8 @@ function sanitizeConfig(raw) {
236
236
  }
237
237
  }
238
238
  const lines = result.error.issues.map((issue) => {
239
- const path13 = issue.path.length > 0 ? issue.path.join(".") : "root";
240
- return ` \u2022 ${path13}: ${issue.message}`;
239
+ const path14 = issue.path.length > 0 ? issue.path.join(".") : "root";
240
+ return ` \u2022 ${path14}: ${issue.message}`;
241
241
  });
242
242
  return {
243
243
  sanitized,
@@ -551,7 +551,9 @@ var DEFAULT_CONFIG = {
551
551
  {
552
552
  field: "command",
553
553
  op: "matches",
554
- value: "rm\\b.*(-[rRfF]*[rR][rRfF]*|--recursive)"
554
+ // Require the recursive flag to be preceded by whitespace so that
555
+ // filenames containing "-r" (e.g. "ai-review.yml") don't false-positive.
556
+ value: "rm\\b.*\\s(-[rRfF]*[rR][rRfF]*|--recursive)(\\s|$)"
555
557
  },
556
558
  {
557
559
  field: "command",
@@ -1591,9 +1593,9 @@ function matchesPattern(text, patterns) {
1591
1593
  const withoutDotSlash = text.replace(/^\.\//, "");
1592
1594
  return isMatch(withoutDotSlash) || isMatch(`./${withoutDotSlash}`);
1593
1595
  }
1594
- function getNestedValue(obj, path13) {
1596
+ function getNestedValue(obj, path14) {
1595
1597
  if (!obj || typeof obj !== "object") return null;
1596
- return path13.split(".").reduce((prev, curr) => prev?.[curr], obj);
1598
+ return path14.split(".").reduce((prev, curr) => prev?.[curr], obj);
1597
1599
  }
1598
1600
  function evaluateSmartConditions(args, rule) {
1599
1601
  if (!rule.conditions || rule.conditions.length === 0) return true;
@@ -2486,6 +2488,7 @@ init_audit();
2486
2488
  init_audit();
2487
2489
  import fs9 from "fs";
2488
2490
  import os8 from "os";
2491
+ import path13 from "path";
2489
2492
  function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
2490
2493
  return fetch(`${creds.apiUrl}/audit`, {
2491
2494
  method: "POST",
@@ -2510,6 +2513,33 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
2510
2513
  async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
2511
2514
  const controller = new AbortController();
2512
2515
  const timeout = setTimeout(() => controller.abort(), 1e4);
2516
+ if (!creds.apiKey) throw new Error("Node9 API Key is missing");
2517
+ let ciContext;
2518
+ if (process.env.CI) {
2519
+ try {
2520
+ const ciContextPath = path13.join(os8.homedir(), ".node9", "ci-context.json");
2521
+ const stats = fs9.statSync(ciContextPath);
2522
+ if (stats.size > 1e4) throw new Error("ci-context.json exceeds 10 KB");
2523
+ const raw = fs9.readFileSync(ciContextPath, "utf8");
2524
+ const parsed = JSON.parse(raw);
2525
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
2526
+ throw new Error("ci-context.json is not a plain object");
2527
+ }
2528
+ const p = parsed;
2529
+ ciContext = {
2530
+ tests_after: p["tests_after"],
2531
+ files_changed: p["files_changed"],
2532
+ issues_found: p["issues_found"],
2533
+ issues_fixed: p["issues_fixed"],
2534
+ github_repository: p["github_repository"],
2535
+ github_head_ref: p["github_head_ref"],
2536
+ iteration: p["iteration"],
2537
+ draft_pr_number: p["draft_pr_number"],
2538
+ draft_pr_url: p["draft_pr_url"]
2539
+ };
2540
+ } catch {
2541
+ }
2542
+ }
2513
2543
  try {
2514
2544
  const response = await fetch(creds.apiUrl, {
2515
2545
  method: "POST",
@@ -2524,7 +2554,8 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
2524
2554
  cwd: process.cwd(),
2525
2555
  platform: os8.platform()
2526
2556
  },
2527
- ...riskMetadata && { riskMetadata }
2557
+ ...riskMetadata && { riskMetadata },
2558
+ ...ciContext && { ciContext }
2528
2559
  }),
2529
2560
  signal: controller.signal
2530
2561
  });
@@ -2550,12 +2581,17 @@ async function pollNode9SaaS(requestId, creds, signal) {
2550
2581
  });
2551
2582
  clearTimeout(pollTimer);
2552
2583
  if (!statusRes.ok) continue;
2553
- const { status, reason } = await statusRes.json();
2584
+ const statusBody = await statusRes.json();
2585
+ const { status } = statusBody;
2554
2586
  if (status === "APPROVED") {
2555
- return { approved: true, reason };
2587
+ return { approved: true, reason: statusBody.reason };
2556
2588
  }
2557
2589
  if (status === "DENIED" || status === "AUTO_BLOCKED" || status === "TIMED_OUT") {
2558
- return { approved: false, reason };
2590
+ return { approved: false, reason: statusBody.reason };
2591
+ }
2592
+ if (status === "FIX") {
2593
+ const feedbackText = statusBody.feedbackText ?? statusBody.reason ?? "Run again with feedback.";
2594
+ return { approved: false, reason: feedbackText };
2559
2595
  }
2560
2596
  } catch {
2561
2597
  }
@@ -2791,7 +2827,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2791
2827
  const policyResult = await evaluatePolicy(toolName, args, meta?.agent);
2792
2828
  if (policyResult.decision === "allow") {
2793
2829
  if (approvers.cloud && creds?.apiKey)
2794
- auditLocalAllow(toolName, args, "local-policy", creds, meta);
2830
+ await auditLocalAllow(toolName, args, "local-policy", creds, meta);
2795
2831
  if (!isManual) appendLocalAudit(toolName, args, "allow", "local-policy", meta, hashAuditArgs);
2796
2832
  return { approved: true, checkedBy: "local-policy" };
2797
2833
  }
@@ -2812,9 +2848,16 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2812
2848
  if (predicatesMet && policyResult.recoveryCommand) {
2813
2849
  statefulRecoveryCommand = policyResult.recoveryCommand;
2814
2850
  }
2851
+ } else if (isDaemonRunning() && !isTestEnv2) {
2852
+ if (!isManual)
2853
+ appendLocalAudit(toolName, args, "deny", "smart-rule-block", meta, hashAuditArgs);
2854
+ if (approvers.cloud && creds?.apiKey)
2855
+ auditLocalAllow(toolName, args, "smart-rule-block", creds, meta);
2815
2856
  } else {
2816
2857
  if (!isManual)
2817
2858
  appendLocalAudit(toolName, args, "deny", "smart-rule-block", meta, hashAuditArgs);
2859
+ if (approvers.cloud && creds?.apiKey)
2860
+ auditLocalAllow(toolName, args, "smart-rule-block", creds, meta);
2818
2861
  return {
2819
2862
  approved: false,
2820
2863
  reason: policyResult.reason ?? "Action explicitly blocked by Smart Policy.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node9/proxy",
3
- "version": "1.5.3",
3
+ "version": "1.5.4",
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",
@@ -58,12 +58,12 @@
58
58
  "format:check": "prettier --check .",
59
59
  "fix": "npm run format && npm run lint:fix",
60
60
  "validate": "npm run format && npm run lint && npm run typecheck && npm run test && npm run test:e2e && npm run build",
61
- "test:e2e": "NODE9_TESTING=1 bash scripts/e2e.sh",
61
+ "test:e2e": "cross-env NODE9_TESTING=1 bash scripts/e2e.sh",
62
+ "test": "cross-env NODE9_TESTING=1 vitest --run",
63
+ "test:coverage": "cross-env NODE9_TESTING=1 vitest --run --coverage",
64
+ "test:watch": "cross-env NODE9_TESTING=1 vitest",
62
65
  "preuninstall": "node9 uninstall || echo 'node9 uninstall failed — remove hooks manually from ~/.claude/settings.json'",
63
66
  "prepublishOnly": "npm run validate",
64
- "test": "vitest --run",
65
- "test:coverage": "vitest --run --coverage",
66
- "test:watch": "vitest",
67
67
  "test:ui": "vitest --ui",
68
68
  "dev:tail": "node -e \"try{const d=JSON.parse(require('fs').readFileSync(require('os').homedir()+'/.node9/daemon.pid','utf8'));const pid=d.pid;if(Number.isInteger(pid)&&pid>0&&pid<4194304)process.kill(pid)}catch(e){if(e.code!=='ESRCH'&&e.code!=='ENOENT')process.stderr.write(e.message+'\\n')}\" && npm run build && node dist/cli.js tail"
69
69
  },
@@ -88,6 +88,7 @@
88
88
  "@types/node": "^25.3.1",
89
89
  "@types/picomatch": "^4.0.2",
90
90
  "@vitest/coverage-v8": "4.1.2",
91
+ "cross-env": "^10.1.0",
91
92
  "prettier": "^3.4.2",
92
93
  "semantic-release": "^25.0.3",
93
94
  "tsup": "^8.5.1",