@privacyscrubber/sdk 2.3.7 → 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 +86 -28
- package/benchmark.js +347 -0
- package/browser.js +2 -2
- package/cli.js +54 -2
- 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 +291 -2
- package/fastmcp.d.ts +18 -0
- package/fastmcp.js +4 -0
- package/index.cjs +211 -134
- package/index.js +211 -134
- 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,206 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PrivacyScrubber ZTDS - FastAPI Zero-Egress Proxy & Microservice Example
|
|
3
|
+
|
|
4
|
+
Demonstrates:
|
|
5
|
+
1. Lifespan event management with AsyncPrivacyScrubber (zero-overhead async IPC).
|
|
6
|
+
2. /v1/chat/completions OpenAI-compatible proxy endpoint:
|
|
7
|
+
- Sanitizes raw user prompts before cloud transmission (zero cleartext PII egress).
|
|
8
|
+
- Re-hydrates LLM answers with original PII in local RAM before responding to the user.
|
|
9
|
+
3. Dedicated microservice endpoints: /api/v1/sanitize and /api/v1/restore.
|
|
10
|
+
4. Direct runnable simulation if uvicorn/fastapi are not installed in the local environment.
|
|
11
|
+
|
|
12
|
+
Run:
|
|
13
|
+
python3 fastapi_example.py
|
|
14
|
+
# or: uvicorn fastapi_example:app --port 8000 --reload
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import asyncio
|
|
20
|
+
import json
|
|
21
|
+
import sys
|
|
22
|
+
from typing import Any, Dict, List, Optional
|
|
23
|
+
|
|
24
|
+
from privacyscrubber import AsyncPrivacyScrubber, SanitizeResult
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
from fastapi import FastAPI, Depends, HTTPException, Header, Request, Response
|
|
28
|
+
from fastapi.responses import JSONResponse
|
|
29
|
+
from pydantic import BaseModel, Field
|
|
30
|
+
FASTAPI_AVAILABLE = True
|
|
31
|
+
except ImportError:
|
|
32
|
+
FASTAPI_AVAILABLE = False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Pydantic Schemas (if FastAPI is available)
|
|
36
|
+
if FASTAPI_AVAILABLE:
|
|
37
|
+
class ChatMessage(BaseModel):
|
|
38
|
+
role: str
|
|
39
|
+
content: str
|
|
40
|
+
|
|
41
|
+
class ChatCompletionRequest(BaseModel):
|
|
42
|
+
model: str = "gpt-4o"
|
|
43
|
+
messages: List[ChatMessage]
|
|
44
|
+
temperature: Optional[float] = 0.7
|
|
45
|
+
profile: Optional[str] = "General"
|
|
46
|
+
|
|
47
|
+
class SanitizeRequest(BaseModel):
|
|
48
|
+
text: str
|
|
49
|
+
profile: Optional[str] = "General"
|
|
50
|
+
detect_secrets: Optional[bool] = False
|
|
51
|
+
|
|
52
|
+
class RestoreRequest(BaseModel):
|
|
53
|
+
text: str
|
|
54
|
+
token_map: Optional[Dict[str, str]] = None
|
|
55
|
+
|
|
56
|
+
# Global Scrubber Instance
|
|
57
|
+
scrubber = AsyncPrivacyScrubber(default_profile="General")
|
|
58
|
+
|
|
59
|
+
app = FastAPI(
|
|
60
|
+
title="PrivacyScrubber ZTDS Sidecar Proxy",
|
|
61
|
+
description="Zero-Trust Data Sanitization & In-Memory PII Shield for LLM Pipelines",
|
|
62
|
+
version="2.4.0"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
@app.on_event("startup")
|
|
66
|
+
async def startup_event():
|
|
67
|
+
await scrubber.start()
|
|
68
|
+
print("[ZTDS Sidecar] PrivacyScrubber async sidecar initialized.")
|
|
69
|
+
|
|
70
|
+
@app.on_event("shutdown")
|
|
71
|
+
async def shutdown_event():
|
|
72
|
+
await scrubber.close()
|
|
73
|
+
print("[ZTDS Sidecar] PrivacyScrubber async sidecar closed.")
|
|
74
|
+
|
|
75
|
+
@app.get("/health")
|
|
76
|
+
async def health_check():
|
|
77
|
+
status = await scrubber.status()
|
|
78
|
+
ping = await scrubber.ping()
|
|
79
|
+
return {
|
|
80
|
+
"status": "healthy",
|
|
81
|
+
"engine": ping.get("engine"),
|
|
82
|
+
"version": ping.get("version"),
|
|
83
|
+
"license_tier": status.get("license_tier"),
|
|
84
|
+
"memory": status.get("memory_usage")
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@app.post("/v1/chat/completions")
|
|
88
|
+
async def chat_completions_proxy(
|
|
89
|
+
req: ChatCompletionRequest,
|
|
90
|
+
response: Response
|
|
91
|
+
):
|
|
92
|
+
"""
|
|
93
|
+
OpenAI-compatible chat completion proxy:
|
|
94
|
+
1. Intercepts incoming messages and sanitizes PII in client RAM (<0.5ms).
|
|
95
|
+
2. Simulates (or forwards) transmission of tokenized prompt to cloud LLM.
|
|
96
|
+
3. Restores tokens in the model response before returning to authorized client.
|
|
97
|
+
"""
|
|
98
|
+
session_token_map: Dict[str, str] = {}
|
|
99
|
+
scrubbed_messages: List[Dict[str, str]] = []
|
|
100
|
+
|
|
101
|
+
# 1. Sanitize incoming conversation messages
|
|
102
|
+
for msg in req.messages:
|
|
103
|
+
if msg.role in ("user", "system"):
|
|
104
|
+
res = await scrubber.sanitize(msg.content, profile=req.profile)
|
|
105
|
+
scrubbed_messages.append({"role": msg.role, "content": res.sanitized})
|
|
106
|
+
session_token_map.update(res.token_map)
|
|
107
|
+
else:
|
|
108
|
+
scrubbed_messages.append({"role": msg.role, "content": msg.content})
|
|
109
|
+
|
|
110
|
+
# 2. Simulated LLM call over tokenized text (Zero Cleartext PII Egress)
|
|
111
|
+
# In production, forward `scrubbed_messages` to httpx.post("https://api.openai.com/v1/chat/completions")
|
|
112
|
+
user_query = scrubbed_messages[-1]["content"] if scrubbed_messages else ""
|
|
113
|
+
simulated_llm_reply = (
|
|
114
|
+
f"Understood. I have recorded your intake for {user_query}. "
|
|
115
|
+
f"Verification receipt confirmed for compliance audit."
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# 3. Restore cleartext tokens in RAM before sending back to user
|
|
119
|
+
restored = await scrubber.restore(simulated_llm_reply, token_map=session_token_map)
|
|
120
|
+
|
|
121
|
+
response.headers["X-ZTDS-Status"] = "ENFORCED"
|
|
122
|
+
response.headers["X-ZTDS-Tokens-Masked"] = str(len(session_token_map))
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
"id": "chatcmpl-ztds-simulated",
|
|
126
|
+
"object": "chat.completion",
|
|
127
|
+
"model": req.model,
|
|
128
|
+
"choices": [
|
|
129
|
+
{
|
|
130
|
+
"index": 0,
|
|
131
|
+
"message": {
|
|
132
|
+
"role": "assistant",
|
|
133
|
+
"content": restored.restored_text
|
|
134
|
+
},
|
|
135
|
+
"finish_reason": "stop"
|
|
136
|
+
}
|
|
137
|
+
],
|
|
138
|
+
"ztds_meta": {
|
|
139
|
+
"tokens_masked": len(session_token_map),
|
|
140
|
+
"zero_egress_verified": True
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
@app.post("/api/v1/sanitize")
|
|
145
|
+
async def sanitize_endpoint(req: SanitizeRequest):
|
|
146
|
+
res = await scrubber.sanitize(
|
|
147
|
+
req.text,
|
|
148
|
+
profile=req.profile,
|
|
149
|
+
detect_secrets=req.detect_secrets
|
|
150
|
+
)
|
|
151
|
+
return {
|
|
152
|
+
"sanitized": res.sanitized,
|
|
153
|
+
"token_map": res.token_map,
|
|
154
|
+
"count": res.count,
|
|
155
|
+
"execution_ms": res.execution_ms,
|
|
156
|
+
"telemetry": res.telemetry,
|
|
157
|
+
"audit_receipt": res.audit_receipt
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
@app.post("/api/v1/restore")
|
|
161
|
+
async def restore_endpoint(req: RestoreRequest):
|
|
162
|
+
res = await scrubber.restore(req.text, token_map=req.token_map)
|
|
163
|
+
return {
|
|
164
|
+
"restored_text": res.restored_text,
|
|
165
|
+
"restored_count": res.restored_count
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# Direct executable simulation runner
|
|
170
|
+
async def run_simulation():
|
|
171
|
+
print("=== PrivacyScrubber ZTDS: FastAPI Zero-Egress Proxy Simulation ===\n")
|
|
172
|
+
async with AsyncPrivacyScrubber(default_profile="Healthcare") as ps:
|
|
173
|
+
# 1. Health check
|
|
174
|
+
ping = await ps.ping()
|
|
175
|
+
print(f"1. Connected to ZTDS Engine: {ping.get('engine')} v{ping.get('version')}")
|
|
176
|
+
|
|
177
|
+
# 2. Incoming sensitive user payload
|
|
178
|
+
raw_user_prompt = "Escalation for Patient Sarah Connor (DOB: 1984-05-12, SSN: 000-11-2222, email: sconnor@cyberdyne.org) complaining of acute migraines."
|
|
179
|
+
print(f"\n2. Incoming Request Payload (Local Client RAM):\n \"{raw_user_prompt}\"")
|
|
180
|
+
|
|
181
|
+
# 3. Pre-LLM Sanitization
|
|
182
|
+
sanitize_result = await ps.sanitize(raw_user_prompt, profile="Healthcare")
|
|
183
|
+
print(f"\n3. Sanitized Prompt for Cloud LLM Transmission (Zero PII Egress):\n \"{sanitize_result.sanitized}\"")
|
|
184
|
+
print(f" Tokens Masked: {sanitize_result.count} | Latency: {sanitize_result.execution_ms:.2f}ms")
|
|
185
|
+
print(f" Token Map (RAM Only): {json.dumps(sanitize_result.token_map)}")
|
|
186
|
+
|
|
187
|
+
# 4. Cloud LLM synthesizes response operating ONLY on surrogate tokens
|
|
188
|
+
simulated_cloud_ai_response = (
|
|
189
|
+
f"Based on clinical records for [NAME_1] (ID [ID_1], contact [EMAIL_1]), "
|
|
190
|
+
f"recommend standard migraine protocol and follow-up consultation."
|
|
191
|
+
)
|
|
192
|
+
print(f"\n4. Raw Cloud LLM Output (Contains Only Tokens):\n \"{simulated_cloud_ai_response}\"")
|
|
193
|
+
|
|
194
|
+
# 5. Post-LLM In-Memory Token Restoration
|
|
195
|
+
restored = await ps.restore(simulated_cloud_ai_response, token_map=sanitize_result.token_map)
|
|
196
|
+
print(f"\n5. Restored Response for Authorized User (Local RAM Only):\n \"{restored.restored_text}\"")
|
|
197
|
+
|
|
198
|
+
print("\n=== Simulation Complete: 0 Bytes PII Transmitted to Cloud LLM ===")
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
if __name__ == "__main__":
|
|
202
|
+
if FASTAPI_AVAILABLE and "uvicorn" in sys.modules:
|
|
203
|
+
import uvicorn
|
|
204
|
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
205
|
+
else:
|
|
206
|
+
asyncio.run(run_simulation())
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PrivacyScrubber ZTDS Adapter for LangChain
|
|
3
|
+
Zero-Trust Data Sanitization & Local PII Shield for LangChain Pipelines.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from privacyscrubber.langchain import (
|
|
9
|
+
ZTDSCallbackHandler,
|
|
10
|
+
ZTDSDocumentTransformer,
|
|
11
|
+
create_ztds_lcel_runnables,
|
|
12
|
+
LANGCHAIN_INSTALLED,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"ZTDSCallbackHandler",
|
|
17
|
+
"ZTDSDocumentTransformer",
|
|
18
|
+
"create_ztds_lcel_runnables",
|
|
19
|
+
"LANGCHAIN_INSTALLED",
|
|
20
|
+
]
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PrivacyScrubber ZTDS - LangChain RAG & Callback Handler Example
|
|
3
|
+
|
|
4
|
+
Demonstrates:
|
|
5
|
+
1. ZTDSCallbackHandler: Automatic pre-LLM prompt tokenization and post-LLM response restoration.
|
|
6
|
+
2. ZTDSDocumentTransformer: Sanitizing document page_content and metadata before vector embedding.
|
|
7
|
+
3. LCEL (LangChain Expression Language) pipeline with create_ztds_lcel_runnables().
|
|
8
|
+
4. Operates 100% in local memory with zero cloud network leakage.
|
|
9
|
+
|
|
10
|
+
Run:
|
|
11
|
+
python3 langchain_example.py
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
from privacyscrubber import PrivacyScrubberClient
|
|
18
|
+
from langchain_adapter import (
|
|
19
|
+
ZTDSCallbackHandler,
|
|
20
|
+
ZTDSDocumentTransformer,
|
|
21
|
+
create_ztds_lcel_runnables,
|
|
22
|
+
LANGCHAIN_INSTALLED
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def run_document_transformer_demo(client: PrivacyScrubberClient):
|
|
27
|
+
print("--- 1. Vector RAG Document Pre-Ingestion Guard ---")
|
|
28
|
+
|
|
29
|
+
class SimpleDoc:
|
|
30
|
+
def __init__(self, page_content: str, metadata: dict):
|
|
31
|
+
self.page_content = page_content
|
|
32
|
+
self.metadata = metadata
|
|
33
|
+
|
|
34
|
+
raw_docs = [
|
|
35
|
+
SimpleDoc(
|
|
36
|
+
page_content="Lead Account Executive Bob Vance (bob.vance@vancerefrigeration.com, phone 570-555-0143) closed contract under EIN 12-3456789.",
|
|
37
|
+
metadata={"doc_id": "contract_901", "author": "bob.vance@vancerefrigeration.com", "source": "HubSpot"}
|
|
38
|
+
),
|
|
39
|
+
SimpleDoc(
|
|
40
|
+
page_content="Customer escalation from Alice Morgan (amorgan@investcorp.com): wire transfer of $250,000 sent to IBAN GB29XAAA14151617181920.",
|
|
41
|
+
metadata={"doc_id": "wire_902", "author": "amorgan@investcorp.com", "source": "Zendesk"}
|
|
42
|
+
)
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
transformer = ZTDSDocumentTransformer(
|
|
46
|
+
scrubber=client,
|
|
47
|
+
profile="Financial",
|
|
48
|
+
sensitive_metadata_keys=["author"]
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
sanitized_docs = transformer.transform_documents(raw_docs)
|
|
52
|
+
|
|
53
|
+
print("Sanitized Documents (Ready for Vector Embeddings):")
|
|
54
|
+
for doc in sanitized_docs:
|
|
55
|
+
print(f" [ID: {doc.metadata['doc_id']}]")
|
|
56
|
+
print(f" Content: {doc.page_content}")
|
|
57
|
+
print(f" Metadata: {doc.metadata}\n")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def run_callback_handler_demo(client: PrivacyScrubberClient):
|
|
61
|
+
print("--- 2. ZTDS Callback Handler (Pre/Post LLM Interceptor) ---")
|
|
62
|
+
|
|
63
|
+
handler = ZTDSCallbackHandler(scrubber=client, profile="Financial", verbose=True)
|
|
64
|
+
|
|
65
|
+
# Simulated LangChain run:
|
|
66
|
+
# 1. Outgoing prompt before transmission
|
|
67
|
+
test_prompt = ["Analyze fraud risk for customer David Miller (SSN 987-65-4321, account # 4400-1122-3344-5566)."]
|
|
68
|
+
print(f"Prompt Before Interception:\n \"{test_prompt[0]}\"")
|
|
69
|
+
|
|
70
|
+
handler.on_llm_start(serialized={}, prompts=test_prompt, run_id="run_abc123")
|
|
71
|
+
print(f"Prompt Sent to Cloud LLM:\n \"{test_prompt[0]}\"")
|
|
72
|
+
|
|
73
|
+
# 2. Simulated LLM answer generated over tokens
|
|
74
|
+
class MockGeneration:
|
|
75
|
+
def __init__(self, text):
|
|
76
|
+
self.text = text
|
|
77
|
+
|
|
78
|
+
class MockLLMResult:
|
|
79
|
+
def __init__(self, text):
|
|
80
|
+
self.generations = [[MockGeneration(text)]]
|
|
81
|
+
|
|
82
|
+
simulated_cloud_ai_reply = (
|
|
83
|
+
"Fraud analysis for customer [NAME_1] (Tax ID [ID_1], account [ID_2]): "
|
|
84
|
+
"No anomalous velocity detected over the past 30 days."
|
|
85
|
+
)
|
|
86
|
+
mock_result = MockLLMResult(simulated_cloud_ai_reply)
|
|
87
|
+
|
|
88
|
+
print(f"\nRaw Cloud LLM Output:\n \"{mock_result.generations[0][0].text}\"")
|
|
89
|
+
|
|
90
|
+
# 3. Intercept and restore cleartext in RAM
|
|
91
|
+
handler.on_llm_end(mock_result, run_id="run_abc123")
|
|
92
|
+
print(f"Restored Output in Local RAM:\n \"{mock_result.generations[0][0].text}\"\n")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def run_lcel_pipeline_demo(client: PrivacyScrubberClient):
|
|
96
|
+
print("--- 3. LCEL Pipeline Integration ---")
|
|
97
|
+
scrub_fn, restore_fn = create_ztds_lcel_runnables(client, profile="General")
|
|
98
|
+
|
|
99
|
+
user_query = "Please generate an introduction email for John Smith at john.smith@acme.com."
|
|
100
|
+
print(f"Input: \"{user_query}\"")
|
|
101
|
+
|
|
102
|
+
# Step 1: Pre-scrub
|
|
103
|
+
sanitized_prompt = scrub_fn(user_query)
|
|
104
|
+
print(f"Sanitized: \"{sanitized_prompt}\"")
|
|
105
|
+
|
|
106
|
+
# Step 2: Simulated LLM
|
|
107
|
+
mock_ai_output = f"Subject: Welcome! Dear [NAME_1], we are thrilled to connect with you at [EMAIL_1]."
|
|
108
|
+
print(f"AI Output: \"{mock_ai_output}\"")
|
|
109
|
+
|
|
110
|
+
# Step 3: Post-restore
|
|
111
|
+
final_output = restore_fn(mock_ai_output)
|
|
112
|
+
print(f"Restored: \"{final_output}\"\n")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def main():
|
|
116
|
+
print("=== PrivacyScrubber ZTDS: LangChain Integration Suite ===\n")
|
|
117
|
+
with PrivacyScrubberClient(default_profile="Financial") as client:
|
|
118
|
+
run_document_transformer_demo(client)
|
|
119
|
+
run_callback_handler_demo(client)
|
|
120
|
+
run_lcel_pipeline_demo(client)
|
|
121
|
+
|
|
122
|
+
print("=== Zero Cleartext PII Egress Verified Across All Pipelines ===")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
if __name__ == "__main__":
|
|
126
|
+
main()
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PrivacyScrubber ZTDS - LlamaIndex Node & RAG Transformation Example
|
|
3
|
+
|
|
4
|
+
Demonstrates:
|
|
5
|
+
1. Sanitizing text nodes before vector embedding (VectorStoreIndex).
|
|
6
|
+
2. Preserving semantic search accuracy with deterministic surrogate tokens.
|
|
7
|
+
3. In-memory response rehydration upon retrieval and answer synthesis.
|
|
8
|
+
4. 100% in-memory RAM execution. Zero external egress.
|
|
9
|
+
|
|
10
|
+
Run:
|
|
11
|
+
python3 llamaindex_example.py
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import copy
|
|
17
|
+
from typing import Any, Dict, List, Optional
|
|
18
|
+
from privacyscrubber import PrivacyScrubberClient
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ZTDSNodeTransformer:
|
|
22
|
+
"""
|
|
23
|
+
Transforms LlamaIndex TextNodes or Document chunks before vector database insertion.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
scrubber: Optional[PrivacyScrubberClient] = None,
|
|
29
|
+
profile: str = "General"
|
|
30
|
+
) -> None:
|
|
31
|
+
self.scrubber = scrubber or PrivacyScrubberClient(default_profile=profile)
|
|
32
|
+
self.profile = profile
|
|
33
|
+
|
|
34
|
+
def transform_nodes(self, nodes: List[Any]) -> List[Any]:
|
|
35
|
+
"""Sanitizes text and metadata in place or creates clean copies."""
|
|
36
|
+
transformed: List[Any] = []
|
|
37
|
+
for node in nodes:
|
|
38
|
+
node_copy = copy.deepcopy(node)
|
|
39
|
+
text = getattr(node_copy, "text", "")
|
|
40
|
+
if text and isinstance(text, str):
|
|
41
|
+
res = self.scrubber.sanitize(text, profile=self.profile)
|
|
42
|
+
node_copy.text = res.sanitized
|
|
43
|
+
|
|
44
|
+
metadata = getattr(node_copy, "metadata", None)
|
|
45
|
+
if isinstance(metadata, dict):
|
|
46
|
+
for k, v in list(metadata.items()):
|
|
47
|
+
if isinstance(v, str):
|
|
48
|
+
res_m = self.scrubber.sanitize(v, profile=self.profile)
|
|
49
|
+
metadata[k] = res_m.sanitized
|
|
50
|
+
|
|
51
|
+
transformed.append(node_copy)
|
|
52
|
+
return transformed
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def main():
|
|
56
|
+
print("=== PrivacyScrubber ZTDS: LlamaIndex Ingestion Demo ===\n")
|
|
57
|
+
|
|
58
|
+
class MockTextNode:
|
|
59
|
+
def __init__(self, node_id: str, text: str, metadata: Dict[str, Any]):
|
|
60
|
+
self.node_id = node_id
|
|
61
|
+
self.text = text
|
|
62
|
+
self.metadata = metadata
|
|
63
|
+
|
|
64
|
+
raw_nodes = [
|
|
65
|
+
MockTextNode(
|
|
66
|
+
node_id="chunk_001",
|
|
67
|
+
text="Employee review for Marcus Brody. Personal mobile is +1-202-555-0199, email mbrody@state.gov.",
|
|
68
|
+
metadata={"department": "Public Relations", "reviewer": "Marcus Brody"}
|
|
69
|
+
),
|
|
70
|
+
MockTextNode(
|
|
71
|
+
node_id="chunk_002",
|
|
72
|
+
text="Compensation plan approved under Tax ID 99-8877665 for total salary $185,000.",
|
|
73
|
+
metadata={"department": "Executive Payroll", "approver": "Finance Board"}
|
|
74
|
+
)
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
with PrivacyScrubberClient(default_profile="HR") as client:
|
|
78
|
+
transformer = ZTDSNodeTransformer(scrubber=client, profile="HR")
|
|
79
|
+
|
|
80
|
+
print("1. Raw LlamaIndex Chunks:")
|
|
81
|
+
for n in raw_nodes:
|
|
82
|
+
print(f" [{n.node_id}] {n.text}")
|
|
83
|
+
|
|
84
|
+
sanitized_nodes = transformer.transform_nodes(raw_nodes)
|
|
85
|
+
|
|
86
|
+
print("\n2. Sanitized Chunks (Safe for Vector Store Ingestion):")
|
|
87
|
+
for n in sanitized_nodes:
|
|
88
|
+
print(f" [{n.node_id}] {n.text}")
|
|
89
|
+
print(f" Metadata: {n.metadata}")
|
|
90
|
+
|
|
91
|
+
# 3. Simulate Query Engine Synthesis
|
|
92
|
+
query_output = "Based on retrieved node [chunk_001], [NAME_1] ([EMAIL_1]) has verified contact info."
|
|
93
|
+
print(f"\n3. Query Engine Output (LLM generated over surrogate tokens):\n \"{query_output}\"")
|
|
94
|
+
|
|
95
|
+
# 4. In-Memory Restoration
|
|
96
|
+
restored = client.restore(query_output)
|
|
97
|
+
print(f"\n4. Final Answer Restored in Local RAM:\n \"{restored.restored_text}\"")
|
|
98
|
+
|
|
99
|
+
print("\n=== LlamaIndex RAG Pipeline Verification Complete ===")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
main()
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PrivacyScrubber - Zero-Trust Data Sanitization & In-Memory PII Shield for Python.
|
|
3
|
+
|
|
4
|
+
RFC v1.0 standard compliant.
|
|
5
|
+
100% in-memory client-side RAM execution. Zero external network egress.
|
|
6
|
+
Sub-millisecond latency (<1ms).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from privacyscrubber.client import (
|
|
12
|
+
PrivacyScrubberClient,
|
|
13
|
+
PrivacyScrubber,
|
|
14
|
+
AsyncPrivacyScrubberClient,
|
|
15
|
+
AsyncPrivacyScrubber,
|
|
16
|
+
)
|
|
17
|
+
from privacyscrubber.types import (
|
|
18
|
+
SanitizeResult,
|
|
19
|
+
RestoreResult,
|
|
20
|
+
ObjectSanitizeResult,
|
|
21
|
+
)
|
|
22
|
+
from privacyscrubber.exceptions import (
|
|
23
|
+
PrivacyScrubberError,
|
|
24
|
+
PrivacyScrubberLicenseError,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__version__ = "2.4.0"
|
|
28
|
+
__author__ = "Ilya Sibiryakov (BrandMeWeb)"
|
|
29
|
+
__license__ = "Proprietary"
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"PrivacyScrubberClient",
|
|
33
|
+
"PrivacyScrubber",
|
|
34
|
+
"AsyncPrivacyScrubberClient",
|
|
35
|
+
"AsyncPrivacyScrubber",
|
|
36
|
+
"SanitizeResult",
|
|
37
|
+
"RestoreResult",
|
|
38
|
+
"ObjectSanitizeResult",
|
|
39
|
+
"PrivacyScrubberError",
|
|
40
|
+
"PrivacyScrubberLicenseError",
|
|
41
|
+
"__version__",
|
|
42
|
+
]
|