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.
- package/LICENSE +190 -0
- package/NOTICE +7 -0
- package/README.md +369 -0
- package/dist/audit.d.ts +46 -0
- package/dist/audit.js +127 -0
- package/dist/canary.d.ts +31 -0
- package/dist/canary.js +73 -0
- package/dist/config.d.ts +27 -0
- package/dist/config.js +123 -0
- package/dist/detectors/base.d.ts +8 -0
- package/dist/detectors/base.js +2 -0
- package/dist/detectors/code.d.ts +25 -0
- package/dist/detectors/code.js +144 -0
- package/dist/detectors/context.d.ts +31 -0
- package/dist/detectors/context.js +357 -0
- package/dist/detectors/patterns.d.ts +15 -0
- package/dist/detectors/patterns.js +58 -0
- package/dist/detectors/regex.d.ts +28 -0
- package/dist/detectors/regex.js +955 -0
- package/dist/generators/base.d.ts +6 -0
- package/dist/generators/base.js +2 -0
- package/dist/generators/codes.d.ts +20 -0
- package/dist/generators/codes.js +231 -0
- package/dist/generators/names.d.ts +29 -0
- package/dist/generators/names.js +194 -0
- package/dist/generators/network.d.ts +86 -0
- package/dist/generators/network.js +477 -0
- package/dist/hooks.d.ts +27 -0
- package/dist/hooks.js +457 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +58 -0
- package/dist/mapping.d.ts +33 -0
- package/dist/mapping.js +72 -0
- package/dist/obfuscator.d.ts +78 -0
- package/dist/obfuscator.js +603 -0
- package/dist/redaction.d.ts +26 -0
- package/dist/redaction.js +76 -0
- package/dist/store.d.ts +40 -0
- package/dist/store.js +79 -0
- package/dist/types.d.ts +101 -0
- package/dist/types.js +35 -0
- package/ncg_adapter.py +530 -0
- package/openclaw.plugin.json +72 -0
- package/package.json +56 -0
- 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));
|