@node9/proxy 1.19.3 → 1.20.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/README.md +53 -98
- package/dist/cli.js +1740 -923
- package/dist/cli.mjs +1740 -922
- package/dist/dashboard.mjs +6809 -0
- package/dist/index.js +142 -16
- package/dist/index.mjs +142 -16
- package/package.json +5 -1
package/dist/index.js
CHANGED
|
@@ -118,12 +118,14 @@ function appendHookDebug(toolName, args, meta, auditHashArgsEnabled) {
|
|
|
118
118
|
function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashArgsEnabled) {
|
|
119
119
|
const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
|
|
120
120
|
const testRun = isTestCall(toolName, args) || process.env.NODE9_TESTING === "1" ? { testRun: true } : {};
|
|
121
|
+
const ruleNameField = meta?.ruleName ? { ruleName: meta.ruleName } : {};
|
|
121
122
|
appendToLog(LOCAL_AUDIT_LOG, {
|
|
122
123
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
123
124
|
tool: toolName,
|
|
124
125
|
...argsField,
|
|
125
126
|
decision,
|
|
126
127
|
checkedBy,
|
|
128
|
+
...ruleNameField,
|
|
127
129
|
...testRun,
|
|
128
130
|
agent: meta?.agent,
|
|
129
131
|
mcpServer: meta?.mcpServer,
|
|
@@ -1089,15 +1091,50 @@ var SENSITIVE_PATH_RULES = [
|
|
|
1089
1091
|
match: (p) => /(^|[\\/])\.aws[\\/]/i.test(p)
|
|
1090
1092
|
},
|
|
1091
1093
|
{
|
|
1094
|
+
// Mirrors the JSON shield's `.env` pattern (project-jail.json's
|
|
1095
|
+
// review-read-env-any-tool) so the AST FS-op path catches the
|
|
1096
|
+
// same set the regex shield does — including Next.js / Vite's
|
|
1097
|
+
// `.env.<env>.local` double-suffix overrides which are commonly
|
|
1098
|
+
// gitignored AND commonly contain real secrets.
|
|
1099
|
+
//
|
|
1100
|
+
// Intentional non-matches (dev fixtures): .env.example, .env.sample,
|
|
1101
|
+
// .env.template, .env.test, .envrc. See shields.test.ts:983-995
|
|
1102
|
+
// for the canonical test-asserted contract.
|
|
1092
1103
|
rule: "shield:project-jail:block-read-env",
|
|
1093
1104
|
reason: "Reading .env files is blocked by project-jail shield",
|
|
1094
|
-
match: (p) => /(?:^|[\\/])\.env(?:\.local
|
|
1105
|
+
match: (p) => /(?:^|[\\/])\.env(?:\.(?:local|production|staging|development|production\.local|staging\.local|development\.local))?$/i.test(
|
|
1106
|
+
p
|
|
1107
|
+
)
|
|
1095
1108
|
},
|
|
1096
1109
|
{
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1110
|
+
// verdict: 'review' (not 'block') is a deliberate design choice
|
|
1111
|
+
// documented in commit 29327a8. SSH keys and AWS credentials are
|
|
1112
|
+
// cryptographic material with no legitimate read use-case for
|
|
1113
|
+
// an AI agent → hard `block`. But .netrc / .npmrc / .docker /
|
|
1114
|
+
// .kube / gcloud are CONFIG files that hold tokens AND have
|
|
1115
|
+
// legitimate diagnostic reads ("which registry am I configured
|
|
1116
|
+
// for", "what cluster am I on"). Hard-blocking those creates
|
|
1117
|
+
// friction without much safety win because the review gate
|
|
1118
|
+
// still catches genuine exfiltration attempts.
|
|
1119
|
+
//
|
|
1120
|
+
// The review gate FAILS CLOSED on timeout (daemon.approvalTimeoutMs
|
|
1121
|
+
// returns a deny verdict via the orchestrator's timeout branch),
|
|
1122
|
+
// so a stuck or unattended approval does NOT silently grant
|
|
1123
|
+
// credential access. If the threat model demands strict block,
|
|
1124
|
+
// a future per-shield strict-mode toggle is the right fix —
|
|
1125
|
+
// not a regex-level upgrade here.
|
|
1126
|
+
rule: "shield:project-jail:review-read-credentials",
|
|
1127
|
+
reason: "Reading credential files requires approval (project-jail shield)",
|
|
1128
|
+
verdict: "review",
|
|
1129
|
+
match: (p) => (
|
|
1130
|
+
// .kube/config holds Kubernetes cluster credentials and was
|
|
1131
|
+
// flagged as missing by the node9-pr-agent review (the comment
|
|
1132
|
+
// above mentioned .kube but the regex didn't include it — a
|
|
1133
|
+
// textbook code-comment vs code drift). The JSON shield's
|
|
1134
|
+
// review-read-credentials-any-tool already had it. Now aligned.
|
|
1135
|
+
/(?:credentials\.json|\.netrc|\.npmrc|\.docker[\\/]config\.json|gcloud[\\/]credentials|\.kube[\\/]config)$/i.test(
|
|
1136
|
+
p
|
|
1137
|
+
)
|
|
1101
1138
|
)
|
|
1102
1139
|
}
|
|
1103
1140
|
];
|
|
@@ -1116,7 +1153,7 @@ var AST_FS_REGEX_RULES = /* @__PURE__ */ new Set([
|
|
|
1116
1153
|
"shield:project-jail:block-read-ssh",
|
|
1117
1154
|
"shield:project-jail:block-read-aws",
|
|
1118
1155
|
"shield:project-jail:block-read-env",
|
|
1119
|
-
"shield:project-jail:
|
|
1156
|
+
"shield:project-jail:review-read-credentials"
|
|
1120
1157
|
]);
|
|
1121
1158
|
function isProtectedHomePath(rawPath) {
|
|
1122
1159
|
let p = rawPath.replace(/^\$HOME[\\/]?|^\$\{HOME\}[\\/]?/, "~/");
|
|
@@ -1228,7 +1265,12 @@ function analyzeFsOperationImpl(command) {
|
|
|
1228
1265
|
for (const p of paths) {
|
|
1229
1266
|
for (const sp of SENSITIVE_PATH_RULES) {
|
|
1230
1267
|
if (sp.match(p)) {
|
|
1231
|
-
result = {
|
|
1268
|
+
result = {
|
|
1269
|
+
ruleName: sp.rule,
|
|
1270
|
+
verdict: sp.verdict ?? "block",
|
|
1271
|
+
reason: sp.reason,
|
|
1272
|
+
path: p
|
|
1273
|
+
};
|
|
1232
1274
|
return false;
|
|
1233
1275
|
}
|
|
1234
1276
|
}
|
|
@@ -2512,7 +2554,7 @@ var project_jail_default = {
|
|
|
2512
2554
|
{
|
|
2513
2555
|
field: "command",
|
|
2514
2556
|
op: "matches",
|
|
2515
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s
|
|
2557
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.ssh[\\/\\\\]",
|
|
2516
2558
|
flags: "i"
|
|
2517
2559
|
}
|
|
2518
2560
|
],
|
|
@@ -2526,7 +2568,7 @@ var project_jail_default = {
|
|
|
2526
2568
|
{
|
|
2527
2569
|
field: "command",
|
|
2528
2570
|
op: "matches",
|
|
2529
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s
|
|
2571
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.aws[\\/\\\\]",
|
|
2530
2572
|
flags: "i"
|
|
2531
2573
|
}
|
|
2532
2574
|
],
|
|
@@ -2548,7 +2590,7 @@ var project_jail_default = {
|
|
|
2548
2590
|
reason: "Reading .env files is blocked by project-jail shield"
|
|
2549
2591
|
},
|
|
2550
2592
|
{
|
|
2551
|
-
name: "shield:project-jail:
|
|
2593
|
+
name: "shield:project-jail:review-read-credentials",
|
|
2552
2594
|
tool: "bash",
|
|
2553
2595
|
conditions: [
|
|
2554
2596
|
{
|
|
@@ -2558,8 +2600,64 @@ var project_jail_default = {
|
|
|
2558
2600
|
flags: "i"
|
|
2559
2601
|
}
|
|
2560
2602
|
],
|
|
2603
|
+
verdict: "review",
|
|
2604
|
+
reason: "Reading credential files requires approval (project-jail shield)"
|
|
2605
|
+
},
|
|
2606
|
+
{
|
|
2607
|
+
name: "shield:project-jail:block-read-ssh-any-tool",
|
|
2608
|
+
tool: "*",
|
|
2609
|
+
conditions: [
|
|
2610
|
+
{
|
|
2611
|
+
field: "file_path",
|
|
2612
|
+
op: "matches",
|
|
2613
|
+
value: "(^|[\\/\\\\])\\.ssh[\\/\\\\]",
|
|
2614
|
+
flags: "i"
|
|
2615
|
+
}
|
|
2616
|
+
],
|
|
2561
2617
|
verdict: "block",
|
|
2562
|
-
reason: "Reading
|
|
2618
|
+
reason: "Reading SSH private keys is blocked by project-jail shield"
|
|
2619
|
+
},
|
|
2620
|
+
{
|
|
2621
|
+
name: "shield:project-jail:block-read-aws-any-tool",
|
|
2622
|
+
tool: "*",
|
|
2623
|
+
conditions: [
|
|
2624
|
+
{
|
|
2625
|
+
field: "file_path",
|
|
2626
|
+
op: "matches",
|
|
2627
|
+
value: "(^|[\\/\\\\])\\.aws[\\/\\\\]",
|
|
2628
|
+
flags: "i"
|
|
2629
|
+
}
|
|
2630
|
+
],
|
|
2631
|
+
verdict: "block",
|
|
2632
|
+
reason: "Reading AWS credentials is blocked by project-jail shield"
|
|
2633
|
+
},
|
|
2634
|
+
{
|
|
2635
|
+
name: "shield:project-jail:review-read-env-any-tool",
|
|
2636
|
+
tool: "*",
|
|
2637
|
+
conditions: [
|
|
2638
|
+
{
|
|
2639
|
+
field: "file_path",
|
|
2640
|
+
op: "matches",
|
|
2641
|
+
value: "(^|[\\/\\\\])\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?$",
|
|
2642
|
+
flags: "i"
|
|
2643
|
+
}
|
|
2644
|
+
],
|
|
2645
|
+
verdict: "review",
|
|
2646
|
+
reason: "Reading .env files requires approval (project-jail shield)"
|
|
2647
|
+
},
|
|
2648
|
+
{
|
|
2649
|
+
name: "shield:project-jail:review-read-credentials-any-tool",
|
|
2650
|
+
tool: "*",
|
|
2651
|
+
conditions: [
|
|
2652
|
+
{
|
|
2653
|
+
field: "file_path",
|
|
2654
|
+
op: "matches",
|
|
2655
|
+
value: ".*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials|\\.kube[\\/\\\\]config)",
|
|
2656
|
+
flags: "i"
|
|
2657
|
+
}
|
|
2658
|
+
],
|
|
2659
|
+
verdict: "review",
|
|
2660
|
+
reason: "Reading credential files requires approval (project-jail shield)"
|
|
2563
2661
|
}
|
|
2564
2662
|
],
|
|
2565
2663
|
dangerousWords: []
|
|
@@ -3731,7 +3829,7 @@ async function waitForDaemonDecision(id, signal) {
|
|
|
3731
3829
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
3732
3830
|
}
|
|
3733
3831
|
}
|
|
3734
|
-
async function notifyDaemonViewer(toolName, args, meta, riskMetadata) {
|
|
3832
|
+
async function notifyDaemonViewer(toolName, args, meta, riskMetadata, activityId, socketActivitySent) {
|
|
3735
3833
|
const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
3736
3834
|
const res = await fetch(`${base}/check`, {
|
|
3737
3835
|
method: "POST",
|
|
@@ -3742,7 +3840,12 @@ async function notifyDaemonViewer(toolName, args, meta, riskMetadata) {
|
|
|
3742
3840
|
slackDelegated: true,
|
|
3743
3841
|
agent: meta?.agent,
|
|
3744
3842
|
mcpServer: meta?.mcpServer,
|
|
3745
|
-
...riskMetadata && { riskMetadata }
|
|
3843
|
+
...riskMetadata && { riskMetadata },
|
|
3844
|
+
// fromCLI=true tells the daemon the CLI already sent the activity
|
|
3845
|
+
// event via socket. Same contract as registerDaemonEntry — without
|
|
3846
|
+
// it the daemon double-emits 'activity' for cloud-enforced flows.
|
|
3847
|
+
fromCLI: socketActivitySent !== false,
|
|
3848
|
+
activityId
|
|
3746
3849
|
}),
|
|
3747
3850
|
signal: AbortSignal.timeout(3e3)
|
|
3748
3851
|
});
|
|
@@ -4659,7 +4762,10 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
4659
4762
|
args,
|
|
4660
4763
|
"deny",
|
|
4661
4764
|
"smart-rule-block-override",
|
|
4662
|
-
|
|
4765
|
+
// Same rationale as the smart-rule-block path above —
|
|
4766
|
+
// pass the specific rule name so [2] SHIELDS can
|
|
4767
|
+
// attribute this override-block to its owning shield.
|
|
4768
|
+
{ ...meta, ruleName: policyResult.ruleName },
|
|
4663
4769
|
hashAuditArgs
|
|
4664
4770
|
);
|
|
4665
4771
|
if (approvers.cloud && creds?.apiKey)
|
|
@@ -4689,7 +4795,20 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
4689
4795
|
}
|
|
4690
4796
|
} else {
|
|
4691
4797
|
if (!isManual)
|
|
4692
|
-
appendLocalAudit(
|
|
4798
|
+
appendLocalAudit(
|
|
4799
|
+
toolName,
|
|
4800
|
+
args,
|
|
4801
|
+
"deny",
|
|
4802
|
+
"smart-rule-block",
|
|
4803
|
+
// Include policyResult.ruleName so the [2] Report SHIELDS
|
|
4804
|
+
// panel can attribute this block to its specific shield
|
|
4805
|
+
// (e.g. `shield:project-jail:block-read-ssh`) via the
|
|
4806
|
+
// rule→shield map. checkedBy stays as the generic
|
|
4807
|
+
// `smart-rule-block` for backward compat with existing
|
|
4808
|
+
// log readers.
|
|
4809
|
+
{ ...meta, ruleName: policyResult.ruleName },
|
|
4810
|
+
hashAuditArgs
|
|
4811
|
+
);
|
|
4693
4812
|
if (approvers.cloud && creds?.apiKey)
|
|
4694
4813
|
auditLocalAllow(toolName, args, "smart-rule-block", creds, meta, void 0, false, {
|
|
4695
4814
|
ruleName: policyResult.ruleName,
|
|
@@ -4837,7 +4956,14 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
4837
4956
|
let daemonAllowCount = 1;
|
|
4838
4957
|
if (approvers.terminal && isDaemonRunning() && !options?.calledFromDaemon) {
|
|
4839
4958
|
if (cloudEnforced && cloudRequestId) {
|
|
4840
|
-
const viewer = await notifyDaemonViewer(
|
|
4959
|
+
const viewer = await notifyDaemonViewer(
|
|
4960
|
+
toolName,
|
|
4961
|
+
args,
|
|
4962
|
+
meta,
|
|
4963
|
+
riskMetadata,
|
|
4964
|
+
options?.activityId,
|
|
4965
|
+
options?.socketActivitySent
|
|
4966
|
+
).catch(() => null);
|
|
4841
4967
|
viewerId = viewer?.id ?? null;
|
|
4842
4968
|
daemonEntryId = viewerId;
|
|
4843
4969
|
if (viewer) daemonAllowCount = viewer.allowCount;
|
package/dist/index.mjs
CHANGED
|
@@ -98,12 +98,14 @@ function appendHookDebug(toolName, args, meta, auditHashArgsEnabled) {
|
|
|
98
98
|
function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashArgsEnabled) {
|
|
99
99
|
const argsField = auditHashArgsEnabled ? { argsHash: hashArgs(args) } : { args: args ? JSON.parse(redactSecrets(JSON.stringify(args))) : {} };
|
|
100
100
|
const testRun = isTestCall(toolName, args) || process.env.NODE9_TESTING === "1" ? { testRun: true } : {};
|
|
101
|
+
const ruleNameField = meta?.ruleName ? { ruleName: meta.ruleName } : {};
|
|
101
102
|
appendToLog(LOCAL_AUDIT_LOG, {
|
|
102
103
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
103
104
|
tool: toolName,
|
|
104
105
|
...argsField,
|
|
105
106
|
decision,
|
|
106
107
|
checkedBy,
|
|
108
|
+
...ruleNameField,
|
|
107
109
|
...testRun,
|
|
108
110
|
agent: meta?.agent,
|
|
109
111
|
mcpServer: meta?.mcpServer,
|
|
@@ -1059,15 +1061,50 @@ var SENSITIVE_PATH_RULES = [
|
|
|
1059
1061
|
match: (p) => /(^|[\\/])\.aws[\\/]/i.test(p)
|
|
1060
1062
|
},
|
|
1061
1063
|
{
|
|
1064
|
+
// Mirrors the JSON shield's `.env` pattern (project-jail.json's
|
|
1065
|
+
// review-read-env-any-tool) so the AST FS-op path catches the
|
|
1066
|
+
// same set the regex shield does — including Next.js / Vite's
|
|
1067
|
+
// `.env.<env>.local` double-suffix overrides which are commonly
|
|
1068
|
+
// gitignored AND commonly contain real secrets.
|
|
1069
|
+
//
|
|
1070
|
+
// Intentional non-matches (dev fixtures): .env.example, .env.sample,
|
|
1071
|
+
// .env.template, .env.test, .envrc. See shields.test.ts:983-995
|
|
1072
|
+
// for the canonical test-asserted contract.
|
|
1062
1073
|
rule: "shield:project-jail:block-read-env",
|
|
1063
1074
|
reason: "Reading .env files is blocked by project-jail shield",
|
|
1064
|
-
match: (p) => /(?:^|[\\/])\.env(?:\.local
|
|
1075
|
+
match: (p) => /(?:^|[\\/])\.env(?:\.(?:local|production|staging|development|production\.local|staging\.local|development\.local))?$/i.test(
|
|
1076
|
+
p
|
|
1077
|
+
)
|
|
1065
1078
|
},
|
|
1066
1079
|
{
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1080
|
+
// verdict: 'review' (not 'block') is a deliberate design choice
|
|
1081
|
+
// documented in commit 29327a8. SSH keys and AWS credentials are
|
|
1082
|
+
// cryptographic material with no legitimate read use-case for
|
|
1083
|
+
// an AI agent → hard `block`. But .netrc / .npmrc / .docker /
|
|
1084
|
+
// .kube / gcloud are CONFIG files that hold tokens AND have
|
|
1085
|
+
// legitimate diagnostic reads ("which registry am I configured
|
|
1086
|
+
// for", "what cluster am I on"). Hard-blocking those creates
|
|
1087
|
+
// friction without much safety win because the review gate
|
|
1088
|
+
// still catches genuine exfiltration attempts.
|
|
1089
|
+
//
|
|
1090
|
+
// The review gate FAILS CLOSED on timeout (daemon.approvalTimeoutMs
|
|
1091
|
+
// returns a deny verdict via the orchestrator's timeout branch),
|
|
1092
|
+
// so a stuck or unattended approval does NOT silently grant
|
|
1093
|
+
// credential access. If the threat model demands strict block,
|
|
1094
|
+
// a future per-shield strict-mode toggle is the right fix —
|
|
1095
|
+
// not a regex-level upgrade here.
|
|
1096
|
+
rule: "shield:project-jail:review-read-credentials",
|
|
1097
|
+
reason: "Reading credential files requires approval (project-jail shield)",
|
|
1098
|
+
verdict: "review",
|
|
1099
|
+
match: (p) => (
|
|
1100
|
+
// .kube/config holds Kubernetes cluster credentials and was
|
|
1101
|
+
// flagged as missing by the node9-pr-agent review (the comment
|
|
1102
|
+
// above mentioned .kube but the regex didn't include it — a
|
|
1103
|
+
// textbook code-comment vs code drift). The JSON shield's
|
|
1104
|
+
// review-read-credentials-any-tool already had it. Now aligned.
|
|
1105
|
+
/(?:credentials\.json|\.netrc|\.npmrc|\.docker[\\/]config\.json|gcloud[\\/]credentials|\.kube[\\/]config)$/i.test(
|
|
1106
|
+
p
|
|
1107
|
+
)
|
|
1071
1108
|
)
|
|
1072
1109
|
}
|
|
1073
1110
|
];
|
|
@@ -1086,7 +1123,7 @@ var AST_FS_REGEX_RULES = /* @__PURE__ */ new Set([
|
|
|
1086
1123
|
"shield:project-jail:block-read-ssh",
|
|
1087
1124
|
"shield:project-jail:block-read-aws",
|
|
1088
1125
|
"shield:project-jail:block-read-env",
|
|
1089
|
-
"shield:project-jail:
|
|
1126
|
+
"shield:project-jail:review-read-credentials"
|
|
1090
1127
|
]);
|
|
1091
1128
|
function isProtectedHomePath(rawPath) {
|
|
1092
1129
|
let p = rawPath.replace(/^\$HOME[\\/]?|^\$\{HOME\}[\\/]?/, "~/");
|
|
@@ -1198,7 +1235,12 @@ function analyzeFsOperationImpl(command) {
|
|
|
1198
1235
|
for (const p of paths) {
|
|
1199
1236
|
for (const sp of SENSITIVE_PATH_RULES) {
|
|
1200
1237
|
if (sp.match(p)) {
|
|
1201
|
-
result = {
|
|
1238
|
+
result = {
|
|
1239
|
+
ruleName: sp.rule,
|
|
1240
|
+
verdict: sp.verdict ?? "block",
|
|
1241
|
+
reason: sp.reason,
|
|
1242
|
+
path: p
|
|
1243
|
+
};
|
|
1202
1244
|
return false;
|
|
1203
1245
|
}
|
|
1204
1246
|
}
|
|
@@ -2482,7 +2524,7 @@ var project_jail_default = {
|
|
|
2482
2524
|
{
|
|
2483
2525
|
field: "command",
|
|
2484
2526
|
op: "matches",
|
|
2485
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s
|
|
2527
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.ssh[\\/\\\\]",
|
|
2486
2528
|
flags: "i"
|
|
2487
2529
|
}
|
|
2488
2530
|
],
|
|
@@ -2496,7 +2538,7 @@ var project_jail_default = {
|
|
|
2496
2538
|
{
|
|
2497
2539
|
field: "command",
|
|
2498
2540
|
op: "matches",
|
|
2499
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s
|
|
2541
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*?\\.aws[\\/\\\\]",
|
|
2500
2542
|
flags: "i"
|
|
2501
2543
|
}
|
|
2502
2544
|
],
|
|
@@ -2518,7 +2560,7 @@ var project_jail_default = {
|
|
|
2518
2560
|
reason: "Reading .env files is blocked by project-jail shield"
|
|
2519
2561
|
},
|
|
2520
2562
|
{
|
|
2521
|
-
name: "shield:project-jail:
|
|
2563
|
+
name: "shield:project-jail:review-read-credentials",
|
|
2522
2564
|
tool: "bash",
|
|
2523
2565
|
conditions: [
|
|
2524
2566
|
{
|
|
@@ -2528,8 +2570,64 @@ var project_jail_default = {
|
|
|
2528
2570
|
flags: "i"
|
|
2529
2571
|
}
|
|
2530
2572
|
],
|
|
2573
|
+
verdict: "review",
|
|
2574
|
+
reason: "Reading credential files requires approval (project-jail shield)"
|
|
2575
|
+
},
|
|
2576
|
+
{
|
|
2577
|
+
name: "shield:project-jail:block-read-ssh-any-tool",
|
|
2578
|
+
tool: "*",
|
|
2579
|
+
conditions: [
|
|
2580
|
+
{
|
|
2581
|
+
field: "file_path",
|
|
2582
|
+
op: "matches",
|
|
2583
|
+
value: "(^|[\\/\\\\])\\.ssh[\\/\\\\]",
|
|
2584
|
+
flags: "i"
|
|
2585
|
+
}
|
|
2586
|
+
],
|
|
2531
2587
|
verdict: "block",
|
|
2532
|
-
reason: "Reading
|
|
2588
|
+
reason: "Reading SSH private keys is blocked by project-jail shield"
|
|
2589
|
+
},
|
|
2590
|
+
{
|
|
2591
|
+
name: "shield:project-jail:block-read-aws-any-tool",
|
|
2592
|
+
tool: "*",
|
|
2593
|
+
conditions: [
|
|
2594
|
+
{
|
|
2595
|
+
field: "file_path",
|
|
2596
|
+
op: "matches",
|
|
2597
|
+
value: "(^|[\\/\\\\])\\.aws[\\/\\\\]",
|
|
2598
|
+
flags: "i"
|
|
2599
|
+
}
|
|
2600
|
+
],
|
|
2601
|
+
verdict: "block",
|
|
2602
|
+
reason: "Reading AWS credentials is blocked by project-jail shield"
|
|
2603
|
+
},
|
|
2604
|
+
{
|
|
2605
|
+
name: "shield:project-jail:review-read-env-any-tool",
|
|
2606
|
+
tool: "*",
|
|
2607
|
+
conditions: [
|
|
2608
|
+
{
|
|
2609
|
+
field: "file_path",
|
|
2610
|
+
op: "matches",
|
|
2611
|
+
value: "(^|[\\/\\\\])\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?$",
|
|
2612
|
+
flags: "i"
|
|
2613
|
+
}
|
|
2614
|
+
],
|
|
2615
|
+
verdict: "review",
|
|
2616
|
+
reason: "Reading .env files requires approval (project-jail shield)"
|
|
2617
|
+
},
|
|
2618
|
+
{
|
|
2619
|
+
name: "shield:project-jail:review-read-credentials-any-tool",
|
|
2620
|
+
tool: "*",
|
|
2621
|
+
conditions: [
|
|
2622
|
+
{
|
|
2623
|
+
field: "file_path",
|
|
2624
|
+
op: "matches",
|
|
2625
|
+
value: ".*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials|\\.kube[\\/\\\\]config)",
|
|
2626
|
+
flags: "i"
|
|
2627
|
+
}
|
|
2628
|
+
],
|
|
2629
|
+
verdict: "review",
|
|
2630
|
+
reason: "Reading credential files requires approval (project-jail shield)"
|
|
2533
2631
|
}
|
|
2534
2632
|
],
|
|
2535
2633
|
dangerousWords: []
|
|
@@ -3701,7 +3799,7 @@ async function waitForDaemonDecision(id, signal) {
|
|
|
3701
3799
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
3702
3800
|
}
|
|
3703
3801
|
}
|
|
3704
|
-
async function notifyDaemonViewer(toolName, args, meta, riskMetadata) {
|
|
3802
|
+
async function notifyDaemonViewer(toolName, args, meta, riskMetadata, activityId, socketActivitySent) {
|
|
3705
3803
|
const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
|
|
3706
3804
|
const res = await fetch(`${base}/check`, {
|
|
3707
3805
|
method: "POST",
|
|
@@ -3712,7 +3810,12 @@ async function notifyDaemonViewer(toolName, args, meta, riskMetadata) {
|
|
|
3712
3810
|
slackDelegated: true,
|
|
3713
3811
|
agent: meta?.agent,
|
|
3714
3812
|
mcpServer: meta?.mcpServer,
|
|
3715
|
-
...riskMetadata && { riskMetadata }
|
|
3813
|
+
...riskMetadata && { riskMetadata },
|
|
3814
|
+
// fromCLI=true tells the daemon the CLI already sent the activity
|
|
3815
|
+
// event via socket. Same contract as registerDaemonEntry — without
|
|
3816
|
+
// it the daemon double-emits 'activity' for cloud-enforced flows.
|
|
3817
|
+
fromCLI: socketActivitySent !== false,
|
|
3818
|
+
activityId
|
|
3716
3819
|
}),
|
|
3717
3820
|
signal: AbortSignal.timeout(3e3)
|
|
3718
3821
|
});
|
|
@@ -4629,7 +4732,10 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
4629
4732
|
args,
|
|
4630
4733
|
"deny",
|
|
4631
4734
|
"smart-rule-block-override",
|
|
4632
|
-
|
|
4735
|
+
// Same rationale as the smart-rule-block path above —
|
|
4736
|
+
// pass the specific rule name so [2] SHIELDS can
|
|
4737
|
+
// attribute this override-block to its owning shield.
|
|
4738
|
+
{ ...meta, ruleName: policyResult.ruleName },
|
|
4633
4739
|
hashAuditArgs
|
|
4634
4740
|
);
|
|
4635
4741
|
if (approvers.cloud && creds?.apiKey)
|
|
@@ -4659,7 +4765,20 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
4659
4765
|
}
|
|
4660
4766
|
} else {
|
|
4661
4767
|
if (!isManual)
|
|
4662
|
-
appendLocalAudit(
|
|
4768
|
+
appendLocalAudit(
|
|
4769
|
+
toolName,
|
|
4770
|
+
args,
|
|
4771
|
+
"deny",
|
|
4772
|
+
"smart-rule-block",
|
|
4773
|
+
// Include policyResult.ruleName so the [2] Report SHIELDS
|
|
4774
|
+
// panel can attribute this block to its specific shield
|
|
4775
|
+
// (e.g. `shield:project-jail:block-read-ssh`) via the
|
|
4776
|
+
// rule→shield map. checkedBy stays as the generic
|
|
4777
|
+
// `smart-rule-block` for backward compat with existing
|
|
4778
|
+
// log readers.
|
|
4779
|
+
{ ...meta, ruleName: policyResult.ruleName },
|
|
4780
|
+
hashAuditArgs
|
|
4781
|
+
);
|
|
4663
4782
|
if (approvers.cloud && creds?.apiKey)
|
|
4664
4783
|
auditLocalAllow(toolName, args, "smart-rule-block", creds, meta, void 0, false, {
|
|
4665
4784
|
ruleName: policyResult.ruleName,
|
|
@@ -4807,7 +4926,14 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
|
|
|
4807
4926
|
let daemonAllowCount = 1;
|
|
4808
4927
|
if (approvers.terminal && isDaemonRunning() && !options?.calledFromDaemon) {
|
|
4809
4928
|
if (cloudEnforced && cloudRequestId) {
|
|
4810
|
-
const viewer = await notifyDaemonViewer(
|
|
4929
|
+
const viewer = await notifyDaemonViewer(
|
|
4930
|
+
toolName,
|
|
4931
|
+
args,
|
|
4932
|
+
meta,
|
|
4933
|
+
riskMetadata,
|
|
4934
|
+
options?.activityId,
|
|
4935
|
+
options?.socketActivitySent
|
|
4936
|
+
).catch(() => null);
|
|
4811
4937
|
viewerId = viewer?.id ?? null;
|
|
4812
4938
|
daemonEntryId = viewerId;
|
|
4813
4939
|
if (viewer) daemonAllowCount = viewer.allowCount;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node9/proxy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.20.0",
|
|
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",
|
|
@@ -77,8 +77,10 @@
|
|
|
77
77
|
"chalk": "^4.1.2",
|
|
78
78
|
"commander": "^14.0.3",
|
|
79
79
|
"execa": "^9.6.1",
|
|
80
|
+
"ink": "^7.0.2",
|
|
80
81
|
"mvdan-sh": "^0.10.1",
|
|
81
82
|
"picomatch": "^4.0.3",
|
|
83
|
+
"react": "^19.2.6",
|
|
82
84
|
"safe-regex2": "^5.1.0",
|
|
83
85
|
"smol-toml": "^1.6.1",
|
|
84
86
|
"zod": "^3.25.76"
|
|
@@ -96,8 +98,10 @@
|
|
|
96
98
|
"@semantic-release/release-notes-generator": "^14.1.0",
|
|
97
99
|
"@types/node": "^25.3.1",
|
|
98
100
|
"@types/picomatch": "^4.0.2",
|
|
101
|
+
"@types/react": "^19.2.14",
|
|
99
102
|
"@vitest/coverage-v8": "4.1.2",
|
|
100
103
|
"cross-env": "^10.1.0",
|
|
104
|
+
"ink-testing-library": "^4.0.0",
|
|
101
105
|
"prettier": "^3.4.2",
|
|
102
106
|
"semantic-release": "^25.0.3",
|
|
103
107
|
"tsup": "^8.5.1",
|