@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,161 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PrivacyScrubber LangChain Adapter
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import copy
|
|
8
|
+
from typing import Any, Dict, List, Optional, Sequence, Union
|
|
9
|
+
|
|
10
|
+
from privacyscrubber.client import PrivacyScrubberClient
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
from langchain_core.callbacks.base import BaseCallbackHandler
|
|
15
|
+
from langchain_core.documents import Document
|
|
16
|
+
from langchain_core.outputs import LLMResult
|
|
17
|
+
from langchain_core.runnables import RunnableLambda
|
|
18
|
+
LANGCHAIN_INSTALLED = True
|
|
19
|
+
except ImportError:
|
|
20
|
+
LANGCHAIN_INSTALLED = False
|
|
21
|
+
BaseCallbackHandler = object # type: ignore
|
|
22
|
+
Document = object # type: ignore
|
|
23
|
+
LLMResult = object # type: ignore
|
|
24
|
+
RunnableLambda = object # type: ignore
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ZTDSCallbackHandler(BaseCallbackHandler):
|
|
28
|
+
"""
|
|
29
|
+
LangChain Callback Handler enforcing Zero-Trust Data Sanitization.
|
|
30
|
+
|
|
31
|
+
1. Intercepts outgoing prompts in on_llm_start -> replaces raw PII with surrogate tokens.
|
|
32
|
+
2. Stores ephemeral token mappings in RAM only (no disk, no network).
|
|
33
|
+
3. Intercepts incoming responses in on_llm_end -> restores cleartext values in RAM.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
scrubber: Optional[PrivacyScrubberClient] = None,
|
|
39
|
+
profile: str = "General",
|
|
40
|
+
detect_secrets: bool = False,
|
|
41
|
+
verbose: bool = False
|
|
42
|
+
) -> None:
|
|
43
|
+
self.scrubber = scrubber or PrivacyScrubberClient(default_profile=profile)
|
|
44
|
+
self.profile = profile
|
|
45
|
+
self.detect_secrets = detect_secrets
|
|
46
|
+
self.verbose = verbose
|
|
47
|
+
self._session_token_maps: Dict[str, Dict[str, str]] = {}
|
|
48
|
+
|
|
49
|
+
def on_llm_start(
|
|
50
|
+
self,
|
|
51
|
+
serialized: Dict[str, Any],
|
|
52
|
+
prompts: List[str],
|
|
53
|
+
*,
|
|
54
|
+
run_id: Optional[Any] = None,
|
|
55
|
+
parent_run_id: Optional[Any] = None,
|
|
56
|
+
tags: Optional[List[str]] = None,
|
|
57
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
58
|
+
**kwargs: Any
|
|
59
|
+
) -> Any:
|
|
60
|
+
"""Sanitizes prompts in place before they are dispatched to the cloud LLM."""
|
|
61
|
+
key = str(run_id) if run_id else "default"
|
|
62
|
+
combined_token_map: Dict[str, str] = {}
|
|
63
|
+
|
|
64
|
+
for i, prompt in enumerate(prompts):
|
|
65
|
+
res = self.scrubber.sanitize(
|
|
66
|
+
prompt,
|
|
67
|
+
profile=self.profile,
|
|
68
|
+
detect_secrets=self.detect_secrets,
|
|
69
|
+
persist_session=True
|
|
70
|
+
)
|
|
71
|
+
prompts[i] = res.sanitized
|
|
72
|
+
combined_token_map.update(res.token_map)
|
|
73
|
+
if self.verbose and res.count > 0:
|
|
74
|
+
print(f"[ZTDS Guard] Sanitized {res.count} PII tokens for run {key}")
|
|
75
|
+
|
|
76
|
+
self._session_token_maps[key] = combined_token_map
|
|
77
|
+
|
|
78
|
+
def on_llm_end(
|
|
79
|
+
self,
|
|
80
|
+
response: Any,
|
|
81
|
+
*,
|
|
82
|
+
run_id: Optional[Any] = None,
|
|
83
|
+
parent_run_id: Optional[Any] = None,
|
|
84
|
+
tags: Optional[List[str]] = None,
|
|
85
|
+
**kwargs: Any
|
|
86
|
+
) -> Any:
|
|
87
|
+
"""Restores cleartext tokens in LLM output before reaching the user."""
|
|
88
|
+
key = str(run_id) if run_id else "default"
|
|
89
|
+
token_map = self._session_token_maps.pop(key, None)
|
|
90
|
+
|
|
91
|
+
generations = getattr(response, "generations", None)
|
|
92
|
+
if generations and isinstance(generations, list):
|
|
93
|
+
for gen_list in generations:
|
|
94
|
+
for gen in gen_list:
|
|
95
|
+
if hasattr(gen, "text") and gen.text:
|
|
96
|
+
restored = self.scrubber.restore(gen.text, token_map=token_map)
|
|
97
|
+
gen.text = restored.restored_text
|
|
98
|
+
elif hasattr(gen, "message") and hasattr(gen.message, "content"):
|
|
99
|
+
if isinstance(gen.message.content, str):
|
|
100
|
+
restored = self.scrubber.restore(gen.message.content, token_map=token_map)
|
|
101
|
+
gen.message.content = restored.restored_text
|
|
102
|
+
|
|
103
|
+
return response
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class ZTDSDocumentTransformer:
|
|
107
|
+
"""
|
|
108
|
+
Sanitizes LangChain Documents before vector database ingestion (Pinecone, Qdrant, Chroma).
|
|
109
|
+
Guarantees no raw PII enters embeddings or persistent vector storage.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
def __init__(
|
|
113
|
+
self,
|
|
114
|
+
scrubber: Optional[PrivacyScrubberClient] = None,
|
|
115
|
+
profile: str = "General",
|
|
116
|
+
sensitive_metadata_keys: Optional[Sequence[str]] = None
|
|
117
|
+
) -> None:
|
|
118
|
+
self.scrubber = scrubber or PrivacyScrubberClient(default_profile=profile)
|
|
119
|
+
self.profile = profile
|
|
120
|
+
self.sensitive_metadata_keys = list(sensitive_metadata_keys or [])
|
|
121
|
+
|
|
122
|
+
def transform_documents(self, documents: Sequence[Any]) -> List[Any]:
|
|
123
|
+
"""Transforms documents, sanitizing page_content and sensitive metadata keys."""
|
|
124
|
+
transformed: List[Any] = []
|
|
125
|
+
for doc in documents:
|
|
126
|
+
doc_copy = copy.deepcopy(doc)
|
|
127
|
+
content = getattr(doc_copy, "page_content", "")
|
|
128
|
+
if isinstance(content, str) and content:
|
|
129
|
+
res = self.scrubber.sanitize(content, profile=self.profile)
|
|
130
|
+
doc_copy.page_content = res.sanitized
|
|
131
|
+
|
|
132
|
+
metadata = getattr(doc_copy, "metadata", None)
|
|
133
|
+
if isinstance(metadata, dict):
|
|
134
|
+
for k in self.sensitive_metadata_keys:
|
|
135
|
+
if k in metadata and isinstance(metadata[k], str):
|
|
136
|
+
res_meta = self.scrubber.sanitize(metadata[k], profile=self.profile)
|
|
137
|
+
metadata[k] = res_meta.sanitized
|
|
138
|
+
|
|
139
|
+
transformed.append(doc_copy)
|
|
140
|
+
return transformed
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def create_ztds_lcel_runnables(
|
|
144
|
+
scrubber: Optional[PrivacyScrubberClient] = None,
|
|
145
|
+
profile: str = "General"
|
|
146
|
+
):
|
|
147
|
+
"""
|
|
148
|
+
Creates pre-scrub and post-restore runnables for LCEL pipelines:
|
|
149
|
+
chain = scrub_runnable | prompt | model | parser | restore_runnable
|
|
150
|
+
"""
|
|
151
|
+
client = scrubber or PrivacyScrubberClient(default_profile=profile)
|
|
152
|
+
|
|
153
|
+
def scrub_func(input_text: str) -> str:
|
|
154
|
+
res = client.sanitize(str(input_text), profile=profile)
|
|
155
|
+
return res.sanitized
|
|
156
|
+
|
|
157
|
+
def restore_func(output_text: str) -> str:
|
|
158
|
+
restored = client.restore(str(output_text))
|
|
159
|
+
return restored.restored_text
|
|
160
|
+
|
|
161
|
+
return scrub_func, restore_func
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PrivacyScrubber Data Types & Response Models
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class SanitizeResult:
|
|
13
|
+
"""Result of a text sanitization operation."""
|
|
14
|
+
sanitized: str
|
|
15
|
+
token_map: Dict[str, str]
|
|
16
|
+
count: int = 0
|
|
17
|
+
execution_ms: float = 0.0
|
|
18
|
+
telemetry: Optional[Dict[str, Any]] = None
|
|
19
|
+
audit_receipt: Optional[str] = None
|
|
20
|
+
license: Optional[Dict[str, Any]] = None
|
|
21
|
+
matches: Optional[List[Dict[str, Any]]] = None
|
|
22
|
+
|
|
23
|
+
def __str__(self) -> str:
|
|
24
|
+
"""Allow direct string usage in prompts and string formatting."""
|
|
25
|
+
return self.sanitized
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def scrubbed_text(self) -> str:
|
|
29
|
+
"""Alias for sanitized text."""
|
|
30
|
+
return self.sanitized
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class RestoreResult:
|
|
35
|
+
"""Result of a token restoration operation."""
|
|
36
|
+
restored_text: str
|
|
37
|
+
restored_count: int = 0
|
|
38
|
+
|
|
39
|
+
def __str__(self) -> str:
|
|
40
|
+
"""Allow direct string usage in final responses."""
|
|
41
|
+
return self.restored_text
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class ObjectSanitizeResult:
|
|
46
|
+
"""Result of a structured dictionary or list sanitization operation."""
|
|
47
|
+
sanitized: Any
|
|
48
|
+
token_map: Dict[str, str]
|
|
49
|
+
count: int = 0
|
|
50
|
+
execution_ms: float = 0.0
|
|
51
|
+
telemetry: Optional[Dict[str, Any]] = None
|
|
52
|
+
audit_receipt: Optional[str] = None
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PrivacyScrubber ZTDS Client for Python
|
|
3
|
+
Zero-Trust Data Sanitization & Local PII Shield for LLM / AI Pipelines.
|
|
4
|
+
|
|
5
|
+
100% in-memory client-side execution. Zero cloud egress. Sub-millisecond latency.
|
|
6
|
+
RFC v1.0 standard-compliant Zero-Trust Data Sanitization engine.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from privacyscrubber import (
|
|
12
|
+
PrivacyScrubberClient,
|
|
13
|
+
PrivacyScrubber,
|
|
14
|
+
AsyncPrivacyScrubberClient,
|
|
15
|
+
AsyncPrivacyScrubber,
|
|
16
|
+
SanitizeResult,
|
|
17
|
+
RestoreResult,
|
|
18
|
+
ObjectSanitizeResult,
|
|
19
|
+
PrivacyScrubberError,
|
|
20
|
+
PrivacyScrubberLicenseError,
|
|
21
|
+
__version__,
|
|
22
|
+
)
|
|
23
|
+
from privacyscrubber.client import _find_daemon_path
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"PrivacyScrubberClient",
|
|
27
|
+
"PrivacyScrubber",
|
|
28
|
+
"AsyncPrivacyScrubberClient",
|
|
29
|
+
"AsyncPrivacyScrubber",
|
|
30
|
+
"SanitizeResult",
|
|
31
|
+
"RestoreResult",
|
|
32
|
+
"ObjectSanitizeResult",
|
|
33
|
+
"PrivacyScrubberError",
|
|
34
|
+
"PrivacyScrubberLicenseError",
|
|
35
|
+
"__version__",
|
|
36
|
+
]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "privacyscrubber"
|
|
7
|
+
version = "2.4.1"
|
|
8
|
+
description = "Zero-Trust Data Sanitization & In-Memory PII Shield for Python AI Pipelines (ZTDS RFC v1.0)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "Commercial / Proprietary" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Ilya Sibiryakov (BrandMeWeb)", email = "contact@privacyscrubber.com" }
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"pii", "redaction", "sanitization", "zero-trust", "ztds", "langchain",
|
|
17
|
+
"fastapi", "presidio", "llm-security", "gdpr", "hipaa", "ai-guardrail"
|
|
18
|
+
]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 5 - Production/Stable",
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"Intended Audience :: Information Technology",
|
|
23
|
+
"Topic :: Security",
|
|
24
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
"Programming Language :: Python :: 3.8",
|
|
27
|
+
"Programming Language :: Python :: 3.9",
|
|
28
|
+
"Programming Language :: Python :: 3.10",
|
|
29
|
+
"Programming Language :: Python :: 3.11",
|
|
30
|
+
"Programming Language :: Python :: 3.12",
|
|
31
|
+
"Programming Language :: Python :: 3.13",
|
|
32
|
+
"Operating System :: OS Independent",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://privacyscrubber.com"
|
|
37
|
+
Documentation = "https://privacyscrubber.com/sdk/"
|
|
38
|
+
Repository = "https://github.com/moxno/privacyscrubber-mcp"
|
|
39
|
+
Standard = "https://datatracker.ietf.org/doc/draft-sibiryakov-ztds-protocol/"
|
|
40
|
+
|
|
41
|
+
[project.optional-dependencies]
|
|
42
|
+
fastapi = ["fastapi>=0.100.0", "uvicorn>=0.20.0", "pydantic>=2.0.0"]
|
|
43
|
+
langchain = ["langchain-core>=0.1.0"]
|
|
44
|
+
llamaindex = ["llama-index-core>=0.10.0"]
|
|
45
|
+
all = [
|
|
46
|
+
"fastapi>=0.100.0",
|
|
47
|
+
"uvicorn>=0.20.0",
|
|
48
|
+
"pydantic>=2.0.0",
|
|
49
|
+
"langchain-core>=0.1.0",
|
|
50
|
+
"llama-index-core>=0.10.0"
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[project.scripts]
|
|
54
|
+
privacyscrubber = "privacyscrubber.cli:main"
|
|
55
|
+
ps-bridge = "privacyscrubber.cli:main"
|
|
56
|
+
|
|
57
|
+
[tool.setuptools.packages.find]
|
|
58
|
+
where = ["."]
|
|
59
|
+
include = ["privacyscrubber*"]
|
|
60
|
+
|
|
61
|
+
[tool.setuptools.package-data]
|
|
62
|
+
privacyscrubber = ["*.cjs", "bridge_daemon.cjs"]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Setup script for privacyscrubber Python package.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from setuptools import setup, find_packages
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
here = os.path.abspath(os.path.dirname(__file__))
|
|
10
|
+
|
|
11
|
+
with open(os.path.join(here, "README.md"), encoding="utf-8") as f:
|
|
12
|
+
long_description = f.read()
|
|
13
|
+
|
|
14
|
+
setup(
|
|
15
|
+
name="privacyscrubber",
|
|
16
|
+
version="2.4.1",
|
|
17
|
+
description="Zero-Trust Data Sanitization & In-Memory PII Shield for Python AI Pipelines (ZTDS RFC v1.0)",
|
|
18
|
+
long_description=long_description,
|
|
19
|
+
long_description_content_type="text/markdown",
|
|
20
|
+
author="Ilya Sibiryakov (BrandMeWeb)",
|
|
21
|
+
author_email="contact@privacyscrubber.com",
|
|
22
|
+
url="https://privacyscrubber.com",
|
|
23
|
+
project_urls={
|
|
24
|
+
"Documentation": "https://privacyscrubber.com/sdk/",
|
|
25
|
+
"Source": "https://github.com/moxno/privacyscrubber-mcp",
|
|
26
|
+
"ZTDS Standard": "https://datatracker.ietf.org/doc/draft-sibiryakov-ztds-protocol/",
|
|
27
|
+
},
|
|
28
|
+
packages=find_packages(),
|
|
29
|
+
package_data={
|
|
30
|
+
"privacyscrubber": ["*.cjs", "bridge_daemon.cjs"],
|
|
31
|
+
},
|
|
32
|
+
include_package_data=True,
|
|
33
|
+
python_requires=">=3.8",
|
|
34
|
+
install_requires=[],
|
|
35
|
+
extras_require={
|
|
36
|
+
"fastapi": ["fastapi>=0.100.0", "uvicorn>=0.20.0", "pydantic>=2.0.0"],
|
|
37
|
+
"langchain": ["langchain-core>=0.1.0"],
|
|
38
|
+
"llamaindex": ["llama-index-core>=0.10.0"],
|
|
39
|
+
"all": [
|
|
40
|
+
"fastapi>=0.100.0",
|
|
41
|
+
"uvicorn>=0.20.0",
|
|
42
|
+
"pydantic>=2.0.0",
|
|
43
|
+
"langchain-core>=0.1.0",
|
|
44
|
+
"llama-index-core>=0.10.0",
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
entry_points={
|
|
48
|
+
"console_scripts": [
|
|
49
|
+
"privacyscrubber=privacyscrubber.cli:main",
|
|
50
|
+
"ps-bridge=privacyscrubber.cli:main",
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
classifiers=[
|
|
54
|
+
"Development Status :: 5 - Production/Stable",
|
|
55
|
+
"Intended Audience :: Developers",
|
|
56
|
+
"Topic :: Security",
|
|
57
|
+
"Programming Language :: Python :: 3",
|
|
58
|
+
"Programming Language :: Python :: 3.8",
|
|
59
|
+
"Programming Language :: Python :: 3.9",
|
|
60
|
+
"Programming Language :: Python :: 3.10",
|
|
61
|
+
"Programming Language :: Python :: 3.11",
|
|
62
|
+
"Programming Language :: Python :: 3.12",
|
|
63
|
+
"Programming Language :: Python :: 3.13",
|
|
64
|
+
"Operating System :: OS Independent",
|
|
65
|
+
],
|
|
66
|
+
)
|