clawmoat 0.8.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/.dockerignore +9 -0
  2. package/CHANGELOG.md +18 -0
  3. package/DEMO.md +87 -0
  4. package/Dockerfile +5 -18
  5. package/README.md +232 -8
  6. package/THREAT_MODEL.md +129 -0
  7. package/agent/README.md +131 -0
  8. package/agent/index.js +471 -0
  9. package/agent/install-service.sh +94 -0
  10. package/agent/openclaw-hook.js +453 -0
  11. package/agent/provider-setup.js +649 -0
  12. package/agent/setup.js +274 -0
  13. package/assets/BADGE-USAGE.md +20 -0
  14. package/assets/clawmoat-badge.svg +21 -0
  15. package/bin/clawmoat.js +468 -111
  16. package/docs/affiliates/dashboard.html +124 -0
  17. package/docs/affiliates/index.html +236 -0
  18. package/docs/agent-install.html +183 -0
  19. package/docs/ai-agent-security-scanner.html +10 -6
  20. package/docs/badge/index.html +149 -0
  21. package/docs/badge/scanning.svg +23 -0
  22. package/docs/blog/386-malicious-skills.html +11 -4
  23. package/docs/blog/40000-exposed-openclaw-instances.html +11 -4
  24. package/docs/blog/agent-trust-protocol.html +5 -4
  25. package/docs/blog/ai-agent-earns-commissions.html +230 -0
  26. package/docs/blog/bugmageddon-agent-firewall.html +174 -0
  27. package/docs/blog/calculator-math.html +180 -0
  28. package/docs/blog/clawmoat-vs-llamafirewall-nemo-guardrails.html +10 -4
  29. package/docs/blog/host-guardian-launch.html +18 -8
  30. package/docs/blog/ibm-experts-agent-runtime-protection.html +15 -6
  31. package/docs/blog/index.html +67 -9
  32. package/docs/blog/langchain-security-tutorial.html +18 -8
  33. package/docs/blog/mcp-30-cves-security-crisis.html +11 -4
  34. package/docs/blog/meta-researcher-rogue-agent.html +201 -0
  35. package/docs/blog/microsoft-openclaw-workstation-security.html +5 -4
  36. package/docs/blog/nist-ai-agent-standards-clawmoat.html +16 -8
  37. package/docs/blog/oasis-websocket-hijack.html +11 -4
  38. package/docs/blog/ollama-openclaw-security.html +10 -4
  39. package/docs/blog/openclaw-enterprise-readiness-claw10.html +5 -4
  40. package/docs/blog/openclaw-security-reckoning-2026.html +11 -4
  41. package/docs/blog/owasp-agentic-ai-top10.html +18 -8
  42. package/docs/blog/securing-ai-agents.html +18 -8
  43. package/docs/blog/supply-chain-agents.html +18 -8
  44. package/docs/business/index.html +11 -16
  45. package/docs/business/install.html +21 -7
  46. package/docs/checklist.html +10 -4
  47. package/docs/compare/index.html +122 -0
  48. package/docs/compare/lakera/index.html +62 -0
  49. package/docs/compare/llm-guard/index.html +49 -0
  50. package/docs/compare/snyk-agent-scan/index.html +63 -0
  51. package/docs/compare.html +10 -6
  52. package/docs/dashboard/index.html +520 -0
  53. package/docs/finance/index.html +9 -6
  54. package/docs/guides/business-deployment.html +770 -0
  55. package/docs/hall-of-fame.html +11 -5
  56. package/docs/index.html +266 -137
  57. package/docs/integrations/langchain.html +14 -6
  58. package/docs/integrations/openai.html +14 -6
  59. package/docs/integrations/openclaw.html +55 -7
  60. package/docs/plans/2026-03-26-threat-intel-api.md +255 -0
  61. package/docs/plans/2026-04-14-bugmageddon-marketing-pack.md +329 -0
  62. package/docs/plans/2026-04-14-clawmoat-v1-bugmageddon.md +248 -0
  63. package/docs/plans/2026-04-14-v1-release-update.md +91 -0
  64. package/docs/plans/2026-04-19-supabase-audit.md +68 -0
  65. package/docs/plans/2026-05-12-sales-push.md +303 -0
  66. package/docs/playground/index.html +893 -0
  67. package/docs/playground.html +4 -7
  68. package/docs/rfcs/defense-in-depth.md +467 -0
  69. package/docs/scan/index.html +156 -12
  70. package/docs/services/case-study.html +255 -0
  71. package/docs/services/downloads/install-openclaw.bat +45 -0
  72. package/docs/services/downloads/install-openclaw.command +38 -0
  73. package/docs/services/downloads/install-openclaw.sh +38 -0
  74. package/docs/services/get-started.html +165 -0
  75. package/docs/services/index.html +598 -0
  76. package/docs/services/multi-agent-security.html +284 -0
  77. package/docs/services/one-pager.html +99 -0
  78. package/docs/services/pitch-deck.html +229 -0
  79. package/docs/services/roi-calculator.html +258 -0
  80. package/docs/sitemap.xml +62 -2
  81. package/docs/support/index.html +12 -1
  82. package/docs/templates/customer-service/HEARTBEAT.md +61 -0
  83. package/docs/templates/customer-service/MEMORY.md +89 -0
  84. package/docs/templates/customer-service/SOUL.md +41 -0
  85. package/docs/templates/customer-service/USER.md +56 -0
  86. package/docs/templates/executive/HEARTBEAT.md +86 -0
  87. package/docs/templates/executive/MEMORY.md +92 -0
  88. package/docs/templates/executive/SOUL.md +44 -0
  89. package/docs/templates/executive/USER.md +62 -0
  90. package/docs/templates/finance/HEARTBEAT.md +58 -0
  91. package/docs/templates/finance/MEMORY.md +87 -0
  92. package/docs/templates/finance/SOUL.md +38 -0
  93. package/docs/templates/finance/USER.md +53 -0
  94. package/docs/templates/index.html +115 -0
  95. package/docs/templates/operations/HEARTBEAT.md +63 -0
  96. package/docs/templates/operations/MEMORY.md +68 -0
  97. package/docs/templates/operations/SOUL.md +38 -0
  98. package/docs/templates/operations/USER.md +49 -0
  99. package/docs/templates/sales/HEARTBEAT.md +55 -0
  100. package/docs/templates/sales/MEMORY.md +89 -0
  101. package/docs/templates/sales/SOUL.md +34 -0
  102. package/docs/templates/sales/USER.md +54 -0
  103. package/eslint.config.js +32 -0
  104. package/evals/README.md +29 -0
  105. package/evals/cases.json +390 -0
  106. package/evals/results.md +68 -0
  107. package/evals/run.js +180 -0
  108. package/examples/demo-attack/demo.js +186 -0
  109. package/examples/python-quickstart/README.md +54 -0
  110. package/examples/python-quickstart/clawmoat_client.py +167 -0
  111. package/examples/video-demo/README.md +14 -0
  112. package/examples/video-demo/scene-a-normal.js +29 -0
  113. package/examples/video-demo/scene-b-attack-arrives.js +31 -0
  114. package/examples/video-demo/scene-c-hijack.js +44 -0
  115. package/examples/video-demo/scene-d-clawmoat.js +46 -0
  116. package/integrations/crewai/README.md +32 -0
  117. package/integrations/crewai/clawmoat_crewai/__init__.py +17 -0
  118. package/integrations/crewai/clawmoat_crewai/guard.py +103 -0
  119. package/integrations/crewai/pyproject.toml +21 -0
  120. package/integrations/langchain/README.md +91 -0
  121. package/integrations/langchain/clawmoat_langchain/__init__.py +17 -0
  122. package/integrations/langchain/clawmoat_langchain/callback.py +489 -0
  123. package/integrations/langchain/pyproject.toml +32 -0
  124. package/integrations/litellm/README.md +324 -0
  125. package/integrations/litellm/clawmoat_litellm/__init__.py +21 -0
  126. package/integrations/litellm/clawmoat_litellm/callback.py +329 -0
  127. package/integrations/litellm/clawmoat_litellm/proxy_middleware.py +224 -0
  128. package/integrations/litellm/pyproject.toml +74 -0
  129. package/integrations/openai-agents/README.md +392 -0
  130. package/integrations/openai-agents/clawmoat_openai_agents/__init__.py +20 -0
  131. package/integrations/openai-agents/clawmoat_openai_agents/guardrail.py +431 -0
  132. package/integrations/openai-agents/clawmoat_openai_agents/middleware.py +311 -0
  133. package/integrations/openai-agents/pyproject.toml +76 -0
  134. package/package.json +6 -5
  135. package/plugins/openclaw-adapter/PHASE1.md +439 -0
  136. package/plugins/openclaw-adapter/README.md +103 -0
  137. package/plugins/openclaw-adapter/SPEC.md +1644 -0
  138. package/plugins/openclaw-adapter/package.json +31 -0
  139. package/plugins/openclaw-adapter/src/index.test.ts +226 -0
  140. package/plugins/openclaw-adapter/src/index.ts +140 -0
  141. package/plugins/openclaw-adapter/tsconfig.json +14 -0
  142. package/server/data/threats.json +290 -0
  143. package/server/index.js +142 -7
  144. package/src/adapters/express.js +161 -0
  145. package/src/adapters/index.js +92 -0
  146. package/src/adapters/langchain.js +185 -0
  147. package/src/approval/index.js +456 -0
  148. package/src/ban-scanner.js +200 -0
  149. package/src/boundary-scanner.js +296 -0
  150. package/src/ci-scanner.js +279 -0
  151. package/src/code-scanner.js +245 -0
  152. package/src/enforce.js +166 -0
  153. package/src/formatters/json.js +80 -0
  154. package/src/formatters/sarif.js +388 -0
  155. package/src/guardian/alerts.js +34 -3
  156. package/src/guardian/index.js +41 -2
  157. package/src/index.js +102 -0
  158. package/src/integrations/agentmesh.js +501 -0
  159. package/src/language-detector.js +201 -0
  160. package/src/mcp-scanner.js +253 -0
  161. package/src/multimodal/index.js +579 -0
  162. package/src/obfuscation-scanner.js +457 -0
  163. package/src/policy-engine.js +402 -0
  164. package/src/scanners/dependency-attacks.js +128 -0
  165. package/src/scanners/prompt-injection.js +18 -0
  166. package/src/scanners/supply-chain.js +14 -0
  167. package/src/templates/default-config.yml +90 -0
  168. package/src/vuln-ops/exploitability.js +46 -0
  169. package/src/watch/live-monitor.js +720 -0
  170. package/clawmoat-0.8.0.tgz +0 -0
  171. package/server/index.js.patch +0 -1
@@ -0,0 +1,439 @@
1
+ # Pluggable Sanitizer Interface — Phase 1: Interface & Loading
2
+
3
+ ## CC Instruction Set
4
+
5
+ Implement Phase 1 of the pluggable sanitizer interface for the OpenClaw
6
+ session memory sanitization system. This phase defines the TypeScript
7
+ interfaces, config schema, module loading, and startup validation. No
8
+ pipeline wiring yet — that's Phase 2+.
9
+
10
+ **Reference spec:** `pluggable-sanitizer-interface-spec-v1_2_1.md`
11
+
12
+ Do not change any existing sanitization behavior. No pipeline integration,
13
+ no audit events, no frequency scoring changes. This phase only adds the
14
+ foundational types and the loader that validates and initializes plugins
15
+ at startup.
16
+
17
+ ---
18
+
19
+ ## Branch
20
+
21
+ Create a new branch from the tip of `feature/sanitization-hardening`
22
+ (or `main` if that PR has merged):
23
+
24
+ ```
25
+ feature/pluggable-sanitizer-interface
26
+ ```
27
+
28
+ ---
29
+
30
+ ## Step 1 — TypeScript Interfaces
31
+
32
+ Create `src/memory/session-sanitization/plugin-interface.ts`.
33
+
34
+ Define and export these interfaces exactly as specified:
35
+
36
+ ### `SanitizerPlugin`
37
+
38
+ ```typescript
39
+ export interface SanitizerPlugin {
40
+ id: string;
41
+ name: string;
42
+ phase: "pre" | "post";
43
+ ruleIdPrefix: string;
44
+ initialize(config: Record<string, unknown>): Promise<void>;
45
+ shutdown(): Promise<void>;
46
+ inspect(input: PluginInput): Promise<PluginResult>;
47
+ }
48
+ ```
49
+
50
+ ### `PluginInput`
51
+
52
+ ```typescript
53
+ export interface PluginInput {
54
+ content: {
55
+ source: "transcript" | "mcp";
56
+ raw: unknown;
57
+ query?: { server: string; tool: string; params: unknown };
58
+ };
59
+ priorResults: {
60
+ syntactic: { pass: boolean; flags: string[]; ruleIds: string[] };
61
+ schema: { pass: boolean; violations: string[]; ruleIds: string[] };
62
+ semantic?: { safe: boolean; flags: string[]; structuredResult: unknown };
63
+ priorPlugins: PluginResultMeta[];
64
+ };
65
+ contextProfile: string;
66
+ }
67
+ ```
68
+
69
+ ### `PluginResult`
70
+
71
+ ```typescript
72
+ export interface PluginResult {
73
+ pluginId: string;
74
+ safe: boolean;
75
+ ruleIds: string[];
76
+ flags: string[];
77
+ confidence: number;
78
+ findingConfidence?: Record<string, number>;
79
+ transformed?: unknown;
80
+ }
81
+ ```
82
+
83
+ ### `PluginResultMeta`
84
+
85
+ ```typescript
86
+ export interface PluginResultMeta extends PluginResult {
87
+ transformApplied?: boolean;
88
+ errored?: boolean;
89
+ }
90
+ ```
91
+
92
+ ### `PluginDeclaration` (config shape)
93
+
94
+ ```typescript
95
+ export interface PluginDeclaration {
96
+ module: string;
97
+ phase: "pre" | "post";
98
+ enabled?: boolean; // default: true
99
+ config?: Record<string, unknown>; // default: {}
100
+ timeoutMs?: number; // default: 1000, min: 100, max: 10000
101
+ allowTransform?: boolean; // default: false
102
+ frequencyWeight?: number; // default: 3
103
+ maxQueueDepth?: number; // default: 10
104
+ }
105
+
106
+ export interface PluginLimits {
107
+ maxTotal?: number; // default: 10
108
+ maxPrePhase?: number; // default: 5
109
+ maxPostPhase?: number; // default: 5
110
+ }
111
+ ```
112
+
113
+ **Checkpoint:** `pnpm tsgo` passes. No runtime behavior changed.
114
+
115
+ ---
116
+
117
+ ## Step 2 — Config Schema
118
+
119
+ Extend the sanitization config namespace to accept plugin declarations.
120
+ Find where `memory.sessions.sanitization` config is defined (likely in
121
+ `src/memory/session-sanitization/config.ts` or a shared config module).
122
+
123
+ Add two new fields:
124
+
125
+ ```typescript
126
+ plugins?: PluginDeclaration[]; // default: []
127
+ pluginLimits?: PluginLimits; // default: { maxTotal: 10, maxPrePhase: 5, maxPostPhase: 5 }
128
+ ```
129
+
130
+ These are **sibling** keys — `plugins` is an array, `pluginLimits` is
131
+ a separate object. They must NOT be nested under the same key.
132
+
133
+ Provide defaults so the system behaves identically when no plugins are
134
+ configured (empty array, nothing loads, nothing changes).
135
+
136
+ **Checkpoint:** `pnpm tsgo` passes. Existing config loading still works.
137
+ No plugins configured = no behavior change.
138
+
139
+ ---
140
+
141
+ ## Step 3 — Built-In Prefix Registry
142
+
143
+ Create `src/memory/session-sanitization/plugin-rule-prefixes.ts`.
144
+
145
+ Export a constant set of built-in rule prefixes that plugins must not
146
+ collide with:
147
+
148
+ ```typescript
149
+ export const BUILTIN_RULE_PREFIXES: ReadonlySet<string> = new Set([
150
+ "INJ-",
151
+ "CRED-",
152
+ "STRUCT-",
153
+ "TYPE-",
154
+ "ENC-",
155
+ "TEMPORAL-",
156
+ "schema.",
157
+ "injection.",
158
+ "credential.",
159
+ "scope-creep.",
160
+ ]);
161
+ ```
162
+
163
+ Export a validation function:
164
+
165
+ ```typescript
166
+ export function collidesWithBuiltin(prefix: string): boolean {
167
+ for (const builtin of BUILTIN_RULE_PREFIXES) {
168
+ if (prefix.startsWith(builtin) || builtin.startsWith(prefix)) {
169
+ return true;
170
+ }
171
+ }
172
+ return false;
173
+ }
174
+ ```
175
+
176
+ **Checkpoint:** Unit test for `collidesWithBuiltin`:
177
+ - `"INJ-custom"` → true
178
+ - `"schema.extra"` → true
179
+ - `"clawmoat.scanner"` → false
180
+ - `"acme.hipaa"` → false
181
+
182
+ ---
183
+
184
+ ## Step 4 — Path Validation
185
+
186
+ Create `src/memory/session-sanitization/plugin-path-validation.ts`.
187
+
188
+ Export a function that validates a plugin module path against the config
189
+ directory. The function must be **platform-agnostic** — works on both
190
+ Windows and POSIX:
191
+
192
+ ```typescript
193
+ import * as path from "path";
194
+ import * as fs from "fs";
195
+
196
+ export function validatePluginPath(
197
+ modulePath: string,
198
+ configDir: string
199
+ ): { valid: true; resolvedPath: string } | { valid: false; reason: string } {
200
+ // 1. Resolve to absolute
201
+ const absolute = path.resolve(configDir, modulePath);
202
+
203
+ // 2. Resolve symlinks
204
+ let realPath: string;
205
+ try {
206
+ realPath = fs.realpathSync(absolute);
207
+ } catch (err) {
208
+ return { valid: false, reason: `Module not found: ${absolute}` };
209
+ }
210
+
211
+ // 3. Containment check — resolved path must start with configDir + separator
212
+ const configDirReal = fs.realpathSync(configDir);
213
+ const boundary = configDirReal + path.sep;
214
+ if (!realPath.startsWith(boundary) && realPath !== configDirReal) {
215
+ return {
216
+ valid: false,
217
+ reason: `Module path escapes config directory: ${realPath} is outside ${configDirReal}`,
218
+ };
219
+ }
220
+
221
+ return { valid: true, resolvedPath: realPath };
222
+ }
223
+ ```
224
+
225
+ **Checkpoint:** Unit tests:
226
+ - Relative path within config dir → valid
227
+ - Path with `..` escaping → invalid
228
+ - Absolute path outside config dir → invalid
229
+ - Non-existent path → invalid with "not found"
230
+ - (If testable on the platform) symlink pointing outside → invalid
231
+
232
+ ---
233
+
234
+ ## Step 5 — Plugin Loader
235
+
236
+ Create `src/memory/session-sanitization/plugin-loader.ts`.
237
+
238
+ This is the core of Phase 1. The loader validates config, loads modules,
239
+ validates the plugin interface, and calls `initialize()`. It does NOT
240
+ spawn worker threads yet — that's Phase 2. For now, plugins load in-process.
241
+
242
+ ```typescript
243
+ export interface LoadedPlugin {
244
+ declaration: PluginDeclaration;
245
+ instance: SanitizerPlugin;
246
+ resolvedPath: string;
247
+ }
248
+
249
+ export async function loadPlugins(
250
+ declarations: PluginDeclaration[],
251
+ limits: Required<PluginLimits>,
252
+ configDir: string
253
+ ): Promise<LoadedPlugin[]> {
254
+ // Implementation steps below
255
+ }
256
+ ```
257
+
258
+ ### Validation order (fail-fast at startup):
259
+
260
+ 1. **Filter disabled plugins.** Skip entries with `enabled: false` entirely
261
+ — do not load, do not validate path, do not count against limits.
262
+
263
+ 2. **Count limits.** Count enabled plugins per phase and total.
264
+ If any limit exceeded → throw with clear message naming the limit and
265
+ the count.
266
+
267
+ 3. **Transform uniqueness.** Per phase, at most one plugin may have
268
+ `allowTransform: true`. If violated → throw naming both plugins.
269
+
270
+ 4. **Path validation.** For each enabled plugin, call `validatePluginPath`.
271
+ If invalid → throw with the reason.
272
+
273
+ 5. **Module loading.** `require()` the resolved path. Apply CJS interop:
274
+ `const factory = mod.default || mod`. If result is not a function →
275
+ throw with "Module does not export a factory function".
276
+
277
+ 6. **Factory call.** `const instance = factory()`. Validate the returned
278
+ object has all required `SanitizerPlugin` fields (id, name, phase,
279
+ ruleIdPrefix, initialize, shutdown, inspect) with correct types.
280
+ If invalid → throw naming the missing/wrong field.
281
+
282
+ 7. **Phase consistency.** `declaration.phase` must equal `instance.phase`.
283
+ If mismatched → throw naming both values.
284
+
285
+ 8. **ruleIdPrefix validation.**
286
+ - `instance.ruleIdPrefix` must equal `instance.id`.
287
+ - Must not collide with built-in prefixes (use `collidesWithBuiltin`).
288
+ - Must not collide with any previously loaded plugin's prefix.
289
+ If violated → throw naming the collision.
290
+
291
+ 9. **Initialize.** Call `await instance.initialize(declaration.config ?? {})`.
292
+ If it throws → let the error propagate (startup failure, fail closed).
293
+
294
+ 10. **Return.** Collect all `LoadedPlugin` entries.
295
+
296
+ ### Shutdown helper:
297
+
298
+ ```typescript
299
+ export async function shutdownPlugins(
300
+ plugins: LoadedPlugin[]
301
+ ): Promise<void> {
302
+ // Reverse order. Best-effort — log errors but don't throw.
303
+ for (const plugin of [...plugins].reverse()) {
304
+ try {
305
+ await plugin.instance.shutdown();
306
+ } catch (err) {
307
+ log.warn(`Plugin ${plugin.instance.id} shutdown error`, err);
308
+ }
309
+ }
310
+ }
311
+ ```
312
+
313
+ **Checkpoint:** `pnpm tsgo` passes. Write unit tests for the full loader:
314
+
315
+ ---
316
+
317
+ ## Step 6 — Loader Tests
318
+
319
+ Create `src/memory/session-sanitization/plugin-loader.test.ts`.
320
+
321
+ Write tests using Vitest. You will need to create small fixture plugins
322
+ as test helpers — either inline mock factories or tiny CJS files in a
323
+ test fixtures directory.
324
+
325
+ **Tests to write:**
326
+
327
+ Loading:
328
+ - Empty declarations array → returns empty array, no errors
329
+ - Single valid plugin → loads, initializes, returns LoadedPlugin
330
+ - Plugin with `enabled: false` → not loaded, not counted
331
+ - Plugin loaded via `module.exports = fn` form → works
332
+ - Plugin loaded via `exports.default = fn` form → works
333
+
334
+ Limit enforcement:
335
+ - Exceeding `maxTotal` → startup error naming the limit
336
+ - Exceeding `maxPrePhase` → startup error
337
+ - Exceeding `maxPostPhase` → startup error
338
+ - Disabled plugins do not count against limits
339
+
340
+ Validation:
341
+ - Phase mismatch (config: "pre", plugin: "post") → startup error
342
+ - ruleIdPrefix not equal to id → startup error
343
+ - ruleIdPrefix colliding with built-in prefix → startup error
344
+ - Two plugins with same id → startup error
345
+ - Two plugins with same ruleIdPrefix → startup error
346
+ - Module not found → startup error with path
347
+ - Module exports non-function → startup error
348
+ - Factory returns object missing required fields → startup error
349
+
350
+ Transform:
351
+ - Two plugins in same phase with `allowTransform: true` → startup error
352
+ - Two plugins in different phases with `allowTransform: true` → OK
353
+
354
+ Path validation:
355
+ - Module path escaping config dir → startup error
356
+ - Valid relative path → resolves and loads
357
+
358
+ Initialize:
359
+ - Plugin that throws in initialize → propagates as startup error
360
+ - Plugin initialize receives the config block from declaration
361
+
362
+ Shutdown:
363
+ - Shutdown calls plugins in reverse order
364
+ - Shutdown logs errors but does not throw
365
+
366
+ ---
367
+
368
+ ## Step 7 — Wire Loader Into Startup
369
+
370
+ Find where the sanitization subsystem initializes at agent startup. This
371
+ is likely in the same path where the sanitization config is loaded and
372
+ the sub-agent is prepared.
373
+
374
+ Add a call to `loadPlugins()` after config is resolved and validated.
375
+ Store the resulting `LoadedPlugin[]` in the sanitization state so it's
376
+ accessible to the pipeline (Phase 2+).
377
+
378
+ If no plugins are configured (empty array), the loader returns immediately
379
+ with an empty array. Zero overhead, zero behavior change.
380
+
381
+ Add a corresponding `shutdownPlugins()` call on the agent shutdown path.
382
+
383
+ **Do NOT wire plugins into the inspection pipeline yet.** The loaded plugins
384
+ sit in state, initialized and ready, but not invoked on any content. That
385
+ wiring is Phase 2 (worker isolation) and Phase 3 (pre-phase integration).
386
+
387
+ **Checkpoint:** Full test suite passes:
388
+ - `pnpm vitest run src/memory/session-sanitization/plugin-loader.test.ts`
389
+ - `pnpm vitest run src/memory/session-sanitization/plugin-path-validation.test.ts`
390
+ - `pnpm vitest run src/memory/session-sanitization/plugin-rule-prefixes.test.ts`
391
+ - `pnpm tsgo`
392
+ - `pnpm test` (full suite — confirm no regressions)
393
+ - `pnpm format`
394
+
395
+ ---
396
+
397
+ ## Commit
398
+
399
+ Single commit on `feature/pluggable-sanitizer-interface`.
400
+
401
+ ```
402
+ feat: add pluggable sanitizer interface types, config schema, and plugin loader
403
+
404
+ Phase 1 of pluggable-sanitizer-interface-spec-v1.2.1.
405
+ Defines SanitizerPlugin/PluginInput/PluginResult/PluginResultMeta interfaces,
406
+ config schema (plugins array + pluginLimits), platform-agnostic path validation,
407
+ built-in prefix collision checking, and startup plugin loading with full
408
+ validation chain. Plugins load and initialize but are not wired into the
409
+ inspection pipeline (Phase 2+).
410
+ ```
411
+
412
+ ---
413
+
414
+ ## What this does NOT include (deferred to later phases)
415
+
416
+ - Worker thread isolation (Phase 2)
417
+ - Pipeline integration — pre-phase or post-phase execution (Phase 3–4)
418
+ - Content transformation support (Phase 5)
419
+ - Audit events (plugin_config_loaded, etc.) (Phase 3–4)
420
+ - Alerting integration (pluginErrorSpike) (Phase 6)
421
+ - Frequency scoring integration (Phase 3)
422
+ - Context profile plugin overrides (Phase 3)
423
+ - Trust tier routing awareness (Phase 3)
424
+
425
+ ---
426
+
427
+ ## Self-Review Checklist (run before committing)
428
+
429
+ - [ ] All new files are under `src/memory/session-sanitization/`
430
+ - [ ] Interfaces match the spec exactly (field names, types, optionality)
431
+ - [ ] Config defaults mean zero behavior change when no plugins configured
432
+ - [ ] Path validation uses `path.resolve` + `fs.realpathSync` + trailing `path.sep`
433
+ - [ ] CJS interop uses `mod.default || mod`
434
+ - [ ] Loader fails fast with clear error messages at each validation step
435
+ - [ ] Shutdown is reverse-order and best-effort
436
+ - [ ] No pipeline changes — loaded plugins sit in state, uninvoked
437
+ - [ ] All tests pass, including full `pnpm test`
438
+ - [ ] `pnpm tsgo` clean
439
+ - [ ] `pnpm format` clean
@@ -0,0 +1,103 @@
1
+ # @openclaw/plugin-clawmoat
2
+
3
+ OpenClaw sanitizer plugin wrapping [ClawMoat](https://github.com/darfaz/clawmoat)'s
4
+ prompt injection detection, secret scanning, and PII detection.
5
+
6
+ ## What it does
7
+
8
+ Runs ClawMoat's `scan()` function as a **pre-phase** sanitizer plugin.
9
+ Every piece of untrusted content (transcript or MCP result) passes through
10
+ ClawMoat's pattern matching and entropy analysis before reaching the
11
+ semantic sub-agent.
12
+
13
+ - Sub-millisecond execution — pure regex/entropy, no model calls, no network
14
+ - 30+ credential patterns, OWASP Agentic AI coverage
15
+ - Deterministic results (confidence always 1.0)
16
+ - Complements OpenClaw's built-in Tier 1 patterns with ClawMoat's own library
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ cd plugins/clawmoat-adapter
22
+ npm install
23
+ npm run build
24
+ ```
25
+
26
+ This produces `dist/index.js` — a single-file CJS bundle with ClawMoat inlined.
27
+
28
+ ## Configure
29
+
30
+ In your OpenClaw config:
31
+
32
+ ```yaml
33
+ memory.sessions.sanitization.plugins:
34
+ - module: "./plugins/clawmoat-adapter/dist/index.js"
35
+ phase: "pre"
36
+ config:
37
+ # ClawMoat policy config (passed through to createPolicy)
38
+ policy:
39
+ secretPatterns: ["AWS_*", "GITHUB_TOKEN"]
40
+ blockedCommands: ["rm -rf", "curl * | sh"]
41
+ # Block threshold — which severity level triggers a block?
42
+ # Default: "low" (any finding blocks)
43
+ # Options: "low" | "medium" | "high" | "critical"
44
+ blockThreshold: "low"
45
+ ```
46
+
47
+ ## Block threshold
48
+
49
+ By default, any ClawMoat finding (even `low` severity) produces a block.
50
+ This is the safest setting. If you're seeing false positives on low-severity
51
+ findings, you can raise the threshold:
52
+
53
+ | `blockThreshold` | Blocks on | Flags (but passes) |
54
+ | --- | --- | --- |
55
+ | `"low"` (default) | low, medium, high, critical | — |
56
+ | `"medium"` | medium, high, critical | low |
57
+ | `"high"` | high, critical | low, medium |
58
+ | `"critical"` | critical only | low, medium, high |
59
+
60
+ Findings below the threshold still appear in the audit trail as flags and
61
+ contribute to frequency scoring. They're not ignored — they just don't
62
+ independently trigger a block.
63
+
64
+ ## How it maps to OpenClaw
65
+
66
+ | ClawMoat concept | OpenClaw mapping |
67
+ | --- | --- |
68
+ | `result.blocked` | `PluginResult.safe` (inverted) |
69
+ | `result.threats[].pattern` | `PluginResult.ruleIds` (prefixed with `clawmoat.scanner.`) |
70
+ | `result.threats[].match` | `PluginResult.flags` |
71
+ | `result.threats[].severity` | Used for block threshold decision |
72
+ | `createPolicy()` config | Passed through from plugin `config.policy` |
73
+
74
+ ## Overlap with built-in Tier 1
75
+
76
+ ClawMoat's injection and credential patterns overlap with OpenClaw's
77
+ built-in Tier 1 pre-filter (INJ-*, CRED-*). This is intentional —
78
+ defense in depth. ClawMoat maintains its own pattern library which may
79
+ catch things the built-in set misses, and vice versa. Duplicate findings
80
+ are deduplicated by ruleId in the final merge.
81
+
82
+ ## Session-Aware Pipeline (Drawbridge)
83
+
84
+ This adapter provides single-scan integration. For production deployments that need session tracking, frequency-based escalation, content redaction, and compliance audit trails, see [clawmoat-drawbridge](https://github.com/ziomancer/clawmoat-drawbridge) — a session-aware pipeline that wraps ClawMoat with:
85
+
86
+ - Exponential-decay frequency tracking with 3 escalation tiers
87
+ - 16-rule syntactic pre-filter
88
+ - Context profiles (general, medical, financial, code, MCP)
89
+ - Structured audit events with typed payloads
90
+ - Cross-session alert rules with aggregation
91
+ - Content sanitization/redaction
92
+
93
+ The adapter and Drawbridge are complementary: the adapter handles OpenClaw plugin interface compliance, Drawbridge handles session-level orchestration.
94
+
95
+ ## Development
96
+
97
+ ```bash
98
+ npm run typecheck # Type check without emitting
99
+ npm run build # Bundle to dist/index.js
100
+ ```
101
+
102
+ The build uses esbuild to produce a single CJS file with clawmoat inlined,
103
+ following the plugin spec's recommended bundling approach.