@node9/proxy 1.13.1 → 1.14.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
@@ -126,6 +126,7 @@ function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashAr
126
126
  ...testRun,
127
127
  agent: meta?.agent,
128
128
  mcpServer: meta?.mcpServer,
129
+ sessionId: meta?.sessionId,
129
130
  hostname: import_os.default.hostname()
130
131
  });
131
132
  }
@@ -3101,14 +3102,61 @@ var import_fs9 = __toESM(require("fs"));
3101
3102
  var import_os8 = __toESM(require("os"));
3102
3103
  var import_path13 = __toESM(require("path"));
3103
3104
  init_audit();
3104
- function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
3105
- return fetch(`${creds.apiUrl}/audit`, {
3105
+ var DLP_SAMPLE_MAX_LEN = 200;
3106
+ var DLP_PATTERN_MAX_LEN = 100;
3107
+ var KNOWN_CHECKED_BY = /* @__PURE__ */ new Set([
3108
+ "dlp-block",
3109
+ "observe-mode-dlp-would-block",
3110
+ "dlp-review-flagged",
3111
+ "loop-detected",
3112
+ "audit-mode",
3113
+ "local-policy",
3114
+ "smart-rule-block",
3115
+ "persistent",
3116
+ "trust",
3117
+ "observe-mode",
3118
+ "observe-mode-would-block"
3119
+ ]);
3120
+ function validateApiUrl(raw) {
3121
+ let u;
3122
+ try {
3123
+ u = new URL(raw);
3124
+ } catch {
3125
+ return null;
3126
+ }
3127
+ if (u.username || u.password) return null;
3128
+ if (u.protocol === "https:") return u;
3129
+ if (u.protocol === "http:") {
3130
+ const h = u.hostname;
3131
+ if (h === "127.0.0.1" || h === "localhost" || h === "::1" || h === "[::1]") return u;
3132
+ }
3133
+ return null;
3134
+ }
3135
+ function auditLocalAllow(toolName, args, checkedBy, creds, meta, dlpInfo, containsSensitiveArgs = false) {
3136
+ const validated = validateApiUrl(creds.apiUrl);
3137
+ if (!validated) {
3138
+ try {
3139
+ import_fs9.default.appendFileSync(
3140
+ HOOK_DEBUG_LOG,
3141
+ `[audit] refused to send: invalid apiUrl scheme/host (got "${String(creds.apiUrl).slice(0, 200)}")
3142
+ `
3143
+ );
3144
+ } catch {
3145
+ }
3146
+ return Promise.resolve();
3147
+ }
3148
+ const safeArgs = containsSensitiveArgs ? { tool: toolName, redacted: true } : args;
3149
+ const dlpSample = dlpInfo && typeof dlpInfo.redactedSample === "string" ? dlpInfo.redactedSample.slice(0, DLP_SAMPLE_MAX_LEN) : void 0;
3150
+ const dlpPattern = dlpInfo && typeof dlpInfo.pattern === "string" ? dlpInfo.pattern.slice(0, DLP_PATTERN_MAX_LEN) : void 0;
3151
+ const safeCheckedBy = KNOWN_CHECKED_BY.has(checkedBy) ? checkedBy : "unknown";
3152
+ return fetch(`${validated.toString().replace(/\/$/, "")}/audit`, {
3106
3153
  method: "POST",
3107
3154
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${creds.apiKey}` },
3108
3155
  body: JSON.stringify({
3109
3156
  toolName,
3110
- args,
3111
- checkedBy,
3157
+ args: safeArgs,
3158
+ checkedBy: safeCheckedBy,
3159
+ ...dlpInfo && { dlpPattern, dlpSample },
3112
3160
  context: {
3113
3161
  agent: meta?.agent,
3114
3162
  mcpServer: meta?.mcpServer,
@@ -3432,6 +3480,16 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3432
3480
  meta,
3433
3481
  true
3434
3482
  );
3483
+ if (approvers.cloud && creds?.apiKey)
3484
+ auditLocalAllow(
3485
+ toolName,
3486
+ args,
3487
+ isObserveMode ? "observe-mode-dlp-would-block" : "dlp-block",
3488
+ creds,
3489
+ meta,
3490
+ { pattern: dlpMatch.patternName, redactedSample: dlpMatch.redactedSample },
3491
+ true
3492
+ );
3435
3493
  if (isWriteTool(toolName) && filePath) {
3436
3494
  await notifyTaint(filePath, `DLP:${dlpMatch.patternName}`);
3437
3495
  }
@@ -3500,6 +3558,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3500
3558
  const reason = `It looks like you've called "${toolName}" ${loopResult.count} times with identical arguments in the last ${ld.windowSeconds}s. Are you stuck? Step back and reconsider your approach \u2014 what are you actually trying to accomplish, and is there a different way to get there?`;
3501
3559
  if (!isManual)
3502
3560
  appendLocalAudit(toolName, args, "deny", "loop-detected", meta, hashAuditArgs);
3561
+ if (approvers.cloud && creds?.apiKey)
3562
+ auditLocalAllow(toolName, args, "loop-detected", creds, meta, void 0, true);
3503
3563
  return {
3504
3564
  approved: false,
3505
3565
  reason,
@@ -3586,8 +3646,22 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3586
3646
  };
3587
3647
  }
3588
3648
  } else if (!taintWarning) {
3589
- if (!isManual) appendLocalAudit(toolName, args, "allow", "ignored", meta, hashAuditArgs);
3590
- return { approved: true };
3649
+ const toolLower = toolName.toLowerCase();
3650
+ const isFileTool = toolLower === "read" || toolLower === "grep" || toolLower === "glob" || toolLower === "read_file" || toolLower === "grep_search" || toolLower === "list_files";
3651
+ if (isFileTool && readActiveShields().includes("project-jail")) {
3652
+ const argsObj = args && typeof args === "object" && !Array.isArray(args) ? args : {};
3653
+ const filePath = String(
3654
+ argsObj.file_path ?? argsObj.path ?? argsObj.pattern ?? argsObj.filename ?? ""
3655
+ );
3656
+ if (filePath && scanFilePath(filePath)) {
3657
+ } else {
3658
+ if (!isManual) appendLocalAudit(toolName, args, "allow", "ignored", meta, hashAuditArgs);
3659
+ return { approved: true };
3660
+ }
3661
+ } else {
3662
+ if (!isManual) appendLocalAudit(toolName, args, "allow", "ignored", meta, hashAuditArgs);
3663
+ return { approved: true };
3664
+ }
3591
3665
  }
3592
3666
  if (!taintWarning && getActiveTrustSession(toolName, args)) {
3593
3667
  if (approvers.cloud && creds?.apiKey)
package/dist/index.mjs CHANGED
@@ -106,6 +106,7 @@ function appendLocalAudit(toolName, args, decision, checkedBy, meta, auditHashAr
106
106
  ...testRun,
107
107
  agent: meta?.agent,
108
108
  mcpServer: meta?.mcpServer,
109
+ sessionId: meta?.sessionId,
109
110
  hostname: os.hostname()
110
111
  });
111
112
  }
@@ -3071,14 +3072,61 @@ init_audit();
3071
3072
  import fs9 from "fs";
3072
3073
  import os8 from "os";
3073
3074
  import path13 from "path";
3074
- function auditLocalAllow(toolName, args, checkedBy, creds, meta) {
3075
- return fetch(`${creds.apiUrl}/audit`, {
3075
+ var DLP_SAMPLE_MAX_LEN = 200;
3076
+ var DLP_PATTERN_MAX_LEN = 100;
3077
+ var KNOWN_CHECKED_BY = /* @__PURE__ */ new Set([
3078
+ "dlp-block",
3079
+ "observe-mode-dlp-would-block",
3080
+ "dlp-review-flagged",
3081
+ "loop-detected",
3082
+ "audit-mode",
3083
+ "local-policy",
3084
+ "smart-rule-block",
3085
+ "persistent",
3086
+ "trust",
3087
+ "observe-mode",
3088
+ "observe-mode-would-block"
3089
+ ]);
3090
+ function validateApiUrl(raw) {
3091
+ let u;
3092
+ try {
3093
+ u = new URL(raw);
3094
+ } catch {
3095
+ return null;
3096
+ }
3097
+ if (u.username || u.password) return null;
3098
+ if (u.protocol === "https:") return u;
3099
+ if (u.protocol === "http:") {
3100
+ const h = u.hostname;
3101
+ if (h === "127.0.0.1" || h === "localhost" || h === "::1" || h === "[::1]") return u;
3102
+ }
3103
+ return null;
3104
+ }
3105
+ function auditLocalAllow(toolName, args, checkedBy, creds, meta, dlpInfo, containsSensitiveArgs = false) {
3106
+ const validated = validateApiUrl(creds.apiUrl);
3107
+ if (!validated) {
3108
+ try {
3109
+ fs9.appendFileSync(
3110
+ HOOK_DEBUG_LOG,
3111
+ `[audit] refused to send: invalid apiUrl scheme/host (got "${String(creds.apiUrl).slice(0, 200)}")
3112
+ `
3113
+ );
3114
+ } catch {
3115
+ }
3116
+ return Promise.resolve();
3117
+ }
3118
+ const safeArgs = containsSensitiveArgs ? { tool: toolName, redacted: true } : args;
3119
+ const dlpSample = dlpInfo && typeof dlpInfo.redactedSample === "string" ? dlpInfo.redactedSample.slice(0, DLP_SAMPLE_MAX_LEN) : void 0;
3120
+ const dlpPattern = dlpInfo && typeof dlpInfo.pattern === "string" ? dlpInfo.pattern.slice(0, DLP_PATTERN_MAX_LEN) : void 0;
3121
+ const safeCheckedBy = KNOWN_CHECKED_BY.has(checkedBy) ? checkedBy : "unknown";
3122
+ return fetch(`${validated.toString().replace(/\/$/, "")}/audit`, {
3076
3123
  method: "POST",
3077
3124
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${creds.apiKey}` },
3078
3125
  body: JSON.stringify({
3079
3126
  toolName,
3080
- args,
3081
- checkedBy,
3127
+ args: safeArgs,
3128
+ checkedBy: safeCheckedBy,
3129
+ ...dlpInfo && { dlpPattern, dlpSample },
3082
3130
  context: {
3083
3131
  agent: meta?.agent,
3084
3132
  mcpServer: meta?.mcpServer,
@@ -3402,6 +3450,16 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3402
3450
  meta,
3403
3451
  true
3404
3452
  );
3453
+ if (approvers.cloud && creds?.apiKey)
3454
+ auditLocalAllow(
3455
+ toolName,
3456
+ args,
3457
+ isObserveMode ? "observe-mode-dlp-would-block" : "dlp-block",
3458
+ creds,
3459
+ meta,
3460
+ { pattern: dlpMatch.patternName, redactedSample: dlpMatch.redactedSample },
3461
+ true
3462
+ );
3405
3463
  if (isWriteTool(toolName) && filePath) {
3406
3464
  await notifyTaint(filePath, `DLP:${dlpMatch.patternName}`);
3407
3465
  }
@@ -3470,6 +3528,8 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3470
3528
  const reason = `It looks like you've called "${toolName}" ${loopResult.count} times with identical arguments in the last ${ld.windowSeconds}s. Are you stuck? Step back and reconsider your approach \u2014 what are you actually trying to accomplish, and is there a different way to get there?`;
3471
3529
  if (!isManual)
3472
3530
  appendLocalAudit(toolName, args, "deny", "loop-detected", meta, hashAuditArgs);
3531
+ if (approvers.cloud && creds?.apiKey)
3532
+ auditLocalAllow(toolName, args, "loop-detected", creds, meta, void 0, true);
3473
3533
  return {
3474
3534
  approved: false,
3475
3535
  reason,
@@ -3556,8 +3616,22 @@ async function _authorizeHeadlessCore(toolName, args, meta, options) {
3556
3616
  };
3557
3617
  }
3558
3618
  } else if (!taintWarning) {
3559
- if (!isManual) appendLocalAudit(toolName, args, "allow", "ignored", meta, hashAuditArgs);
3560
- return { approved: true };
3619
+ const toolLower = toolName.toLowerCase();
3620
+ const isFileTool = toolLower === "read" || toolLower === "grep" || toolLower === "glob" || toolLower === "read_file" || toolLower === "grep_search" || toolLower === "list_files";
3621
+ if (isFileTool && readActiveShields().includes("project-jail")) {
3622
+ const argsObj = args && typeof args === "object" && !Array.isArray(args) ? args : {};
3623
+ const filePath = String(
3624
+ argsObj.file_path ?? argsObj.path ?? argsObj.pattern ?? argsObj.filename ?? ""
3625
+ );
3626
+ if (filePath && scanFilePath(filePath)) {
3627
+ } else {
3628
+ if (!isManual) appendLocalAudit(toolName, args, "allow", "ignored", meta, hashAuditArgs);
3629
+ return { approved: true };
3630
+ }
3631
+ } else {
3632
+ if (!isManual) appendLocalAudit(toolName, args, "allow", "ignored", meta, hashAuditArgs);
3633
+ return { approved: true };
3634
+ }
3561
3635
  }
3562
3636
  if (!taintWarning && getActiveTrustSession(toolName, args)) {
3563
3637
  if (approvers.cloud && creds?.apiKey)
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "project-jail",
3
+ "description": "Restricts AI agents from reading sensitive credential files outside the current project",
4
+ "aliases": ["jail"],
5
+ "smartRules": [
6
+ {
7
+ "name": "shield:project-jail:block-read-ssh",
8
+ "tool": "bash",
9
+ "conditions": [
10
+ {
11
+ "field": "command",
12
+ "op": "matches",
13
+ "value": "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*[\\/\\\\]\\.ssh[\\/\\\\]",
14
+ "flags": "i"
15
+ }
16
+ ],
17
+ "verdict": "block",
18
+ "reason": "Reading SSH private keys is blocked by project-jail shield"
19
+ },
20
+ {
21
+ "name": "shield:project-jail:block-read-aws",
22
+ "tool": "bash",
23
+ "conditions": [
24
+ {
25
+ "field": "command",
26
+ "op": "matches",
27
+ "value": "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*[\\/\\\\]\\.aws[\\/\\\\]",
28
+ "flags": "i"
29
+ }
30
+ ],
31
+ "verdict": "block",
32
+ "reason": "Reading AWS credentials is blocked by project-jail shield"
33
+ },
34
+ {
35
+ "name": "shield:project-jail:block-read-env",
36
+ "tool": "bash",
37
+ "conditions": [
38
+ {
39
+ "field": "command",
40
+ "op": "matches",
41
+ "value": "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*\\.env(\\.local|\\.production|\\.staging)?\\b",
42
+ "flags": "i"
43
+ }
44
+ ],
45
+ "verdict": "block",
46
+ "reason": "Reading .env files is blocked by project-jail shield"
47
+ },
48
+ {
49
+ "name": "shield:project-jail:block-read-credentials",
50
+ "tool": "bash",
51
+ "conditions": [
52
+ {
53
+ "field": "command",
54
+ "op": "matches",
55
+ "value": "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
56
+ "flags": "i"
57
+ }
58
+ ],
59
+ "verdict": "block",
60
+ "reason": "Reading credential files is blocked by project-jail shield"
61
+ }
62
+ ],
63
+ "dangerousWords": []
64
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node9/proxy",
3
- "version": "1.13.1",
3
+ "version": "1.14.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",