@privacyscrubber/sdk 2.3.9 → 2.4.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/README.md +85 -27
  2. package/benchmark.js +347 -0
  3. package/browser.js +2 -2
  4. package/cli.js +1 -1
  5. package/examples/python-bridge/MANIFEST.in +5 -0
  6. package/examples/python-bridge/README.md +265 -0
  7. package/examples/python-bridge/bridge_daemon.cjs +241 -0
  8. package/examples/python-bridge/dist/privacyscrubber-2.3.9-py3-none-any.whl +0 -0
  9. package/examples/python-bridge/dist/privacyscrubber-2.3.9.tar.gz +0 -0
  10. package/examples/python-bridge/fastapi_example.py +206 -0
  11. package/examples/python-bridge/langchain_adapter.py +20 -0
  12. package/examples/python-bridge/langchain_example.py +126 -0
  13. package/examples/python-bridge/llamaindex_example.py +103 -0
  14. package/examples/python-bridge/privacyscrubber/__init__.py +42 -0
  15. package/examples/python-bridge/privacyscrubber/bridge_daemon.cjs +241 -0
  16. package/examples/python-bridge/privacyscrubber/cli.py +138 -0
  17. package/examples/python-bridge/privacyscrubber/client.py +542 -0
  18. package/examples/python-bridge/privacyscrubber/exceptions.py +26 -0
  19. package/examples/python-bridge/privacyscrubber/langchain.py +161 -0
  20. package/examples/python-bridge/privacyscrubber/types.py +52 -0
  21. package/examples/python-bridge/privacyscrubber.egg-info/PKG-INFO +311 -0
  22. package/examples/python-bridge/privacyscrubber.egg-info/SOURCES.txt +18 -0
  23. package/examples/python-bridge/privacyscrubber.egg-info/dependency_links.txt +1 -0
  24. package/examples/python-bridge/privacyscrubber.egg-info/entry_points.txt +3 -0
  25. package/examples/python-bridge/privacyscrubber.egg-info/requires.txt +18 -0
  26. package/examples/python-bridge/privacyscrubber.egg-info/top_level.txt +1 -0
  27. package/examples/python-bridge/privacyscrubber.py +36 -0
  28. package/examples/python-bridge/pyproject.toml +62 -0
  29. package/examples/python-bridge/setup.py +66 -0
  30. package/examples/python-bridge/test_python_bridge.py +320 -0
  31. package/examples/vercel-ai-stream.js +195 -0
  32. package/fastmcp.cjs +271 -1
  33. package/fastmcp.d.ts +18 -0
  34. package/fastmcp.js +4 -0
  35. package/index.cjs +89 -36
  36. package/index.js +89 -36
  37. package/package.json +15 -3
  38. package/ps-license-manager.js +1 -1
  39. package/ps-pii-engine.cjs +1 -1
  40. package/scrubber-core.cjs +1 -1
  41. package/vercel-ai.cjs +400 -0
  42. package/vercel-ai.d.ts +156 -0
  43. package/vercel-ai.js +36 -0
  44. package/vision.cjs +13 -5
  45. 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.0',
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.0")
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()