@privacyscrubber/sdk 2.4.0 → 2.4.2
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/benchmark.js +33 -25
- package/browser.js +274 -37
- package/cli.js +7 -1
- package/examples/python-bridge/bridge_daemon.cjs +2 -2
- package/examples/python-bridge/fastapi_example.py +1 -1
- package/examples/python-bridge/privacyscrubber/__init__.py +1 -1
- package/examples/python-bridge/privacyscrubber/bridge_daemon.cjs +1 -1
- package/examples/python-bridge/privacyscrubber/cli.py +1 -1
- package/examples/python-bridge/privacyscrubber/client.py +11 -5
- package/examples/python-bridge/pyproject.toml +1 -1
- package/examples/python-bridge/setup.py +1 -1
- package/examples/python-bridge/test_python_bridge.py +1 -1
- package/fastmcp.cjs +29 -18
- package/index.cjs +31 -13
- package/index.js +35 -17
- package/package.json +5 -1
- package/ps-license-manager.js +261 -11
- package/ps-pii-engine.cjs +3611 -8
- package/scrubber-core.cjs +1801 -8
- package/vercel-ai.cjs +1 -1
- package/vercel-ai.js +1 -1
- 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/privacyscrubber.egg-info/PKG-INFO +0 -311
- package/examples/python-bridge/privacyscrubber.egg-info/SOURCES.txt +0 -18
- package/examples/python-bridge/privacyscrubber.egg-info/dependency_links.txt +0 -1
- package/examples/python-bridge/privacyscrubber.egg-info/entry_points.txt +0 -3
- package/examples/python-bridge/privacyscrubber.egg-info/requires.txt +0 -18
- package/examples/python-bridge/privacyscrubber.egg-info/top_level.txt +0 -1
|
@@ -89,14 +89,16 @@ class PrivacyScrubberClient:
|
|
|
89
89
|
self.daemon_path,
|
|
90
90
|
"--profile", self.default_profile
|
|
91
91
|
]
|
|
92
|
+
proc_env = os.environ.copy()
|
|
92
93
|
if self.license_key:
|
|
93
|
-
|
|
94
|
+
proc_env["PRIVACYSCRUBBER_LICENSE_KEY"] = self.license_key
|
|
94
95
|
|
|
95
96
|
self._process = subprocess.Popen(
|
|
96
97
|
cmd,
|
|
97
98
|
stdin=subprocess.PIPE,
|
|
98
99
|
stdout=subprocess.PIPE,
|
|
99
100
|
stderr=subprocess.PIPE,
|
|
101
|
+
env=proc_env,
|
|
100
102
|
text=True,
|
|
101
103
|
bufsize=1
|
|
102
104
|
)
|
|
@@ -341,14 +343,16 @@ class AsyncPrivacyScrubberClient:
|
|
|
341
343
|
self.daemon_path,
|
|
342
344
|
"--profile", self.default_profile
|
|
343
345
|
]
|
|
346
|
+
proc_env = os.environ.copy()
|
|
344
347
|
if self.license_key:
|
|
345
|
-
|
|
348
|
+
proc_env["PRIVACYSCRUBBER_LICENSE_KEY"] = self.license_key
|
|
346
349
|
|
|
347
350
|
self._process = await asyncio.create_subprocess_exec(
|
|
348
351
|
*cmd,
|
|
349
352
|
stdin=asyncio.subprocess.PIPE,
|
|
350
353
|
stdout=asyncio.subprocess.PIPE,
|
|
351
|
-
stderr=asyncio.subprocess.PIPE
|
|
354
|
+
stderr=asyncio.subprocess.PIPE,
|
|
355
|
+
env=proc_env
|
|
352
356
|
)
|
|
353
357
|
|
|
354
358
|
async def close(self) -> None:
|
|
@@ -387,13 +391,15 @@ class AsyncPrivacyScrubberClient:
|
|
|
387
391
|
self.daemon_path,
|
|
388
392
|
"--profile", self.default_profile
|
|
389
393
|
]
|
|
394
|
+
proc_env = os.environ.copy()
|
|
390
395
|
if self.license_key:
|
|
391
|
-
|
|
396
|
+
proc_env["PRIVACYSCRUBBER_LICENSE_KEY"] = self.license_key
|
|
392
397
|
self._process = await asyncio.create_subprocess_exec(
|
|
393
398
|
*cmd,
|
|
394
399
|
stdin=asyncio.subprocess.PIPE,
|
|
395
400
|
stdout=asyncio.subprocess.PIPE,
|
|
396
|
-
stderr=asyncio.subprocess.PIPE
|
|
401
|
+
stderr=asyncio.subprocess.PIPE,
|
|
402
|
+
env=proc_env
|
|
397
403
|
)
|
|
398
404
|
|
|
399
405
|
self._req_id += 1
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "privacyscrubber"
|
|
7
|
-
version = "2.4.
|
|
7
|
+
version = "2.4.2"
|
|
8
8
|
description = "Zero-Trust Data Sanitization & In-Memory PII Shield for Python AI Pipelines (ZTDS RFC v1.0)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -13,7 +13,7 @@ with open(os.path.join(here, "README.md"), encoding="utf-8") as f:
|
|
|
13
13
|
|
|
14
14
|
setup(
|
|
15
15
|
name="privacyscrubber",
|
|
16
|
-
version="2.4.
|
|
16
|
+
version="2.4.2",
|
|
17
17
|
description="Zero-Trust Data Sanitization & In-Memory PII Shield for Python AI Pipelines (ZTDS RFC v1.0)",
|
|
18
18
|
long_description=long_description,
|
|
19
19
|
long_description_content_type="text/markdown",
|
package/fastmcp.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @privacyscrubber/fastmcp-guard
|
|
3
3
|
* Zero-Trust Data Sanitization (ZTDS) In-Memory Middleware for FastMCP
|
|
4
|
-
* Conforms to IETF Standards Track: draft-sibiryakov-ztds-protocol-
|
|
4
|
+
* Conforms to IETF Standards Track: draft-sibiryakov-ztds-protocol-01
|
|
5
5
|
*
|
|
6
6
|
* Core Protocol Invariants:
|
|
7
7
|
* 1. Zero External Egress Prior to Sanitization
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
const crypto = require("node:crypto");
|
|
15
15
|
|
|
16
16
|
const ZTDS_PATTERNS = {
|
|
17
|
-
API_SECRET: /\b(?:sk-(?:proj-)?[A-Za-z0-9_-]{20,}|(?:ghp|gho|ghu|ghs|ghr|github_pat_)[A-Za-z0-9_]{
|
|
17
|
+
API_SECRET: /\b(?:sk-ant-(?:api\d{2}|admin\d{2}|sid\d{2})-[a-zA-Z0-9_-]{16,128}|sk-(?:proj-)?[A-Za-z0-9_-]{20,}|AIza[0-9A-Za-z_-]{35}|(?:ghp|gho|ghu|ghs|ghr|github_pat_|glpat-|xox[baprs]-)[A-Za-z0-9_-]{16,}|(?:[rs]k)_(?:test|live)_[a-zA-Z0-9]{10,}|eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,})\b/g,
|
|
18
18
|
AWS_KEY: /\b(?:AKIA|ASIA|AGPA|AIDA|AROA|AIPA)[A-Z0-9]{16}\b/g,
|
|
19
19
|
CREDIT_CARD: /\b(?:\d{4}[-\s]?){3}\d{4}\b/g,
|
|
20
20
|
EMAIL: /[A-Za-z0-9._%+-]{1,64}@[A-Za-z0-9-]{1,63}(?:\.[A-Za-z0-9-]{1,63})*\.[A-Za-z]{2,24}/g,
|
|
@@ -24,7 +24,7 @@ const ZTDS_PATTERNS = {
|
|
|
24
24
|
SSN: /\b\d{3}-\d{2}-\d{4}\b/g,
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
const TOKEN_PATTERN =
|
|
27
|
+
const TOKEN_PATTERN = /(?:%5[Bb]|\\u005[Bb]|&(?:amp;)?#0*91;|\[|<|\{\{?|__|【|[)[*`_~\s]*([A-Z_]+)_TOKEN_([a-zA-Z0-9_-]+)[*`_~.,;:!?\s]*(?:%5[Dd]|\\u005[Dd]|&(?:amp;)?#0*93;|\]|>|\}\}?|__|】|])/gi;
|
|
28
28
|
|
|
29
29
|
function escapeRegex(s) {
|
|
30
30
|
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
@@ -41,15 +41,18 @@ function buildStreamReplacer(tokenMap = {}, options = {}) {
|
|
|
41
41
|
|
|
42
42
|
for (const k of sortedKeys) {
|
|
43
43
|
const val = tokenMap[k];
|
|
44
|
-
const inner = k.replace(
|
|
45
|
-
const match = inner.match(/^([A-Za-z_0-9]+?)[-_ ]
|
|
44
|
+
const inner = k.replace(/^(?:\[|<|\{\{?|__|【|[|〔|「|『)|(?:\]|>|\}\}?|__|】|]|〕|」|』)$/g, '');
|
|
45
|
+
const match = inner.match(/^([A-Za-z_0-9]+?)[-_ ]([a-zA-Z0-9]+)$/);
|
|
46
46
|
|
|
47
47
|
if (match) {
|
|
48
48
|
const label = escapeRegex(match[1]);
|
|
49
|
-
const idx = match[2];
|
|
50
|
-
const
|
|
49
|
+
const idx = escapeRegex(match[2]);
|
|
50
|
+
const idxPat = /^\d+$/.test(match[2]) ? '0*' + idx : idx;
|
|
51
|
+
const corePat = '[*`_~\\s]*' + label + '[*`_~]*[-_\\s]*' + idxPat + '[*`_~.,;:!?\\s]*';
|
|
52
|
+
const patternStr = '(?:\\[' + corePat + '\\]|<' + corePat + '>|\\{\\{' + corePat + '\\}\\}|\\{' + corePat + '\\}|__' + corePat + '__|[【[〔「『]' + corePat + '[】]〕」』]|%5B' + corePat + '%5D|&#(?:91|091|x5[bB]);' + corePat + '&#(?:93|093|x5[dD]);|&(?:lbrack|lsqb);' + corePat + '&(?:rbrack|rsqb);|\\\\u005[bB]' + corePat + '\\\\u005[dD]|\\b' + label + '[-_\\s]+' + idxPat + '\\b)';
|
|
51
53
|
patterns.push(patternStr);
|
|
52
|
-
const
|
|
54
|
+
const normSuffix = /^\d+$/.test(match[2]) ? String(parseInt(match[2], 10)) : match[2].toUpperCase();
|
|
55
|
+
const normKey = (match[1] + '_' + normSuffix).toUpperCase();
|
|
53
56
|
lookup.set(normKey, val);
|
|
54
57
|
lookup.set(k, val);
|
|
55
58
|
lookup.set(k.toUpperCase(), val);
|
|
@@ -76,10 +79,15 @@ function buildStreamReplacer(tokenMap = {}, options = {}) {
|
|
|
76
79
|
} else if (lookup.has(coreMatch.toUpperCase())) {
|
|
77
80
|
restored = lookup.get(coreMatch.toUpperCase());
|
|
78
81
|
} else {
|
|
79
|
-
const inner = coreMatch
|
|
80
|
-
|
|
82
|
+
const inner = coreMatch
|
|
83
|
+
.replace(/^(?:%5B|&#(?:91|091|x5[bB]);|&(?:lbrack|lsqb);|\\u005[bB]|\[|<|\{\{?|__|【|[|〔|「|『)/i, '')
|
|
84
|
+
.replace(/(?:%5D|&#(?:93|093|x5[dD]);|&(?:rbrack|rsqb);|\\u005[dD]|\]|>|\}\}?|__|】|]|〕|」|』)$/i, '')
|
|
85
|
+
.replace(/[*`_~.,;:!?\s]+/g, ' ')
|
|
86
|
+
.trim();
|
|
87
|
+
const m = inner.match(/^([A-Za-z_0-9]+?)[-_ ]([a-zA-Z0-9]+)$/);
|
|
81
88
|
if (m) {
|
|
82
|
-
const
|
|
89
|
+
const normSuffix = /^\d+$/.test(m[2]) ? String(parseInt(m[2], 10)) : m[2].toUpperCase();
|
|
90
|
+
const normKey = (m[1] + '_' + normSuffix).toUpperCase();
|
|
83
91
|
if (lookup.has(normKey)) {
|
|
84
92
|
restored = lookup.get(normKey);
|
|
85
93
|
}
|
|
@@ -158,12 +166,12 @@ class StreamingDetokenizer {
|
|
|
158
166
|
const tailLen = 160;
|
|
159
167
|
const tail = this.pendingBuffer.length > tailLen ? this.pendingBuffer.slice(-tailLen) : this.pendingBuffer;
|
|
160
168
|
const offset = this.pendingBuffer.length > tailLen ? this.pendingBuffer.length - tailLen : 0;
|
|
161
|
-
const m = tail.match(/(?:\[
|
|
169
|
+
const m = tail.match(/(?:(?:\[|【|[|〔|「|『|<|\{\{?|__?|%5[Bb]|\\u005[Bb]|&(?:amp;)?(?:#0*91;|#[xX]0*5[Bb];|lbrack;))[A-Za-z0-9_\-А-Яа-яЁё *`~.\\]{0,128}(?:%5?|&(?:a(?:m(?:p(?:;)?)?)?)?(?:#(?:0*9?3?|[xX]0*5?[Dd]?)?|r(?:b(?:r(?:a(?:c(?:k)?)?)?)?)?)?)?|%[0-9A-Fa-f]?|\\(?:u(?:0(?:0(?:5)?)?)?)?|&(?:a(?:m(?:p(?:;)?)?)?)?(?:#(?:0*9?1?|[xX]0*5?)?|l(?:b(?:r(?:a(?:c(?:k)?)?)?)?)?)?)$/);
|
|
162
170
|
|
|
163
171
|
if (m) {
|
|
164
172
|
const candidate = m[0];
|
|
165
|
-
const
|
|
166
|
-
if (
|
|
173
|
+
const isClosedUnderscoreOrUnicode = (candidate.length >= 4 && candidate.startsWith('__') && candidate.endsWith('__')) || /\\u005[Dd]/.test(candidate);
|
|
174
|
+
if (isClosedUnderscoreOrUnicode && this.replacer(candidate) !== candidate) {
|
|
167
175
|
const toEmit = this.pendingBuffer;
|
|
168
176
|
this.pendingBuffer = '';
|
|
169
177
|
return this.replacer(toEmit);
|
|
@@ -268,7 +276,9 @@ class ZTDSFastMCPGuard {
|
|
|
268
276
|
|
|
269
277
|
const tokenMap = this._sessionMaps.get(sessionId);
|
|
270
278
|
const entityMap = this._entityMaps.get(sessionId);
|
|
271
|
-
let sanitized = text
|
|
279
|
+
let sanitized = text
|
|
280
|
+
.replace(/[\u200B-\u200F\u202A-\u202E\u2060-\u206F\uFEFF\u00AD]/g, "")
|
|
281
|
+
.replace(/[\uFF01-\uFF5E]/g, (ch) => String.fromCharCode(ch.charCodeAt(0) - 0xFEE0));
|
|
272
282
|
|
|
273
283
|
for (const entityType of this.enabledEntities) {
|
|
274
284
|
const pattern = ZTDS_PATTERNS[entityType];
|
|
@@ -317,8 +327,9 @@ class ZTDSFastMCPGuard {
|
|
|
317
327
|
return text;
|
|
318
328
|
}
|
|
319
329
|
|
|
320
|
-
return text.replace(TOKEN_PATTERN, (match) => {
|
|
321
|
-
|
|
330
|
+
return text.replace(TOKEN_PATTERN, (match, entityType, tokenId) => {
|
|
331
|
+
const canonical = `[${entityType.toUpperCase()}_TOKEN_${tokenId}]`;
|
|
332
|
+
return tokenMap.get(canonical) || tokenMap.get(match) || match;
|
|
322
333
|
});
|
|
323
334
|
}
|
|
324
335
|
|
|
@@ -475,7 +486,7 @@ class ZTDSFastMCPGuard {
|
|
|
475
486
|
|
|
476
487
|
const ztdsMeta = {
|
|
477
488
|
sessionId,
|
|
478
|
-
standard: "IETF draft-sibiryakov-ztds-protocol-
|
|
489
|
+
standard: "IETF draft-sibiryakov-ztds-protocol-01",
|
|
479
490
|
zeroEgress: true,
|
|
480
491
|
};
|
|
481
492
|
|
package/index.cjs
CHANGED
|
@@ -179,7 +179,7 @@ function buildAuditTelemetry(currentTokenMap = {}) {
|
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
const ipHoldings = {
|
|
182
|
-
standard: 'IETF draft-sibiryakov-ztds-protocol-
|
|
182
|
+
standard: 'IETF draft-sibiryakov-ztds-protocol-01',
|
|
183
183
|
standardUrl: 'https://datatracker.ietf.org/doc/draft-sibiryakov-ztds-protocol/',
|
|
184
184
|
patent: "U.S. & Int'l Patents Pending (Paris Convention Priority)",
|
|
185
185
|
trademark: 'ZTDS™ Reg. #182655957 (ILPO Cl 9 & 42)'
|
|
@@ -189,27 +189,27 @@ function buildAuditTelemetry(currentTokenMap = {}) {
|
|
|
189
189
|
{
|
|
190
190
|
statute: 'EU AI Act (Art. 50)',
|
|
191
191
|
scope: 'AI Transparency & Data Minimization',
|
|
192
|
-
rule: '
|
|
192
|
+
rule: 'Sanitization runs locally by design; reduces the content sent to external AI models. Informational mapping, not a compliance determination.'
|
|
193
193
|
},
|
|
194
194
|
{
|
|
195
195
|
statute: 'GDPR (Art. 4, 25, 28, 32)',
|
|
196
196
|
scope: 'Pseudonymization & Processor Liability',
|
|
197
|
-
rule: '
|
|
197
|
+
rule: 'Client-side pseudonymization is a technical measure supporting Art. 25 and 32. It does not by itself remove controller or processor obligations.'
|
|
198
198
|
},
|
|
199
199
|
{
|
|
200
200
|
statute: 'HIPAA 45 CFR § 164.514(b)',
|
|
201
201
|
scope: 'Safe Harbor De-Identification',
|
|
202
|
-
rule: '
|
|
202
|
+
rule: 'Pattern-based masking of direct identifiers supports Safe Harbor de-identification workflows. Review output for residual identifiers before sharing.'
|
|
203
203
|
},
|
|
204
204
|
{
|
|
205
205
|
statute: 'IRC § 7216 & IRS Pub. 1075',
|
|
206
206
|
scope: 'Tax Return Information Confidentiality',
|
|
207
|
-
rule: '
|
|
207
|
+
rule: 'Masking identifiers before AI use can reduce exposure of tax return information. Informational mapping, not legal advice.'
|
|
208
208
|
},
|
|
209
209
|
{
|
|
210
210
|
statute: 'FRE 502',
|
|
211
211
|
scope: 'Attorney-Client Privilege Preservation',
|
|
212
|
-
rule: '
|
|
212
|
+
rule: 'In-memory redaction can reduce disclosure of privileged details in AI workflows. Privilege determinations remain a legal judgment.'
|
|
213
213
|
}
|
|
214
214
|
];
|
|
215
215
|
|
|
@@ -238,7 +238,7 @@ const buildCisoAuditTelemetry = buildAuditTelemetry;
|
|
|
238
238
|
function formatAuditReceipt(telemetry) {
|
|
239
239
|
if (!telemetry || telemetry.totalCount === 0) {
|
|
240
240
|
return "> 🛡️ **PrivacyScrubber Audit Receipt**: CLEAN (ZERO PII DETECTED)\n" +
|
|
241
|
-
"> * ⭐ **Star on GitHub:** [moxno/privacyscrubber-mcp](https://github.com/moxno/privacyscrubber-mcp) | **SDK & Enterprise:** [privacyscrubber.com/pricing](https://privacyscrubber.com/pricing)\n";
|
|
241
|
+
"> * ⭐ **Star on GitHub:** [moxno/privacyscrubber-mcp](https://github.com/moxno/privacyscrubber-mcp) | **7-Day B2B Trial:** `npx @privacyscrubber/sdk trial` | **SDK & Enterprise:** [privacyscrubber.com/pricing](https://privacyscrubber.com/pricing/?tier=sdk&utm_source=npm_sdk&utm_medium=audit_receipt)\n";
|
|
242
242
|
}
|
|
243
243
|
const entitiesList = Object.entries(telemetry.entities)
|
|
244
244
|
.map(([type, count]) => `${count} ${type}`)
|
|
@@ -250,7 +250,7 @@ function formatAuditReceipt(telemetry) {
|
|
|
250
250
|
`> * **Risk Level:** ${icon} ${telemetry.riskLevel}\n` +
|
|
251
251
|
`> * **Framework References (informational):** ${telemetry.frameworksList.join(', ')}\n` +
|
|
252
252
|
`> * **Tokens Masked:** ${telemetry.totalCount} (${entitiesList})\n` +
|
|
253
|
-
`> * ⭐ **Star on GitHub:** [moxno/privacyscrubber-mcp](https://github.com/moxno/privacyscrubber-mcp) | **SDK & Enterprise:** [privacyscrubber.com/pricing](https://privacyscrubber.com/pricing)\n`;
|
|
253
|
+
`> * ⭐ **Star on GitHub:** [moxno/privacyscrubber-mcp](https://github.com/moxno/privacyscrubber-mcp) | **7-Day B2B Trial:** \`npx @privacyscrubber/sdk trial\` | **SDK & Enterprise:** [privacyscrubber.com/pricing](https://privacyscrubber.com/pricing/?tier=sdk&utm_source=npm_sdk&utm_medium=audit_receipt)\n`;
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
let communityNoticeLogged = false;
|
|
@@ -259,7 +259,7 @@ function emitCommunityNoticeIfNeeded(isPro) {
|
|
|
259
259
|
if (communityNoticeLogged || isPro) return;
|
|
260
260
|
if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'production') {
|
|
261
261
|
communityNoticeLogged = true;
|
|
262
|
-
console.info('[@privacyscrubber/sdk] Running Community Edition (Core PII).\nTo unlock
|
|
262
|
+
console.info('[@privacyscrubber/sdk] Running Community Edition (Core PII).\nTo unlock 30 Enterprise Profiles & DevOps Secrets in production: https://privacyscrubber.com/pricing/?tier=sdk&utm_source=npm_sdk&utm_medium=prod_notice (7-Day Trial: npx @privacyscrubber/sdk trial | CISO Memo: npx @privacyscrubber/sdk memo)');
|
|
263
263
|
}
|
|
264
264
|
}
|
|
265
265
|
|
|
@@ -291,7 +291,8 @@ function emitDevSecretNoticeIfNeeded(isPro, tokenMap = {}, limit = 15000) {
|
|
|
291
291
|
process.stderr.write(
|
|
292
292
|
`\n[PrivacyScrubber] Intercepted ${count} API secret(s) before LLM egress: ${secretSample}\n` +
|
|
293
293
|
`[PrivacyScrubber] Protected in local RAM (0 byte network egress). Free Tier active (${limit.toLocaleString()} char cap).\n` +
|
|
294
|
-
`[PrivacyScrubber] Developer SDK ($299/mo flat) for unlimited backend nodes, CI/CD gates & ETL pipelines: https://privacyscrubber.com/pricing?tier=sdk&src=cli_leak_guard\n
|
|
294
|
+
`[PrivacyScrubber] Developer SDK ($299/mo flat) for unlimited backend nodes, CI/CD gates & ETL pipelines: https://privacyscrubber.com/pricing?tier=sdk&src=cli_leak_guard\n` +
|
|
295
|
+
`[PrivacyScrubber] Instant 7-Day Free B2B Trial: npx @privacyscrubber/sdk trial | CISO Approval Memo: npx @privacyscrubber/sdk memo\n\n`
|
|
295
296
|
);
|
|
296
297
|
}
|
|
297
298
|
}
|
|
@@ -478,6 +479,18 @@ function sanitize(text, options = {}) {
|
|
|
478
479
|
|
|
479
480
|
const licenseCheck = validateLicense(licenseKey);
|
|
480
481
|
const rawTier = licenseCheck.valid ? (licenseCheck.tier || 'PRO') : 'FREE';
|
|
482
|
+
const isExpiredKey = !licenseCheck.valid && typeof licenseCheck.error === 'string' && /expired/i.test(licenseCheck.error);
|
|
483
|
+
const isExpiringSoon = licenseCheck.valid && typeof licenseCheck.expires === 'number' && (licenseCheck.expires - Date.now() <= 48 * 3600 * 1000);
|
|
484
|
+
|
|
485
|
+
if (isExpiringSoon && typeof process !== 'undefined' && process.stderr && !globalThis.__psSdkPreExpiryLogged) {
|
|
486
|
+
globalThis.__psSdkPreExpiryLogged = true;
|
|
487
|
+
const hrs = Math.max(0, Math.round((licenseCheck.expires - Date.now()) / 3600000));
|
|
488
|
+
process.stderr.write(
|
|
489
|
+
`\n[@privacyscrubber/sdk] ⏳ 7-Day Evaluation Key expires in ~${hrs}h. Prevent pipeline interruption:\n` +
|
|
490
|
+
` - Commercial SDK ($299/mo flat): https://privacyscrubber.com/pricing/?tier=sdk&utm_source=npm_sdk&utm_medium=pilot_expiring_48h\n` +
|
|
491
|
+
` - CISO Approval Memo: npx @privacyscrubber/sdk memo\n\n`
|
|
492
|
+
);
|
|
493
|
+
}
|
|
481
494
|
|
|
482
495
|
// Anti-Cannibalization Barrier (SSOT):
|
|
483
496
|
// PRO ($15/mo) and TEAMS ($99/mo) licenses are strictly human-seat licenses for Web App & Chrome Extension.
|
|
@@ -511,7 +524,8 @@ function sanitize(text, options = {}) {
|
|
|
511
524
|
|
|
512
525
|
if (!isPro && text.length > limit) {
|
|
513
526
|
if (shouldThrow) {
|
|
514
|
-
const
|
|
527
|
+
const expiredPrefix = isExpiredKey ? `[PILOT/TRIAL EXPIRED: ${licenseCheck.error}] ` : '';
|
|
528
|
+
const errorMsg = `[PrivacyScrubber] ${expiredPrefix}Free Tier character limit exceeded (${limit.toLocaleString()} chars for profile '${profile}', received ${text.length.toLocaleString()} chars). Upgrade to Developer SDK ($299/mo) for unlimited throughput: https://privacyscrubber.com/pricing?tier=sdk&src=${isExpiredKey ? 'pilot_expired' : 'dev_limit'} (Run 'npx @privacyscrubber/sdk memo' for CISO approval memo)`;
|
|
515
529
|
throw new PrivacyScrubberLicenseError(errorMsg, {
|
|
516
530
|
tier: 'FREE',
|
|
517
531
|
currentLength: text.length,
|
|
@@ -1917,7 +1931,7 @@ function formatCefEvent(eventData = {}) {
|
|
|
1917
1931
|
const {
|
|
1918
1932
|
vendor = 'PrivacyScrubber',
|
|
1919
1933
|
product = 'ZTDS',
|
|
1920
|
-
version = '2.4.
|
|
1934
|
+
version = '2.4.2',
|
|
1921
1935
|
eventClassId = 'PII_REDACTION',
|
|
1922
1936
|
eventName = 'Zero-Trust PII Sanitization Executed',
|
|
1923
1937
|
riskLevel = 'LOW EXPOSURE',
|
|
@@ -1986,7 +2000,7 @@ function formatSyslogEvent(eventData = {}) {
|
|
|
1986
2000
|
function formatJsonlEvent(eventData = {}) {
|
|
1987
2001
|
const {
|
|
1988
2002
|
timestamp = new Date().toISOString(),
|
|
1989
|
-
version = '2.4.
|
|
2003
|
+
version = '2.4.2',
|
|
1990
2004
|
protocol = 'ZTDS',
|
|
1991
2005
|
event = 'sanitization',
|
|
1992
2006
|
riskLevel = 'LOW EXPOSURE',
|
|
@@ -2717,6 +2731,10 @@ const _defaultExport = {
|
|
|
2717
2731
|
buildCisoAuditTelemetry,
|
|
2718
2732
|
formatAuditReceipt,
|
|
2719
2733
|
generateAuditReport,
|
|
2734
|
+
PSR2_VERSION,
|
|
2735
|
+
PSR2_STATEMENTS,
|
|
2736
|
+
buildPsr2Receipt,
|
|
2737
|
+
psr2Lines,
|
|
2720
2738
|
formatCefEvent,
|
|
2721
2739
|
formatSyslogEvent,
|
|
2722
2740
|
formatJsonlEvent,
|
package/index.js
CHANGED
|
@@ -181,7 +181,7 @@ export function buildAuditTelemetry(currentTokenMap = {}) {
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
const ipHoldings = {
|
|
184
|
-
standard: 'IETF draft-sibiryakov-ztds-protocol-
|
|
184
|
+
standard: 'IETF draft-sibiryakov-ztds-protocol-01',
|
|
185
185
|
standardUrl: 'https://datatracker.ietf.org/doc/draft-sibiryakov-ztds-protocol/',
|
|
186
186
|
patent: "U.S. & Int'l Patents Pending (Paris Convention Priority)",
|
|
187
187
|
trademark: 'ZTDS™ Reg. #182655957 (ILPO Cl 9 & 42)'
|
|
@@ -191,27 +191,27 @@ export function buildAuditTelemetry(currentTokenMap = {}) {
|
|
|
191
191
|
{
|
|
192
192
|
statute: 'EU AI Act (Art. 50)',
|
|
193
193
|
scope: 'AI Transparency & Data Minimization',
|
|
194
|
-
rule: '
|
|
194
|
+
rule: 'Sanitization runs locally by design; reduces the content sent to external AI models. Informational mapping, not a compliance determination.'
|
|
195
195
|
},
|
|
196
196
|
{
|
|
197
197
|
statute: 'GDPR (Art. 4, 25, 28, 32)',
|
|
198
198
|
scope: 'Pseudonymization & Processor Liability',
|
|
199
|
-
rule: '
|
|
199
|
+
rule: 'Client-side pseudonymization is a technical measure supporting Art. 25 and 32. It does not by itself remove controller or processor obligations.'
|
|
200
200
|
},
|
|
201
201
|
{
|
|
202
202
|
statute: 'HIPAA 45 CFR § 164.514(b)',
|
|
203
203
|
scope: 'Safe Harbor De-Identification',
|
|
204
|
-
rule: '
|
|
204
|
+
rule: 'Pattern-based masking of direct identifiers supports Safe Harbor de-identification workflows. Review output for residual identifiers before sharing.'
|
|
205
205
|
},
|
|
206
206
|
{
|
|
207
207
|
statute: 'IRC § 7216 & IRS Pub. 1075',
|
|
208
208
|
scope: 'Tax Return Information Confidentiality',
|
|
209
|
-
rule: '
|
|
209
|
+
rule: 'Masking identifiers before AI use can reduce exposure of tax return information. Informational mapping, not legal advice.'
|
|
210
210
|
},
|
|
211
211
|
{
|
|
212
212
|
statute: 'FRE 502',
|
|
213
213
|
scope: 'Attorney-Client Privilege Preservation',
|
|
214
|
-
rule: '
|
|
214
|
+
rule: 'In-memory redaction can reduce disclosure of privileged details in AI workflows. Privilege determinations remain a legal judgment.'
|
|
215
215
|
}
|
|
216
216
|
];
|
|
217
217
|
|
|
@@ -240,7 +240,7 @@ export const buildCisoAuditTelemetry = buildAuditTelemetry;
|
|
|
240
240
|
export function formatAuditReceipt(telemetry) {
|
|
241
241
|
if (!telemetry || telemetry.totalCount === 0) {
|
|
242
242
|
return "> 🛡️ **PrivacyScrubber Audit Receipt**: CLEAN (ZERO PII DETECTED)\n" +
|
|
243
|
-
"> * ⭐ **Star on GitHub:** [moxno/privacyscrubber-mcp](https://github.com/moxno/privacyscrubber-mcp) | **SDK & Enterprise:** [privacyscrubber.com/pricing](https://privacyscrubber.com/pricing)\n";
|
|
243
|
+
"> * ⭐ **Star on GitHub:** [moxno/privacyscrubber-mcp](https://github.com/moxno/privacyscrubber-mcp) | **7-Day B2B Trial:** `npx @privacyscrubber/sdk trial` | **SDK & Enterprise:** [privacyscrubber.com/pricing](https://privacyscrubber.com/pricing/?tier=sdk&utm_source=npm_sdk&utm_medium=audit_receipt)\n";
|
|
244
244
|
}
|
|
245
245
|
const entitiesList = Object.entries(telemetry.entities)
|
|
246
246
|
.map(([type, count]) => `${count} ${type}`)
|
|
@@ -252,7 +252,7 @@ export function formatAuditReceipt(telemetry) {
|
|
|
252
252
|
`> * **Risk Level:** ${icon} ${telemetry.riskLevel}\n` +
|
|
253
253
|
`> * **Framework References (informational):** ${telemetry.frameworksList.join(', ')}\n` +
|
|
254
254
|
`> * **Tokens Masked:** ${telemetry.totalCount} (${entitiesList})\n` +
|
|
255
|
-
`> * ⭐ **Star on GitHub:** [moxno/privacyscrubber-mcp](https://github.com/moxno/privacyscrubber-mcp) | **SDK & Enterprise:** [privacyscrubber.com/pricing](https://privacyscrubber.com/pricing)\n`;
|
|
255
|
+
`> * ⭐ **Star on GitHub:** [moxno/privacyscrubber-mcp](https://github.com/moxno/privacyscrubber-mcp) | **7-Day B2B Trial:** \`npx @privacyscrubber/sdk trial\` | **SDK & Enterprise:** [privacyscrubber.com/pricing](https://privacyscrubber.com/pricing/?tier=sdk&utm_source=npm_sdk&utm_medium=audit_receipt)\n`;
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
let communityNoticeLogged = false;
|
|
@@ -261,7 +261,7 @@ function emitCommunityNoticeIfNeeded(isPro) {
|
|
|
261
261
|
if (communityNoticeLogged || isPro) return;
|
|
262
262
|
if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'production') {
|
|
263
263
|
communityNoticeLogged = true;
|
|
264
|
-
console.info('[@privacyscrubber/sdk] Running Community Edition (Core PII).\nTo unlock
|
|
264
|
+
console.info('[@privacyscrubber/sdk] Running Community Edition (Core PII).\nTo unlock 30 Enterprise Profiles & DevOps Secrets in production: https://privacyscrubber.com/pricing/?tier=sdk&utm_source=npm_sdk&utm_medium=prod_notice (7-Day Trial: npx @privacyscrubber/sdk trial | CISO Memo: npx @privacyscrubber/sdk memo)');
|
|
265
265
|
}
|
|
266
266
|
}
|
|
267
267
|
|
|
@@ -293,7 +293,8 @@ function emitDevSecretNoticeIfNeeded(isPro, tokenMap = {}, limit = 15000) {
|
|
|
293
293
|
process.stderr.write(
|
|
294
294
|
`\n[PrivacyScrubber] Intercepted ${count} API secret(s) before LLM egress: ${secretSample}\n` +
|
|
295
295
|
`[PrivacyScrubber] Protected in local RAM (0 byte network egress). Free Tier active (${limit.toLocaleString()} char cap).\n` +
|
|
296
|
-
`[PrivacyScrubber] Developer SDK ($299/mo flat) for unlimited backend nodes, CI/CD gates & ETL pipelines: https://privacyscrubber.com/pricing?tier=sdk&src=cli_leak_guard\n
|
|
296
|
+
`[PrivacyScrubber] Developer SDK ($299/mo flat) for unlimited backend nodes, CI/CD gates & ETL pipelines: https://privacyscrubber.com/pricing?tier=sdk&src=cli_leak_guard\n` +
|
|
297
|
+
`[PrivacyScrubber] Instant 7-Day Free B2B Trial: npx @privacyscrubber/sdk trial | CISO Approval Memo: npx @privacyscrubber/sdk memo\n\n`
|
|
297
298
|
);
|
|
298
299
|
}
|
|
299
300
|
}
|
|
@@ -480,6 +481,18 @@ export function sanitize(text, options = {}) {
|
|
|
480
481
|
|
|
481
482
|
const licenseCheck = validateLicense(licenseKey);
|
|
482
483
|
const rawTier = licenseCheck.valid ? (licenseCheck.tier || 'PRO') : 'FREE';
|
|
484
|
+
const isExpiredKey = !licenseCheck.valid && typeof licenseCheck.error === 'string' && /expired/i.test(licenseCheck.error);
|
|
485
|
+
const isExpiringSoon = licenseCheck.valid && typeof licenseCheck.expires === 'number' && (licenseCheck.expires - Date.now() <= 48 * 3600 * 1000);
|
|
486
|
+
|
|
487
|
+
if (isExpiringSoon && typeof process !== 'undefined' && process.stderr && !globalThis.__psSdkPreExpiryLogged) {
|
|
488
|
+
globalThis.__psSdkPreExpiryLogged = true;
|
|
489
|
+
const hrs = Math.max(0, Math.round((licenseCheck.expires - Date.now()) / 3600000));
|
|
490
|
+
process.stderr.write(
|
|
491
|
+
`\n[@privacyscrubber/sdk] ⏳ 7-Day Evaluation Key expires in ~${hrs}h. Prevent pipeline interruption:\n` +
|
|
492
|
+
` - Commercial SDK ($299/mo flat): https://privacyscrubber.com/pricing/?tier=sdk&utm_source=npm_sdk&utm_medium=pilot_expiring_48h\n` +
|
|
493
|
+
` - CISO Approval Memo: npx @privacyscrubber/sdk memo\n\n`
|
|
494
|
+
);
|
|
495
|
+
}
|
|
483
496
|
|
|
484
497
|
// Anti-Cannibalization Barrier (SSOT):
|
|
485
498
|
// PRO ($15/mo) and TEAMS ($99/mo) licenses are strictly human-seat licenses for Web App & Chrome Extension.
|
|
@@ -513,7 +526,8 @@ export function sanitize(text, options = {}) {
|
|
|
513
526
|
|
|
514
527
|
if (!isPro && text.length > limit) {
|
|
515
528
|
if (shouldThrow) {
|
|
516
|
-
const
|
|
529
|
+
const expiredPrefix = isExpiredKey ? `[PILOT/TRIAL EXPIRED: ${licenseCheck.error}] ` : '';
|
|
530
|
+
const errorMsg = `[PrivacyScrubber] ${expiredPrefix}Free Tier character limit exceeded (${limit.toLocaleString()} chars for profile '${profile}', received ${text.length.toLocaleString()} chars). Upgrade to Developer SDK ($299/mo) for unlimited throughput: https://privacyscrubber.com/pricing?tier=sdk&src=${isExpiredKey ? 'pilot_expired' : 'dev_limit'} (Run 'npx @privacyscrubber/sdk memo' for CISO approval memo)`;
|
|
517
531
|
throw new PrivacyScrubberLicenseError(errorMsg, {
|
|
518
532
|
tier: 'FREE',
|
|
519
533
|
currentLength: text.length,
|
|
@@ -1919,7 +1933,7 @@ export function formatCefEvent(eventData = {}) {
|
|
|
1919
1933
|
const {
|
|
1920
1934
|
vendor = 'PrivacyScrubber',
|
|
1921
1935
|
product = 'ZTDS',
|
|
1922
|
-
version = '2.4.
|
|
1936
|
+
version = '2.4.2',
|
|
1923
1937
|
eventClassId = 'PII_REDACTION',
|
|
1924
1938
|
eventName = 'Zero-Trust PII Sanitization Executed',
|
|
1925
1939
|
riskLevel = 'LOW EXPOSURE',
|
|
@@ -1988,7 +2002,7 @@ export function formatSyslogEvent(eventData = {}) {
|
|
|
1988
2002
|
export function formatJsonlEvent(eventData = {}) {
|
|
1989
2003
|
const {
|
|
1990
2004
|
timestamp = new Date().toISOString(),
|
|
1991
|
-
version = '2.4.
|
|
2005
|
+
version = '2.4.2',
|
|
1992
2006
|
protocol = 'ZTDS',
|
|
1993
2007
|
event = 'sanitization',
|
|
1994
2008
|
riskLevel = 'LOW EXPOSURE',
|
|
@@ -2152,8 +2166,8 @@ export function createAuditLogger(options = {}) {
|
|
|
2152
2166
|
}
|
|
2153
2167
|
|
|
2154
2168
|
// ── PSR-2 Receipt Integrity (real SHA-256; parity with src/modules/receipt-integrity.js) ──
|
|
2155
|
-
const PSR2_VERSION = 'PSR-2';
|
|
2156
|
-
const PSR2_STATEMENTS = {
|
|
2169
|
+
export const PSR2_VERSION = 'PSR-2';
|
|
2170
|
+
export const PSR2_STATEMENTS = {
|
|
2157
2171
|
measured: [
|
|
2158
2172
|
'Token Manifest SHA-256: digest of the sorted token labels held in this session (original values are never hashed or exported).',
|
|
2159
2173
|
'Entity counts per type.',
|
|
@@ -2173,7 +2187,7 @@ const PSR2_STATEMENTS = {
|
|
|
2173
2187
|
function psr2Sha256(input) {
|
|
2174
2188
|
return require('crypto').createHash('sha256').update(String(input == null ? '' : input), 'utf8').digest('hex');
|
|
2175
2189
|
}
|
|
2176
|
-
function buildPsr2Receipt(tokenMap, timestamp = new Date().toISOString()) {
|
|
2190
|
+
export function buildPsr2Receipt(tokenMap, timestamp = new Date().toISOString()) {
|
|
2177
2191
|
const labels = (tokenMap && typeof tokenMap === 'object' ? Object.keys(tokenMap) : []).slice().sort();
|
|
2178
2192
|
const counts = {};
|
|
2179
2193
|
for (const t of labels) {
|
|
@@ -2186,7 +2200,7 @@ function buildPsr2Receipt(tokenMap, timestamp = new Date().toISOString()) {
|
|
|
2186
2200
|
const canonical = [PSR2_VERSION, timestamp, 'none', manifestSha256, entityCounts, 'na'].join('|');
|
|
2187
2201
|
return { version: PSR2_VERSION, timestamp, outputSha256: null, manifestSha256, entityCounts, tokensTotal: labels.length, tokensPresentInOutput: null, canonical, receiptSha256: psr2Sha256(canonical) };
|
|
2188
2202
|
}
|
|
2189
|
-
function psr2Lines(r) {
|
|
2203
|
+
export function psr2Lines(r) {
|
|
2190
2204
|
return [
|
|
2191
2205
|
`Receipt Version: ${r.version}`,
|
|
2192
2206
|
`Timestamp (UTC): ${r.timestamp}`,
|
|
@@ -2724,6 +2738,10 @@ export default {
|
|
|
2724
2738
|
buildCisoAuditTelemetry,
|
|
2725
2739
|
formatAuditReceipt,
|
|
2726
2740
|
generateAuditReport,
|
|
2741
|
+
PSR2_VERSION,
|
|
2742
|
+
PSR2_STATEMENTS,
|
|
2743
|
+
buildPsr2Receipt,
|
|
2744
|
+
psr2Lines,
|
|
2727
2745
|
formatCefEvent,
|
|
2728
2746
|
formatSyslogEvent,
|
|
2729
2747
|
formatJsonlEvent,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@privacyscrubber/sdk",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.2",
|
|
4
4
|
"description": "In-process consumer privacy defense and pre-emptive PII interception SDK for Node.js, Next.js, and AI pipelines. Zero data loss, zero third parties, sub-millisecond in-memory execution.",
|
|
5
5
|
"main": "index.cjs",
|
|
6
6
|
"module": "index.js",
|
|
@@ -101,6 +101,10 @@
|
|
|
101
101
|
"type": "git",
|
|
102
102
|
"url": "git+https://github.com/moxno/privacyscrubber-mcp.git"
|
|
103
103
|
},
|
|
104
|
+
"funding": {
|
|
105
|
+
"type": "github",
|
|
106
|
+
"url": "https://github.com/moxno/privacyscrubber-mcp"
|
|
107
|
+
},
|
|
104
108
|
"engines": {
|
|
105
109
|
"node": ">=18.0.0"
|
|
106
110
|
}
|