@node9/proxy 1.10.0 → 1.10.1

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
@@ -238,7 +238,8 @@ var ConfigFileSchema = import_zod.z.object({
238
238
  slackEnabled: import_zod.z.boolean().optional(),
239
239
  enableTrustSessions: import_zod.z.boolean().optional(),
240
240
  allowGlobalPause: import_zod.z.boolean().optional(),
241
- auditHashArgs: import_zod.z.boolean().optional()
241
+ auditHashArgs: import_zod.z.boolean().optional(),
242
+ agentPolicy: import_zod.z.enum(["require_approval", "block_on_rules"]).optional()
242
243
  }).optional(),
243
244
  policy: import_zod.z.object({
244
245
  sandboxPaths: import_zod.z.array(import_zod.z.string()).optional(),
@@ -2321,11 +2322,12 @@ ${smartTruncate(str, 500)}`
2321
2322
  function escapePango(text) {
2322
2323
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2323
2324
  }
2324
- function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
2325
+ function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
2325
2326
  const lines = [];
2326
2327
  if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
2327
2328
  lines.push(`\u{1F916} ${agent || "AI Agent"} | \u{1F527} ${toolName}`);
2328
2329
  lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
2330
+ if (ruleDescription) lines.push(`\u2139 ${ruleDescription}`);
2329
2331
  lines.push("");
2330
2332
  lines.push(formattedArgs);
2331
2333
  if (allowCount >= 3) {
@@ -2338,7 +2340,7 @@ function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, loc
2338
2340
  }
2339
2341
  return lines.join("\n");
2340
2342
  }
2341
- function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
2343
+ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
2342
2344
  const lines = [];
2343
2345
  if (locked) {
2344
2346
  lines.push('<span foreground="red" weight="bold">\u26A0\uFE0F LOCKED BY ADMIN POLICY</span>');
@@ -2348,6 +2350,7 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
2348
2350
  `<b>\u{1F916} ${escapePango(agent || "AI Agent")}</b> | <b>\u{1F527} <tt>${escapePango(toolName)}</tt></b>`
2349
2351
  );
2350
2352
  lines.push(`<i>\u{1F6E1}\uFE0F ${escapePango(explainableLabel || "Security Policy")}</i>`);
2353
+ if (ruleDescription) lines.push(`<i>\u2139 ${escapePango(ruleDescription)}</i>`);
2351
2354
  lines.push("");
2352
2355
  lines.push(`<tt>${escapePango(formattedArgs)}</tt>`);
2353
2356
  if (allowCount >= 3) {
@@ -2364,7 +2367,7 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
2364
2367
  }
2365
2368
  return lines.join("\n");
2366
2369
  }
2367
- async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1) {
2370
+ async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1, ruleDescription) {
2368
2371
  if (isTestEnv()) return "deny";
2369
2372
  const { message: formattedArgs, intent } = formatArgs(args, matchedField, matchedWord);
2370
2373
  const intentLabel = intent === "EDIT" ? "Code Edit" : "Action Approval";
@@ -2375,7 +2378,8 @@ async function askNativePopup(toolName, args, agent, explainableLabel, locked =
2375
2378
  agent,
2376
2379
  explainableLabel,
2377
2380
  locked,
2378
- allowCount
2381
+ allowCount,
2382
+ ruleDescription
2379
2383
  );
2380
2384
  return new Promise((resolve) => {
2381
2385
  let childProcess = null;
@@ -2409,7 +2413,8 @@ end run`;
2409
2413
  agent,
2410
2414
  explainableLabel,
2411
2415
  locked,
2412
- allowCount
2416
+ allowCount,
2417
+ ruleDescription
2413
2418
  );
2414
2419
  const argsList = [
2415
2420
  locked ? "--info" : "--question",
@@ -2477,7 +2482,7 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
2477
2482
  }).catch(() => {
2478
2483
  });
2479
2484
  }
2480
- async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
2485
+ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPolicy, forceReview) {
2481
2486
  const controller = new AbortController();
2482
2487
  const timeout = setTimeout(() => controller.abort(), 1e4);
2483
2488
  if (!creds.apiKey) throw new Error("Node9 API Key is missing");
@@ -2522,7 +2527,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
2522
2527
  platform: import_os8.default.platform()
2523
2528
  },
2524
2529
  ...riskMetadata && { riskMetadata },
2525
- ...ciContext && { ciContext }
2530
+ ...ciContext && { ciContext },
2531
+ ...agentPolicy && { policy: agentPolicy },
2532
+ ...forceReview && { forceReview: true }
2526
2533
  }),
2527
2534
  signal: controller.signal
2528
2535
  });
@@ -2914,6 +2921,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2914
2921
  policyMatchedWord,
2915
2922
  policyResult.ruleName
2916
2923
  );
2924
+ if (policyRuleDescription) riskMetadata.ruleDescription = policyRuleDescription.slice(0, 200);
2917
2925
  const persistent = policyResult.ruleName ? null : getPersistentDecision(toolName);
2918
2926
  if (persistent === "allow") {
2919
2927
  if (approvers.cloud && creds?.apiKey)
@@ -2948,9 +2956,18 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2948
2956
  }
2949
2957
  let cloudRequestId = null;
2950
2958
  const cloudEnforced = approvers.cloud && !!creds?.apiKey;
2951
- if (cloudEnforced && !localSmartRuleMatched && !options?.localSmartRuleMatched) {
2959
+ const forceReview = localSmartRuleMatched === true || options?.localSmartRuleMatched === true || void 0;
2960
+ if (cloudEnforced) {
2952
2961
  try {
2953
- const initResult = await initNode9SaaS(toolName, args, creds, meta, riskMetadata);
2962
+ const initResult = await initNode9SaaS(
2963
+ toolName,
2964
+ args,
2965
+ creds,
2966
+ meta,
2967
+ riskMetadata,
2968
+ config.settings.agentPolicy,
2969
+ forceReview
2970
+ );
2954
2971
  if (!initResult.pending) {
2955
2972
  if (initResult.shadowMode) {
2956
2973
  return { approved: true, checkedBy: "cloud" };
@@ -2965,9 +2982,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2965
2982
  };
2966
2983
  }
2967
2984
  }
2968
- if (!localSmartRuleMatched && !options?.localSmartRuleMatched) {
2969
- cloudRequestId = initResult.requestId || null;
2970
- }
2985
+ if (initResult.pending) cloudRequestId = initResult.requestId || null;
2971
2986
  if (!taintWarning) explainableLabel = "Organization Policy (SaaS)";
2972
2987
  } catch {
2973
2988
  }
@@ -3024,7 +3039,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3024
3039
  }
3025
3040
  }
3026
3041
  }
3027
- if (cloudEnforced && cloudRequestId && !localSmartRuleMatched && !options?.localSmartRuleMatched) {
3042
+ if (cloudEnforced && cloudRequestId) {
3028
3043
  racePromises.push(
3029
3044
  (async () => {
3030
3045
  try {
@@ -3056,7 +3071,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3056
3071
  signal,
3057
3072
  policyMatchedField,
3058
3073
  policyMatchedWord,
3059
- daemonAllowCount
3074
+ daemonAllowCount,
3075
+ riskMetadata?.ruleDescription
3060
3076
  );
3061
3077
  if (decision === "always_allow") {
3062
3078
  writeTrustSession(toolName, 36e5);
package/dist/index.mjs CHANGED
@@ -208,7 +208,8 @@ var ConfigFileSchema = z.object({
208
208
  slackEnabled: z.boolean().optional(),
209
209
  enableTrustSessions: z.boolean().optional(),
210
210
  allowGlobalPause: z.boolean().optional(),
211
- auditHashArgs: z.boolean().optional()
211
+ auditHashArgs: z.boolean().optional(),
212
+ agentPolicy: z.enum(["require_approval", "block_on_rules"]).optional()
212
213
  }).optional(),
213
214
  policy: z.object({
214
215
  sandboxPaths: z.array(z.string()).optional(),
@@ -2291,11 +2292,12 @@ ${smartTruncate(str, 500)}`
2291
2292
  function escapePango(text) {
2292
2293
  return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2293
2294
  }
2294
- function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
2295
+ function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
2295
2296
  const lines = [];
2296
2297
  if (locked) lines.push("\u26A0\uFE0F LOCKED BY ADMIN POLICY\n");
2297
2298
  lines.push(`\u{1F916} ${agent || "AI Agent"} | \u{1F527} ${toolName}`);
2298
2299
  lines.push(`\u{1F6E1}\uFE0F ${explainableLabel || "Security Policy"}`);
2300
+ if (ruleDescription) lines.push(`\u2139 ${ruleDescription}`);
2299
2301
  lines.push("");
2300
2302
  lines.push(formattedArgs);
2301
2303
  if (allowCount >= 3) {
@@ -2308,7 +2310,7 @@ function buildPlainMessage(toolName, formattedArgs, agent, explainableLabel, loc
2308
2310
  }
2309
2311
  return lines.join("\n");
2310
2312
  }
2311
- function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1) {
2313
+ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, locked, allowCount = 1, ruleDescription) {
2312
2314
  const lines = [];
2313
2315
  if (locked) {
2314
2316
  lines.push('<span foreground="red" weight="bold">\u26A0\uFE0F LOCKED BY ADMIN POLICY</span>');
@@ -2318,6 +2320,7 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
2318
2320
  `<b>\u{1F916} ${escapePango(agent || "AI Agent")}</b> | <b>\u{1F527} <tt>${escapePango(toolName)}</tt></b>`
2319
2321
  );
2320
2322
  lines.push(`<i>\u{1F6E1}\uFE0F ${escapePango(explainableLabel || "Security Policy")}</i>`);
2323
+ if (ruleDescription) lines.push(`<i>\u2139 ${escapePango(ruleDescription)}</i>`);
2321
2324
  lines.push("");
2322
2325
  lines.push(`<tt>${escapePango(formattedArgs)}</tt>`);
2323
2326
  if (allowCount >= 3) {
@@ -2334,7 +2337,7 @@ function buildPangoMessage(toolName, formattedArgs, agent, explainableLabel, loc
2334
2337
  }
2335
2338
  return lines.join("\n");
2336
2339
  }
2337
- async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1) {
2340
+ async function askNativePopup(toolName, args, agent, explainableLabel, locked = false, signal, matchedField, matchedWord, allowCount = 1, ruleDescription) {
2338
2341
  if (isTestEnv()) return "deny";
2339
2342
  const { message: formattedArgs, intent } = formatArgs(args, matchedField, matchedWord);
2340
2343
  const intentLabel = intent === "EDIT" ? "Code Edit" : "Action Approval";
@@ -2345,7 +2348,8 @@ async function askNativePopup(toolName, args, agent, explainableLabel, locked =
2345
2348
  agent,
2346
2349
  explainableLabel,
2347
2350
  locked,
2348
- allowCount
2351
+ allowCount,
2352
+ ruleDescription
2349
2353
  );
2350
2354
  return new Promise((resolve) => {
2351
2355
  let childProcess = null;
@@ -2379,7 +2383,8 @@ end run`;
2379
2383
  agent,
2380
2384
  explainableLabel,
2381
2385
  locked,
2382
- allowCount
2386
+ allowCount,
2387
+ ruleDescription
2383
2388
  );
2384
2389
  const argsList = [
2385
2390
  locked ? "--info" : "--question",
@@ -2447,7 +2452,7 @@ function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
2447
2452
  }).catch(() => {
2448
2453
  });
2449
2454
  }
2450
- async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
2455
+ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata, agentPolicy, forceReview) {
2451
2456
  const controller = new AbortController();
2452
2457
  const timeout = setTimeout(() => controller.abort(), 1e4);
2453
2458
  if (!creds.apiKey) throw new Error("Node9 API Key is missing");
@@ -2492,7 +2497,9 @@ async function initNode9SaaS(toolName, args, creds, meta, riskMetadata) {
2492
2497
  platform: os8.platform()
2493
2498
  },
2494
2499
  ...riskMetadata && { riskMetadata },
2495
- ...ciContext && { ciContext }
2500
+ ...ciContext && { ciContext },
2501
+ ...agentPolicy && { policy: agentPolicy },
2502
+ ...forceReview && { forceReview: true }
2496
2503
  }),
2497
2504
  signal: controller.signal
2498
2505
  });
@@ -2884,6 +2891,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2884
2891
  policyMatchedWord,
2885
2892
  policyResult.ruleName
2886
2893
  );
2894
+ if (policyRuleDescription) riskMetadata.ruleDescription = policyRuleDescription.slice(0, 200);
2887
2895
  const persistent = policyResult.ruleName ? null : getPersistentDecision(toolName);
2888
2896
  if (persistent === "allow") {
2889
2897
  if (approvers.cloud && creds?.apiKey)
@@ -2918,9 +2926,18 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2918
2926
  }
2919
2927
  let cloudRequestId = null;
2920
2928
  const cloudEnforced = approvers.cloud && !!creds?.apiKey;
2921
- if (cloudEnforced && !localSmartRuleMatched && !options?.localSmartRuleMatched) {
2929
+ const forceReview = localSmartRuleMatched === true || options?.localSmartRuleMatched === true || void 0;
2930
+ if (cloudEnforced) {
2922
2931
  try {
2923
- const initResult = await initNode9SaaS(toolName, args, creds, meta, riskMetadata);
2932
+ const initResult = await initNode9SaaS(
2933
+ toolName,
2934
+ args,
2935
+ creds,
2936
+ meta,
2937
+ riskMetadata,
2938
+ config.settings.agentPolicy,
2939
+ forceReview
2940
+ );
2924
2941
  if (!initResult.pending) {
2925
2942
  if (initResult.shadowMode) {
2926
2943
  return { approved: true, checkedBy: "cloud" };
@@ -2935,9 +2952,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2935
2952
  };
2936
2953
  }
2937
2954
  }
2938
- if (!localSmartRuleMatched && !options?.localSmartRuleMatched) {
2939
- cloudRequestId = initResult.requestId || null;
2940
- }
2955
+ if (initResult.pending) cloudRequestId = initResult.requestId || null;
2941
2956
  if (!taintWarning) explainableLabel = "Organization Policy (SaaS)";
2942
2957
  } catch {
2943
2958
  }
@@ -2994,7 +3009,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2994
3009
  }
2995
3010
  }
2996
3011
  }
2997
- if (cloudEnforced && cloudRequestId && !localSmartRuleMatched && !options?.localSmartRuleMatched) {
3012
+ if (cloudEnforced && cloudRequestId) {
2998
3013
  racePromises.push(
2999
3014
  (async () => {
3000
3015
  try {
@@ -3026,7 +3041,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3026
3041
  signal,
3027
3042
  policyMatchedField,
3028
3043
  policyMatchedWord,
3029
- daemonAllowCount
3044
+ daemonAllowCount,
3045
+ riskMetadata?.ruleDescription
3030
3046
  );
3031
3047
  if (decision === "always_allow") {
3032
3048
  writeTrustSession(toolName, 36e5);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node9/proxy",
3
- "version": "1.10.0",
3
+ "version": "1.10.1",
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",