@clawdstrike/openclaw 0.1.3 → 0.2.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.
Files changed (136) hide show
  1. package/README.md +11 -0
  2. package/dist/audit/adapter-logger.d.ts +3 -3
  3. package/dist/audit/adapter-logger.d.ts.map +1 -1
  4. package/dist/audit/adapter-logger.js +3 -3
  5. package/dist/audit/adapter-logger.js.map +1 -1
  6. package/dist/audit/store.d.ts +2 -2
  7. package/dist/audit/store.d.ts.map +1 -1
  8. package/dist/audit/store.js +13 -13
  9. package/dist/audit/store.js.map +1 -1
  10. package/dist/classification.d.ts +2 -2
  11. package/dist/classification.d.ts.map +1 -1
  12. package/dist/classification.js +96 -28
  13. package/dist/classification.js.map +1 -1
  14. package/dist/cli/bin.js +1 -1
  15. package/dist/cli/commands/audit.d.ts.map +1 -1
  16. package/dist/cli/commands/audit.js +29 -29
  17. package/dist/cli/commands/audit.js.map +1 -1
  18. package/dist/cli/commands/policy.d.ts.map +1 -1
  19. package/dist/cli/commands/policy.js +33 -33
  20. package/dist/cli/commands/policy.js.map +1 -1
  21. package/dist/cli/index.d.ts +1 -1
  22. package/dist/cli/index.d.ts.map +1 -1
  23. package/dist/cli/index.js +45 -56
  24. package/dist/cli/index.js.map +1 -1
  25. package/dist/config.d.ts +1 -1
  26. package/dist/config.d.ts.map +1 -1
  27. package/dist/config.js +9 -9
  28. package/dist/config.js.map +1 -1
  29. package/dist/e2e/openclaw-e2e.js +58 -49
  30. package/dist/e2e/openclaw-e2e.js.map +1 -1
  31. package/dist/engine-holder.d.ts +2 -2
  32. package/dist/engine-holder.js +1 -1
  33. package/dist/guards/egress.d.ts +2 -2
  34. package/dist/guards/egress.d.ts.map +1 -1
  35. package/dist/guards/egress.js +71 -73
  36. package/dist/guards/egress.js.map +1 -1
  37. package/dist/guards/forbidden-path.d.ts +2 -2
  38. package/dist/guards/forbidden-path.d.ts.map +1 -1
  39. package/dist/guards/forbidden-path.js +41 -43
  40. package/dist/guards/forbidden-path.js.map +1 -1
  41. package/dist/guards/index.d.ts +6 -6
  42. package/dist/guards/index.d.ts.map +1 -1
  43. package/dist/guards/index.js +5 -5
  44. package/dist/guards/index.js.map +1 -1
  45. package/dist/guards/patch-integrity.d.ts +2 -2
  46. package/dist/guards/patch-integrity.d.ts.map +1 -1
  47. package/dist/guards/patch-integrity.js +69 -70
  48. package/dist/guards/patch-integrity.js.map +1 -1
  49. package/dist/guards/secret-leak.d.ts +2 -2
  50. package/dist/guards/secret-leak.d.ts.map +1 -1
  51. package/dist/guards/secret-leak.js +81 -82
  52. package/dist/guards/secret-leak.js.map +1 -1
  53. package/dist/guards/types.d.ts +2 -2
  54. package/dist/guards/types.d.ts.map +1 -1
  55. package/dist/guards/types.js +4 -4
  56. package/dist/guards/types.js.map +1 -1
  57. package/dist/hooks/agent-bootstrap/handler.d.ts +1 -1
  58. package/dist/hooks/agent-bootstrap/handler.d.ts.map +1 -1
  59. package/dist/hooks/agent-bootstrap/handler.js +5 -5
  60. package/dist/hooks/agent-bootstrap/handler.js.map +1 -1
  61. package/dist/hooks/approval-state.d.ts +1 -1
  62. package/dist/hooks/approval-state.d.ts.map +1 -1
  63. package/dist/hooks/approval-state.js +15 -15
  64. package/dist/hooks/approval-state.js.map +1 -1
  65. package/dist/hooks/approval-utils.d.ts +1 -1
  66. package/dist/hooks/approval-utils.d.ts.map +1 -1
  67. package/dist/hooks/approval-utils.js +41 -20
  68. package/dist/hooks/approval-utils.js.map +1 -1
  69. package/dist/hooks/audit-logger/handler.d.ts +1 -1
  70. package/dist/hooks/audit-logger/handler.d.ts.map +1 -1
  71. package/dist/hooks/audit-logger/handler.js +9 -9
  72. package/dist/hooks/audit-logger/handler.js.map +1 -1
  73. package/dist/hooks/cua-bridge/handler.d.ts +4 -4
  74. package/dist/hooks/cua-bridge/handler.d.ts.map +1 -1
  75. package/dist/hooks/cua-bridge/handler.js +85 -70
  76. package/dist/hooks/cua-bridge/handler.js.map +1 -1
  77. package/dist/hooks/tool-guard/handler.d.ts +1 -1
  78. package/dist/hooks/tool-guard/handler.d.ts.map +1 -1
  79. package/dist/hooks/tool-guard/handler.js +112 -101
  80. package/dist/hooks/tool-guard/handler.js.map +1 -1
  81. package/dist/hooks/tool-preflight/handler.d.ts +2 -2
  82. package/dist/hooks/tool-preflight/handler.d.ts.map +1 -1
  83. package/dist/hooks/tool-preflight/handler.js +115 -91
  84. package/dist/hooks/tool-preflight/handler.js.map +1 -1
  85. package/dist/index.d.ts +16 -16
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +18 -18
  88. package/dist/index.js.map +1 -1
  89. package/dist/openclaw-adapter.d.ts +2 -2
  90. package/dist/openclaw-adapter.d.ts.map +1 -1
  91. package/dist/openclaw-adapter.js +4 -4
  92. package/dist/openclaw-adapter.js.map +1 -1
  93. package/dist/plugin.d.ts.map +1 -1
  94. package/dist/plugin.js +39 -40
  95. package/dist/plugin.js.map +1 -1
  96. package/dist/policy/engine.d.ts +1 -1
  97. package/dist/policy/engine.d.ts.map +1 -1
  98. package/dist/policy/engine.js +237 -221
  99. package/dist/policy/engine.js.map +1 -1
  100. package/dist/policy/index.d.ts +3 -3
  101. package/dist/policy/index.d.ts.map +1 -1
  102. package/dist/policy/index.js +3 -3
  103. package/dist/policy/index.js.map +1 -1
  104. package/dist/policy/loader.d.ts +1 -1
  105. package/dist/policy/loader.d.ts.map +1 -1
  106. package/dist/policy/loader.js +76 -63
  107. package/dist/policy/loader.js.map +1 -1
  108. package/dist/policy/validator.d.ts +1 -1
  109. package/dist/policy/validator.d.ts.map +1 -1
  110. package/dist/policy/validator.js +158 -151
  111. package/dist/policy/validator.js.map +1 -1
  112. package/dist/receipt/signer.d.ts +2 -2
  113. package/dist/receipt/signer.d.ts.map +1 -1
  114. package/dist/receipt/signer.js +12 -12
  115. package/dist/receipt/signer.js.map +1 -1
  116. package/dist/receipt/types.d.ts +2 -2
  117. package/dist/receipt/types.d.ts.map +1 -1
  118. package/dist/sanitizer/output-sanitizer.d.ts +1 -1
  119. package/dist/sanitizer/output-sanitizer.d.ts.map +1 -1
  120. package/dist/sanitizer/output-sanitizer.js +8 -8
  121. package/dist/sanitizer/output-sanitizer.js.map +1 -1
  122. package/dist/security-prompt.d.ts +1 -1
  123. package/dist/security-prompt.d.ts.map +1 -1
  124. package/dist/security-prompt.js +16 -12
  125. package/dist/security-prompt.js.map +1 -1
  126. package/dist/tools/policy-check.d.ts +3 -3
  127. package/dist/tools/policy-check.d.ts.map +1 -1
  128. package/dist/tools/policy-check.js +60 -52
  129. package/dist/tools/policy-check.js.map +1 -1
  130. package/dist/translator/openclaw-translator.d.ts +1 -1
  131. package/dist/translator/openclaw-translator.d.ts.map +1 -1
  132. package/dist/translator/openclaw-translator.js +100 -80
  133. package/dist/translator/openclaw-translator.js.map +1 -1
  134. package/dist/types.d.ts +11 -13
  135. package/dist/types.d.ts.map +1 -1
  136. package/package.json +9 -4
@@ -1,12 +1,12 @@
1
- import { homedir } from 'node:os';
2
- import path from 'node:path';
3
- import { parseNetworkTarget } from '@clawdstrike/adapter-core';
4
- import { createPolicyEngineFromPolicy } from '@clawdstrike/policy';
5
- import { mergeConfig } from '../config.js';
6
- import { EgressGuard, ForbiddenPathGuard, PatchIntegrityGuard, SecretLeakGuard } from '../guards/index.js';
7
- import { sanitizeOutputText } from '../sanitizer/output-sanitizer.js';
8
- import { loadPolicy } from './loader.js';
9
- import { validatePolicy } from './validator.js';
1
+ import { homedir } from "node:os";
2
+ import path from "node:path";
3
+ import { parseNetworkTarget } from "@clawdstrike/adapter-core";
4
+ import { createPolicyEngineFromPolicy } from "@clawdstrike/policy";
5
+ import { mergeConfig } from "../config.js";
6
+ import { EgressGuard, ForbiddenPathGuard, PatchIntegrityGuard, SecretLeakGuard, } from "../guards/index.js";
7
+ import { sanitizeOutputText } from "../sanitizer/output-sanitizer.js";
8
+ import { loadPolicy } from "./loader.js";
9
+ import { validatePolicy } from "./validator.js";
10
10
  function expandHome(p) {
11
11
  return p.replace(/^~(?=\/|$)/, homedir());
12
12
  }
@@ -14,10 +14,20 @@ function normalizePathForPrefix(p) {
14
14
  return path.resolve(expandHome(p));
15
15
  }
16
16
  function cleanPathToken(t) {
17
- return t.trim().replace(/^[("'`]+/, '').replace(/[)"'`;,\]}]+$/, '');
17
+ return t
18
+ .trim()
19
+ .replace(/^[("'`]+/, "")
20
+ .replace(/[)"'`;,\]}]+$/, "");
18
21
  }
19
22
  function isRedirectionOp(t) {
20
- return t === '>' || t === '>>' || t === '1>' || t === '1>>' || t === '2>' || t === '2>>' || t === '<' || t === '<<';
23
+ return (t === ">" ||
24
+ t === ">>" ||
25
+ t === "1>" ||
26
+ t === "1>>" ||
27
+ t === "2>" ||
28
+ t === "2>>" ||
29
+ t === "<" ||
30
+ t === "<<");
21
31
  }
22
32
  function splitInlineRedirection(t) {
23
33
  // Support forms like ">/path", "2>>/path", "<input".
@@ -32,39 +42,42 @@ function splitInlineRedirection(t) {
32
42
  function looksLikePathToken(t) {
33
43
  if (!t)
34
44
  return false;
35
- if (t.includes('://'))
45
+ if (t.includes("://"))
36
46
  return false;
37
- if (t.startsWith('/') || t.startsWith('~') || t.startsWith('./') || t.startsWith('../'))
47
+ if (t.startsWith("/") || t.startsWith("~") || t.startsWith("./") || t.startsWith("../"))
38
48
  return true;
39
- if (t === '.env' || t.startsWith('.env.'))
49
+ if (t === ".env" || t.startsWith(".env."))
40
50
  return true;
41
- if (t.includes('/.ssh/') || t.includes('/.aws/') || t.includes('/.gnupg/') || t.includes('/.kube/'))
51
+ if (t.includes("/.ssh/") ||
52
+ t.includes("/.aws/") ||
53
+ t.includes("/.gnupg/") ||
54
+ t.includes("/.kube/"))
42
55
  return true;
43
56
  return false;
44
57
  }
45
58
  const WRITE_PATH_FLAG_NAMES = new Set([
46
59
  // Common output flags
47
- 'o',
48
- 'out',
49
- 'output',
50
- 'outfile',
51
- 'output-file',
60
+ "o",
61
+ "out",
62
+ "output",
63
+ "outfile",
64
+ "output-file",
52
65
  // Common log file flags
53
- 'log-file',
54
- 'logfile',
55
- 'log-path',
56
- 'logpath',
66
+ "log-file",
67
+ "logfile",
68
+ "log-path",
69
+ "logpath",
57
70
  ]);
58
71
  function isWritePathFlagToken(t) {
59
72
  if (!t)
60
73
  return false;
61
- if (!t.startsWith('-'))
74
+ if (!t.startsWith("-"))
62
75
  return false;
63
- const normalized = t.replace(/^-+/, '').toLowerCase().replace(/_/g, '-');
76
+ const normalized = t.replace(/^-+/, "").toLowerCase().replace(/_/g, "-");
64
77
  return WRITE_PATH_FLAG_NAMES.has(normalized);
65
78
  }
66
79
  function extractCommandPathCandidates(command, args) {
67
- const tokens = [command, ...args].map((t) => String(t ?? '')).filter(Boolean);
80
+ const tokens = [command, ...args].map((t) => String(t ?? "")).filter(Boolean);
68
81
  const reads = [];
69
82
  const writes = [];
70
83
  for (let i = 0; i < tokens.length; i++) {
@@ -72,10 +85,16 @@ function extractCommandPathCandidates(command, args) {
72
85
  // Redirection operators: treat as write/read targets.
73
86
  if (isRedirectionOp(t)) {
74
87
  const next = tokens[i + 1];
75
- if (typeof next === 'string' && next.length > 0) {
88
+ if (typeof next === "string" && next.length > 0) {
76
89
  const cleaned = cleanPathToken(next);
77
90
  if (cleaned) {
78
- if (t.startsWith('>') || t === '>' || t === '>>' || t === '1>' || t === '1>>' || t === '2>' || t === '2>>') {
91
+ if (t.startsWith(">") ||
92
+ t === ">" ||
93
+ t === ">>" ||
94
+ t === "1>" ||
95
+ t === "1>>" ||
96
+ t === "2>" ||
97
+ t === "2>>") {
79
98
  writes.push(cleaned);
80
99
  }
81
100
  else {
@@ -89,7 +108,7 @@ function extractCommandPathCandidates(command, args) {
89
108
  if (inline) {
90
109
  const cleaned = cleanPathToken(inline);
91
110
  if (cleaned) {
92
- if (t.includes('>'))
111
+ if (t.includes(">"))
93
112
  writes.push(cleaned);
94
113
  else
95
114
  reads.push(cleaned);
@@ -99,7 +118,7 @@ function extractCommandPathCandidates(command, args) {
99
118
  // Flags like --output /path or -o /path (write targets)
100
119
  if (isWritePathFlagToken(t)) {
101
120
  const next = tokens[i + 1];
102
- if (typeof next === 'string' && next.length > 0) {
121
+ if (typeof next === "string" && next.length > 0) {
103
122
  const cleaned = cleanPathToken(next);
104
123
  if (looksLikePathToken(cleaned)) {
105
124
  writes.push(cleaned);
@@ -109,7 +128,7 @@ function extractCommandPathCandidates(command, args) {
109
128
  }
110
129
  }
111
130
  // Flags like --output=/path
112
- const eq = t.indexOf('=');
131
+ const eq = t.indexOf("=");
113
132
  if (eq > 0) {
114
133
  const lhs = t.slice(0, eq);
115
134
  const rhs = cleanPathToken(t.slice(eq + 1));
@@ -129,33 +148,33 @@ function extractCommandPathCandidates(command, args) {
129
148
  return { reads: uniq(reads), writes: uniq(writes) };
130
149
  }
131
150
  const POLICY_REASON_CODES = {
132
- POLICY_DENY: 'ADC_POLICY_DENY',
133
- POLICY_WARN: 'ADC_POLICY_WARN',
134
- GUARD_ERROR: 'ADC_GUARD_ERROR',
135
- CUA_MALFORMED_EVENT: 'OCLAW_CUA_MALFORMED_EVENT',
136
- CUA_COMPUTER_USE_CONFIG_MISSING: 'OCLAW_CUA_COMPUTER_USE_CONFIG_MISSING',
137
- CUA_COMPUTER_USE_DISABLED: 'OCLAW_CUA_COMPUTER_USE_DISABLED',
138
- CUA_ACTION_NOT_ALLOWED: 'OCLAW_CUA_ACTION_NOT_ALLOWED',
139
- CUA_MODE_UNSUPPORTED: 'OCLAW_CUA_MODE_UNSUPPORTED',
140
- CUA_CONNECT_METADATA_MISSING: 'OCLAW_CUA_CONNECT_METADATA_MISSING',
141
- CUA_SIDE_CHANNEL_CONFIG_MISSING: 'OCLAW_CUA_SIDE_CHANNEL_CONFIG_MISSING',
142
- CUA_SIDE_CHANNEL_DISABLED: 'OCLAW_CUA_SIDE_CHANNEL_DISABLED',
143
- CUA_SIDE_CHANNEL_POLICY_DENY: 'OCLAW_CUA_SIDE_CHANNEL_POLICY_DENY',
144
- CUA_TRANSFER_SIZE_CONFIG_INVALID: 'OCLAW_CUA_TRANSFER_SIZE_CONFIG_INVALID',
145
- CUA_TRANSFER_SIZE_MISSING: 'OCLAW_CUA_TRANSFER_SIZE_MISSING',
146
- CUA_TRANSFER_SIZE_EXCEEDED: 'OCLAW_CUA_TRANSFER_SIZE_EXCEEDED',
147
- CUA_INPUT_CONFIG_MISSING: 'OCLAW_CUA_INPUT_CONFIG_MISSING',
148
- CUA_INPUT_DISABLED: 'OCLAW_CUA_INPUT_DISABLED',
149
- CUA_INPUT_TYPE_MISSING: 'OCLAW_CUA_INPUT_TYPE_MISSING',
150
- CUA_INPUT_TYPE_NOT_ALLOWED: 'OCLAW_CUA_INPUT_TYPE_NOT_ALLOWED',
151
- CUA_POSTCONDITION_PROBE_REQUIRED: 'OCLAW_CUA_POSTCONDITION_PROBE_REQUIRED',
152
- FILESYSTEM_WRITE_ROOT_DENY: 'OCLAW_FILESYSTEM_WRITE_ROOT_DENY',
153
- TOOL_DENIED: 'OCLAW_TOOL_DENIED',
154
- TOOL_NOT_ALLOWLISTED: 'OCLAW_TOOL_NOT_ALLOWLISTED',
151
+ POLICY_DENY: "ADC_POLICY_DENY",
152
+ POLICY_WARN: "ADC_POLICY_WARN",
153
+ GUARD_ERROR: "ADC_GUARD_ERROR",
154
+ CUA_MALFORMED_EVENT: "OCLAW_CUA_MALFORMED_EVENT",
155
+ CUA_COMPUTER_USE_CONFIG_MISSING: "OCLAW_CUA_COMPUTER_USE_CONFIG_MISSING",
156
+ CUA_COMPUTER_USE_DISABLED: "OCLAW_CUA_COMPUTER_USE_DISABLED",
157
+ CUA_ACTION_NOT_ALLOWED: "OCLAW_CUA_ACTION_NOT_ALLOWED",
158
+ CUA_MODE_UNSUPPORTED: "OCLAW_CUA_MODE_UNSUPPORTED",
159
+ CUA_CONNECT_METADATA_MISSING: "OCLAW_CUA_CONNECT_METADATA_MISSING",
160
+ CUA_SIDE_CHANNEL_CONFIG_MISSING: "OCLAW_CUA_SIDE_CHANNEL_CONFIG_MISSING",
161
+ CUA_SIDE_CHANNEL_DISABLED: "OCLAW_CUA_SIDE_CHANNEL_DISABLED",
162
+ CUA_SIDE_CHANNEL_POLICY_DENY: "OCLAW_CUA_SIDE_CHANNEL_POLICY_DENY",
163
+ CUA_TRANSFER_SIZE_CONFIG_INVALID: "OCLAW_CUA_TRANSFER_SIZE_CONFIG_INVALID",
164
+ CUA_TRANSFER_SIZE_MISSING: "OCLAW_CUA_TRANSFER_SIZE_MISSING",
165
+ CUA_TRANSFER_SIZE_EXCEEDED: "OCLAW_CUA_TRANSFER_SIZE_EXCEEDED",
166
+ CUA_INPUT_CONFIG_MISSING: "OCLAW_CUA_INPUT_CONFIG_MISSING",
167
+ CUA_INPUT_DISABLED: "OCLAW_CUA_INPUT_DISABLED",
168
+ CUA_INPUT_TYPE_MISSING: "OCLAW_CUA_INPUT_TYPE_MISSING",
169
+ CUA_INPUT_TYPE_NOT_ALLOWED: "OCLAW_CUA_INPUT_TYPE_NOT_ALLOWED",
170
+ CUA_POSTCONDITION_PROBE_REQUIRED: "OCLAW_CUA_POSTCONDITION_PROBE_REQUIRED",
171
+ FILESYSTEM_WRITE_ROOT_DENY: "OCLAW_FILESYSTEM_WRITE_ROOT_DENY",
172
+ TOOL_DENIED: "OCLAW_TOOL_DENIED",
173
+ TOOL_NOT_ALLOWLISTED: "OCLAW_TOOL_NOT_ALLOWLISTED",
155
174
  };
156
- function denyDecision(reason_code, reason, guard, severity = 'high') {
175
+ function denyDecision(reason_code, reason, guard, severity = "high") {
157
176
  return {
158
- status: 'deny',
177
+ status: "deny",
159
178
  reason_code,
160
179
  reason,
161
180
  message: reason,
@@ -163,9 +182,9 @@ function denyDecision(reason_code, reason, guard, severity = 'high') {
163
182
  ...(severity !== undefined && { severity }),
164
183
  };
165
184
  }
166
- function warnDecision(reason_code, reason, guard, severity = 'medium') {
185
+ function warnDecision(reason_code, reason, guard, severity = "medium") {
167
186
  return {
168
- status: 'warn',
187
+ status: "warn",
169
188
  reason_code,
170
189
  reason,
171
190
  message: reason,
@@ -174,13 +193,15 @@ function warnDecision(reason_code, reason, guard, severity = 'medium') {
174
193
  };
175
194
  }
176
195
  function ensureReasonCode(decision) {
177
- if (decision.status === 'allow')
196
+ if (decision.status === "allow")
178
197
  return decision;
179
- if (typeof decision.reason_code === 'string' && decision.reason_code.trim().length > 0)
198
+ if (typeof decision.reason_code === "string" && decision.reason_code.trim().length > 0)
180
199
  return decision;
181
200
  return {
182
201
  ...decision,
183
- reason_code: decision.status === 'warn' ? POLICY_REASON_CODES.POLICY_WARN : POLICY_REASON_CODES.GUARD_ERROR,
202
+ reason_code: decision.status === "warn"
203
+ ? POLICY_REASON_CODES.POLICY_WARN
204
+ : POLICY_REASON_CODES.GUARD_ERROR,
184
205
  };
185
206
  }
186
207
  export class PolicyEngine {
@@ -204,15 +225,15 @@ export class PolicyEngine {
204
225
  const g = this.config.guards;
205
226
  const enabled = [];
206
227
  if (g.forbidden_path)
207
- enabled.push('forbidden_path');
228
+ enabled.push("forbidden_path");
208
229
  if (g.egress)
209
- enabled.push('egress');
230
+ enabled.push("egress");
210
231
  if (g.secret_leak)
211
- enabled.push('secret_leak');
232
+ enabled.push("secret_leak");
212
233
  if (g.patch_integrity)
213
- enabled.push('patch_integrity');
234
+ enabled.push("patch_integrity");
214
235
  if (g.mcp_tool)
215
- enabled.push('mcp_tool');
236
+ enabled.push("mcp_tool");
216
237
  return enabled;
217
238
  }
218
239
  getPolicy() {
@@ -239,7 +260,7 @@ export class PolicyEngine {
239
260
  async evaluate(event) {
240
261
  const base = this.evaluateDeterministic(event);
241
262
  // Fail fast on deterministic violations to avoid unnecessary external calls.
242
- if (base.status === 'deny' || base.status === 'warn') {
263
+ if (base.status === "deny" || base.status === "warn") {
243
264
  return this.applyMode(base, this.config.mode);
244
265
  }
245
266
  if (this.threatIntelEngine) {
@@ -251,143 +272,143 @@ export class PolicyEngine {
251
272
  return this.applyMode(base, this.config.mode);
252
273
  }
253
274
  applyMode(result, mode) {
254
- if (mode === 'audit') {
275
+ if (mode === "audit") {
255
276
  return {
256
- status: 'allow',
277
+ status: "allow",
257
278
  reason_code: result.reason_code,
258
279
  reason: result.reason,
259
- message: `[audit] Original decision: ${result.status} — ${result.message ?? result.reason ?? 'no reason'}`,
280
+ message: `[audit] Original decision: ${result.status} — ${result.message ?? result.reason ?? "no reason"}`,
260
281
  guard: result.guard,
261
282
  severity: result.severity,
262
283
  };
263
284
  }
264
- if (mode === 'advisory' && result.status === 'deny') {
265
- return ensureReasonCode(warnDecision(result.reason_code, result.reason ?? result.message ?? 'policy deny converted to advisory warning', result.guard, result.severity ?? 'medium'));
285
+ if (mode === "advisory" && result.status === "deny") {
286
+ return ensureReasonCode(warnDecision(result.reason_code, result.reason ?? result.message ?? "policy deny converted to advisory warning", result.guard, result.severity ?? "medium"));
266
287
  }
267
288
  return ensureReasonCode(result);
268
289
  }
269
290
  getExpectedDataType(eventType) {
270
291
  switch (eventType) {
271
- case 'file_read':
272
- case 'file_write':
273
- return 'file';
274
- case 'command_exec':
275
- return 'command';
276
- case 'network_egress':
277
- return 'network';
278
- case 'tool_call':
279
- return 'tool';
280
- case 'patch_apply':
281
- return 'patch';
282
- case 'secret_access':
283
- return 'secret';
284
- case 'custom':
292
+ case "file_read":
293
+ case "file_write":
294
+ return "file";
295
+ case "command_exec":
296
+ return "command";
297
+ case "network_egress":
298
+ return "network";
299
+ case "tool_call":
300
+ return "tool";
301
+ case "patch_apply":
302
+ return "patch";
303
+ case "secret_access":
304
+ return "secret";
305
+ case "custom":
285
306
  return undefined;
286
307
  default:
287
308
  // CUA event types (starting with 'remote.' or 'input.')
288
- if (eventType.startsWith('remote.') || eventType.startsWith('input.')) {
289
- return 'cua';
309
+ if (eventType.startsWith("remote.") || eventType.startsWith("input.")) {
310
+ return "cua";
290
311
  }
291
312
  return undefined;
292
313
  }
293
314
  }
294
315
  evaluateDeterministic(event) {
295
- const allowed = { status: 'allow' };
316
+ const allowed = { status: "allow" };
296
317
  // Validate eventType/data.type consistency to prevent guard bypass
297
318
  const expectedDataType = this.getExpectedDataType(event.eventType);
298
319
  if (expectedDataType && event.data.type !== expectedDataType) {
299
320
  return {
300
- status: 'deny',
301
- reason_code: 'event_type_mismatch',
321
+ status: "deny",
322
+ reason_code: "event_type_mismatch",
302
323
  reason: `Event type "${event.eventType}" requires data.type "${expectedDataType}" but got "${event.data.type}"`,
303
- guard: 'policy_engine',
304
- severity: 'critical',
324
+ guard: "policy_engine",
325
+ severity: "critical",
305
326
  };
306
327
  }
307
328
  switch (event.eventType) {
308
- case 'file_read':
309
- case 'file_write':
329
+ case "file_read":
330
+ case "file_write":
310
331
  return this.checkFilesystem(event);
311
- case 'network_egress':
332
+ case "network_egress":
312
333
  return this.checkEgress(event);
313
- case 'command_exec':
334
+ case "command_exec":
314
335
  return this.checkExecution(event);
315
- case 'tool_call':
336
+ case "tool_call":
316
337
  return this.checkToolCall(event);
317
- case 'patch_apply':
338
+ case "patch_apply":
318
339
  return this.checkPatch(event);
319
- case 'remote.session.connect':
320
- case 'remote.session.disconnect':
321
- case 'remote.session.reconnect':
322
- case 'input.inject':
323
- case 'remote.clipboard':
324
- case 'remote.file_transfer':
325
- case 'remote.audio':
326
- case 'remote.drive_mapping':
327
- case 'remote.printing':
328
- case 'remote.session_share':
340
+ case "remote.session.connect":
341
+ case "remote.session.disconnect":
342
+ case "remote.session.reconnect":
343
+ case "input.inject":
344
+ case "remote.clipboard":
345
+ case "remote.file_transfer":
346
+ case "remote.audio":
347
+ case "remote.drive_mapping":
348
+ case "remote.printing":
349
+ case "remote.session_share":
329
350
  return this.checkCua(event);
330
351
  default:
331
352
  return allowed;
332
353
  }
333
354
  }
334
355
  checkCua(event) {
335
- if (event.data.type !== 'cua') {
336
- return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_MALFORMED_EVENT, `Malformed CUA event payload for ${event.eventType}: data.type must be 'cua'`, 'computer_use', 'high'));
356
+ if (event.data.type !== "cua") {
357
+ return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_MALFORMED_EVENT, `Malformed CUA event payload for ${event.eventType}: data.type must be 'cua'`, "computer_use", "high"));
337
358
  }
338
359
  const cuaData = event.data;
339
360
  const connectEgressDecision = this.checkCuaConnectEgress(event, cuaData);
340
- if (connectEgressDecision.status === 'deny' || connectEgressDecision.status === 'warn') {
361
+ if (connectEgressDecision.status === "deny" || connectEgressDecision.status === "warn") {
341
362
  return connectEgressDecision;
342
363
  }
343
364
  const computerUse = this.policy.guards?.computer_use;
344
365
  if (!computerUse) {
345
- return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_COMPUTER_USE_CONFIG_MISSING, `CUA action '${event.eventType}' denied: missing guards.computer_use policy config`, 'computer_use', 'high'));
366
+ return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_COMPUTER_USE_CONFIG_MISSING, `CUA action '${event.eventType}' denied: missing guards.computer_use policy config`, "computer_use", "high"));
346
367
  }
347
368
  if (computerUse.enabled === false) {
348
- return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_COMPUTER_USE_DISABLED, `CUA action '${event.eventType}' denied: computer_use guard is disabled`, 'computer_use', 'high'));
369
+ return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_COMPUTER_USE_DISABLED, `CUA action '${event.eventType}' denied: computer_use guard is disabled`, "computer_use", "high"));
349
370
  }
350
- const mode = computerUse.mode ?? 'guardrail';
371
+ const mode = computerUse.mode ?? "guardrail";
351
372
  const allowedActions = normalizeStringList(computerUse.allowed_actions);
352
373
  const actionAllowed = allowedActions.length === 0 || allowedActions.includes(event.eventType);
353
374
  if (!actionAllowed) {
354
375
  const reason = `CUA action '${event.eventType}' is not listed in guards.computer_use.allowed_actions`;
355
- if (mode === 'observe' || mode === 'guardrail') {
356
- return warnDecision(POLICY_REASON_CODES.CUA_ACTION_NOT_ALLOWED, reason, 'computer_use', 'medium');
376
+ if (mode === "observe" || mode === "guardrail") {
377
+ return warnDecision(POLICY_REASON_CODES.CUA_ACTION_NOT_ALLOWED, reason, "computer_use", "medium");
357
378
  }
358
- if (mode !== 'fail_closed') {
359
- return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_MODE_UNSUPPORTED, `CUA action '${event.eventType}' denied: unsupported computer_use mode '${mode}'`, 'computer_use', 'high'));
379
+ if (mode !== "fail_closed") {
380
+ return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_MODE_UNSUPPORTED, `CUA action '${event.eventType}' denied: unsupported computer_use mode '${mode}'`, "computer_use", "high"));
360
381
  }
361
- return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_ACTION_NOT_ALLOWED, reason, 'computer_use', 'high'));
382
+ return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_ACTION_NOT_ALLOWED, reason, "computer_use", "high"));
362
383
  }
363
384
  const sideChannelDecision = this.checkRemoteDesktopSideChannel(event, cuaData);
364
- if (sideChannelDecision.status === 'deny' || sideChannelDecision.status === 'warn') {
385
+ if (sideChannelDecision.status === "deny" || sideChannelDecision.status === "warn") {
365
386
  return sideChannelDecision;
366
387
  }
367
388
  const inputDecision = this.checkInputInjectionCapability(event, cuaData);
368
- if (inputDecision.status === 'deny' || inputDecision.status === 'warn') {
389
+ if (inputDecision.status === "deny" || inputDecision.status === "warn") {
369
390
  return inputDecision;
370
391
  }
371
- return { status: 'allow' };
392
+ return { status: "allow" };
372
393
  }
373
394
  checkCuaConnectEgress(event, data) {
374
- if (event.eventType !== 'remote.session.connect') {
375
- return { status: 'allow' };
395
+ if (event.eventType !== "remote.session.connect") {
396
+ return { status: "allow" };
376
397
  }
377
398
  if (!this.config.guards.egress) {
378
- return { status: 'allow' };
399
+ return { status: "allow" };
379
400
  }
380
401
  const target = extractCuaNetworkTarget(data);
381
402
  if (!target) {
382
- return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_CONNECT_METADATA_MISSING, "CUA connect action denied: missing destination host/url metadata required for egress evaluation", 'egress', 'high'));
403
+ return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_CONNECT_METADATA_MISSING, "CUA connect action denied: missing destination host/url metadata required for egress evaluation", "egress", "high"));
383
404
  }
384
405
  const egressEvent = {
385
406
  eventId: `${event.eventId}:cua-connect-egress`,
386
- eventType: 'network_egress',
407
+ eventType: "network_egress",
387
408
  timestamp: event.timestamp,
388
409
  sessionId: event.sessionId,
389
410
  data: {
390
- type: 'network',
411
+ type: "network",
391
412
  host: target.host,
392
413
  port: target.port,
393
414
  ...(target.protocol ? { protocol: target.protocol } : {}),
@@ -403,76 +424,76 @@ export class PolicyEngine {
403
424
  checkRemoteDesktopSideChannel(event, data) {
404
425
  const sideChannelFlag = eventTypeToSideChannelFlag(event.eventType);
405
426
  if (!sideChannelFlag) {
406
- return { status: 'allow' };
427
+ return { status: "allow" };
407
428
  }
408
429
  const cfg = this.policy.guards?.remote_desktop_side_channel;
409
430
  if (!cfg) {
410
- return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_SIDE_CHANNEL_CONFIG_MISSING, `CUA side-channel action '${event.eventType}' denied: missing guards.remote_desktop_side_channel policy config`, 'remote_desktop_side_channel', 'high'));
431
+ return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_SIDE_CHANNEL_CONFIG_MISSING, `CUA side-channel action '${event.eventType}' denied: missing guards.remote_desktop_side_channel policy config`, "remote_desktop_side_channel", "high"));
411
432
  }
412
433
  if (cfg.enabled === false) {
413
- return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_SIDE_CHANNEL_DISABLED, `CUA side-channel action '${event.eventType}' denied: remote_desktop_side_channel guard is disabled`, 'remote_desktop_side_channel', 'high'));
434
+ return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_SIDE_CHANNEL_DISABLED, `CUA side-channel action '${event.eventType}' denied: remote_desktop_side_channel guard is disabled`, "remote_desktop_side_channel", "high"));
414
435
  }
415
436
  if (cfg[sideChannelFlag] === false) {
416
- return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_SIDE_CHANNEL_POLICY_DENY, `CUA side-channel action '${event.eventType}' denied by policy`, 'remote_desktop_side_channel', 'high'));
437
+ return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_SIDE_CHANNEL_POLICY_DENY, `CUA side-channel action '${event.eventType}' denied by policy`, "remote_desktop_side_channel", "high"));
417
438
  }
418
- if (event.eventType === 'remote.file_transfer') {
439
+ if (event.eventType === "remote.file_transfer") {
419
440
  const maxBytes = cfg.max_transfer_size_bytes;
420
441
  if (maxBytes !== undefined) {
421
- if (typeof maxBytes !== 'number' || !Number.isFinite(maxBytes) || maxBytes < 0) {
422
- return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_TRANSFER_SIZE_CONFIG_INVALID, `CUA file transfer denied: invalid max_transfer_size_bytes '${String(maxBytes)}'`, 'remote_desktop_side_channel', 'high'));
442
+ if (typeof maxBytes !== "number" || !Number.isFinite(maxBytes) || maxBytes < 0) {
443
+ return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_TRANSFER_SIZE_CONFIG_INVALID, `CUA file transfer denied: invalid max_transfer_size_bytes '${String(maxBytes)}'`, "remote_desktop_side_channel", "high"));
423
444
  }
424
445
  const transferSize = extractTransferSize(data);
425
446
  if (transferSize === null) {
426
- return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_TRANSFER_SIZE_MISSING, 'CUA file transfer denied: missing required transfer_size metadata', 'remote_desktop_side_channel', 'high'));
447
+ return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_TRANSFER_SIZE_MISSING, "CUA file transfer denied: missing required transfer_size metadata", "remote_desktop_side_channel", "high"));
427
448
  }
428
449
  if (transferSize > maxBytes) {
429
- return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_TRANSFER_SIZE_EXCEEDED, `CUA file transfer size ${transferSize} exceeds max_transfer_size_bytes ${maxBytes}`, 'remote_desktop_side_channel', 'high'));
450
+ return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_TRANSFER_SIZE_EXCEEDED, `CUA file transfer size ${transferSize} exceeds max_transfer_size_bytes ${maxBytes}`, "remote_desktop_side_channel", "high"));
430
451
  }
431
452
  }
432
453
  }
433
- return { status: 'allow' };
454
+ return { status: "allow" };
434
455
  }
435
456
  checkInputInjectionCapability(event, data) {
436
- if (event.eventType !== 'input.inject') {
437
- return { status: 'allow' };
457
+ if (event.eventType !== "input.inject") {
458
+ return { status: "allow" };
438
459
  }
439
460
  const cfg = this.policy.guards?.input_injection_capability;
440
461
  if (!cfg) {
441
- return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_INPUT_CONFIG_MISSING, `CUA input action '${event.eventType}' denied: missing guards.input_injection_capability policy config`, 'input_injection_capability', 'high'));
462
+ return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_INPUT_CONFIG_MISSING, `CUA input action '${event.eventType}' denied: missing guards.input_injection_capability policy config`, "input_injection_capability", "high"));
442
463
  }
443
464
  if (cfg.enabled === false) {
444
- return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_INPUT_DISABLED, `CUA input action '${event.eventType}' denied: input_injection_capability guard is disabled`, 'input_injection_capability', 'high'));
465
+ return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_INPUT_DISABLED, `CUA input action '${event.eventType}' denied: input_injection_capability guard is disabled`, "input_injection_capability", "high"));
445
466
  }
446
467
  const allowedInputTypes = normalizeStringList(cfg.allowed_input_types);
447
468
  const inputType = extractInputType(data);
448
469
  if (allowedInputTypes.length > 0) {
449
470
  if (!inputType) {
450
- return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_INPUT_TYPE_MISSING, "CUA input action denied: missing required 'input_type'", 'input_injection_capability', 'high'));
471
+ return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_INPUT_TYPE_MISSING, "CUA input action denied: missing required 'input_type'", "input_injection_capability", "high"));
451
472
  }
452
473
  if (!allowedInputTypes.includes(inputType)) {
453
- return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_INPUT_TYPE_NOT_ALLOWED, `CUA input action denied: input_type '${inputType}' is not allowed`, 'input_injection_capability', 'high'));
474
+ return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_INPUT_TYPE_NOT_ALLOWED, `CUA input action denied: input_type '${inputType}' is not allowed`, "input_injection_capability", "high"));
454
475
  }
455
476
  }
456
477
  if (cfg.require_postcondition_probe === true) {
457
478
  const probeHash = data.postconditionProbeHash;
458
- if (typeof probeHash !== 'string' || probeHash.trim().length === 0) {
459
- return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_POSTCONDITION_PROBE_REQUIRED, 'CUA input action denied: postcondition probe hash is required', 'input_injection_capability', 'high'));
479
+ if (typeof probeHash !== "string" || probeHash.trim().length === 0) {
480
+ return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.CUA_POSTCONDITION_PROBE_REQUIRED, "CUA input action denied: postcondition probe hash is required", "input_injection_capability", "high"));
460
481
  }
461
482
  }
462
- return { status: 'allow' };
483
+ return { status: "allow" };
463
484
  }
464
485
  checkFilesystem(event) {
465
486
  if (!this.config.guards.forbidden_path) {
466
- return { status: 'allow' };
487
+ return { status: "allow" };
467
488
  }
468
489
  // First, enforce forbidden path patterns.
469
490
  const forbidden = this.forbiddenPathGuard.checkSync(event, this.policy);
470
491
  const mapped = this.guardResultToDecision(forbidden);
471
- if (mapped.status === 'deny' || mapped.status === 'warn') {
492
+ if (mapped.status === "deny" || mapped.status === "warn") {
472
493
  return this.applyOnViolation(mapped);
473
494
  }
474
495
  // Then, enforce write roots if configured.
475
- if (event.eventType === 'file_write' && event.data.type === 'file') {
496
+ if (event.eventType === "file_write" && event.data.type === "file") {
476
497
  const allowedWriteRoots = this.policy.filesystem?.allowed_write_roots;
477
498
  if (allowedWriteRoots && allowedWriteRoots.length > 0) {
478
499
  const filePath = normalizePathForPrefix(event.data.path);
@@ -481,15 +502,15 @@ export class PolicyEngine {
481
502
  return filePath === rootPath || filePath.startsWith(rootPath + path.sep);
482
503
  });
483
504
  if (!ok) {
484
- return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.FILESYSTEM_WRITE_ROOT_DENY, 'Write path not in allowed roots', 'forbidden_path', 'high'));
505
+ return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.FILESYSTEM_WRITE_ROOT_DENY, "Write path not in allowed roots", "forbidden_path", "high"));
485
506
  }
486
507
  }
487
508
  }
488
- return { status: 'allow' };
509
+ return { status: "allow" };
489
510
  }
490
511
  checkEgress(event) {
491
512
  if (!this.config.guards.egress) {
492
- return { status: 'allow' };
513
+ return { status: "allow" };
493
514
  }
494
515
  const res = this.egressGuard.checkSync(event, this.policy);
495
516
  const mapped = this.guardResultToDecision(res);
@@ -499,7 +520,7 @@ export class PolicyEngine {
499
520
  // Defense in depth: shell/command execution can still touch the filesystem.
500
521
  // Best-effort extract path-like tokens (including redirections) and run them through the
501
522
  // filesystem policy checks (forbidden paths + allowed write roots).
502
- if (this.config.guards.forbidden_path && event.data.type === 'command') {
523
+ if (this.config.guards.forbidden_path && event.data.type === "command") {
503
524
  const { reads, writes } = extractCommandPathCandidates(event.data.command, event.data.args);
504
525
  const maxChecks = 64;
505
526
  let checks = 0;
@@ -509,14 +530,14 @@ export class PolicyEngine {
509
530
  break;
510
531
  const synthetic = {
511
532
  eventId: `${event.eventId}:cmdwrite:${checks}`,
512
- eventType: 'file_write',
533
+ eventType: "file_write",
513
534
  timestamp: event.timestamp,
514
535
  sessionId: event.sessionId,
515
- data: { type: 'file', path: p, operation: 'write' },
516
- metadata: { ...event.metadata, derivedFrom: 'command_exec' },
536
+ data: { type: "file", path: p, operation: "write" },
537
+ metadata: { ...event.metadata, derivedFrom: "command_exec" },
517
538
  };
518
539
  const d = this.checkFilesystem(synthetic);
519
- if (d.status === 'deny' || d.status === 'warn')
540
+ if (d.status === "deny" || d.status === "warn")
520
541
  return d;
521
542
  }
522
543
  for (const p of reads) {
@@ -524,19 +545,19 @@ export class PolicyEngine {
524
545
  break;
525
546
  const synthetic = {
526
547
  eventId: `${event.eventId}:cmdread:${checks}`,
527
- eventType: 'file_read',
548
+ eventType: "file_read",
528
549
  timestamp: event.timestamp,
529
550
  sessionId: event.sessionId,
530
- data: { type: 'file', path: p, operation: 'read' },
531
- metadata: { ...event.metadata, derivedFrom: 'command_exec' },
551
+ data: { type: "file", path: p, operation: "read" },
552
+ metadata: { ...event.metadata, derivedFrom: "command_exec" },
532
553
  };
533
554
  const d = this.checkFilesystem(synthetic);
534
- if (d.status === 'deny' || d.status === 'warn')
555
+ if (d.status === "deny" || d.status === "warn")
535
556
  return d;
536
557
  }
537
558
  }
538
559
  if (!this.config.guards.patch_integrity) {
539
- return { status: 'allow' };
560
+ return { status: "allow" };
540
561
  }
541
562
  const res = this.patchIntegrityGuard.checkSync(event, this.policy);
542
563
  const mapped = this.guardResultToDecision(res);
@@ -544,40 +565,40 @@ export class PolicyEngine {
544
565
  }
545
566
  checkToolCall(event) {
546
567
  // Optional tool allow/deny list.
547
- if (event.data.type === 'tool') {
568
+ if (event.data.type === "tool") {
548
569
  const tools = this.policy.tools;
549
570
  const toolName = event.data.toolName.toLowerCase();
550
571
  const deniedTools = tools?.denied?.map((x) => x.toLowerCase()) ?? [];
551
572
  if (deniedTools.includes(toolName)) {
552
- return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.TOOL_DENIED, `Tool '${event.data.toolName}' is denied by policy`, 'mcp_tool', 'high'));
573
+ return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.TOOL_DENIED, `Tool '${event.data.toolName}' is denied by policy`, "mcp_tool", "high"));
553
574
  }
554
575
  const allowedTools = tools?.allowed?.map((x) => x.toLowerCase()) ?? [];
555
576
  if (allowedTools.length > 0 && !allowedTools.includes(toolName)) {
556
- return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.TOOL_NOT_ALLOWLISTED, `Tool '${event.data.toolName}' is not in allowed tool list`, 'mcp_tool', 'high'));
577
+ return this.applyOnViolation(denyDecision(POLICY_REASON_CODES.TOOL_NOT_ALLOWLISTED, `Tool '${event.data.toolName}' is not in allowed tool list`, "mcp_tool", "high"));
557
578
  }
558
579
  }
559
580
  // Also check forbidden paths in tool parameters (defense in depth).
560
- if (this.config.guards.forbidden_path && event.data.type === 'tool') {
581
+ if (this.config.guards.forbidden_path && event.data.type === "tool") {
561
582
  const params = event.data.parameters ?? {};
562
- const pathKeys = ['path', 'file', 'file_path', 'filepath', 'filename', 'target'];
583
+ const pathKeys = ["path", "file", "file_path", "filepath", "filename", "target"];
563
584
  for (const key of pathKeys) {
564
585
  const val = params[key];
565
- if (typeof val === 'string' && val.length > 0) {
586
+ if (typeof val === "string" && val.length > 0) {
566
587
  const pathEvent = {
567
588
  ...event,
568
- eventType: 'file_write',
569
- data: { type: 'file', path: val, operation: 'write' },
589
+ eventType: "file_write",
590
+ data: { type: "file", path: val, operation: "write" },
570
591
  };
571
592
  const pathCheck = this.forbiddenPathGuard.checkSync(pathEvent, this.policy);
572
593
  const pathDecision = this.guardResultToDecision(pathCheck);
573
- if (pathDecision.status === 'deny' || pathDecision.status === 'warn') {
594
+ if (pathDecision.status === "deny" || pathDecision.status === "warn") {
574
595
  return this.applyOnViolation(pathDecision);
575
596
  }
576
597
  }
577
598
  }
578
599
  }
579
600
  if (!this.config.guards.secret_leak) {
580
- return { status: 'allow' };
601
+ return { status: "allow" };
581
602
  }
582
603
  const res = this.secretLeakGuard.checkSync(event, this.policy);
583
604
  const mapped = this.guardResultToDecision(res);
@@ -588,37 +609,37 @@ export class PolicyEngine {
588
609
  const r1 = this.patchIntegrityGuard.checkSync(event, this.policy);
589
610
  const mapped1 = this.guardResultToDecision(r1);
590
611
  const applied1 = this.applyOnViolation(mapped1);
591
- if (applied1.status === 'deny' || applied1.status === 'warn')
612
+ if (applied1.status === "deny" || applied1.status === "warn")
592
613
  return applied1;
593
614
  }
594
615
  if (this.config.guards.secret_leak) {
595
616
  const r2 = this.secretLeakGuard.checkSync(event, this.policy);
596
617
  const mapped2 = this.guardResultToDecision(r2);
597
618
  const applied2 = this.applyOnViolation(mapped2);
598
- if (applied2.status === 'deny' || applied2.status === 'warn')
619
+ if (applied2.status === "deny" || applied2.status === "warn")
599
620
  return applied2;
600
621
  }
601
- return { status: 'allow' };
622
+ return { status: "allow" };
602
623
  }
603
624
  applyOnViolation(decision) {
604
625
  const action = this.policy.on_violation;
605
- if (decision.status !== 'deny')
626
+ if (decision.status !== "deny")
606
627
  return decision;
607
- if (action === 'warn') {
608
- return warnDecision(decision.reason_code, decision.reason ?? decision.message ?? 'Policy violation downgraded to warning', decision.guard, decision.severity ?? 'medium');
628
+ if (action === "warn") {
629
+ return warnDecision(decision.reason_code, decision.reason ?? decision.message ?? "Policy violation downgraded to warning", decision.guard, decision.severity ?? "medium");
609
630
  }
610
- if (action && action !== 'cancel') {
631
+ if (action && action !== "cancel") {
611
632
  console.warn(`[clawdstrike] Unhandled on_violation action: "${action}" — treating as deny`);
612
633
  }
613
634
  return decision;
614
635
  }
615
636
  guardResultToDecision(result) {
616
- if (result.status === 'allow')
617
- return { status: 'allow' };
618
- if (result.status === 'warn') {
619
- return warnDecision(POLICY_REASON_CODES.POLICY_WARN, result.reason ?? `${result.guard} returned warning`, result.guard, 'medium');
637
+ if (result.status === "allow")
638
+ return { status: "allow" };
639
+ if (result.status === "warn") {
640
+ return warnDecision(POLICY_REASON_CODES.POLICY_WARN, result.reason ?? `${result.guard} returned warning`, result.guard, "medium");
620
641
  }
621
- return denyDecision(POLICY_REASON_CODES.GUARD_ERROR, result.reason ?? `${result.guard} denied request`, result.guard, result.severity ?? 'high');
642
+ return denyDecision(POLICY_REASON_CODES.GUARD_ERROR, result.reason ?? `${result.guard} denied request`, result.guard, result.severity ?? "high");
622
643
  }
623
644
  }
624
645
  function buildThreatIntelEngine(policy) {
@@ -630,7 +651,7 @@ function buildThreatIntelEngine(policy) {
630
651
  // expects `CustomGuardSpec[]`. We've validated it's an array above.
631
652
  // GuardConfigs has an index signature so `unknown[]` is assignable.
632
653
  const canonicalPolicy = {
633
- version: '1.1.0',
654
+ version: "1.1.0",
634
655
  guards: { custom },
635
656
  };
636
657
  return createPolicyEngineFromPolicy(canonicalPolicy);
@@ -647,7 +668,7 @@ function combineDecisions(base, next) {
647
668
  ...base,
648
669
  message: base.message
649
670
  ? `${base.message}; ${next.message ?? next.reason}`
650
- : next.message ?? next.reason,
671
+ : (next.message ?? next.reason),
651
672
  };
652
673
  }
653
674
  return base;
@@ -657,7 +678,7 @@ function normalizeStringList(values) {
657
678
  return [];
658
679
  const out = [];
659
680
  for (const value of values) {
660
- if (typeof value !== 'string')
681
+ if (typeof value !== "string")
661
682
  continue;
662
683
  const normalized = value.trim();
663
684
  if (normalized.length > 0)
@@ -668,7 +689,7 @@ function normalizeStringList(values) {
668
689
  function extractInputType(data) {
669
690
  const candidates = [data.input_type, data.inputType];
670
691
  for (const candidate of candidates) {
671
- if (typeof candidate === 'string') {
692
+ if (typeof candidate === "string") {
672
693
  const normalized = candidate.trim().toLowerCase();
673
694
  if (normalized.length > 0)
674
695
  return normalized;
@@ -677,17 +698,12 @@ function extractInputType(data) {
677
698
  return null;
678
699
  }
679
700
  function extractTransferSize(data) {
680
- const candidates = [
681
- data.transfer_size,
682
- data.transferSize,
683
- data.size_bytes,
684
- data.sizeBytes,
685
- ];
701
+ const candidates = [data.transfer_size, data.transferSize, data.size_bytes, data.sizeBytes];
686
702
  for (const candidate of candidates) {
687
- if (typeof candidate === 'number' && Number.isFinite(candidate) && candidate >= 0) {
703
+ if (typeof candidate === "number" && Number.isFinite(candidate) && candidate >= 0) {
688
704
  return candidate;
689
705
  }
690
- if (typeof candidate === 'string') {
706
+ if (typeof candidate === "string") {
691
707
  const parsed = Number.parseInt(candidate, 10);
692
708
  if (Number.isFinite(parsed) && parsed >= 0) {
693
709
  return parsed;
@@ -697,12 +713,12 @@ function extractTransferSize(data) {
697
713
  return null;
698
714
  }
699
715
  function parsePort(value) {
700
- if (typeof value === 'number' && Number.isFinite(value)) {
716
+ if (typeof value === "number" && Number.isFinite(value)) {
701
717
  const port = Math.trunc(value);
702
718
  if (port > 0 && port <= 65535)
703
719
  return port;
704
720
  }
705
- if (typeof value === 'string') {
721
+ if (typeof value === "string") {
706
722
  const trimmed = value.trim();
707
723
  if (/^[0-9]+$/.test(trimmed)) {
708
724
  const parsed = Number.parseInt(trimmed, 10);
@@ -714,7 +730,7 @@ function parsePort(value) {
714
730
  }
715
731
  function firstNonEmptyString(values) {
716
732
  for (const value of values) {
717
- if (typeof value !== 'string')
733
+ if (typeof value !== "string")
718
734
  continue;
719
735
  const trimmed = value.trim();
720
736
  if (trimmed.length > 0)
@@ -730,7 +746,7 @@ function extractCuaNetworkTarget(data) {
730
746
  data.target_url,
731
747
  data.targetUrl,
732
748
  ]);
733
- const parsed = parseNetworkTarget(url ?? '', { emptyPort: 'default' });
749
+ const parsed = parseNetworkTarget(url ?? "", { emptyPort: "default" });
734
750
  const host = firstNonEmptyString([
735
751
  data.host,
736
752
  data.hostname,
@@ -744,12 +760,12 @@ function extractCuaNetworkTarget(data) {
744
760
  return null;
745
761
  }
746
762
  const protocol = firstNonEmptyString([data.protocol, data.scheme])?.toLowerCase();
747
- const explicitPort = parsePort(data.port
748
- ?? data.remote_port
749
- ?? data.remotePort
750
- ?? data.destination_port
751
- ?? data.destinationPort);
752
- const port = explicitPort ?? (parsed.host ? parsed.port : protocol === 'http' ? 80 : 443);
763
+ const explicitPort = parsePort(data.port ??
764
+ data.remote_port ??
765
+ data.remotePort ??
766
+ data.destination_port ??
767
+ data.destinationPort);
768
+ const port = explicitPort ?? (parsed.host ? parsed.port : protocol === "http" ? 80 : 443);
753
769
  return {
754
770
  host,
755
771
  port,
@@ -759,18 +775,18 @@ function extractCuaNetworkTarget(data) {
759
775
  }
760
776
  function eventTypeToSideChannelFlag(eventType) {
761
777
  switch (eventType) {
762
- case 'remote.clipboard':
763
- return 'clipboard_enabled';
764
- case 'remote.file_transfer':
765
- return 'file_transfer_enabled';
766
- case 'remote.audio':
767
- return 'audio_enabled';
768
- case 'remote.drive_mapping':
769
- return 'drive_mapping_enabled';
770
- case 'remote.printing':
771
- return 'printing_enabled';
772
- case 'remote.session_share':
773
- return 'session_share_enabled';
778
+ case "remote.clipboard":
779
+ return "clipboard_enabled";
780
+ case "remote.file_transfer":
781
+ return "file_transfer_enabled";
782
+ case "remote.audio":
783
+ return "audio_enabled";
784
+ case "remote.drive_mapping":
785
+ return "drive_mapping_enabled";
786
+ case "remote.printing":
787
+ return "printing_enabled";
788
+ case "remote.session_share":
789
+ return "session_share_enabled";
774
790
  default:
775
791
  return null;
776
792
  }