@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/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([...BASH_TOOL_NAMES, "exec", "run_command", "terminal"]);
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.32.0",
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",