@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.
- package/README.md +85 -27
- package/benchmark.js +347 -0
- package/browser.js +2 -2
- 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/dist/privacyscrubber-2.3.9-py3-none-any.whl +0 -0
- package/examples/python-bridge/dist/privacyscrubber-2.3.9.tar.gz +0 -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 +542 -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.egg-info/PKG-INFO +311 -0
- package/examples/python-bridge/privacyscrubber.egg-info/SOURCES.txt +18 -0
- package/examples/python-bridge/privacyscrubber.egg-info/dependency_links.txt +1 -0
- package/examples/python-bridge/privacyscrubber.egg-info/entry_points.txt +3 -0
- package/examples/python-bridge/privacyscrubber.egg-info/requires.txt +18 -0
- package/examples/python-bridge/privacyscrubber.egg-info/top_level.txt +1 -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 +271 -1
- package/fastmcp.d.ts +18 -0
- package/fastmcp.js +4 -0
- package/index.cjs +89 -36
- package/index.js +89 -36
- 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,265 @@
|
|
|
1
|
+
# PrivacyScrubber Python Bridge (ZTDS Zero-Egress Sidecar)
|
|
2
|
+
|
|
3
|
+
Sub-millisecond, zero-dependency Python adapter for **PrivacyScrubber SDK** (`@privacyscrubber/sdk`).
|
|
4
|
+
|
|
5
|
+
Enforces **Zero-Trust Data Sanitization (ZTDS RFC v1.0)** across Python AI and data pipelines:
|
|
6
|
+
- **FastAPI / Flask / Django**: Pre-LLM sanitization and post-LLM in-memory rehydration.
|
|
7
|
+
- **LangChain**: `ZTDSCallbackHandler` and `ZTDSDocumentTransformer`.
|
|
8
|
+
- **LlamaIndex**: Pre-embedding chunk and metadata sanitization.
|
|
9
|
+
- **Raw OpenAI / Anthropic / Gemini SDKs**: Direct string and dictionary tokenization.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Architectural Comparison: PrivacyScrubber vs. Microsoft Presidio
|
|
14
|
+
|
|
15
|
+
| Metric | Microsoft Presidio | PrivacyScrubber ZTDS Bridge |
|
|
16
|
+
| :--- | :--- | :--- |
|
|
17
|
+
| **Inference Latency** | ~35ms – 65ms per request | **~0.15ms – 0.40ms per request (<1ms SLA)** |
|
|
18
|
+
| **Memory Footprint** | 1.8 GB – 3.2 GB RAM (PyTorch / spaCy) | **< 25 MB RAM (Zero PyTorch / zero spaCy)** |
|
|
19
|
+
| **Python Dependencies** | `presidio-analyzer`, `presidio-anonymizer`, `spacy`, `torch` | **0 Dependencies (Python Standard Library Only)** |
|
|
20
|
+
| **Round-Trip Re-hydration** | Non-trivial (custom hash table mapping required) | **Native 100% Round-Trip (`client.restore()`)** |
|
|
21
|
+
| **Pre-Configured Profiles** | Basic PII only (requires custom YAML recognizers) | **30 Built-in Industry Profiles (HIPAA, Financial, Legal, HR, etc.)** |
|
|
22
|
+
| **Network Egress** | Potential telemetry unless air-gapped | **0.00 Bytes Network Egress (RAM-only volatile mapping)** |
|
|
23
|
+
| **Container Startup** | 15 – 30 seconds model loading time | **< 100 milliseconds** |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Installation & Prerequisites
|
|
30
|
+
|
|
31
|
+
1. Ensure **Node.js** (v18+) and **Python** (3.8+) are available on your system.
|
|
32
|
+
2. Install `@privacyscrubber/sdk`:
|
|
33
|
+
```bash
|
|
34
|
+
npm install @privacyscrubber/sdk
|
|
35
|
+
```
|
|
36
|
+
3. Install the Python package via `pip`:
|
|
37
|
+
```bash
|
|
38
|
+
# Editable local development:
|
|
39
|
+
pip install -e npm-sdk/examples/python-bridge
|
|
40
|
+
|
|
41
|
+
# Or standard pip install:
|
|
42
|
+
pip install ./npm-sdk/examples/python-bridge
|
|
43
|
+
```
|
|
44
|
+
*Note: Zero external Python dependencies are required.*
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Command Line Interface (CLI)
|
|
49
|
+
|
|
50
|
+
The package bundles a terminal CLI utility `privacyscrubber` (alias: `ps-bridge`):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Check ZTDS engine status and uptime
|
|
54
|
+
privacyscrubber ping
|
|
55
|
+
privacyscrubber status
|
|
56
|
+
|
|
57
|
+
# Sanitize text directly
|
|
58
|
+
privacyscrubber sanitize "Customer Alice Cooper (alice@hospital.org)" --profile Healthcare
|
|
59
|
+
|
|
60
|
+
# Pipe text through standard input:
|
|
61
|
+
echo "Analyze wire for John Doe john@corp.com" | privacyscrubber sanitize
|
|
62
|
+
|
|
63
|
+
# Output structured JSON with tokens and compliance telemetry:
|
|
64
|
+
privacyscrubber sanitize "Patient John Doe" --json
|
|
65
|
+
|
|
66
|
+
# Restore surrogate tokens:
|
|
67
|
+
privacyscrubber restore "Patient [NAME_1]" --token-map '{"[NAME_1]": "John Doe"}'
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Quickstart
|
|
73
|
+
|
|
74
|
+
### 1. Synchronous Client
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from privacyscrubber import PrivacyScrubber
|
|
78
|
+
|
|
79
|
+
# Initialize sidecar in local RAM (auto-starts daemon)
|
|
80
|
+
with PrivacyScrubber(default_profile="General") as ps:
|
|
81
|
+
# Sanitize prompt before sending to cloud LLM
|
|
82
|
+
result = ps.sanitize("Please review intake for patient John Doe (john@acme.com, phone +1-555-0199).")
|
|
83
|
+
|
|
84
|
+
print("Sanitized Prompt:", result.sanitized)
|
|
85
|
+
# Output: Please review intake for patient [NAME_1] ([EMAIL_1], phone [PHONE_1]).
|
|
86
|
+
|
|
87
|
+
# Simulate LLM operating strictly on tokens
|
|
88
|
+
ai_response = "I have noted records for [NAME_1] ([EMAIL_1])."
|
|
89
|
+
|
|
90
|
+
# Restore cleartext tokens in local RAM
|
|
91
|
+
restored = ps.restore(ai_response)
|
|
92
|
+
print("Restored Answer:", restored.restored_text)
|
|
93
|
+
# Output: I have noted records for John Doe (john@acme.com).
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### 2. Asynchronous Client (FastAPI / asyncio)
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
import asyncio
|
|
100
|
+
from privacyscrubber import AsyncPrivacyScrubber
|
|
101
|
+
|
|
102
|
+
async def main():
|
|
103
|
+
async with AsyncPrivacyScrubber(default_profile="Healthcare") as ps:
|
|
104
|
+
res = await ps.sanitize("Patient Alice Cooper (DOB: 1984-05-12, SSN: 000-11-2222).")
|
|
105
|
+
print("Sanitized:", res.sanitized)
|
|
106
|
+
# Patient [NAME_1] (DOB: [DATE_1], SSN: [ID_1]).
|
|
107
|
+
|
|
108
|
+
restored = await ps.restore("Prescription confirmed for [NAME_1].")
|
|
109
|
+
print("Restored:", restored.restored_text)
|
|
110
|
+
|
|
111
|
+
asyncio.run(main())
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Framework Integrations
|
|
117
|
+
|
|
118
|
+
### FastAPI Zero-Egress Proxy (`fastapi_example.py`)
|
|
119
|
+
|
|
120
|
+
Run an OpenAI-compatible proxy that guarantees no raw PII ever touches OpenAI or external model APIs:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from fastapi import FastAPI
|
|
124
|
+
from privacyscrubber import AsyncPrivacyScrubber
|
|
125
|
+
|
|
126
|
+
app = FastAPI()
|
|
127
|
+
scrubber = AsyncPrivacyScrubber()
|
|
128
|
+
|
|
129
|
+
@app.on_event("startup")
|
|
130
|
+
async def on_startup():
|
|
131
|
+
await scrubber.start()
|
|
132
|
+
|
|
133
|
+
@app.on_event("shutdown")
|
|
134
|
+
async def on_shutdown():
|
|
135
|
+
await scrubber.close()
|
|
136
|
+
|
|
137
|
+
@app.post("/v1/chat/completions")
|
|
138
|
+
async def chat_proxy(payload: dict):
|
|
139
|
+
# 1. Sanitize user prompt in client memory (<0.2ms)
|
|
140
|
+
messages = payload.get("messages", [])
|
|
141
|
+
user_prompt = messages[-1]["content"]
|
|
142
|
+
|
|
143
|
+
sanitized = await scrubber.sanitize(user_prompt)
|
|
144
|
+
messages[-1]["content"] = sanitized.sanitized
|
|
145
|
+
|
|
146
|
+
# 2. Forward sanitized payload to cloud LLM (httpx call)
|
|
147
|
+
# raw_llm_response = await client.post("https://api.openai.com/v1/chat/completions", json=payload)
|
|
148
|
+
mock_llm_reply = f"Acknowledged request for {sanitized.sanitized}"
|
|
149
|
+
|
|
150
|
+
# 3. Restore cleartext in RAM before returning to end-user
|
|
151
|
+
restored = await scrubber.restore(mock_llm_reply, token_map=sanitized.token_map)
|
|
152
|
+
return {"choices": [{"message": {"role": "assistant", "content": restored.restored_text}}]}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### LangChain (`langchain_adapter.py`)
|
|
156
|
+
|
|
157
|
+
#### Callback Handler (Transparent Pre/Post LLM Guard)
|
|
158
|
+
```python
|
|
159
|
+
from langchain_openai import ChatOpenAI
|
|
160
|
+
from langchain_adapter import ZTDSCallbackHandler
|
|
161
|
+
|
|
162
|
+
llm = ChatOpenAI(
|
|
163
|
+
model="gpt-4o",
|
|
164
|
+
callbacks=[ZTDSCallbackHandler(profile="Financial")]
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# Prompts are automatically sanitized before leaving process memory;
|
|
168
|
+
# LLM outputs are automatically restored before returning to your code.
|
|
169
|
+
response = llm.invoke("What is the credit rating for customer David Miller (SSN 987-65-4321)?")
|
|
170
|
+
print(response.content)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
#### Vector Ingestion Guard (Pinecone, Chroma, Qdrant)
|
|
174
|
+
```python
|
|
175
|
+
from langchain_adapter import ZTDSDocumentTransformer
|
|
176
|
+
|
|
177
|
+
transformer = ZTDSDocumentTransformer(
|
|
178
|
+
profile="Financial",
|
|
179
|
+
sensitive_metadata_keys=["customer_email", "account_id"]
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Sanitizes both page_content and sensitive metadata dictionaries
|
|
183
|
+
safe_docs = transformer.transform_documents(raw_docs)
|
|
184
|
+
vector_store.add_documents(safe_docs)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Nested Dictionary & JSON Sanitization
|
|
190
|
+
|
|
191
|
+
Sanitize complex structured payloads (REST API payloads, MongoDB documents, JSON logs):
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
payload = {
|
|
195
|
+
"user_id": 9012,
|
|
196
|
+
"customer": {
|
|
197
|
+
"name": "Sarah Connor",
|
|
198
|
+
"email": "sconnor@cyberdyne.org",
|
|
199
|
+
"phones": ["+1-555-0199", "+1-555-0188"]
|
|
200
|
+
},
|
|
201
|
+
"status": "active"
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
# Recursively sanitizes strings, preserving keys and types intact:
|
|
205
|
+
result = ps.sanitize_object(payload)
|
|
206
|
+
print(result.sanitized)
|
|
207
|
+
# {
|
|
208
|
+
# "user_id": 9012,
|
|
209
|
+
# "customer": {
|
|
210
|
+
# "name": "[NAME_1]",
|
|
211
|
+
# "email": "[EMAIL_1]",
|
|
212
|
+
# "phones": ["[PHONE_1]", "[PHONE_2]"]
|
|
213
|
+
# },
|
|
214
|
+
# "status": "active"
|
|
215
|
+
# }
|
|
216
|
+
|
|
217
|
+
# Restore structure:
|
|
218
|
+
restored = ps.restore_object(result.sanitized, token_map=result.token_map)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Production Docker Deployment
|
|
224
|
+
|
|
225
|
+
Deploying Python applications alongside the PrivacyScrubber sidecar in a single multi-runtime container:
|
|
226
|
+
|
|
227
|
+
```dockerfile
|
|
228
|
+
FROM node:22-bookworm-slim AS node-base
|
|
229
|
+
|
|
230
|
+
FROM python:3.11-slim-bookworm
|
|
231
|
+
|
|
232
|
+
# Copy Node.js runtime into Python container
|
|
233
|
+
COPY --from=node-base /usr/local/bin/node /usr/local/bin/node
|
|
234
|
+
COPY --from=node-base /usr/local/lib/node_modules /usr/local/lib/node_modules
|
|
235
|
+
|
|
236
|
+
WORKDIR /app
|
|
237
|
+
|
|
238
|
+
# Install SDK bridge
|
|
239
|
+
COPY package*.json ./
|
|
240
|
+
RUN npm install @privacyscrubber/sdk --production
|
|
241
|
+
|
|
242
|
+
# Install Python requirements
|
|
243
|
+
COPY requirements.txt ./
|
|
244
|
+
RUN pip install --no-cache-dir -r requirements.txt
|
|
245
|
+
|
|
246
|
+
COPY . .
|
|
247
|
+
|
|
248
|
+
CMD ["uvicorn", "fastapi_example:app", "--host", "0.0.0.0", "--port", "8000"]
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Running the Verification Suite
|
|
254
|
+
|
|
255
|
+
Run all tests, benchmarks, and concurrency assertions:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
# Automated Test Suite (10 tests, 0.3s)
|
|
259
|
+
python3 test_python_bridge.py
|
|
260
|
+
|
|
261
|
+
# Interactive Examples
|
|
262
|
+
python3 fastapi_example.py
|
|
263
|
+
python3 langchain_example.py
|
|
264
|
+
python3 llamaindex_example.py
|
|
265
|
+
```
|
|
@@ -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
|
+
'@privacyscrubber/sdk'
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
for (const p of candidatePaths) {
|
|
34
|
+
if (!p) continue;
|
|
35
|
+
try {
|
|
36
|
+
sdk = require(p);
|
|
37
|
+
break;
|
|
38
|
+
} catch (e) {
|
|
39
|
+
// try next candidate
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!sdk) {
|
|
44
|
+
try {
|
|
45
|
+
const resolved = require.resolve('@privacyscrubber/sdk', { paths: [process.cwd(), __dirname] });
|
|
46
|
+
sdk = require(resolved);
|
|
47
|
+
} catch (e2) {}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!sdk) {
|
|
51
|
+
process.stderr.write('Fatal: Could not load @privacyscrubber/sdk or index.cjs\n');
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Parse initial CLI options
|
|
56
|
+
const args = process.argv.slice(2);
|
|
57
|
+
let initialProfile = process.env.PRIVACYSCRUBBER_PROFILE || 'General';
|
|
58
|
+
let licenseKey = process.env.PRIVACYSCRUBBER_KEY || process.env.PRIVACYSCRUBBER_LICENSE_KEY || '';
|
|
59
|
+
|
|
60
|
+
for (let i = 0; i < args.length; i++) {
|
|
61
|
+
if (args[i] === '--profile' && args[i + 1]) {
|
|
62
|
+
initialProfile = args[++i];
|
|
63
|
+
} else if (args[i] === '--license-key' && args[i + 1]) {
|
|
64
|
+
licenseKey = args[++i];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Instantiate long-lived engine in local RAM
|
|
69
|
+
const engine = new sdk.PrivacyScrubberEngine({
|
|
70
|
+
licenseKey,
|
|
71
|
+
defaultProfile: initialProfile,
|
|
72
|
+
generateReceipt: true
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
function sendResponse(id, result, error = null) {
|
|
76
|
+
const payload = { id };
|
|
77
|
+
if (error) {
|
|
78
|
+
payload.error = {
|
|
79
|
+
message: error.message || String(error),
|
|
80
|
+
name: error.name || 'Error',
|
|
81
|
+
tier: error.tier,
|
|
82
|
+
limit: error.limit,
|
|
83
|
+
profile: error.profile
|
|
84
|
+
};
|
|
85
|
+
} else {
|
|
86
|
+
payload.result = result;
|
|
87
|
+
}
|
|
88
|
+
process.stdout.write(JSON.stringify(payload) + '\n');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Setup readline interface on stdin
|
|
92
|
+
const rl = readline.createInterface({
|
|
93
|
+
input: process.stdin,
|
|
94
|
+
output: process.stdout,
|
|
95
|
+
terminal: false
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
rl.on('line', (line) => {
|
|
99
|
+
const trimmed = line.trim();
|
|
100
|
+
if (!trimmed) return;
|
|
101
|
+
|
|
102
|
+
let req;
|
|
103
|
+
try {
|
|
104
|
+
req = JSON.parse(trimmed);
|
|
105
|
+
} catch (parseErr) {
|
|
106
|
+
sendResponse(null, null, new Error(`Invalid JSON request: ${parseErr.message}`));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const { id, action } = req;
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
switch (action) {
|
|
114
|
+
case 'ping': {
|
|
115
|
+
sendResponse(id, {
|
|
116
|
+
pong: true,
|
|
117
|
+
engine: 'PrivacyScrubber ZTDS',
|
|
118
|
+
version: '2.4.0',
|
|
119
|
+
uptime: process.uptime()
|
|
120
|
+
});
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
case 'sanitize': {
|
|
125
|
+
const text = req.text || '';
|
|
126
|
+
const options = {
|
|
127
|
+
profile: req.profile || engine.defaultProfile,
|
|
128
|
+
detectSecrets: req.detect_secrets ?? req.detectSecrets ?? false,
|
|
129
|
+
licenseKey: req.license_key || req.licenseKey || engine.licenseKey,
|
|
130
|
+
persistSession: req.persist_session ?? req.persistSession ?? true,
|
|
131
|
+
generateReceipt: req.generate_receipt ?? req.generateReceipt ?? true,
|
|
132
|
+
customRules: req.custom_rules || req.customRules || [],
|
|
133
|
+
ignoreList: req.ignore_list || req.ignoreList || null,
|
|
134
|
+
throwOnLimit: req.throw_on_limit ?? req.throwOnLimit ?? true
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const res = engine.sanitize(text, options);
|
|
138
|
+
sendResponse(id, {
|
|
139
|
+
sanitized: res.scrubbedText,
|
|
140
|
+
scrubbed_text: res.scrubbedText,
|
|
141
|
+
token_map: res.tokenMap,
|
|
142
|
+
matches: res.matches,
|
|
143
|
+
count: res.count,
|
|
144
|
+
execution_ms: res.executionMs,
|
|
145
|
+
telemetry: res.telemetry,
|
|
146
|
+
audit_receipt: res.auditReceipt,
|
|
147
|
+
license: res.license
|
|
148
|
+
});
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
case 'restore': {
|
|
153
|
+
const text = req.text || '';
|
|
154
|
+
const tokenMap = req.token_map || req.tokenMap || null;
|
|
155
|
+
const res = engine.restore(text, tokenMap);
|
|
156
|
+
sendResponse(id, {
|
|
157
|
+
restored_text: res.restoredText,
|
|
158
|
+
restored_count: res.restoredCount
|
|
159
|
+
});
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
case 'sanitize_object': {
|
|
164
|
+
const data = req.data ?? {};
|
|
165
|
+
const options = {
|
|
166
|
+
profile: req.profile || engine.defaultProfile,
|
|
167
|
+
detectSecrets: req.detect_secrets ?? req.detectSecrets ?? false,
|
|
168
|
+
licenseKey: req.license_key || req.licenseKey || engine.licenseKey,
|
|
169
|
+
customRules: req.custom_rules || req.customRules || [],
|
|
170
|
+
throwOnLimit: req.throw_on_limit ?? req.throwOnLimit ?? true
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const res = sdk.sanitizeObject(data, options);
|
|
174
|
+
// Also merge into engine sessionMap if requested
|
|
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));
|
|
Binary file
|