@node9/proxy 1.5.3 → 1.5.5
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/README.md +56 -14
- package/dist/cli.js +665 -357
- package/dist/cli.mjs +660 -352
- package/dist/index.js +53 -10
- package/dist/index.mjs +53 -10
- package/package.json +6 -5
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
|
|
270
|
-
return ` \u2022 ${
|
|
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
|
-
|
|
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,
|
|
1626
|
+
function getNestedValue(obj, path14) {
|
|
1625
1627
|
if (!obj || typeof obj !== "object") return null;
|
|
1626
|
-
return
|
|
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
|
|
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
|
|
240
|
-
return ` \u2022 ${
|
|
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
|
-
|
|
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,
|
|
1596
|
+
function getNestedValue(obj, path14) {
|
|
1595
1597
|
if (!obj || typeof obj !== "object") return null;
|
|
1596
|
-
return
|
|
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
|
|
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
|
+
"version": "1.5.5",
|
|
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",
|