shroud-privacy 2.0.20 → 2.2.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.
- package/README.md +195 -36
- package/app-server.mjs +534 -0
- package/clients/python/shroud_client.py +369 -0
- package/dist/hooks.js +427 -42
- package/dist/index.js +93 -78
- package/dist/obfuscator.d.ts +4 -0
- package/dist/obfuscator.js +18 -0
- package/openclaw.plugin.json +2 -2
- package/package.json +15 -7
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<h1 align="center">Shroud — Community Edition</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
Privacy obfuscation
|
|
8
|
+
Privacy obfuscation for AI agents. Detects sensitive data (PII, network infrastructure, credentials) and replaces it with deterministic fake values before anything reaches the LLM. Tool calls still work because Shroud deobfuscates on the way back. Works with <a href="https://openclaw.ai">OpenClaw</a> (plugin) or any agent via the Agent Privacy Protocol (APP).
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
> **Open-source Community Edition** — free to use under Apache 2.0 license. [Enterprise Edition](#enterprise-edition) available with additional features for teams.
|
|
@@ -26,57 +26,94 @@
|
|
|
26
26
|
| `before_tool_call` | LLM → Tool | Deobfuscate tool parameters + track tool chain depth |
|
|
27
27
|
| `tool_result_persist` | Tool → History | Obfuscate tool results before storing |
|
|
28
28
|
| `message_sending` | Agent → User | Deobfuscate outbound messages (all channels) |
|
|
29
|
+
| `globalThis.__shroudDeobfuscate` | Agent → Channel | Global deobfuscation hook — called by OpenClaw before ANY channel send |
|
|
29
30
|
|
|
30
|
-
> **
|
|
31
|
+
> **Privacy guarantee:** Shroud intercepts ALL outbound LLM API calls (Anthropic, OpenAI, Google, any provider) at the `fetch` level and obfuscates PII in every message — including assistant history and Slack `<mailto:>` markup — before it leaves the process. No PII reaches the LLM. On the channel delivery side, Shroud registers `globalThis.__shroudDeobfuscate` — a single function that OpenClaw calls before sending to ANY channel (Slack, WhatsApp, Signal, web, etc.). One hook, all channels, transparent no-op if Shroud isn't loaded.
|
|
32
|
+
|
|
33
|
+
> **Requires OpenClaw 2026.3.24 or later** with the channel delivery patch (see [OpenClaw patch](#openclaw-channel-delivery-patch) below).
|
|
31
34
|
|
|
32
35
|
## Install
|
|
33
36
|
|
|
34
|
-
### OpenClaw
|
|
37
|
+
### OpenClaw (2026.3.24+)
|
|
35
38
|
|
|
36
39
|
```bash
|
|
40
|
+
# Ensure you're on OpenClaw 2026.3.24 or later
|
|
41
|
+
openclaw --version
|
|
42
|
+
|
|
43
|
+
# Install Shroud
|
|
37
44
|
openclaw plugins install shroud-privacy
|
|
38
45
|
```
|
|
39
46
|
|
|
40
|
-
|
|
47
|
+
Configure in `~/.openclaw/openclaw.json` under `plugins.entries."shroud-privacy".config`. No OpenClaw file modifications needed — Shroud uses runtime prototype patches only.
|
|
41
48
|
|
|
42
|
-
###
|
|
49
|
+
### Any agent (via APP)
|
|
50
|
+
|
|
51
|
+
The **Agent Privacy Protocol** (APP) lets any AI agent add privacy obfuscation — no OpenClaw required. Shroud ships with an APP server and a Python client.
|
|
43
52
|
|
|
44
53
|
```bash
|
|
45
|
-
|
|
46
|
-
cd shroud
|
|
47
|
-
npm install && npm run build
|
|
48
|
-
bash deploy-local.sh # → OpenClaw (~/.openclaw/extensions/)
|
|
54
|
+
npm install shroud-privacy
|
|
49
55
|
```
|
|
50
56
|
|
|
51
|
-
|
|
57
|
+
**Python:**
|
|
52
58
|
|
|
53
|
-
|
|
59
|
+
```python
|
|
60
|
+
from shroud_client import ShroudClient
|
|
54
61
|
|
|
55
|
-
|
|
56
|
-
#
|
|
57
|
-
|
|
62
|
+
with ShroudClient() as shroud:
|
|
63
|
+
# Before sending to LLM
|
|
64
|
+
result = shroud.obfuscate("Contact admin@acme.com about 10.1.0.1")
|
|
65
|
+
send_to_llm(result.text) # "Contact user@example.net about 100.64.0.12"
|
|
58
66
|
|
|
59
|
-
#
|
|
60
|
-
|
|
67
|
+
# After receiving from LLM
|
|
68
|
+
restored = shroud.deobfuscate(llm_response)
|
|
69
|
+
show_to_user(restored.text) # original values restored
|
|
61
70
|
```
|
|
62
71
|
|
|
63
|
-
|
|
72
|
+
Copy `clients/python/shroud_client.py` into your project, or import it directly from the npm install path. Requires Node.js on the PATH.
|
|
64
73
|
|
|
65
|
-
|
|
74
|
+
**Any language:**
|
|
66
75
|
|
|
67
|
-
|
|
76
|
+
Spawn the APP server and talk JSON-RPC over stdin/stdout:
|
|
68
77
|
|
|
69
78
|
```bash
|
|
70
|
-
|
|
71
|
-
|
|
79
|
+
node node_modules/shroud-privacy/app-server.mjs node_modules/shroud-privacy/dist
|
|
80
|
+
```
|
|
72
81
|
|
|
73
|
-
|
|
74
|
-
|
|
82
|
+
Handshake (server writes on startup):
|
|
83
|
+
```json
|
|
84
|
+
{"app":"1.0","engine":"shroud","version":"2.1.0","capabilities":["obfuscate","deobfuscate","batch","stats","health","configure","audit","partitions"]}
|
|
85
|
+
```
|
|
75
86
|
|
|
76
|
-
|
|
77
|
-
|
|
87
|
+
Obfuscate:
|
|
88
|
+
```json
|
|
89
|
+
→ {"id":1,"method":"obfuscate","params":{"text":"Contact admin@acme.com"}}
|
|
90
|
+
← {"id":1,"result":{"text":"Contact user@example.net","entityCount":1,"categories":{"email":1},"modified":true}}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Deobfuscate:
|
|
94
|
+
```json
|
|
95
|
+
→ {"id":2,"method":"deobfuscate","params":{"text":"Contact user@example.net"}}
|
|
96
|
+
← {"id":2,"result":{"text":"Contact admin@acme.com","replacementCount":1,"modified":true}}
|
|
97
|
+
```
|
|
78
98
|
|
|
79
|
-
|
|
99
|
+
Other methods: `reset`, `stats`, `health`, `configure`, `shutdown`.
|
|
100
|
+
|
|
101
|
+
### From source (development)
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
git clone https://github.com/walterkeating-stack/shroud.git
|
|
105
|
+
cd shroud
|
|
106
|
+
npm install && npm run build
|
|
107
|
+
openclaw plugins install --path .
|
|
108
|
+
openclaw gateway restart
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Updating
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
# Remove old plugin, reinstall from npm, restart
|
|
115
|
+
openclaw plugins remove shroud-privacy
|
|
116
|
+
openclaw plugins install shroud-privacy
|
|
80
117
|
openclaw gateway restart
|
|
81
118
|
```
|
|
82
119
|
|
|
@@ -195,16 +232,35 @@ alias shroud-stats="node ~/.openclaw/extensions/shroud-privacy/scripts/shroud-st
|
|
|
195
232
|
|
|
196
233
|
The CLI reads live stats from `/tmp/shroud-stats.json` (override with `SHROUD_STATS_FILE` env var). The stats file is updated by the running gateway on every obfuscation event.
|
|
197
234
|
|
|
198
|
-
###
|
|
235
|
+
### How privacy works
|
|
236
|
+
|
|
237
|
+
Shroud uses runtime prototype patches — **no OpenClaw files are modified**:
|
|
238
|
+
|
|
239
|
+
**Outbound (PII → LLM):** Patches `globalThis.fetch` to intercept all POST requests to LLM API endpoints (`/messages`, `/chat/completions`). Obfuscates every message in the request body — user, assistant, system, tool results — before the request leaves the process. Strips Slack `<mailto:>` markup to prevent PII leaking through chat formatting. Re-obfuscates deobfuscated assistant messages in conversation history to prevent multi-turn PII leaks. Works for every LLM provider.
|
|
240
|
+
|
|
241
|
+
**Inbound (LLM → User):** Patches `EventStream.prototype.push()` to deobfuscate streaming responses in real-time. Fake values are replaced with real values as they stream.
|
|
242
|
+
|
|
243
|
+
**Channel delivery:** Shroud registers `globalThis.__shroudDeobfuscate(text)` — a single global function that converts fake values back to real values. OpenClaw calls this once in its generic message delivery function, before sending to any channel. If Shroud isn't loaded, the function doesn't exist — transparent no-op. The `message_sending` hook provides a backup deobfuscation path.
|
|
199
244
|
|
|
200
|
-
|
|
245
|
+
All patches are applied once at plugin load and are idempotent — subsequent loads detect and skip them.
|
|
246
|
+
|
|
247
|
+
### OpenClaw channel delivery patch
|
|
248
|
+
|
|
249
|
+
Shroud requires a one-line addition to OpenClaw's message delivery function — the single point where all outbound channel messages pass through. This covers ALL channels (Slack, WhatsApp, Signal, Telegram, web, etc.) with one change:
|
|
250
|
+
|
|
251
|
+
```js
|
|
252
|
+
// In OpenClaw's generic message delivery function:
|
|
253
|
+
const deob = globalThis.__shroudDeobfuscate;
|
|
254
|
+
if (deob && typeof text === 'string') text = deob(text);
|
|
255
|
+
```
|
|
201
256
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
257
|
+
**Why this works:**
|
|
258
|
+
- **One change, all channels** — no per-channel hooks needed
|
|
259
|
+
- **Transparent** — if Shroud isn't loaded, `globalThis.__shroudDeobfuscate` is `undefined`, the `if` is false, zero overhead
|
|
260
|
+
- **All releases** — works on any OpenClaw version that sends channel messages through a common delivery path
|
|
261
|
+
- **Last-moment deobfuscation** — fakes are replaced with real values at the latest possible point, just before the HTTP API call
|
|
206
262
|
|
|
207
|
-
|
|
263
|
+
The `deploy-local.sh` script includes a post-install verification step that tests the full chain.
|
|
208
264
|
|
|
209
265
|
### Rule hit counters
|
|
210
266
|
|
|
@@ -303,6 +359,109 @@ With proof hashes enabled:
|
|
|
303
359
|
|
|
304
360
|
OpenClaw logs each plugin message twice (once under the plugin subsystem logger, once under the parent `openclaw` logger). This is normal OpenClaw behavior. Filter to `"name":"openclaw"` to get one line per event, as shown in the verify command above.
|
|
305
361
|
|
|
362
|
+
## Agent Privacy Protocol (APP)
|
|
363
|
+
|
|
364
|
+
APP is an open protocol for adding privacy obfuscation to any AI agent. Shroud is the reference implementation.
|
|
365
|
+
|
|
366
|
+
### Overview
|
|
367
|
+
|
|
368
|
+
```
|
|
369
|
+
┌─────────────────┐ stdin/stdout ┌──────────────────┐
|
|
370
|
+
│ Your Agent │ ◄──── JSON-RPC ────► │ APP Server │
|
|
371
|
+
│ (any language) │ │ (app-server.mjs)│
|
|
372
|
+
└─────────────────┘ └──────────────────┘
|
|
373
|
+
│ │
|
|
374
|
+
│ 1. obfuscate(user_input) │ detects PII,
|
|
375
|
+
│ 2. send to LLM ──────────────► │ returns fakes
|
|
376
|
+
│ 3. deobfuscate(llm_response) │ restores reals
|
|
377
|
+
│ 4. show to user │
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Protocol specification
|
|
381
|
+
|
|
382
|
+
- **Transport**: Newline-delimited JSON-RPC 2.0 over stdin/stdout
|
|
383
|
+
- **Encoding**: UTF-8
|
|
384
|
+
- **Process model**: Agent spawns APP server as subprocess, one per agent instance
|
|
385
|
+
|
|
386
|
+
### Handshake
|
|
387
|
+
|
|
388
|
+
On startup, the server writes a single JSON line to stdout:
|
|
389
|
+
|
|
390
|
+
```json
|
|
391
|
+
{"app":"1.0","engine":"shroud","version":"2.1.0","capabilities":["obfuscate","deobfuscate","batch","stats","health","configure","audit","partitions"]}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
The agent must read this line before sending requests. Fields:
|
|
395
|
+
- `app` — protocol version (always `"1.0"`)
|
|
396
|
+
- `engine` — implementation name
|
|
397
|
+
- `version` — implementation version
|
|
398
|
+
- `capabilities` — supported methods
|
|
399
|
+
|
|
400
|
+
### Methods
|
|
401
|
+
|
|
402
|
+
| Method | Params | Returns | Description |
|
|
403
|
+
|--------|--------|---------|-------------|
|
|
404
|
+
| `obfuscate` | `{text}` | `{text, entityCount, categories, modified, audit}` | Replace real values with fakes |
|
|
405
|
+
| `deobfuscate` | `{text}` | `{text, replacementCount, modified, audit}` | Restore fakes to real values |
|
|
406
|
+
| `reset` | `{}` | `{ok, summary}` | Clear all mappings |
|
|
407
|
+
| `stats` | `{}` | `{storeMappings, ruleHits, ...}` | Engine statistics |
|
|
408
|
+
| `health` | `{}` | `{uptime, requests, avgLatencyMs}` | Liveness check |
|
|
409
|
+
| `configure` | `{config}` | `{ok}` | Hot-reload configuration |
|
|
410
|
+
| `batch` | `{operations: [{direction, text}]}` | `{results: [...]}` | Batch obfuscate/deobfuscate |
|
|
411
|
+
| `shutdown` | `{}` | `{ok}` | Graceful shutdown (flushes stats) |
|
|
412
|
+
|
|
413
|
+
### Request/response format
|
|
414
|
+
|
|
415
|
+
```
|
|
416
|
+
→ {"id":1,"method":"obfuscate","params":{"text":"Server 10.1.0.1 is down"}}
|
|
417
|
+
← {"id":1,"result":{"text":"Server 100.64.0.12 is down","entityCount":1,"categories":{"ip_address":1},"modified":true,"audit":{"requestId":"a1b2c3","proofIn":"8a3c1f","proofOut":"f7d2a1"}}}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
Errors:
|
|
421
|
+
```
|
|
422
|
+
← {"id":1,"error":{"code":-32602,"message":"Missing required param: text"}}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Heartbeat
|
|
426
|
+
|
|
427
|
+
The server writes JSON heartbeats to stderr every 30 seconds:
|
|
428
|
+
```json
|
|
429
|
+
{"heartbeat":true,"pid":12345,"uptime":120,"requests":42,"avgLatencyMs":1.2,"storeSize":15,"memoryMB":28}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### Integration checklist
|
|
433
|
+
|
|
434
|
+
1. `npm install shroud-privacy`
|
|
435
|
+
2. Spawn: `node node_modules/shroud-privacy/app-server.mjs node_modules/shroud-privacy/dist`
|
|
436
|
+
3. Read handshake line from stdout
|
|
437
|
+
4. Before LLM: send `obfuscate`, use returned `text`
|
|
438
|
+
5. After LLM: send `deobfuscate`, show returned `text` to user
|
|
439
|
+
6. On agent shutdown: send `shutdown`
|
|
440
|
+
|
|
441
|
+
### Python client
|
|
442
|
+
|
|
443
|
+
A ready-made Python client is included at `clients/python/shroud_client.py`:
|
|
444
|
+
|
|
445
|
+
```python
|
|
446
|
+
from shroud_client import ShroudClient
|
|
447
|
+
|
|
448
|
+
client = ShroudClient()
|
|
449
|
+
client.start()
|
|
450
|
+
|
|
451
|
+
safe = client.obfuscate("Contact admin@acme.com about 10.1.0.1")
|
|
452
|
+
print(safe.text) # fakes
|
|
453
|
+
print(safe.entity_count) # 2
|
|
454
|
+
print(safe.categories) # {"email": 1, "ip_address": 1}
|
|
455
|
+
|
|
456
|
+
real = client.deobfuscate(llm_response)
|
|
457
|
+
print(real.text) # originals restored
|
|
458
|
+
print(real.residual_fakes) # any CGNAT/ULA IPs that survived
|
|
459
|
+
|
|
460
|
+
client.stop()
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
Supports context manager, auto-restart on crash, residual fake detection, and hot-reload via `configure()`.
|
|
464
|
+
|
|
306
465
|
## Development
|
|
307
466
|
|
|
308
467
|
```bash
|
|
@@ -316,7 +475,7 @@ npm run lint # type-check without emitting
|
|
|
316
475
|
|
|
317
476
|
```bash
|
|
318
477
|
npm run build
|
|
319
|
-
|
|
478
|
+
openclaw plugins install --path .
|
|
320
479
|
openclaw gateway restart
|
|
321
480
|
```
|
|
322
481
|
|
|
@@ -329,8 +488,8 @@ openclaw gateway restart
|
|
|
329
488
|
# 2. Update CHANGELOG.md
|
|
330
489
|
# 3. Commit and tag
|
|
331
490
|
git add -A
|
|
332
|
-
git commit -m "
|
|
333
|
-
git tag
|
|
491
|
+
git commit -m "release: vX.Y.Z"
|
|
492
|
+
git tag vX.Y.Z
|
|
334
493
|
git push && git push --tags
|
|
335
494
|
```
|
|
336
495
|
|