@privacyscrubber/sdk 2.3.9 → 2.4.1
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 +85 -27
- package/benchmark.js +355 -0
- package/browser.js +276 -39
- package/cli.js +1 -1
- package/examples/python-bridge/MANIFEST.in +5 -0
- package/examples/python-bridge/README.md +265 -0
- package/examples/python-bridge/bridge_daemon.cjs +241 -0
- package/examples/python-bridge/fastapi_example.py +206 -0
- package/examples/python-bridge/langchain_adapter.py +20 -0
- package/examples/python-bridge/langchain_example.py +126 -0
- package/examples/python-bridge/llamaindex_example.py +103 -0
- package/examples/python-bridge/privacyscrubber/__init__.py +42 -0
- package/examples/python-bridge/privacyscrubber/bridge_daemon.cjs +241 -0
- package/examples/python-bridge/privacyscrubber/cli.py +138 -0
- package/examples/python-bridge/privacyscrubber/client.py +548 -0
- package/examples/python-bridge/privacyscrubber/exceptions.py +26 -0
- package/examples/python-bridge/privacyscrubber/langchain.py +161 -0
- package/examples/python-bridge/privacyscrubber/types.py +52 -0
- package/examples/python-bridge/privacyscrubber.py +36 -0
- package/examples/python-bridge/pyproject.toml +62 -0
- package/examples/python-bridge/setup.py +66 -0
- package/examples/python-bridge/test_python_bridge.py +320 -0
- package/examples/vercel-ai-stream.js +195 -0
- package/fastmcp.cjs +289 -8
- package/fastmcp.d.ts +18 -0
- package/fastmcp.js +4 -0
- package/index.cjs +99 -42
- package/index.js +103 -46
- package/package.json +15 -3
- package/ps-license-manager.js +1 -1
- package/ps-pii-engine.cjs +1 -1
- package/scrubber-core.cjs +1 -1
- package/vercel-ai.cjs +400 -0
- package/vercel-ai.d.ts +156 -0
- package/vercel-ai.js +36 -0
- package/vision.cjs +13 -5
- package/vision.js +13 -5
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PrivacyScrubber ZTDS Python Bridge Daemon
|
|
4
|
+
*
|
|
5
|
+
* Provides a low-latency, zero-network-egress stdio NDJSON RPC daemon
|
|
6
|
+
* for Python, FastAPI, LangChain, and LlamaIndex environments.
|
|
7
|
+
*
|
|
8
|
+
* Communication protocol:
|
|
9
|
+
* - Request (stdin): Single-line JSON {"id": number|string, "action": string, ...}\n
|
|
10
|
+
* - Response (stdout): Single-line JSON {"id": number|string, "result": {...}}\n
|
|
11
|
+
* or {"id": number|string, "error": {"message": string, ...}}\n
|
|
12
|
+
*
|
|
13
|
+
* Zero external network egress. 100% in-memory RAM execution.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const readline = require('readline');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
|
|
21
|
+
// Resolve @privacyscrubber/sdk across local monorepo, parent dirs, cwd, and node_modules
|
|
22
|
+
let sdk;
|
|
23
|
+
const candidatePaths = [
|
|
24
|
+
process.env.PRIVACYSCRUBBER_SDK_PATH,
|
|
25
|
+
path.resolve(process.cwd(), 'npm-sdk/index.cjs'),
|
|
26
|
+
path.resolve(process.cwd(), 'node_modules/@privacyscrubber/sdk'),
|
|
27
|
+
path.resolve(__dirname, '../../../index.cjs'),
|
|
28
|
+
path.resolve(__dirname, '../../index.cjs'),
|
|
29
|
+
path.resolve(__dirname, '../index.cjs'),
|
|
30
|
+
path.resolve(__dirname, './index.cjs'),
|
|
31
|
+
'@privacyscrubber/sdk'
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
for (const p of candidatePaths) {
|
|
35
|
+
if (!p) continue;
|
|
36
|
+
try {
|
|
37
|
+
sdk = require(p);
|
|
38
|
+
break;
|
|
39
|
+
} catch (e) {
|
|
40
|
+
// try next candidate
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!sdk) {
|
|
45
|
+
try {
|
|
46
|
+
const resolved = require.resolve('@privacyscrubber/sdk', { paths: [process.cwd(), __dirname] });
|
|
47
|
+
sdk = require(resolved);
|
|
48
|
+
} catch (e2) {}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!sdk) {
|
|
52
|
+
process.stderr.write('Fatal: Could not load @privacyscrubber/sdk or index.cjs\n');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Parse initial CLI options
|
|
57
|
+
const args = process.argv.slice(2);
|
|
58
|
+
let initialProfile = process.env.PRIVACYSCRUBBER_PROFILE || 'General';
|
|
59
|
+
let licenseKey = process.env.PRIVACYSCRUBBER_KEY || process.env.PRIVACYSCRUBBER_LICENSE_KEY || '';
|
|
60
|
+
|
|
61
|
+
for (let i = 0; i < args.length; i++) {
|
|
62
|
+
if (args[i] === '--profile' && args[i + 1]) {
|
|
63
|
+
initialProfile = args[++i];
|
|
64
|
+
} else if (args[i] === '--license-key' && args[i + 1]) {
|
|
65
|
+
licenseKey = args[++i];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Instantiate long-lived engine in local RAM
|
|
70
|
+
const engine = new sdk.PrivacyScrubberEngine({
|
|
71
|
+
licenseKey,
|
|
72
|
+
defaultProfile: initialProfile,
|
|
73
|
+
generateReceipt: true
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
function sendResponse(id, result, error = null) {
|
|
77
|
+
const payload = { id };
|
|
78
|
+
if (error) {
|
|
79
|
+
payload.error = {
|
|
80
|
+
message: error.message || String(error),
|
|
81
|
+
name: error.name || 'Error',
|
|
82
|
+
tier: error.tier,
|
|
83
|
+
limit: error.limit,
|
|
84
|
+
profile: error.profile
|
|
85
|
+
};
|
|
86
|
+
} else {
|
|
87
|
+
payload.result = result;
|
|
88
|
+
}
|
|
89
|
+
process.stdout.write(JSON.stringify(payload) + '\n');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Setup readline interface on stdin
|
|
93
|
+
const rl = readline.createInterface({
|
|
94
|
+
input: process.stdin,
|
|
95
|
+
output: process.stdout,
|
|
96
|
+
terminal: false
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
rl.on('line', (line) => {
|
|
100
|
+
const trimmed = line.trim();
|
|
101
|
+
if (!trimmed) return;
|
|
102
|
+
|
|
103
|
+
let req;
|
|
104
|
+
try {
|
|
105
|
+
req = JSON.parse(trimmed);
|
|
106
|
+
} catch (parseErr) {
|
|
107
|
+
sendResponse(null, null, new Error(`Invalid JSON request: ${parseErr.message}`));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const { id, action } = req;
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
switch (action) {
|
|
115
|
+
case 'ping': {
|
|
116
|
+
sendResponse(id, {
|
|
117
|
+
pong: true,
|
|
118
|
+
engine: 'PrivacyScrubber ZTDS',
|
|
119
|
+
version: '2.4.1',
|
|
120
|
+
uptime: process.uptime()
|
|
121
|
+
});
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
case 'sanitize': {
|
|
126
|
+
const text = req.text || '';
|
|
127
|
+
const options = {
|
|
128
|
+
profile: req.profile || engine.defaultProfile,
|
|
129
|
+
detectSecrets: req.detect_secrets ?? req.detectSecrets ?? false,
|
|
130
|
+
licenseKey: req.license_key || req.licenseKey || engine.licenseKey,
|
|
131
|
+
persistSession: req.persist_session ?? req.persistSession ?? true,
|
|
132
|
+
generateReceipt: req.generate_receipt ?? req.generateReceipt ?? true,
|
|
133
|
+
customRules: req.custom_rules || req.customRules || [],
|
|
134
|
+
ignoreList: req.ignore_list || req.ignoreList || null,
|
|
135
|
+
throwOnLimit: req.throw_on_limit ?? req.throwOnLimit ?? true
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const res = engine.sanitize(text, options);
|
|
139
|
+
sendResponse(id, {
|
|
140
|
+
sanitized: res.scrubbedText,
|
|
141
|
+
scrubbed_text: res.scrubbedText,
|
|
142
|
+
token_map: res.tokenMap,
|
|
143
|
+
matches: res.matches,
|
|
144
|
+
count: res.count,
|
|
145
|
+
execution_ms: res.executionMs,
|
|
146
|
+
telemetry: res.telemetry,
|
|
147
|
+
audit_receipt: res.auditReceipt,
|
|
148
|
+
license: res.license
|
|
149
|
+
});
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
case 'restore': {
|
|
154
|
+
const text = req.text || '';
|
|
155
|
+
const tokenMap = req.token_map || req.tokenMap || null;
|
|
156
|
+
const res = engine.restore(text, tokenMap);
|
|
157
|
+
sendResponse(id, {
|
|
158
|
+
restored_text: res.restoredText,
|
|
159
|
+
restored_count: res.restoredCount
|
|
160
|
+
});
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
case 'sanitize_object': {
|
|
165
|
+
const data = req.data ?? {};
|
|
166
|
+
const options = {
|
|
167
|
+
profile: req.profile || engine.defaultProfile,
|
|
168
|
+
detectSecrets: req.detect_secrets ?? req.detectSecrets ?? false,
|
|
169
|
+
licenseKey: req.license_key || req.licenseKey || engine.licenseKey,
|
|
170
|
+
customRules: req.custom_rules || req.customRules || [],
|
|
171
|
+
throwOnLimit: req.throw_on_limit ?? req.throwOnLimit ?? true
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const res = sdk.sanitizeObject(data, options);
|
|
175
|
+
if (req.persist_session !== false && res.tokenMap) {
|
|
176
|
+
Object.assign(engine.sessionMap, res.tokenMap);
|
|
177
|
+
}
|
|
178
|
+
sendResponse(id, {
|
|
179
|
+
sanitized: res.sanitized,
|
|
180
|
+
token_map: res.tokenMap,
|
|
181
|
+
count: res.count,
|
|
182
|
+
execution_ms: res.executionMs,
|
|
183
|
+
telemetry: res.telemetry,
|
|
184
|
+
audit_receipt: res.auditReceipt
|
|
185
|
+
});
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
case 'restore_object': {
|
|
190
|
+
const data = req.data ?? {};
|
|
191
|
+
const tokenMap = req.token_map || req.tokenMap || engine.sessionMap;
|
|
192
|
+
const restored = sdk.restoreObject(data, tokenMap);
|
|
193
|
+
sendResponse(id, {
|
|
194
|
+
restored
|
|
195
|
+
});
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
case 'reset_session': {
|
|
200
|
+
engine.resetSession();
|
|
201
|
+
sendResponse(id, { success: true });
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
case 'mark_false_positive': {
|
|
206
|
+
const token = req.token;
|
|
207
|
+
if (!token) {
|
|
208
|
+
throw new Error('Missing token argument for mark_false_positive');
|
|
209
|
+
}
|
|
210
|
+
const fpRes = engine.markFalsePositive(token);
|
|
211
|
+
sendResponse(id, fpRes);
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
case 'status': {
|
|
216
|
+
sendResponse(id, {
|
|
217
|
+
profile: engine.defaultProfile,
|
|
218
|
+
license_tier: engine.license?.tier || 'FREE',
|
|
219
|
+
is_pro: engine.license?.isPro || false,
|
|
220
|
+
session_tokens_count: Object.keys(engine.sessionMap).length,
|
|
221
|
+
ignore_list_size: engine.ignoreList.size,
|
|
222
|
+
memory_usage: process.memoryUsage()
|
|
223
|
+
});
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
default:
|
|
228
|
+
sendResponse(id, null, new Error(`Unknown action: ${action}`));
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
} catch (err) {
|
|
232
|
+
sendResponse(id, null, err);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
rl.on('close', () => {
|
|
237
|
+
process.exit(0);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
process.on('SIGINT', () => process.exit(0));
|
|
241
|
+
process.on('SIGTERM', () => process.exit(0));
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PrivacyScrubber Command Line Interface (CLI)
|
|
3
|
+
Zero-Trust Data Sanitization & Local PII Shield for Terminals & Automation.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
from privacyscrubber.client import PrivacyScrubberClient, _find_daemon_path
|
|
15
|
+
from privacyscrubber.exceptions import PrivacyScrubberError
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main() -> None:
|
|
19
|
+
parser = argparse.ArgumentParser(
|
|
20
|
+
prog="privacyscrubber",
|
|
21
|
+
description="PrivacyScrubber ZTDS CLI - Zero-Trust In-Memory PII Redaction (<1ms latency)"
|
|
22
|
+
)
|
|
23
|
+
parser.add_argument("--version", action="store_true", help="Print PrivacyScrubber version")
|
|
24
|
+
|
|
25
|
+
subparsers = parser.add_subparsers(dest="command", help="Available subcommands")
|
|
26
|
+
|
|
27
|
+
# Command: ping
|
|
28
|
+
subparsers.add_parser("ping", help="Ping the underlying ZTDS engine and check uptime")
|
|
29
|
+
|
|
30
|
+
# Command: status
|
|
31
|
+
subparsers.add_parser("status", help="Get engine status, memory footprint, and profile")
|
|
32
|
+
|
|
33
|
+
# Command: sanitize
|
|
34
|
+
sanitize_parser = subparsers.add_parser("sanitize", help="Sanitize text or stdin stream")
|
|
35
|
+
sanitize_parser.add_argument("text", nargs="?", default="", help="Text to sanitize (or pipe via stdin)")
|
|
36
|
+
sanitize_parser.add_argument("--profile", "-p", default="General", help="Specialized profile (e.g. Healthcare, Financial, Legal)")
|
|
37
|
+
sanitize_parser.add_argument("--detect-secrets", "-s", action="store_true", help="Detect DevOps and cloud API secrets")
|
|
38
|
+
sanitize_parser.add_argument("--json", action="store_true", help="Output JSON with token map and telemetry")
|
|
39
|
+
|
|
40
|
+
# Command: restore
|
|
41
|
+
restore_parser = subparsers.add_parser("restore", help="Restore surrogate tokens with cleartext values")
|
|
42
|
+
restore_parser.add_argument("text", nargs="?", default="", help="Text containing tokens to restore")
|
|
43
|
+
restore_parser.add_argument("--token-map", "-m", help="JSON string representing token map")
|
|
44
|
+
|
|
45
|
+
# Command: daemon
|
|
46
|
+
daemon_parser = subparsers.add_parser("daemon", help="Run the persistent stdio NDJSON RPC daemon directly")
|
|
47
|
+
daemon_parser.add_argument("--profile", default="General", help="Default profile")
|
|
48
|
+
daemon_parser.add_argument("--license-key", default="", help="License key")
|
|
49
|
+
|
|
50
|
+
args = parser.parse_args()
|
|
51
|
+
|
|
52
|
+
if args.version:
|
|
53
|
+
print("PrivacyScrubber ZTDS Python SDK v2.4.1")
|
|
54
|
+
sys.exit(0)
|
|
55
|
+
|
|
56
|
+
if not args.command:
|
|
57
|
+
parser.print_help()
|
|
58
|
+
sys.exit(0)
|
|
59
|
+
|
|
60
|
+
if args.command == "daemon":
|
|
61
|
+
# Launch bridge_daemon.cjs directly with current stdin/stdout
|
|
62
|
+
daemon_script = _find_daemon_path()
|
|
63
|
+
node_bin = os.environ.get("PRIVACYSCRUBBER_NODE_PATH", "node")
|
|
64
|
+
cmd = [node_bin, daemon_script, "--profile", args.profile]
|
|
65
|
+
if args.license_key:
|
|
66
|
+
cmd.extend(["--license-key", args.license_key])
|
|
67
|
+
try:
|
|
68
|
+
p = subprocess.Popen(cmd)
|
|
69
|
+
p.wait()
|
|
70
|
+
except KeyboardInterrupt:
|
|
71
|
+
pass
|
|
72
|
+
sys.exit(0)
|
|
73
|
+
|
|
74
|
+
# Standard client-based commands
|
|
75
|
+
try:
|
|
76
|
+
with PrivacyScrubberClient(default_profile=getattr(args, "profile", "General")) as client:
|
|
77
|
+
if args.command == "ping":
|
|
78
|
+
res = client.ping()
|
|
79
|
+
print(json.dumps(res, indent=2))
|
|
80
|
+
|
|
81
|
+
elif args.command == "status":
|
|
82
|
+
res = client.status()
|
|
83
|
+
print(json.dumps(res, indent=2))
|
|
84
|
+
|
|
85
|
+
elif args.command == "sanitize":
|
|
86
|
+
text = args.text
|
|
87
|
+
if not text and not sys.stdin.isatty():
|
|
88
|
+
text = sys.stdin.read()
|
|
89
|
+
if not text:
|
|
90
|
+
print("Error: No text provided. Pass as argument or pipe via stdin.", file=sys.stderr)
|
|
91
|
+
sys.exit(1)
|
|
92
|
+
|
|
93
|
+
res = client.sanitize(
|
|
94
|
+
text,
|
|
95
|
+
profile=args.profile,
|
|
96
|
+
detect_secrets=args.detect_secrets
|
|
97
|
+
)
|
|
98
|
+
if args.json:
|
|
99
|
+
output = {
|
|
100
|
+
"sanitized": res.sanitized,
|
|
101
|
+
"token_map": res.token_map,
|
|
102
|
+
"count": res.count,
|
|
103
|
+
"execution_ms": res.execution_ms,
|
|
104
|
+
"telemetry": res.telemetry
|
|
105
|
+
}
|
|
106
|
+
print(json.dumps(output, indent=2))
|
|
107
|
+
else:
|
|
108
|
+
print(res.sanitized)
|
|
109
|
+
|
|
110
|
+
elif args.command == "restore":
|
|
111
|
+
text = args.text
|
|
112
|
+
if not text and not sys.stdin.isatty():
|
|
113
|
+
text = sys.stdin.read()
|
|
114
|
+
if not text:
|
|
115
|
+
print("Error: No text provided to restore.", file=sys.stderr)
|
|
116
|
+
sys.exit(1)
|
|
117
|
+
|
|
118
|
+
token_map = None
|
|
119
|
+
if args.token_map:
|
|
120
|
+
try:
|
|
121
|
+
token_map = json.loads(args.token_map)
|
|
122
|
+
except json.JSONDecodeError as e:
|
|
123
|
+
print(f"Error: Invalid JSON token map: {e}", file=sys.stderr)
|
|
124
|
+
sys.exit(1)
|
|
125
|
+
|
|
126
|
+
res = client.restore(text, token_map=token_map)
|
|
127
|
+
print(res.restored_text)
|
|
128
|
+
|
|
129
|
+
except PrivacyScrubberError as err:
|
|
130
|
+
print(f"PrivacyScrubber Error: {err}", file=sys.stderr)
|
|
131
|
+
sys.exit(1)
|
|
132
|
+
except Exception as err:
|
|
133
|
+
print(f"Unexpected Error: {err}", file=sys.stderr)
|
|
134
|
+
sys.exit(1)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
if __name__ == "__main__":
|
|
138
|
+
main()
|