@node9/proxy 1.7.0 → 1.8.2

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
@@ -280,173 +280,73 @@ ${lines.join("\n")}`
280
280
  var import_fs2 = __toESM(require("fs"));
281
281
  var import_path2 = __toESM(require("path"));
282
282
  var import_os2 = __toESM(require("os"));
283
- var SHIELDS = {
284
- postgres: {
285
- name: "postgres",
286
- description: "Protects PostgreSQL databases from destructive AI operations",
287
- aliases: ["pg", "postgresql"],
288
- smartRules: [
289
- {
290
- name: "shield:postgres:block-drop-table",
291
- tool: "*",
292
- conditions: [{ field: "sql", op: "matches", value: "DROP\\s+TABLE", flags: "i" }],
293
- verdict: "block",
294
- reason: "DROP TABLE is irreversible \u2014 blocked by Postgres shield"
295
- },
296
- {
297
- name: "shield:postgres:block-truncate",
298
- tool: "*",
299
- conditions: [{ field: "sql", op: "matches", value: "TRUNCATE\\s+TABLE", flags: "i" }],
300
- verdict: "block",
301
- reason: "TRUNCATE is irreversible \u2014 blocked by Postgres shield"
302
- },
303
- {
304
- name: "shield:postgres:block-drop-column",
305
- tool: "*",
306
- conditions: [
307
- { field: "sql", op: "matches", value: "ALTER\\s+TABLE.*DROP\\s+COLUMN", flags: "i" }
308
- ],
309
- verdict: "block",
310
- reason: "DROP COLUMN is irreversible \u2014 blocked by Postgres shield"
311
- },
312
- {
313
- name: "shield:postgres:review-grant-revoke",
314
- tool: "*",
315
- conditions: [{ field: "sql", op: "matches", value: "\\b(GRANT|REVOKE)\\b", flags: "i" }],
316
- verdict: "review",
317
- reason: "Permission changes require human approval (Postgres shield)"
318
- }
319
- ],
320
- dangerousWords: ["dropdb", "pg_dropcluster"]
321
- },
322
- github: {
323
- name: "github",
324
- description: "Protects GitHub repositories from destructive AI operations",
325
- aliases: ["git"],
326
- smartRules: [
327
- {
328
- // Note: git branch -d/-D is already caught by the built-in review-git-destructive rule.
329
- // This rule adds coverage for `git push --delete` which the built-in does not match.
330
- name: "shield:github:review-delete-branch-remote",
331
- tool: "bash",
332
- conditions: [
333
- {
334
- field: "command",
335
- op: "matches",
336
- value: "git\\s+push\\s+.*--delete",
337
- flags: "i"
338
- }
339
- ],
340
- verdict: "review",
341
- reason: "Remote branch deletion requires human approval (GitHub shield)"
342
- },
343
- {
344
- name: "shield:github:block-delete-repo",
345
- tool: "*",
346
- conditions: [
347
- { field: "command", op: "matches", value: "gh\\s+repo\\s+delete", flags: "i" }
348
- ],
349
- verdict: "block",
350
- reason: "Repository deletion is irreversible \u2014 blocked by GitHub shield"
351
- }
352
- ],
353
- dangerousWords: []
354
- },
355
- aws: {
356
- name: "aws",
357
- description: "Protects AWS infrastructure from destructive AI operations",
358
- aliases: ["amazon"],
359
- smartRules: [
360
- {
361
- name: "shield:aws:block-delete-s3-bucket",
362
- tool: "*",
363
- conditions: [
364
- {
365
- field: "command",
366
- op: "matches",
367
- value: "aws\\s+s3.*rb\\s|aws\\s+s3api\\s+delete-bucket",
368
- flags: "i"
369
- }
370
- ],
371
- verdict: "block",
372
- reason: "S3 bucket deletion is irreversible \u2014 blocked by AWS shield"
373
- },
374
- {
375
- name: "shield:aws:review-iam-changes",
376
- tool: "*",
377
- conditions: [
378
- {
379
- field: "command",
380
- op: "matches",
381
- value: "aws\\s+iam\\s+(create|delete|attach|detach|put|remove)",
382
- flags: "i"
383
- }
384
- ],
385
- verdict: "review",
386
- reason: "IAM changes require human approval (AWS shield)"
387
- },
388
- {
389
- name: "shield:aws:block-ec2-terminate",
390
- tool: "*",
391
- conditions: [
392
- {
393
- field: "command",
394
- op: "matches",
395
- value: "aws\\s+ec2\\s+terminate-instances",
396
- flags: "i"
397
- }
398
- ],
399
- verdict: "block",
400
- reason: "EC2 instance termination is irreversible \u2014 blocked by AWS shield"
401
- },
402
- {
403
- name: "shield:aws:review-rds-delete",
404
- tool: "*",
405
- conditions: [
406
- { field: "command", op: "matches", value: "aws\\s+rds\\s+delete-", flags: "i" }
407
- ],
408
- verdict: "review",
409
- reason: "RDS deletion requires human approval (AWS shield)"
410
- }
411
- ],
412
- dangerousWords: []
413
- },
414
- filesystem: {
415
- name: "filesystem",
416
- description: "Protects the local filesystem from dangerous AI operations",
417
- aliases: ["fs"],
418
- smartRules: [
419
- {
420
- name: "shield:filesystem:review-chmod-777",
421
- tool: "bash",
422
- conditions: [
423
- { field: "command", op: "matches", value: "chmod\\s+(777|a\\+rwx)", flags: "i" }
424
- ],
425
- verdict: "review",
426
- reason: "chmod 777 requires human approval (filesystem shield)"
427
- },
428
- {
429
- name: "shield:filesystem:review-write-etc",
430
- tool: "bash",
431
- conditions: [
432
- {
433
- field: "command",
434
- // Narrow to write-indicative operations to avoid approval fatigue on reads.
435
- // Matches: tee /etc/*, cp .../etc/*, mv .../etc/*, > /etc/*, install .../etc/*
436
- op: "matches",
437
- value: "(tee|\\bcp\\b|\\bmv\\b|install|>+)\\s+.*\\/etc\\/"
438
- }
439
- ],
440
- verdict: "review",
441
- reason: "Writing to /etc requires human approval (filesystem shield)"
442
- }
443
- ],
444
- // dd removed: too common as a legitimate tool (disk imaging, file ops).
445
- // mkfs removed: already in the built-in DANGEROUS_WORDS baseline.
446
- // wipefs retained: rarely legitimate in an agent context and not in built-ins.
447
- dangerousWords: ["wipefs"]
283
+ var BUILTIN_DIR = import_path2.default.join(__dirname, "shields", "builtin");
284
+ var USER_SHIELDS_DIR = import_path2.default.join(import_os2.default.homedir(), ".node9", "shields");
285
+ function validateShieldDefinition(raw, filePath) {
286
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
287
+ process.stderr.write(`[node9] Shield file is not an object: ${filePath}
288
+ `);
289
+ return null;
448
290
  }
449
- };
291
+ const r = raw;
292
+ if (typeof r.name !== "string" || !r.name) {
293
+ process.stderr.write(`[node9] Shield file missing 'name': ${filePath}
294
+ `);
295
+ return null;
296
+ }
297
+ if (typeof r.description !== "string") {
298
+ process.stderr.write(`[node9] Shield file missing 'description': ${filePath}
299
+ `);
300
+ return null;
301
+ }
302
+ if (!Array.isArray(r.aliases)) {
303
+ process.stderr.write(`[node9] Shield file missing 'aliases' array: ${filePath}
304
+ `);
305
+ return null;
306
+ }
307
+ if (!Array.isArray(r.smartRules)) {
308
+ process.stderr.write(`[node9] Shield file missing 'smartRules' array: ${filePath}
309
+ `);
310
+ return null;
311
+ }
312
+ if (!Array.isArray(r.dangerousWords)) {
313
+ process.stderr.write(`[node9] Shield file missing 'dangerousWords' array: ${filePath}
314
+ `);
315
+ return null;
316
+ }
317
+ return r;
318
+ }
319
+ function loadShieldsFromDir(dir, label) {
320
+ const result = {};
321
+ let entries;
322
+ try {
323
+ entries = import_fs2.default.readdirSync(dir).filter((f) => f.endsWith(".json"));
324
+ } catch (err) {
325
+ if (err.code !== "ENOENT") {
326
+ process.stderr.write(`[node9] Could not read ${label} shields dir ${dir}: ${String(err)}
327
+ `);
328
+ }
329
+ return result;
330
+ }
331
+ for (const file of entries) {
332
+ const filePath = import_path2.default.join(dir, file);
333
+ try {
334
+ const raw = JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8"));
335
+ const shield = validateShieldDefinition(raw, filePath);
336
+ if (shield) result[shield.name] = shield;
337
+ } catch (err) {
338
+ process.stderr.write(`[node9] Failed to load ${label} shield ${file}: ${String(err)}
339
+ `);
340
+ }
341
+ }
342
+ return result;
343
+ }
344
+ function buildSHIELDS() {
345
+ const builtins = loadShieldsFromDir(BUILTIN_DIR, "builtin");
346
+ const userShields = loadShieldsFromDir(USER_SHIELDS_DIR, "user");
347
+ return { ...builtins, ...userShields };
348
+ }
349
+ var SHIELDS = buildSHIELDS();
450
350
  function resolveShieldName(input) {
451
351
  const lower = input.toLowerCase();
452
352
  if (SHIELDS[lower]) return lower;
@@ -2070,7 +1970,7 @@ function isDaemonRunning() {
2070
1970
  return false;
2071
1971
  }
2072
1972
  }
2073
- async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd, recoveryCommand, skipBackgroundAuth, viewOnly) {
1973
+ async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd, recoveryCommand, skipBackgroundAuth, viewOnly, localSmartRuleMatched) {
2074
1974
  const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
2075
1975
  const ctrl = new AbortController();
2076
1976
  const timer = setTimeout(() => ctrl.abort(), 5e3);
@@ -2091,7 +1991,8 @@ async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityI
2091
1991
  ...cwd && { cwd },
2092
1992
  ...recoveryCommand && { recoveryCommand },
2093
1993
  ...skipBackgroundAuth && { skipBackgroundAuth: true },
2094
- ...viewOnly && { viewOnly: true }
1994
+ ...viewOnly && { viewOnly: true },
1995
+ ...localSmartRuleMatched && { localSmartRuleMatched: true }
2095
1996
  }),
2096
1997
  signal: ctrl.signal
2097
1998
  });
@@ -2759,6 +2660,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2759
2660
  let policyMatchedWord;
2760
2661
  let riskMetadata;
2761
2662
  let statefulRecoveryCommand;
2663
+ let localSmartRuleMatched = false;
2762
2664
  let taintWarning = null;
2763
2665
  if (isNetworkTool(toolName, args)) {
2764
2666
  const filePaths = extractFilePaths(toolName, args);
@@ -2902,6 +2804,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2902
2804
  explainableLabel = policyResult.blockedByLabel || "Local Config";
2903
2805
  policyMatchedField = policyResult.matchedField;
2904
2806
  policyMatchedWord = policyResult.matchedWord;
2807
+ if (policyResult.ruleName) localSmartRuleMatched = true;
2905
2808
  riskMetadata = computeRiskMetadata(
2906
2809
  args,
2907
2810
  policyResult.tier ?? 6,
@@ -2944,22 +2847,26 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2944
2847
  }
2945
2848
  let cloudRequestId = null;
2946
2849
  const cloudEnforced = approvers.cloud && !!creds?.apiKey;
2947
- if (cloudEnforced) {
2850
+ if (cloudEnforced && !localSmartRuleMatched && !options?.localSmartRuleMatched) {
2948
2851
  try {
2949
2852
  const initResult = await initNode9SaaS(toolName, args, creds, meta, riskMetadata);
2950
2853
  if (!initResult.pending) {
2951
2854
  if (initResult.shadowMode) {
2952
2855
  return { approved: true, checkedBy: "cloud" };
2953
2856
  }
2954
- return {
2955
- approved: !!initResult.approved,
2956
- reason: initResult.reason || (initResult.approved ? void 0 : "Action rejected by organization policy."),
2957
- checkedBy: initResult.approved ? "cloud" : void 0,
2958
- blockedBy: initResult.approved ? void 0 : "team-policy",
2959
- blockedByLabel: "Organization Policy (SaaS)"
2960
- };
2857
+ if (!localSmartRuleMatched && !options?.localSmartRuleMatched) {
2858
+ return {
2859
+ approved: !!initResult.approved,
2860
+ reason: initResult.reason || (initResult.approved ? void 0 : "Action rejected by organization policy."),
2861
+ checkedBy: initResult.approved ? "cloud" : void 0,
2862
+ blockedBy: initResult.approved ? void 0 : "team-policy",
2863
+ blockedByLabel: "Organization Policy (SaaS)"
2864
+ };
2865
+ }
2866
+ }
2867
+ if (!localSmartRuleMatched && !options?.localSmartRuleMatched) {
2868
+ cloudRequestId = initResult.requestId || null;
2961
2869
  }
2962
- cloudRequestId = initResult.requestId || null;
2963
2870
  if (!taintWarning) explainableLabel = "Organization Policy (SaaS)";
2964
2871
  } catch {
2965
2872
  }
@@ -3005,7 +2912,10 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3005
2912
  riskMetadata,
3006
2913
  options?.activityId,
3007
2914
  options?.cwd,
3008
- statefulRecoveryCommand
2915
+ statefulRecoveryCommand,
2916
+ void 0,
2917
+ void 0,
2918
+ localSmartRuleMatched || options?.localSmartRuleMatched
3009
2919
  );
3010
2920
  daemonEntryId = entry.id;
3011
2921
  daemonAllowCount = entry.allowCount;
@@ -3013,7 +2923,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3013
2923
  }
3014
2924
  }
3015
2925
  }
3016
- if (cloudEnforced && cloudRequestId) {
2926
+ if (cloudEnforced && cloudRequestId && !localSmartRuleMatched && !options?.localSmartRuleMatched) {
3017
2927
  racePromises.push(
3018
2928
  (async () => {
3019
2929
  try {
package/dist/index.mjs CHANGED
@@ -250,173 +250,73 @@ ${lines.join("\n")}`
250
250
  import fs2 from "fs";
251
251
  import path2 from "path";
252
252
  import os2 from "os";
253
- var SHIELDS = {
254
- postgres: {
255
- name: "postgres",
256
- description: "Protects PostgreSQL databases from destructive AI operations",
257
- aliases: ["pg", "postgresql"],
258
- smartRules: [
259
- {
260
- name: "shield:postgres:block-drop-table",
261
- tool: "*",
262
- conditions: [{ field: "sql", op: "matches", value: "DROP\\s+TABLE", flags: "i" }],
263
- verdict: "block",
264
- reason: "DROP TABLE is irreversible \u2014 blocked by Postgres shield"
265
- },
266
- {
267
- name: "shield:postgres:block-truncate",
268
- tool: "*",
269
- conditions: [{ field: "sql", op: "matches", value: "TRUNCATE\\s+TABLE", flags: "i" }],
270
- verdict: "block",
271
- reason: "TRUNCATE is irreversible \u2014 blocked by Postgres shield"
272
- },
273
- {
274
- name: "shield:postgres:block-drop-column",
275
- tool: "*",
276
- conditions: [
277
- { field: "sql", op: "matches", value: "ALTER\\s+TABLE.*DROP\\s+COLUMN", flags: "i" }
278
- ],
279
- verdict: "block",
280
- reason: "DROP COLUMN is irreversible \u2014 blocked by Postgres shield"
281
- },
282
- {
283
- name: "shield:postgres:review-grant-revoke",
284
- tool: "*",
285
- conditions: [{ field: "sql", op: "matches", value: "\\b(GRANT|REVOKE)\\b", flags: "i" }],
286
- verdict: "review",
287
- reason: "Permission changes require human approval (Postgres shield)"
288
- }
289
- ],
290
- dangerousWords: ["dropdb", "pg_dropcluster"]
291
- },
292
- github: {
293
- name: "github",
294
- description: "Protects GitHub repositories from destructive AI operations",
295
- aliases: ["git"],
296
- smartRules: [
297
- {
298
- // Note: git branch -d/-D is already caught by the built-in review-git-destructive rule.
299
- // This rule adds coverage for `git push --delete` which the built-in does not match.
300
- name: "shield:github:review-delete-branch-remote",
301
- tool: "bash",
302
- conditions: [
303
- {
304
- field: "command",
305
- op: "matches",
306
- value: "git\\s+push\\s+.*--delete",
307
- flags: "i"
308
- }
309
- ],
310
- verdict: "review",
311
- reason: "Remote branch deletion requires human approval (GitHub shield)"
312
- },
313
- {
314
- name: "shield:github:block-delete-repo",
315
- tool: "*",
316
- conditions: [
317
- { field: "command", op: "matches", value: "gh\\s+repo\\s+delete", flags: "i" }
318
- ],
319
- verdict: "block",
320
- reason: "Repository deletion is irreversible \u2014 blocked by GitHub shield"
321
- }
322
- ],
323
- dangerousWords: []
324
- },
325
- aws: {
326
- name: "aws",
327
- description: "Protects AWS infrastructure from destructive AI operations",
328
- aliases: ["amazon"],
329
- smartRules: [
330
- {
331
- name: "shield:aws:block-delete-s3-bucket",
332
- tool: "*",
333
- conditions: [
334
- {
335
- field: "command",
336
- op: "matches",
337
- value: "aws\\s+s3.*rb\\s|aws\\s+s3api\\s+delete-bucket",
338
- flags: "i"
339
- }
340
- ],
341
- verdict: "block",
342
- reason: "S3 bucket deletion is irreversible \u2014 blocked by AWS shield"
343
- },
344
- {
345
- name: "shield:aws:review-iam-changes",
346
- tool: "*",
347
- conditions: [
348
- {
349
- field: "command",
350
- op: "matches",
351
- value: "aws\\s+iam\\s+(create|delete|attach|detach|put|remove)",
352
- flags: "i"
353
- }
354
- ],
355
- verdict: "review",
356
- reason: "IAM changes require human approval (AWS shield)"
357
- },
358
- {
359
- name: "shield:aws:block-ec2-terminate",
360
- tool: "*",
361
- conditions: [
362
- {
363
- field: "command",
364
- op: "matches",
365
- value: "aws\\s+ec2\\s+terminate-instances",
366
- flags: "i"
367
- }
368
- ],
369
- verdict: "block",
370
- reason: "EC2 instance termination is irreversible \u2014 blocked by AWS shield"
371
- },
372
- {
373
- name: "shield:aws:review-rds-delete",
374
- tool: "*",
375
- conditions: [
376
- { field: "command", op: "matches", value: "aws\\s+rds\\s+delete-", flags: "i" }
377
- ],
378
- verdict: "review",
379
- reason: "RDS deletion requires human approval (AWS shield)"
380
- }
381
- ],
382
- dangerousWords: []
383
- },
384
- filesystem: {
385
- name: "filesystem",
386
- description: "Protects the local filesystem from dangerous AI operations",
387
- aliases: ["fs"],
388
- smartRules: [
389
- {
390
- name: "shield:filesystem:review-chmod-777",
391
- tool: "bash",
392
- conditions: [
393
- { field: "command", op: "matches", value: "chmod\\s+(777|a\\+rwx)", flags: "i" }
394
- ],
395
- verdict: "review",
396
- reason: "chmod 777 requires human approval (filesystem shield)"
397
- },
398
- {
399
- name: "shield:filesystem:review-write-etc",
400
- tool: "bash",
401
- conditions: [
402
- {
403
- field: "command",
404
- // Narrow to write-indicative operations to avoid approval fatigue on reads.
405
- // Matches: tee /etc/*, cp .../etc/*, mv .../etc/*, > /etc/*, install .../etc/*
406
- op: "matches",
407
- value: "(tee|\\bcp\\b|\\bmv\\b|install|>+)\\s+.*\\/etc\\/"
408
- }
409
- ],
410
- verdict: "review",
411
- reason: "Writing to /etc requires human approval (filesystem shield)"
412
- }
413
- ],
414
- // dd removed: too common as a legitimate tool (disk imaging, file ops).
415
- // mkfs removed: already in the built-in DANGEROUS_WORDS baseline.
416
- // wipefs retained: rarely legitimate in an agent context and not in built-ins.
417
- dangerousWords: ["wipefs"]
253
+ var BUILTIN_DIR = path2.join(__dirname, "shields", "builtin");
254
+ var USER_SHIELDS_DIR = path2.join(os2.homedir(), ".node9", "shields");
255
+ function validateShieldDefinition(raw, filePath) {
256
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
257
+ process.stderr.write(`[node9] Shield file is not an object: ${filePath}
258
+ `);
259
+ return null;
418
260
  }
419
- };
261
+ const r = raw;
262
+ if (typeof r.name !== "string" || !r.name) {
263
+ process.stderr.write(`[node9] Shield file missing 'name': ${filePath}
264
+ `);
265
+ return null;
266
+ }
267
+ if (typeof r.description !== "string") {
268
+ process.stderr.write(`[node9] Shield file missing 'description': ${filePath}
269
+ `);
270
+ return null;
271
+ }
272
+ if (!Array.isArray(r.aliases)) {
273
+ process.stderr.write(`[node9] Shield file missing 'aliases' array: ${filePath}
274
+ `);
275
+ return null;
276
+ }
277
+ if (!Array.isArray(r.smartRules)) {
278
+ process.stderr.write(`[node9] Shield file missing 'smartRules' array: ${filePath}
279
+ `);
280
+ return null;
281
+ }
282
+ if (!Array.isArray(r.dangerousWords)) {
283
+ process.stderr.write(`[node9] Shield file missing 'dangerousWords' array: ${filePath}
284
+ `);
285
+ return null;
286
+ }
287
+ return r;
288
+ }
289
+ function loadShieldsFromDir(dir, label) {
290
+ const result = {};
291
+ let entries;
292
+ try {
293
+ entries = fs2.readdirSync(dir).filter((f) => f.endsWith(".json"));
294
+ } catch (err) {
295
+ if (err.code !== "ENOENT") {
296
+ process.stderr.write(`[node9] Could not read ${label} shields dir ${dir}: ${String(err)}
297
+ `);
298
+ }
299
+ return result;
300
+ }
301
+ for (const file of entries) {
302
+ const filePath = path2.join(dir, file);
303
+ try {
304
+ const raw = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
305
+ const shield = validateShieldDefinition(raw, filePath);
306
+ if (shield) result[shield.name] = shield;
307
+ } catch (err) {
308
+ process.stderr.write(`[node9] Failed to load ${label} shield ${file}: ${String(err)}
309
+ `);
310
+ }
311
+ }
312
+ return result;
313
+ }
314
+ function buildSHIELDS() {
315
+ const builtins = loadShieldsFromDir(BUILTIN_DIR, "builtin");
316
+ const userShields = loadShieldsFromDir(USER_SHIELDS_DIR, "user");
317
+ return { ...builtins, ...userShields };
318
+ }
319
+ var SHIELDS = buildSHIELDS();
420
320
  function resolveShieldName(input) {
421
321
  const lower = input.toLowerCase();
422
322
  if (SHIELDS[lower]) return lower;
@@ -2040,7 +1940,7 @@ function isDaemonRunning() {
2040
1940
  return false;
2041
1941
  }
2042
1942
  }
2043
- async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd, recoveryCommand, skipBackgroundAuth, viewOnly) {
1943
+ async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityId, cwd, recoveryCommand, skipBackgroundAuth, viewOnly, localSmartRuleMatched) {
2044
1944
  const base = `http://${DAEMON_HOST}:${DAEMON_PORT}`;
2045
1945
  const ctrl = new AbortController();
2046
1946
  const timer = setTimeout(() => ctrl.abort(), 5e3);
@@ -2061,7 +1961,8 @@ async function registerDaemonEntry(toolName, args, meta, riskMetadata, activityI
2061
1961
  ...cwd && { cwd },
2062
1962
  ...recoveryCommand && { recoveryCommand },
2063
1963
  ...skipBackgroundAuth && { skipBackgroundAuth: true },
2064
- ...viewOnly && { viewOnly: true }
1964
+ ...viewOnly && { viewOnly: true },
1965
+ ...localSmartRuleMatched && { localSmartRuleMatched: true }
2065
1966
  }),
2066
1967
  signal: ctrl.signal
2067
1968
  });
@@ -2729,6 +2630,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2729
2630
  let policyMatchedWord;
2730
2631
  let riskMetadata;
2731
2632
  let statefulRecoveryCommand;
2633
+ let localSmartRuleMatched = false;
2732
2634
  let taintWarning = null;
2733
2635
  if (isNetworkTool(toolName, args)) {
2734
2636
  const filePaths = extractFilePaths(toolName, args);
@@ -2872,6 +2774,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2872
2774
  explainableLabel = policyResult.blockedByLabel || "Local Config";
2873
2775
  policyMatchedField = policyResult.matchedField;
2874
2776
  policyMatchedWord = policyResult.matchedWord;
2777
+ if (policyResult.ruleName) localSmartRuleMatched = true;
2875
2778
  riskMetadata = computeRiskMetadata(
2876
2779
  args,
2877
2780
  policyResult.tier ?? 6,
@@ -2914,22 +2817,26 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2914
2817
  }
2915
2818
  let cloudRequestId = null;
2916
2819
  const cloudEnforced = approvers.cloud && !!creds?.apiKey;
2917
- if (cloudEnforced) {
2820
+ if (cloudEnforced && !localSmartRuleMatched && !options?.localSmartRuleMatched) {
2918
2821
  try {
2919
2822
  const initResult = await initNode9SaaS(toolName, args, creds, meta, riskMetadata);
2920
2823
  if (!initResult.pending) {
2921
2824
  if (initResult.shadowMode) {
2922
2825
  return { approved: true, checkedBy: "cloud" };
2923
2826
  }
2924
- return {
2925
- approved: !!initResult.approved,
2926
- reason: initResult.reason || (initResult.approved ? void 0 : "Action rejected by organization policy."),
2927
- checkedBy: initResult.approved ? "cloud" : void 0,
2928
- blockedBy: initResult.approved ? void 0 : "team-policy",
2929
- blockedByLabel: "Organization Policy (SaaS)"
2930
- };
2827
+ if (!localSmartRuleMatched && !options?.localSmartRuleMatched) {
2828
+ return {
2829
+ approved: !!initResult.approved,
2830
+ reason: initResult.reason || (initResult.approved ? void 0 : "Action rejected by organization policy."),
2831
+ checkedBy: initResult.approved ? "cloud" : void 0,
2832
+ blockedBy: initResult.approved ? void 0 : "team-policy",
2833
+ blockedByLabel: "Organization Policy (SaaS)"
2834
+ };
2835
+ }
2836
+ }
2837
+ if (!localSmartRuleMatched && !options?.localSmartRuleMatched) {
2838
+ cloudRequestId = initResult.requestId || null;
2931
2839
  }
2932
- cloudRequestId = initResult.requestId || null;
2933
2840
  if (!taintWarning) explainableLabel = "Organization Policy (SaaS)";
2934
2841
  } catch {
2935
2842
  }
@@ -2975,7 +2882,10 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2975
2882
  riskMetadata,
2976
2883
  options?.activityId,
2977
2884
  options?.cwd,
2978
- statefulRecoveryCommand
2885
+ statefulRecoveryCommand,
2886
+ void 0,
2887
+ void 0,
2888
+ localSmartRuleMatched || options?.localSmartRuleMatched
2979
2889
  );
2980
2890
  daemonEntryId = entry.id;
2981
2891
  daemonAllowCount = entry.allowCount;
@@ -2983,7 +2893,7 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
2983
2893
  }
2984
2894
  }
2985
2895
  }
2986
- if (cloudEnforced && cloudRequestId) {
2896
+ if (cloudEnforced && cloudRequestId && !localSmartRuleMatched && !options?.localSmartRuleMatched) {
2987
2897
  racePromises.push(
2988
2898
  (async () => {
2989
2899
  try {