@node9/proxy 2.32.0 → 2.33.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/cli.js +780 -452
- package/dist/cli.mjs +762 -434
- package/dist/dashboard.mjs +369 -29
- package/dist/index.js +583 -325
- package/dist/index.mjs +583 -325
- package/dist/scan-ink.mjs +202 -190
- package/package.json +1 -1
package/dist/scan-ink.mjs
CHANGED
|
@@ -42,195 +42,6 @@ function formatCost(usd) {
|
|
|
42
42
|
// src/config/keyed-guard.ts
|
|
43
43
|
import chalk from "chalk";
|
|
44
44
|
|
|
45
|
-
// src/config-schema.ts
|
|
46
|
-
import { z } from "zod";
|
|
47
|
-
var noNewlines = z.string().refine((s) => !s.includes("\n") && !s.includes("\r"), {
|
|
48
|
-
message: "Value must not contain literal newline characters (use \\n instead)"
|
|
49
|
-
});
|
|
50
|
-
var SmartConditionSchema = z.object({
|
|
51
|
-
field: z.string().min(1, "Condition field must not be empty"),
|
|
52
|
-
op: z.enum(
|
|
53
|
-
[
|
|
54
|
-
"matches",
|
|
55
|
-
"notMatches",
|
|
56
|
-
"contains",
|
|
57
|
-
"notContains",
|
|
58
|
-
"exists",
|
|
59
|
-
"notExists",
|
|
60
|
-
"matchesGlob",
|
|
61
|
-
"notMatchesGlob"
|
|
62
|
-
],
|
|
63
|
-
{
|
|
64
|
-
// zod 4 replaced errorMap with `error`. The wording is kept verbatim:
|
|
65
|
-
// it is what a user sees when their config is rejected.
|
|
66
|
-
error: () => "op must be one of: matches, notMatches, contains, notContains, exists, notExists, matchesGlob, notMatchesGlob"
|
|
67
|
-
}
|
|
68
|
-
),
|
|
69
|
-
value: z.string().optional(),
|
|
70
|
-
flags: z.string().optional()
|
|
71
|
-
}).refine(
|
|
72
|
-
(c) => {
|
|
73
|
-
if (c.op === "matchesGlob" || c.op === "notMatchesGlob") return c.value !== void 0;
|
|
74
|
-
return true;
|
|
75
|
-
},
|
|
76
|
-
{ message: "matchesGlob and notMatchesGlob conditions require a value field" }
|
|
77
|
-
);
|
|
78
|
-
var SmartRuleSchema = z.object({
|
|
79
|
-
name: z.string().optional(),
|
|
80
|
-
tool: z.string().min(1, "Smart rule tool must not be empty"),
|
|
81
|
-
conditions: z.array(SmartConditionSchema).min(1, "Smart rule must have at least one condition"),
|
|
82
|
-
conditionMode: z.enum(["all", "any"]).optional(),
|
|
83
|
-
verdict: z.enum(["allow", "review", "block"], {
|
|
84
|
-
error: () => "verdict must be one of: allow, review, block"
|
|
85
|
-
}),
|
|
86
|
-
reason: z.string().optional(),
|
|
87
|
-
description: z.string().optional(),
|
|
88
|
-
// Unknown predicate names are filtered out rather than failing the whole rule.
|
|
89
|
-
// Failing the whole z.array() would cause sanitizeConfig to drop the entire
|
|
90
|
-
// `policy` top-level key, silently disabling ALL smart rules in the config.
|
|
91
|
-
dependsOnState: z.array(z.string()).transform(
|
|
92
|
-
(arr) => arr.filter(
|
|
93
|
-
(p) => p === "no_test_passed_since_last_edit"
|
|
94
|
-
)
|
|
95
|
-
).optional(),
|
|
96
|
-
recoveryCommand: z.string().optional()
|
|
97
|
-
});
|
|
98
|
-
var ConfigFileSchema = z.object({
|
|
99
|
-
version: z.string().optional(),
|
|
100
|
-
settings: z.object({
|
|
101
|
-
mode: z.enum(["standard", "strict", "audit", "observe"]).optional(),
|
|
102
|
-
autoStartDaemon: z.boolean().optional(),
|
|
103
|
-
// enableUndo / policy.snapshot: removed with the undo feature. The
|
|
104
|
-
// schema strips undeclared keys silently (plain zod object — no
|
|
105
|
-
// .strict(), no .passthrough()), so a config still carrying them stays
|
|
106
|
-
// valid and simply loses them. Keeping the fields would instead make
|
|
107
|
-
// `node9_config_get` advertise a removed feature as a live knob.
|
|
108
|
-
enableHookLogDebug: z.boolean().optional(),
|
|
109
|
-
approvalTimeoutMs: z.number().nonnegative().optional(),
|
|
110
|
-
approvalTimeoutSeconds: z.number().nonnegative().optional(),
|
|
111
|
-
flightRecorder: z.boolean().optional(),
|
|
112
|
-
approvers: z.object({
|
|
113
|
-
native: z.boolean().optional(),
|
|
114
|
-
browser: z.boolean().optional(),
|
|
115
|
-
cloud: z.boolean().optional(),
|
|
116
|
-
terminal: z.boolean().optional()
|
|
117
|
-
}).optional(),
|
|
118
|
-
environment: z.string().optional(),
|
|
119
|
-
slackEnabled: z.boolean().optional(),
|
|
120
|
-
enableTrustSessions: z.boolean().optional(),
|
|
121
|
-
allowGlobalPause: z.boolean().optional(),
|
|
122
|
-
auditHashArgs: z.boolean().optional(),
|
|
123
|
-
agentPolicy: z.enum(["require_approval", "block_on_rules"]).optional(),
|
|
124
|
-
// Where a `review` verdict's prompt is rendered: 'ask' = the agent's own
|
|
125
|
-
// inline approve/deny prompt (Claude Code / GitHub Copilot); 'approver' =
|
|
126
|
-
// node9's own approver (terminal/native/cloud). Unset → default ASK for
|
|
127
|
-
// ask-capable agents (v2: cloud no longer disables inline — the outcome
|
|
128
|
-
// ships to the dashboard as audit; admins force routing via managed
|
|
129
|
-
// reviewChannel, which outranks the local --ask flag).
|
|
130
|
-
reviewChannel: z.enum(["ask", "approver"]).optional(),
|
|
131
|
-
// When true, agents may call WEAKENING node9 MCP tools (shield_disable,
|
|
132
|
-
// approver_set). Default (unset/false): those tools refuse over MCP — a human
|
|
133
|
-
// must run them from the CLI. node9's threat model is the agent itself.
|
|
134
|
-
mcpAllowWeakening: z.boolean().optional(),
|
|
135
|
-
// Auto-wire reconciler (P3 2.6): auto-wrap new ungoverned MCP servers vs
|
|
136
|
-
// nudge-only (default), and the scan cadence in minutes.
|
|
137
|
-
mcpAutoWrap: z.boolean().optional(),
|
|
138
|
-
mcpReconcileIntervalMinutes: z.number().positive().optional(),
|
|
139
|
-
mcpStaleAfterDays: z.number().min(0).optional(),
|
|
140
|
-
cloudSyncIntervalHours: z.number().positive().optional(),
|
|
141
|
-
// Seconds-granular override for the cloud policy sync cadence. Wins over
|
|
142
|
-
// cloudSyncIntervalHours when set. Lets you opt into fast apply (e.g. 20)
|
|
143
|
-
// for an incident; clamped to a 15s floor so it can't hammer the API.
|
|
144
|
-
// Unset → falls back to hours, then the 5h default.
|
|
145
|
-
cloudSyncIntervalSeconds: z.number().positive().optional(),
|
|
146
|
-
// Outbox shipper (audit.log → SaaS batch ingest). enabled defaults
|
|
147
|
-
// to true; set false to fall back to local-only auditing.
|
|
148
|
-
shipper: z.object({
|
|
149
|
-
enabled: z.boolean().optional(),
|
|
150
|
-
intervalSeconds: z.number().min(5).optional()
|
|
151
|
-
}).optional()
|
|
152
|
-
}).optional(),
|
|
153
|
-
policy: z.object({
|
|
154
|
-
sandboxPaths: z.array(z.string()).optional(),
|
|
155
|
-
dangerousWords: z.array(noNewlines).optional(),
|
|
156
|
-
ignoredTools: z.array(z.string()).optional(),
|
|
157
|
-
toolInspection: z.record(
|
|
158
|
-
z.string(),
|
|
159
|
-
z.union([
|
|
160
|
-
z.string(),
|
|
161
|
-
z.object({ kind: z.literal("shell"), field: z.string().min(1) }).strict()
|
|
162
|
-
])
|
|
163
|
-
).optional(),
|
|
164
|
-
smartRules: z.array(SmartRuleSchema).optional(),
|
|
165
|
-
dlp: z.object({
|
|
166
|
-
enabled: z.boolean().optional(),
|
|
167
|
-
scanIgnoredTools: z.boolean().optional(),
|
|
168
|
-
pii: z.enum(["off", "block"]).optional(),
|
|
169
|
-
reviewAction: z.enum(["review", "block"]).optional()
|
|
170
|
-
}).optional(),
|
|
171
|
-
// Command-checks governance. Class-B keys (evalDynamic, pipeChainHigh)
|
|
172
|
-
// deliberately exclude 'off' — tighten-only.
|
|
173
|
-
commandChecks: z.object({
|
|
174
|
-
inlineExec: z.enum(["off", "review", "block"]).optional(),
|
|
175
|
-
rmAdvisory: z.enum(["off", "review", "block"]).optional(),
|
|
176
|
-
chmod: z.enum(["off", "review", "block"]).optional(),
|
|
177
|
-
sqlDdl: z.enum(["off", "review", "block"]).optional(),
|
|
178
|
-
evalDynamic: z.enum(["review", "block"]).optional(),
|
|
179
|
-
pipeChainHigh: z.enum(["review", "block"]).optional()
|
|
180
|
-
}).optional(),
|
|
181
|
-
egress: z.object({
|
|
182
|
-
enabled: z.boolean().optional(),
|
|
183
|
-
mode: z.enum(["off", "review", "block"]).optional(),
|
|
184
|
-
allow: z.array(z.string()).optional(),
|
|
185
|
-
deny: z.array(z.string()).optional(),
|
|
186
|
-
allowPrivate: z.boolean().optional(),
|
|
187
|
-
// SSRF floor. `ssrfAllow` exempts OVERRIDABLE tiers only; a tier-1
|
|
188
|
-
// entry is dropped with a warning at load, never silently honoured.
|
|
189
|
-
ssrfAllow: z.array(z.string()).optional(),
|
|
190
|
-
ssrfStrict: z.boolean().optional()
|
|
191
|
-
}).optional(),
|
|
192
|
-
loopDetection: z.object({
|
|
193
|
-
enabled: z.boolean().optional(),
|
|
194
|
-
threshold: z.number().min(2).optional(),
|
|
195
|
-
windowSeconds: z.number().min(10).optional()
|
|
196
|
-
}).optional(),
|
|
197
|
-
injectionScan: z.object({
|
|
198
|
-
enabled: z.boolean().optional(),
|
|
199
|
-
minConfidence: z.enum(["medium", "high"]).optional(),
|
|
200
|
-
allow: z.array(z.string()).optional()
|
|
201
|
-
}).optional(),
|
|
202
|
-
packageCheck: z.object({
|
|
203
|
-
enabled: z.boolean().optional(),
|
|
204
|
-
onMalicious: z.enum(["block", "review"]).optional(),
|
|
205
|
-
// 2.27.0 shape, still accepted: false = newPackage and installScript off.
|
|
206
|
-
registrySignals: z.boolean().optional(),
|
|
207
|
-
newPackage: z.enum(["review", "off"]).optional(),
|
|
208
|
-
installScript: z.enum(["review", "off"]).optional(),
|
|
209
|
-
maxAgeHours: z.number().min(0).optional(),
|
|
210
|
-
onlineFallback: z.boolean().optional(),
|
|
211
|
-
allow: z.array(z.string()).optional()
|
|
212
|
-
}).optional(),
|
|
213
|
-
untrustedContent: z.object({
|
|
214
|
-
enabled: z.boolean().optional(),
|
|
215
|
-
spotlight: z.boolean().optional(),
|
|
216
|
-
afterExposure: z.enum(["ask-once", "ask-every-time", "log-only"]).optional(),
|
|
217
|
-
orgDomains: z.array(z.string()).optional(),
|
|
218
|
-
roles: z.record(z.string(), z.record(z.string(), z.enum(["source", "action", "read"]))).optional()
|
|
219
|
-
}).optional(),
|
|
220
|
-
skillPinning: z.object({
|
|
221
|
-
enabled: z.boolean().optional(),
|
|
222
|
-
mode: z.enum(["warn", "block"]).optional(),
|
|
223
|
-
roots: z.array(z.string()).optional()
|
|
224
|
-
}).optional()
|
|
225
|
-
}).optional(),
|
|
226
|
-
environments: z.record(z.string(), z.object({ requireApproval: z.boolean().optional() })).optional()
|
|
227
|
-
}).strict();
|
|
228
|
-
|
|
229
|
-
// src/shields.ts
|
|
230
|
-
import fs from "fs";
|
|
231
|
-
import path from "path";
|
|
232
|
-
import os from "os";
|
|
233
|
-
|
|
234
45
|
// packages/policy-engine/dist/index.mjs
|
|
235
46
|
import picomatch from "picomatch";
|
|
236
47
|
import safeRegex from "safe-regex2";
|
|
@@ -2194,7 +2005,19 @@ var BASH_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
|
2194
2005
|
"shell",
|
|
2195
2006
|
"exec_command"
|
|
2196
2007
|
]);
|
|
2197
|
-
var MCP_EXECUTORS = /* @__PURE__ */ new Set([
|
|
2008
|
+
var MCP_EXECUTORS = /* @__PURE__ */ new Set([
|
|
2009
|
+
...BASH_TOOL_NAMES,
|
|
2010
|
+
"exec",
|
|
2011
|
+
"run_command",
|
|
2012
|
+
"terminal",
|
|
2013
|
+
"execute_command",
|
|
2014
|
+
"execute_shell",
|
|
2015
|
+
"execute_shell_command",
|
|
2016
|
+
"shell_execute",
|
|
2017
|
+
"run_shell",
|
|
2018
|
+
"run_terminal_command",
|
|
2019
|
+
"start_process"
|
|
2020
|
+
]);
|
|
2198
2021
|
var { syntax: syntax2 } = mvdanSh2;
|
|
2199
2022
|
function opOf(sample) {
|
|
2200
2023
|
return parseShared(sample).Stmts[0].Redirs[0].Op;
|
|
@@ -4367,6 +4190,8 @@ function validateCask(raw) {
|
|
|
4367
4190
|
const second = b64urlIndex(s[tsAt + 5]);
|
|
4368
4191
|
return month <= 11 && day <= 30 && hour <= 23 && minute <= 59 && second <= 59;
|
|
4369
4192
|
}
|
|
4193
|
+
var INVISIBLE_RE = /[͏----︀-️]|[\u{E0000}-\u{E007F}]/gu;
|
|
4194
|
+
var INVISIBLE_ONE = new RegExp(`^(?:${INVISIBLE_RE.source.split("|")[0]})$`, "u");
|
|
4370
4195
|
var HOMOGLYPHS = {
|
|
4371
4196
|
// Cyrillic lowercase
|
|
4372
4197
|
\u0430: "a",
|
|
@@ -5224,7 +5049,194 @@ var PUBLISH = {
|
|
|
5224
5049
|
var OWN_HEADS = /* @__PURE__ */ new Set([...Object.keys(PUBLISH), "git", "gh"]);
|
|
5225
5050
|
var LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
|
|
5226
5051
|
|
|
5052
|
+
// src/config-schema.ts
|
|
5053
|
+
import { z } from "zod";
|
|
5054
|
+
var noNewlines = z.string().refine((s) => !s.includes("\n") && !s.includes("\r"), {
|
|
5055
|
+
message: "Value must not contain literal newline characters (use \\n instead)"
|
|
5056
|
+
});
|
|
5057
|
+
var SmartConditionSchema = z.object({
|
|
5058
|
+
field: z.string().min(1, "Condition field must not be empty"),
|
|
5059
|
+
op: z.enum(
|
|
5060
|
+
[
|
|
5061
|
+
"matches",
|
|
5062
|
+
"notMatches",
|
|
5063
|
+
"contains",
|
|
5064
|
+
"notContains",
|
|
5065
|
+
"exists",
|
|
5066
|
+
"notExists",
|
|
5067
|
+
"matchesGlob",
|
|
5068
|
+
"notMatchesGlob"
|
|
5069
|
+
],
|
|
5070
|
+
{
|
|
5071
|
+
// zod 4 replaced errorMap with `error`. The wording is kept verbatim:
|
|
5072
|
+
// it is what a user sees when their config is rejected.
|
|
5073
|
+
error: () => "op must be one of: matches, notMatches, contains, notContains, exists, notExists, matchesGlob, notMatchesGlob"
|
|
5074
|
+
}
|
|
5075
|
+
),
|
|
5076
|
+
value: z.string().optional(),
|
|
5077
|
+
flags: z.string().optional()
|
|
5078
|
+
}).refine(
|
|
5079
|
+
(c) => {
|
|
5080
|
+
if (c.op === "matchesGlob" || c.op === "notMatchesGlob") return c.value !== void 0;
|
|
5081
|
+
return true;
|
|
5082
|
+
},
|
|
5083
|
+
{ message: "matchesGlob and notMatchesGlob conditions require a value field" }
|
|
5084
|
+
);
|
|
5085
|
+
var SmartRuleSchema = z.object({
|
|
5086
|
+
name: z.string().optional(),
|
|
5087
|
+
tool: z.string().min(1, "Smart rule tool must not be empty"),
|
|
5088
|
+
conditions: z.array(SmartConditionSchema).min(1, "Smart rule must have at least one condition"),
|
|
5089
|
+
conditionMode: z.enum(["all", "any"]).optional(),
|
|
5090
|
+
verdict: z.enum(["allow", "review", "block"], {
|
|
5091
|
+
error: () => "verdict must be one of: allow, review, block"
|
|
5092
|
+
}),
|
|
5093
|
+
reason: z.string().optional(),
|
|
5094
|
+
description: z.string().optional(),
|
|
5095
|
+
// Unknown predicate names are filtered out rather than failing the whole rule.
|
|
5096
|
+
// Failing the whole z.array() would cause sanitizeConfig to drop the entire
|
|
5097
|
+
// `policy` top-level key, silently disabling ALL smart rules in the config.
|
|
5098
|
+
dependsOnState: z.array(z.string()).transform(
|
|
5099
|
+
(arr) => arr.filter(
|
|
5100
|
+
(p) => p === "no_test_passed_since_last_edit"
|
|
5101
|
+
)
|
|
5102
|
+
).optional(),
|
|
5103
|
+
recoveryCommand: z.string().optional()
|
|
5104
|
+
});
|
|
5105
|
+
var ConfigFileSchema = z.object({
|
|
5106
|
+
version: z.string().optional(),
|
|
5107
|
+
settings: z.object({
|
|
5108
|
+
mode: z.enum(["standard", "strict", "audit", "observe"]).optional(),
|
|
5109
|
+
autoStartDaemon: z.boolean().optional(),
|
|
5110
|
+
// enableUndo / policy.snapshot: removed with the undo feature. The
|
|
5111
|
+
// schema strips undeclared keys silently (plain zod object — no
|
|
5112
|
+
// .strict(), no .passthrough()), so a config still carrying them stays
|
|
5113
|
+
// valid and simply loses them. Keeping the fields would instead make
|
|
5114
|
+
// `node9_config_get` advertise a removed feature as a live knob.
|
|
5115
|
+
enableHookLogDebug: z.boolean().optional(),
|
|
5116
|
+
approvalTimeoutMs: z.number().nonnegative().optional(),
|
|
5117
|
+
approvalTimeoutSeconds: z.number().nonnegative().optional(),
|
|
5118
|
+
flightRecorder: z.boolean().optional(),
|
|
5119
|
+
approvers: z.object({
|
|
5120
|
+
native: z.boolean().optional(),
|
|
5121
|
+
browser: z.boolean().optional(),
|
|
5122
|
+
cloud: z.boolean().optional(),
|
|
5123
|
+
terminal: z.boolean().optional()
|
|
5124
|
+
}).optional(),
|
|
5125
|
+
environment: z.string().optional(),
|
|
5126
|
+
slackEnabled: z.boolean().optional(),
|
|
5127
|
+
enableTrustSessions: z.boolean().optional(),
|
|
5128
|
+
allowGlobalPause: z.boolean().optional(),
|
|
5129
|
+
auditHashArgs: z.boolean().optional(),
|
|
5130
|
+
agentPolicy: z.enum(["require_approval", "block_on_rules"]).optional(),
|
|
5131
|
+
// Where a `review` verdict's prompt is rendered: 'ask' = the agent's own
|
|
5132
|
+
// inline approve/deny prompt (Claude Code / GitHub Copilot); 'approver' =
|
|
5133
|
+
// node9's own approver (terminal/native/cloud). Unset → default ASK for
|
|
5134
|
+
// ask-capable agents (v2: cloud no longer disables inline — the outcome
|
|
5135
|
+
// ships to the dashboard as audit; admins force routing via managed
|
|
5136
|
+
// reviewChannel, which outranks the local --ask flag).
|
|
5137
|
+
reviewChannel: z.enum(["ask", "approver"]).optional(),
|
|
5138
|
+
// When true, agents may call WEAKENING node9 MCP tools (shield_disable,
|
|
5139
|
+
// approver_set). Default (unset/false): those tools refuse over MCP — a human
|
|
5140
|
+
// must run them from the CLI. node9's threat model is the agent itself.
|
|
5141
|
+
mcpAllowWeakening: z.boolean().optional(),
|
|
5142
|
+
// Auto-wire reconciler (P3 2.6): auto-wrap new ungoverned MCP servers vs
|
|
5143
|
+
// nudge-only (default), and the scan cadence in minutes.
|
|
5144
|
+
mcpAutoWrap: z.boolean().optional(),
|
|
5145
|
+
mcpReconcileIntervalMinutes: z.number().positive().optional(),
|
|
5146
|
+
mcpStaleAfterDays: z.number().min(0).optional(),
|
|
5147
|
+
cloudSyncIntervalHours: z.number().positive().optional(),
|
|
5148
|
+
// Seconds-granular override for the cloud policy sync cadence. Wins over
|
|
5149
|
+
// cloudSyncIntervalHours when set. Lets you opt into fast apply (e.g. 20)
|
|
5150
|
+
// for an incident; clamped to a 15s floor so it can't hammer the API.
|
|
5151
|
+
// Unset → falls back to hours, then the 5h default.
|
|
5152
|
+
cloudSyncIntervalSeconds: z.number().positive().optional(),
|
|
5153
|
+
// Outbox shipper (audit.log → SaaS batch ingest). enabled defaults
|
|
5154
|
+
// to true; set false to fall back to local-only auditing.
|
|
5155
|
+
shipper: z.object({
|
|
5156
|
+
enabled: z.boolean().optional(),
|
|
5157
|
+
intervalSeconds: z.number().min(5).optional()
|
|
5158
|
+
}).optional()
|
|
5159
|
+
}).optional(),
|
|
5160
|
+
policy: z.object({
|
|
5161
|
+
sandboxPaths: z.array(z.string()).optional(),
|
|
5162
|
+
dangerousWords: z.array(noNewlines).optional(),
|
|
5163
|
+
ignoredTools: z.array(z.string()).optional(),
|
|
5164
|
+
toolInspection: z.record(
|
|
5165
|
+
z.string(),
|
|
5166
|
+
z.union([
|
|
5167
|
+
z.string(),
|
|
5168
|
+
z.object({ kind: z.literal("shell"), field: z.string().min(1) }).strict()
|
|
5169
|
+
])
|
|
5170
|
+
).optional(),
|
|
5171
|
+
smartRules: z.array(SmartRuleSchema).optional(),
|
|
5172
|
+
dlp: z.object({
|
|
5173
|
+
enabled: z.boolean().optional(),
|
|
5174
|
+
scanIgnoredTools: z.boolean().optional(),
|
|
5175
|
+
pii: z.enum(["off", "block"]).optional(),
|
|
5176
|
+
reviewAction: z.enum(["review", "block"]).optional()
|
|
5177
|
+
}).optional(),
|
|
5178
|
+
// Command-checks governance. Class-B keys (evalDynamic, pipeChainHigh)
|
|
5179
|
+
// deliberately exclude 'off' — tighten-only.
|
|
5180
|
+
commandChecks: z.object({
|
|
5181
|
+
inlineExec: z.enum(["off", "review", "block"]).optional(),
|
|
5182
|
+
rmAdvisory: z.enum(["off", "review", "block"]).optional(),
|
|
5183
|
+
chmod: z.enum(["off", "review", "block"]).optional(),
|
|
5184
|
+
sqlDdl: z.enum(["off", "review", "block"]).optional(),
|
|
5185
|
+
evalDynamic: z.enum(["review", "block"]).optional(),
|
|
5186
|
+
pipeChainHigh: z.enum(["review", "block"]).optional()
|
|
5187
|
+
}).optional(),
|
|
5188
|
+
egress: z.object({
|
|
5189
|
+
enabled: z.boolean().optional(),
|
|
5190
|
+
mode: z.enum(["off", "review", "block"]).optional(),
|
|
5191
|
+
allow: z.array(z.string()).optional(),
|
|
5192
|
+
deny: z.array(z.string()).optional(),
|
|
5193
|
+
allowPrivate: z.boolean().optional(),
|
|
5194
|
+
// SSRF floor. `ssrfAllow` exempts OVERRIDABLE tiers only; a tier-1
|
|
5195
|
+
// entry is dropped with a warning at load, never silently honoured.
|
|
5196
|
+
ssrfAllow: z.array(z.string()).optional(),
|
|
5197
|
+
ssrfStrict: z.boolean().optional()
|
|
5198
|
+
}).optional(),
|
|
5199
|
+
loopDetection: z.object({
|
|
5200
|
+
enabled: z.boolean().optional(),
|
|
5201
|
+
threshold: z.number().min(2).optional(),
|
|
5202
|
+
windowSeconds: z.number().min(10).optional()
|
|
5203
|
+
}).optional(),
|
|
5204
|
+
injectionScan: z.object({
|
|
5205
|
+
enabled: z.boolean().optional(),
|
|
5206
|
+
minConfidence: z.enum(["medium", "high"]).optional(),
|
|
5207
|
+
allow: z.array(z.string()).optional()
|
|
5208
|
+
}).optional(),
|
|
5209
|
+
packageCheck: z.object({
|
|
5210
|
+
enabled: z.boolean().optional(),
|
|
5211
|
+
onMalicious: z.enum(["block", "review"]).optional(),
|
|
5212
|
+
// 2.27.0 shape, still accepted: false = newPackage and installScript off.
|
|
5213
|
+
registrySignals: z.boolean().optional(),
|
|
5214
|
+
newPackage: z.enum(["review", "off"]).optional(),
|
|
5215
|
+
installScript: z.enum(["review", "off"]).optional(),
|
|
5216
|
+
maxAgeHours: z.number().min(0).optional(),
|
|
5217
|
+
onlineFallback: z.boolean().optional(),
|
|
5218
|
+
allow: z.array(z.string()).optional()
|
|
5219
|
+
}).optional(),
|
|
5220
|
+
untrustedContent: z.object({
|
|
5221
|
+
enabled: z.boolean().optional(),
|
|
5222
|
+
spotlight: z.boolean().optional(),
|
|
5223
|
+
afterExposure: z.enum(["ask-once", "ask-every-time", "log-only"]).optional(),
|
|
5224
|
+
orgDomains: z.array(z.string()).optional(),
|
|
5225
|
+
roles: z.record(z.string(), z.record(z.string(), z.enum(["source", "action", "read"]))).optional()
|
|
5226
|
+
}).optional(),
|
|
5227
|
+
skillPinning: z.object({
|
|
5228
|
+
enabled: z.boolean().optional(),
|
|
5229
|
+
mode: z.enum(["warn", "block"]).optional(),
|
|
5230
|
+
roots: z.array(z.string()).optional()
|
|
5231
|
+
}).optional()
|
|
5232
|
+
}).optional(),
|
|
5233
|
+
environments: z.record(z.string(), z.object({ requireApproval: z.boolean().optional() })).optional()
|
|
5234
|
+
}).strict();
|
|
5235
|
+
|
|
5227
5236
|
// src/shields.ts
|
|
5237
|
+
import fs from "fs";
|
|
5238
|
+
import path from "path";
|
|
5239
|
+
import os from "os";
|
|
5228
5240
|
var USER_SHIELDS_DIR = path.join(os.homedir(), ".node9", "shields");
|
|
5229
5241
|
function loadUserShields() {
|
|
5230
5242
|
const result = {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@node9/proxy",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.33.0",
|
|
4
4
|
"description": "Access control for AI agents. Set what Claude Code, Codex, Gemini, Cursor and any MCP server are allowed to do, review risky actions before they run, and keep every action on the record.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|