shroud-privacy 2.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 (45) hide show
  1. package/LICENSE +190 -0
  2. package/NOTICE +7 -0
  3. package/README.md +369 -0
  4. package/dist/audit.d.ts +46 -0
  5. package/dist/audit.js +127 -0
  6. package/dist/canary.d.ts +31 -0
  7. package/dist/canary.js +73 -0
  8. package/dist/config.d.ts +27 -0
  9. package/dist/config.js +123 -0
  10. package/dist/detectors/base.d.ts +8 -0
  11. package/dist/detectors/base.js +2 -0
  12. package/dist/detectors/code.d.ts +25 -0
  13. package/dist/detectors/code.js +144 -0
  14. package/dist/detectors/context.d.ts +31 -0
  15. package/dist/detectors/context.js +357 -0
  16. package/dist/detectors/patterns.d.ts +15 -0
  17. package/dist/detectors/patterns.js +58 -0
  18. package/dist/detectors/regex.d.ts +28 -0
  19. package/dist/detectors/regex.js +955 -0
  20. package/dist/generators/base.d.ts +6 -0
  21. package/dist/generators/base.js +2 -0
  22. package/dist/generators/codes.d.ts +20 -0
  23. package/dist/generators/codes.js +231 -0
  24. package/dist/generators/names.d.ts +29 -0
  25. package/dist/generators/names.js +194 -0
  26. package/dist/generators/network.d.ts +86 -0
  27. package/dist/generators/network.js +477 -0
  28. package/dist/hooks.d.ts +27 -0
  29. package/dist/hooks.js +457 -0
  30. package/dist/index.d.ts +12 -0
  31. package/dist/index.js +58 -0
  32. package/dist/mapping.d.ts +33 -0
  33. package/dist/mapping.js +72 -0
  34. package/dist/obfuscator.d.ts +78 -0
  35. package/dist/obfuscator.js +603 -0
  36. package/dist/redaction.d.ts +26 -0
  37. package/dist/redaction.js +76 -0
  38. package/dist/store.d.ts +40 -0
  39. package/dist/store.js +79 -0
  40. package/dist/types.d.ts +101 -0
  41. package/dist/types.js +35 -0
  42. package/ncg_adapter.py +530 -0
  43. package/openclaw.plugin.json +72 -0
  44. package/package.json +56 -0
  45. package/shroud_bridge.mjs +225 -0
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Shroud Bridge — JSON-RPC server over stdin/stdout.
4
+ *
5
+ * Loads the shroud Obfuscator from a configurable dist path and exposes
6
+ * obfuscate / deobfuscate / reset / getStats / configure via newline-
7
+ * delimited JSON messages.
8
+ *
9
+ * Protocol:
10
+ * → {"id":1,"method":"obfuscate","params":{"text":"..."}}
11
+ * ← {"id":1,"result":{"obfuscated":"...","entityCount":3,"audit":{...}}}
12
+ *
13
+ * → {"id":2,"method":"deobfuscate","params":{"text":"..."}}
14
+ * ← {"id":2,"result":{"text":"...","replacementCount":2,"audit":{...}}}
15
+ *
16
+ * → {"id":3,"method":"reset"}
17
+ * ← {"id":3,"result":{"ok":true}}
18
+ *
19
+ * → {"id":4,"method":"getStats"}
20
+ * ← {"id":4,"result":{...}}
21
+ *
22
+ * → {"id":5,"method":"ping"}
23
+ * ← {"id":5,"result":{"ok":true,"version":"1.3.0"}}
24
+ */
25
+
26
+ import { createHash, randomBytes } from "node:crypto";
27
+ import { createInterface } from "node:readline";
28
+ import { pathToFileURL } from "node:url";
29
+ import { resolve } from "node:path";
30
+ import { writeFileSync } from "node:fs";
31
+
32
+ // Shroud dist path passed as first CLI arg (default: ./dist relative to this script)
33
+ import { dirname } from "node:path";
34
+ import { fileURLToPath } from "node:url";
35
+ const __dirname = dirname(fileURLToPath(import.meta.url));
36
+ const shroudDist = process.argv[2] || resolve(__dirname, "dist");
37
+
38
+ // Dynamically import the Obfuscator and config resolver
39
+ const { Obfuscator } = await import(
40
+ pathToFileURL(resolve(shroudDist, "obfuscator.js")).href
41
+ );
42
+ const { resolveConfig } = await import(
43
+ pathToFileURL(resolve(shroudDist, "config.js")).href
44
+ );
45
+
46
+ // Read plugin config from env var (JSON) or use defaults
47
+ let pluginConfig = {};
48
+ if (process.env.SHROUD_PLUGIN_CONFIG) {
49
+ try {
50
+ pluginConfig = JSON.parse(process.env.SHROUD_PLUGIN_CONFIG);
51
+ } catch (e) {
52
+ process.stderr.write(`[shroud-bridge] Bad SHROUD_PLUGIN_CONFIG: ${e.message}\n`);
53
+ }
54
+ }
55
+
56
+ const config = resolveConfig(pluginConfig);
57
+ let obfuscator = new Obfuscator(config);
58
+
59
+ // Hash chain: each audit entry includes hash of previous entry for tamper evidence
60
+ let chainHash = "0000000000000000";
61
+
62
+ function advanceChain(data) {
63
+ const payload = chainHash + JSON.stringify(data);
64
+ chainHash = createHash("sha256").update(payload).digest("hex").slice(0, 16);
65
+ return chainHash;
66
+ }
67
+
68
+ function proofHash(text) {
69
+ const salt = config.auditHashSalt || "";
70
+ const truncate = config.auditHashTruncate || 12;
71
+ return createHash("sha256")
72
+ .update(salt + text)
73
+ .digest("hex")
74
+ .slice(0, truncate);
75
+ }
76
+
77
+ const STATS_FILE = process.env.SHROUD_STATS_FILE || "/tmp/shroud-stats.json";
78
+
79
+ function dumpStats() {
80
+ try {
81
+ const stats = obfuscator.getStats();
82
+ stats.updatedAt = new Date().toISOString();
83
+ stats.pid = process.pid;
84
+ writeFileSync(STATS_FILE, JSON.stringify(stats, null, 2) + "\n");
85
+ } catch {
86
+ // best-effort
87
+ }
88
+ }
89
+
90
+ process.stderr.write("[shroud-bridge] Ready.\n");
91
+
92
+ // Signal readiness to parent process
93
+ process.stdout.write(JSON.stringify({ ready: true, version: "1.3.0" }) + "\n");
94
+
95
+ const rl = createInterface({ input: process.stdin, crlfDelay: Infinity });
96
+
97
+ rl.on("line", (line) => {
98
+ if (!line.trim()) return;
99
+ let req;
100
+ try {
101
+ req = JSON.parse(line);
102
+ } catch (e) {
103
+ process.stdout.write(
104
+ JSON.stringify({ id: null, error: `Parse error: ${e.message}` }) + "\n"
105
+ );
106
+ return;
107
+ }
108
+
109
+ const { id, method, params } = req;
110
+ let result;
111
+ try {
112
+ switch (method) {
113
+ case "ping":
114
+ result = { ok: true, version: "1.3.0" };
115
+ break;
116
+
117
+ case "obfuscate": {
118
+ const text = params?.text ?? "";
119
+ const out = obfuscator.obfuscate(text);
120
+ const categories = {};
121
+ for (const e of out.entities) {
122
+ categories[e.category] = (categories[e.category] || 0) + 1;
123
+ }
124
+ result = {
125
+ obfuscated: out.obfuscated,
126
+ entityCount: out.entities.length,
127
+ categories,
128
+ };
129
+
130
+ // Always include audit data in response — Python side decides what to log
131
+ if (config.auditEnabled || config.verboseLogging) {
132
+ const reqId = randomBytes(8).toString("hex");
133
+ const audit = {
134
+ req: reqId,
135
+ totalEntities: out.entities.length,
136
+ inputChars: text.length,
137
+ outputChars: out.obfuscated.length,
138
+ charDelta: out.obfuscated.length - text.length,
139
+ byCategory: categories,
140
+ modified: out.obfuscated !== text,
141
+ proofIn: proofHash(text),
142
+ proofOut: proofHash(out.obfuscated),
143
+ };
144
+
145
+ // Fake samples (only fake values, never real)
146
+ const maxFakes = config.auditMaxFakesSample || 3;
147
+ audit.fakesSample = Object.values(out.mappingsUsed).slice(0, maxFakes);
148
+
149
+ // Advance tamper-evident hash chain
150
+ audit.chainHash = advanceChain(audit);
151
+
152
+ result.audit = audit;
153
+ }
154
+ dumpStats();
155
+ break;
156
+ }
157
+
158
+ case "deobfuscate": {
159
+ const text = params?.text ?? "";
160
+ const deobResult = obfuscator.deobfuscateWithStats
161
+ ? obfuscator.deobfuscateWithStats(text)
162
+ : { text: obfuscator.deobfuscate(text), replacementCount: 0 };
163
+
164
+ // Always include store size for diagnostics
165
+ const stats = obfuscator.getStats();
166
+ result = {
167
+ text: deobResult.text,
168
+ replacementCount: deobResult.replacementCount,
169
+ storeSize: stats.storeMappings ?? 0,
170
+ };
171
+
172
+ if (config.auditEnabled || config.verboseLogging) {
173
+ const audit = {
174
+ replacementCount: deobResult.replacementCount,
175
+ storeSize: stats.storeMappings ?? 0,
176
+ inputChars: text.length,
177
+ outputChars: deobResult.text.length,
178
+ modified: deobResult.text !== text,
179
+ proofIn: proofHash(text),
180
+ proofOut: proofHash(deobResult.text),
181
+ };
182
+ // Correlate with request via chain
183
+ audit.chainHash = advanceChain(audit);
184
+ result.audit = audit;
185
+ }
186
+ dumpStats();
187
+ break;
188
+ }
189
+
190
+ case "reset":
191
+ obfuscator.reset();
192
+ chainHash = "0000000000000000";
193
+ dumpStats();
194
+ result = { ok: true };
195
+ break;
196
+
197
+ case "getStats":
198
+ result = obfuscator.getStats();
199
+ result.chainHash = chainHash;
200
+ break;
201
+
202
+ case "reconfigure": {
203
+ // Hot-reload config without restarting the process
204
+ const newConfig = resolveConfig(params?.config ?? {});
205
+ obfuscator = new Obfuscator(newConfig);
206
+ result = { ok: true, config: newConfig };
207
+ break;
208
+ }
209
+
210
+ default:
211
+ result = undefined;
212
+ process.stdout.write(
213
+ JSON.stringify({ id, error: `Unknown method: ${method}` }) + "\n"
214
+ );
215
+ return;
216
+ }
217
+ process.stdout.write(JSON.stringify({ id, result }) + "\n");
218
+ } catch (e) {
219
+ process.stdout.write(
220
+ JSON.stringify({ id, error: `${method} failed: ${e.message}` }) + "\n"
221
+ );
222
+ }
223
+ });
224
+
225
+ rl.on("close", () => process.exit(0));