shroud-privacy 2.2.1 → 2.2.3
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 +2 -2
- package/dist/detectors/regex.js +26 -0
- package/dist/hooks.js +1 -4
- package/dist/index.js +8 -8
- package/openclaw.plugin.json +1 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -183,7 +183,6 @@ To enable proof hashes and fake samples for deeper audit:
|
|
|
183
183
|
| `auditHashSalt` | string | `""` | Salt for proof hashes |
|
|
184
184
|
| `auditHashTruncate` | number | `12` | Truncate proof hashes to N hex chars |
|
|
185
185
|
| `auditMaxFakesSample` | number | `0` | Include up to N fake values in audit (0 = off) |
|
|
186
|
-
| `logMappings` | boolean | `false` | Log mapping table (debug only) |
|
|
187
186
|
| `customPatterns` | array | `[]` | User-defined regex detection patterns |
|
|
188
187
|
| `detectorOverrides` | object | `{}` | Override built-in rules: disable or change confidence per rule name |
|
|
189
188
|
| `maxToolDepth` | number | `10` | Max nested tool call depth before warning |
|
|
@@ -299,6 +298,7 @@ Shroud includes a `ContextDetector` that wraps the regex engine with post-detect
|
|
|
299
298
|
- **Hostname propagation**: `hostname FCNETR1` in one place → bare `FCNETR1` detected everywhere in the text.
|
|
300
299
|
- **Learned entities**: Hostnames and infra identifiers seen in previous messages are remembered and detected in future messages without requiring config-line context.
|
|
301
300
|
- **Documentation filtering**: RFC 5737 TEST-NET IPs (192.0.2.x, 198.51.100.x, 203.0.113.x), RFC 3849 IPv6 doc prefix (`2001:db8::/32`), IPv6 loopback (`::1`), `example.com` emails, and well-known placeholders are automatically skipped.
|
|
301
|
+
- **Public URL filtering**: URLs pointing to well-known public platforms (YouTube, GitHub, Wikipedia, Google, Reddit, Stack Overflow, npm, PyPI, Docker Hub, etc.) are never obfuscated — they aren't PII. Emails at these domains are still detected.
|
|
302
302
|
- **Common word decay**: Words like `permit`, `deny`, `default` that happen to match patterns get 50% confidence reduction.
|
|
303
303
|
- **Recursive deobfuscation**: Up to 3 passes for nested structures (fakes inside JSON-encoded strings).
|
|
304
304
|
- **Subnet-aware deobfuscation**: When an LLM derives network/broadcast addresses from fake host IPs (e.g., computing `.0` or `.255`), Shroud reverse-maps them via the SubnetMapper. Works for both CGNAT (IPv4) and ULA (IPv6) fake ranges, including LLM-compressed IPv6 forms.
|
|
@@ -452,7 +452,7 @@ Supports context manager, auto-restart on crash, residual fake detection, and ho
|
|
|
452
452
|
|
|
453
453
|
```bash
|
|
454
454
|
npm install
|
|
455
|
-
npm test # run vitest (
|
|
455
|
+
npm test # run vitest (777 tests)
|
|
456
456
|
npm run build # compile TypeScript
|
|
457
457
|
npm run lint # type-check without emitting
|
|
458
458
|
```
|
package/dist/detectors/regex.js
CHANGED
|
@@ -24,6 +24,24 @@ const DOC_DOMAINS = new Set([
|
|
|
24
24
|
"example.com", "example.net", "example.org", // RFC 2606
|
|
25
25
|
"localhost", "invalid",
|
|
26
26
|
]);
|
|
27
|
+
/** Well-known public domains whose URLs are never PII. */
|
|
28
|
+
const PUBLIC_DOMAINS = new Set([
|
|
29
|
+
"youtube.com", "youtu.be", "m.youtube.com",
|
|
30
|
+
"google.com", "google.co.uk", "google.de", "google.fr",
|
|
31
|
+
"github.com", "gitlab.com", "bitbucket.org",
|
|
32
|
+
"stackoverflow.com", "stackexchange.com",
|
|
33
|
+
"wikipedia.org", "wikimedia.org",
|
|
34
|
+
"twitter.com", "x.com",
|
|
35
|
+
"reddit.com",
|
|
36
|
+
"linkedin.com",
|
|
37
|
+
"medium.com",
|
|
38
|
+
"npmjs.com", "pypi.org", "crates.io",
|
|
39
|
+
"docker.com", "hub.docker.com",
|
|
40
|
+
"microsoft.com", "apple.com",
|
|
41
|
+
"mozilla.org",
|
|
42
|
+
"w3.org",
|
|
43
|
+
"archive.org",
|
|
44
|
+
]);
|
|
27
45
|
const DOC_HOSTNAMES = new Set([
|
|
28
46
|
"localhost", "HOSTNAME", "EXAMPLE", "CHANGEME",
|
|
29
47
|
"YOUR_HOST", "YOURHOST", "hostname", "example",
|
|
@@ -73,6 +91,14 @@ export function isDocExample(value, category) {
|
|
|
73
91
|
return true;
|
|
74
92
|
}
|
|
75
93
|
}
|
|
94
|
+
// Public domains — skip for URLs only (emails @youtube.com are still PII)
|
|
95
|
+
if (category === Category.URL) {
|
|
96
|
+
for (const d of PUBLIC_DOMAINS) {
|
|
97
|
+
if (lower.includes(`//${d}`) || lower.includes(`//${d}/`) || lower.includes(`.${d}`)) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
76
102
|
return false;
|
|
77
103
|
}
|
|
78
104
|
case Category.BGP_ASN:
|
package/dist/hooks.js
CHANGED
|
@@ -182,6 +182,7 @@ export function registerHooks(api, obfuscator) {
|
|
|
182
182
|
text = text.replace(/<(https?:\/\/[^>]+)>/g, "$1");
|
|
183
183
|
return text;
|
|
184
184
|
}
|
|
185
|
+
const isFirstLoad = !globalThis.__shroudObfuscator;
|
|
185
186
|
if (!IS_TEST) {
|
|
186
187
|
const g = globalThis;
|
|
187
188
|
if (g.__shroudObfuscator) {
|
|
@@ -618,7 +619,6 @@ export function registerHooks(api, obfuscator) {
|
|
|
618
619
|
}
|
|
619
620
|
return event;
|
|
620
621
|
};
|
|
621
|
-
api.logger?.info("[shroud] Installed global streaming deobfuscation hook");
|
|
622
622
|
// -----------------------------------------------------------------------
|
|
623
623
|
// 7. Global deobfuscation hook for channel delivery (defense-in-depth).
|
|
624
624
|
// Primary deobfuscation happens in the fetch response interceptor (8).
|
|
@@ -630,7 +630,6 @@ export function registerHooks(api, obfuscator) {
|
|
|
630
630
|
return text;
|
|
631
631
|
return ob().deobfuscate(text);
|
|
632
632
|
};
|
|
633
|
-
api.logger?.info("[shroud] Registered globalThis.__shroudDeobfuscate for channel delivery");
|
|
634
633
|
// -----------------------------------------------------------------------
|
|
635
634
|
// 8. Fetch intercept — the universal privacy boundary.
|
|
636
635
|
//
|
|
@@ -658,7 +657,6 @@ export function registerHooks(api, obfuscator) {
|
|
|
658
657
|
"/v1/models/", // Google Vertex AI
|
|
659
658
|
];
|
|
660
659
|
const originalFetch = globalThis.fetch;
|
|
661
|
-
api.logger?.info(`[shroud][fetch-guard] hasFetch=${!!originalFetch} patched=${!!globalThis.__shroudFetchPatched}`);
|
|
662
660
|
if (originalFetch && !(globalThis.__shroudFetchPatched)) {
|
|
663
661
|
globalThis.__shroudFetchPatched = true;
|
|
664
662
|
globalThis.fetch = async function shroudFetchInterceptor(input, init) {
|
|
@@ -1124,6 +1122,5 @@ export function registerHooks(api, obfuscator) {
|
|
|
1124
1122
|
}
|
|
1125
1123
|
return response;
|
|
1126
1124
|
}
|
|
1127
|
-
api.logger?.info("[shroud] Installed outbound fetch intercept — PII obfuscated before ALL LLM API calls");
|
|
1128
1125
|
}
|
|
1129
1126
|
}
|
package/dist/index.js
CHANGED
|
@@ -87,18 +87,14 @@ function patchEventStreamPrototype(logger) {
|
|
|
87
87
|
// Try requiring from the file's own directory (no exports restriction from parent)
|
|
88
88
|
const mod = localRequire("./event-stream.js");
|
|
89
89
|
EventStream = mod?.EventStream ?? mod?.default?.EventStream;
|
|
90
|
-
if (EventStream)
|
|
91
|
-
logger?.info(`[shroud] Found EventStream via direct file path: ${candidate}`);
|
|
90
|
+
if (EventStream)
|
|
92
91
|
break;
|
|
93
|
-
}
|
|
94
92
|
}
|
|
95
93
|
catch { /* try next */ }
|
|
96
94
|
}
|
|
97
95
|
}
|
|
98
|
-
if (!EventStream?.prototype?.push)
|
|
99
|
-
logger?.info("[shroud] Could not locate EventStream class — streaming deobfuscation unavailable");
|
|
96
|
+
if (!EventStream?.prototype?.push)
|
|
100
97
|
return;
|
|
101
|
-
}
|
|
102
98
|
// Wrap prototype.push with the deobfuscation hook
|
|
103
99
|
const originalPush = EventStream.prototype.push;
|
|
104
100
|
EventStream.prototype.push = function shroudPatchedPush(event) {
|
|
@@ -110,7 +106,6 @@ function patchEventStreamPrototype(logger) {
|
|
|
110
106
|
};
|
|
111
107
|
// Mark as patched to prevent double-wrapping
|
|
112
108
|
globalThis[PATCH_MARKER] = true;
|
|
113
|
-
logger?.info("[shroud] Patched EventStream.prototype.push — zero-file streaming deobfuscation active");
|
|
114
109
|
}
|
|
115
110
|
export default {
|
|
116
111
|
id: "shroud-privacy",
|
|
@@ -160,6 +155,11 @@ export default {
|
|
|
160
155
|
};
|
|
161
156
|
},
|
|
162
157
|
});
|
|
163
|
-
|
|
158
|
+
// Single load confirmation — used by test harness to verify plugin loaded.
|
|
159
|
+
// Only logs once per process (suppressed on subsequent agent loads).
|
|
160
|
+
if (!globalThis.__shroudLoadLogged) {
|
|
161
|
+
globalThis.__shroudLoadLogged = true;
|
|
162
|
+
api.logger?.info("[shroud] Plugin loaded.");
|
|
163
|
+
}
|
|
164
164
|
},
|
|
165
165
|
};
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "shroud-privacy",
|
|
3
3
|
"name": "Shroud",
|
|
4
|
-
"version": "2.2.
|
|
4
|
+
"version": "2.2.3",
|
|
5
5
|
"description": "Privacy obfuscation with deterministic fake values and deobfuscation — PII never reaches the LLM, tool calls still work",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
"auditHashSalt": { "type": "string", "default": "", "description": "Salt for proof hashes" },
|
|
22
22
|
"auditHashTruncate": { "type": "integer", "default": 12, "minimum": 4, "maximum": 64, "description": "Truncate proof hashes to N hex chars" },
|
|
23
23
|
"auditMaxFakesSample": { "type": "integer", "default": 0, "minimum": 0, "maximum": 20, "description": "Include up to N fake replacement values in audit log (0 = disabled)" },
|
|
24
|
-
"logMappings": { "type": "boolean", "default": false, "description": "Log mapping table (debug only)" },
|
|
25
24
|
"detectorOverrides": {
|
|
26
25
|
"type": "object",
|
|
27
26
|
"additionalProperties": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shroud-privacy",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.3",
|
|
4
4
|
"description": "Privacy obfuscation for AI agents — detects PII and replaces with deterministic fake values before anything reaches the LLM. Works with OpenClaw (plugin) or any agent (APP protocol).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|