@privacyscrubber/sdk 2.4.0 → 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/benchmark.js +33 -25
- package/browser.js +274 -37
- package/cli.js +1 -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 +12 -8
- package/index.js +16 -12
- package/package.json +1 -1
- package/ps-license-manager.js +1 -1
- package/ps-pii-engine.cjs +1 -1
- package/scrubber-core.cjs +1 -1
- 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
package/benchmark.js
CHANGED
|
@@ -114,17 +114,8 @@ function benchmarkWorkload(label, targetBytes, warmupCount, measurementCount, pr
|
|
|
114
114
|
const rawPayload = generateRealisticWorkload(targetBytes);
|
|
115
115
|
const actualBytes = Buffer.byteLength(rawPayload, 'utf8');
|
|
116
116
|
|
|
117
|
-
//
|
|
118
|
-
const
|
|
119
|
-
const core = "PROD9876KEY5";
|
|
120
|
-
const tier = "SDK";
|
|
121
|
-
const inputStr = core + salt + tier;
|
|
122
|
-
let sum = 0;
|
|
123
|
-
for (let j = 0; j < inputStr.length; j++) {
|
|
124
|
-
sum += inputStr.charCodeAt(j) * (j + 1);
|
|
125
|
-
}
|
|
126
|
-
const checksum = (sum % 9999).toString().padStart(4, '0');
|
|
127
|
-
const licenseKey = `PS-${tier}-${core}-${checksum}`;
|
|
117
|
+
// Optional commercial evaluation license key from environment (defaults to Community tier)
|
|
118
|
+
const licenseKey = process.env.PRIVACYSCRUBBER_KEY || undefined;
|
|
128
119
|
|
|
129
120
|
// Warm-up phase (V8 TurboFan JIT optimization)
|
|
130
121
|
let warmupResult;
|
|
@@ -198,7 +189,15 @@ export function runBenchmarks(options = {}) {
|
|
|
198
189
|
const profile = options.profile || REQUESTED_PROFILE;
|
|
199
190
|
const itersOverride = options.iterations || ITERATIONS_OVERRIDE;
|
|
200
191
|
|
|
192
|
+
const hasCommercialKey = Boolean(process.env.PRIVACYSCRUBBER_KEY);
|
|
201
193
|
const suites = [
|
|
194
|
+
{
|
|
195
|
+
label: '1 KB (Short Agentic Tool Call / Chat Turn)',
|
|
196
|
+
bytes: 1 * 1024,
|
|
197
|
+
warmup: 50,
|
|
198
|
+
iterations: itersOverride > 0 ? itersOverride : 500,
|
|
199
|
+
targetSlaMs: 0.8
|
|
200
|
+
},
|
|
202
201
|
{
|
|
203
202
|
label: '2 KB (Interactive LLM Chat Prompt)',
|
|
204
203
|
bytes: 2 * 1024,
|
|
@@ -206,6 +205,13 @@ export function runBenchmarks(options = {}) {
|
|
|
206
205
|
iterations: itersOverride > 0 ? itersOverride : 500,
|
|
207
206
|
targetSlaMs: 1.0 // Sub-millisecond ZTDS SLA invariant
|
|
208
207
|
},
|
|
208
|
+
{
|
|
209
|
+
label: '4 KB (Multi-Entity Solid Pod / Clinical Note)',
|
|
210
|
+
bytes: 4 * 1024,
|
|
211
|
+
warmup: 30,
|
|
212
|
+
iterations: itersOverride > 0 ? itersOverride : 300,
|
|
213
|
+
targetSlaMs: 2.5
|
|
214
|
+
},
|
|
209
215
|
{
|
|
210
216
|
label: '10 KB (Enterprise Support Ticket / Incident)',
|
|
211
217
|
bytes: 10 * 1024,
|
|
@@ -213,20 +219,22 @@ export function runBenchmarks(options = {}) {
|
|
|
213
219
|
iterations: itersOverride > 0 ? itersOverride : 250,
|
|
214
220
|
targetSlaMs: 5.0
|
|
215
221
|
},
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
222
|
+
...(hasCommercialKey ? [
|
|
223
|
+
{
|
|
224
|
+
label: '50 KB (RAG Context Chunk / Knowledge Retrieval)',
|
|
225
|
+
bytes: 50 * 1024,
|
|
226
|
+
warmup: 15,
|
|
227
|
+
iterations: itersOverride > 0 ? itersOverride : 100,
|
|
228
|
+
targetSlaMs: 25.0
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
label: '250 KB (Batch Processing / Multi-Page Document)',
|
|
232
|
+
bytes: 250 * 1024,
|
|
233
|
+
warmup: 5,
|
|
234
|
+
iterations: itersOverride > 0 ? itersOverride : 30,
|
|
235
|
+
targetSlaMs: 150.0
|
|
236
|
+
}
|
|
237
|
+
] : [])
|
|
230
238
|
];
|
|
231
239
|
|
|
232
240
|
const results = [];
|
package/browser.js
CHANGED
|
@@ -28,16 +28,33 @@ if (scrubberPkg && typeof scrubberPkg.init === 'function') {
|
|
|
28
28
|
* Built-in patterns for DevOps & Cloud Secrets detection.
|
|
29
29
|
*/
|
|
30
30
|
export const DEVOPS_SECRETS_DETECTOR = [
|
|
31
|
-
{ name: 'AWS Credentials', regex: /\b(?:AKIA|ASIA|AGPA|AIDA|AROA|AIPA)[A-Z0-9]{
|
|
31
|
+
{ name: 'AWS Credentials', regex: /\b(?:AKIA|ASIA|AGPA|AIDA|AROA|AIPA)[A-Z0-9]{12,28}\b/g, label: 'AWS_KEY' },
|
|
32
|
+
{ name: 'Google Cloud / Gemini API Key', regex: /\bAIza[0-9A-Za-z_-]{35}\b/g, label: 'API_TOKEN' },
|
|
33
|
+
{ name: 'Anthropic API Key', regex: /\bsk-ant-(?:api\d{2}|admin\d{2}|sid\d{2})-[a-zA-Z0-9_-]{16,128}\b/g, label: 'API_TOKEN' },
|
|
34
|
+
{ name: 'HuggingFace / Perplexity API Key', regex: /\b(?:hf_[a-zA-Z0-9]{20,64}|pplx-[a-zA-Z0-9]{24,64})\b/g, label: 'API_TOKEN' },
|
|
35
|
+
{ name: 'SendGrid API Key', regex: /\bSG\.[a-zA-Z0-9_-]{16,32}\.[a-zA-Z0-9_-]{32,64}\b/g, label: 'API_TOKEN' },
|
|
36
|
+
{ name: 'Postman API Key', regex: /\bPMAK-[a-fA-F0-9]{24}-[a-fA-F0-9]{34}\b/g, label: 'API_TOKEN' },
|
|
37
|
+
{ name: 'GitHub Fine-Grained PAT', regex: /\bgithub_pat_[a-zA-Z0-9]{20,30}_[a-zA-Z0-9]{40,80}\b/g, label: 'API_TOKEN' },
|
|
38
|
+
{ name: 'Slack Enterprise / Refresh Token', regex: /\bxoxe\.xox[baprs]-\d-[a-zA-Z0-9-]{10,}\b|\bxox[eapbrs]-[a-zA-Z0-9-]{10,}\b/g, label: 'API_TOKEN' },
|
|
39
|
+
{ name: 'Telegram Bot API Token', regex: /\b\d{8,10}:AA[A-Za-z0-9_-]{33}\b/g, label: 'API_TOKEN' },
|
|
40
|
+
{ name: 'Age Encryption Secret Key', regex: /\bAGE-SECRET-KEY-1[A-Z0-9]{58}\b/g, label: 'SECRET' },
|
|
41
|
+
{ name: 'Modern Cloud & AI Platform Key', regex: /\b(?:sbp_[a-fA-F0-9]{40}|dop_v1_[a-fA-F0-9]{64}|doo_v1_[a-fA-F0-9]{64}|dp\.(?:st|pt|ct|sa)\.[a-zA-Z0-9_-]{20,}|lin_api_[a-zA-Z0-9]{32,64}|ntn_[a-zA-Z0-9]{32,64}|secret_[a-zA-Z0-9]{32,64}|gsk_[a-zA-Z0-9]{32,64}|r8_[a-zA-Z0-9]{24,64}|tvly-[a-zA-Z0-9_-]{20,64}|fc-[a-fA-F0-9]{32}|dapi[a-fA-F0-9]{32}(?:-\d)?|re_[a-zA-Z0-9_]{20,64}|napi_[a-zA-Z0-9]{32,64}|[a-zA-Z0-9]{14}\.atlasv1\.[a-zA-Z0-9]{50,80}|pypi-[A-Za-z0-9_-]{16,128}|whsec_[A-Za-z0-9+/=_-]{24,128}|shpat_[a-fA-F0-9]{32}|shpss_[a-fA-F0-9]{32}|shpca_[a-fA-F0-9]{32}|shppa_[a-fA-F0-9]{32}|hvs\.[A-Za-z0-9_-]{24,128}|hvb\.[A-Za-z0-9_-]{24,128}|glsa_[A-Za-z0-9]{32}_[A-Fa-f0-9]{8}|glc_[A-Za-z0-9+/=_-]{20,128}|pscale_tkn_[A-Za-z0-9_.-]{20,64}|pscale_pw_[A-Za-z0-9_.-]{20,64}|pat[A-Za-z0-9]{14}\.[a-fA-F0-9]{64}|pat-(?:na1|eu1)-[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}|sq0atp-[A-Za-z0-9_-]{22,64}|sq0csp-[A-Za-z0-9_-]{32,64}|NRAK-[A-Z0-9]{27}|v1\.0-[a-fA-F0-9]{24}-[a-fA-F0-9]{16,160}|ATATT3xFfGF0[A-Za-z0-9+/=_-]{24,128}|tskey-(?:auth|api|client)-[A-Za-z0-9_-]{20,96}|sntry[su]_[A-Za-z0-9+/=_-]{32,160}|(?:pk|sk)\.eyJ1[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{16,}|key-[a-f0-9]{32}|[a-f0-9]{32}-us\d{1,2})\b/g, label: 'API_TOKEN' },
|
|
42
|
+
{ name: 'Twilio Account SID / API Key', regex: /\b(?:AC|SK)[a-fA-F0-9]{32}\b/g, label: 'API_TOKEN' },
|
|
43
|
+
{ name: 'Bcrypt / Argon2 Password Hash', regex: /(?<![A-Za-z0-9])(?:\$2[aby]\$\d{2}\$[./A-Za-z0-9=]{20,60}|\$argon2(?:id|i|d)\$[^\s"']{15,120})/g, label: 'SECRET' },
|
|
32
44
|
{ name: 'JSON Web Token (JWT)', regex: /\beyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\b/g, label: 'JWT_TOKEN' },
|
|
33
|
-
{ name: 'API Token/Key (GitHub/Slack/NPM)', regex: /\b(?:ghp|gho|ghu|ghs|ghr|glpat|npm|xox[baprs])[-_][A-Za-z0-9_]{10,}\b/g, label: 'API_TOKEN' },
|
|
34
|
-
{ name: 'Stripe API Key', regex: /\b(?:[rs]k)_(?:test|live)_[a-zA-Z0-9]{
|
|
45
|
+
{ name: 'API Token/Key (GitHub/Slack/NPM)', regex: /\b(?:ghp|gho|ghu|ghs|ghr|glpat|npm|xox[baprs])[-_][A-Za-z0-9_-]{10,}\b/g, label: 'API_TOKEN' },
|
|
46
|
+
{ name: 'Stripe API Key', regex: /\b(?:[rs]k)_(?:test|live)_[a-zA-Z0-9]{10,}\b/g, label: 'STRIPE_KEY' },
|
|
35
47
|
{ name: 'OpenAI Project API Key', regex: /\b(?:sk|pk)-(?:proj-)?[a-zA-Z0-9_-]{16,}\b/gi, label: 'OPENAI_KEY' },
|
|
48
|
+
{ name: 'Slack / Discord Incoming Webhook URL', regex: /\bhttps:\/\/(?:hooks\.slack\.com\/services\/T[A-Z0-9_]+\/B[A-Z0-9_]+\/[A-Za-z0-9_-]+|(?:ptb\.|canary\.)?discord(?:app)?\.com\/api\/webhooks\/\d{17,20}\/[A-Za-z0-9_-]{60,70})\b/gi, label: 'SECRET' },
|
|
49
|
+
{ name: 'Azure Connection String / SAS Token', regex: /(?:DefaultEndpointsProtocol=https?;AccountName=[^;\s"']+;AccountKey=[A-Za-z0-9+/=]{20,}(?:;EndpointSuffix=[^;\s"']+)?|Endpoint=sb:\/\/[^;\s"']+;SharedAccessKeyName=[^;\s"']+;SharedAccessKey=[A-Za-z0-9+/=]{16,}|\bSharedAccessSignature=sr=[^\s"']+&sig=[A-Za-z0-9%/+=]{16,}|\bAccountKey=[A-Za-z0-9+/=]{40,88}==)/gi, label: 'SECRET' },
|
|
36
50
|
{ name: 'Database Connection URI', regex: /\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqp|mssql):\/\/[^\s"']+/gi, label: 'DB_URI' },
|
|
37
|
-
{ name: 'Embedded Credentials URI', regex: /\b(?:https?|s?ftp):\/\/[a-zA-Z0-9_.%+-]+:[^\s/@:"']+@[a-zA-Z0-9_.-]+(?::\d+)?(?:\/[^\s"']*)?/gi, label: 'SECRET' },
|
|
51
|
+
{ name: 'Embedded Credentials URI', regex: /\b(?:https?|hxxps?|s?ftp|fxp|wss?):\/\/[a-zA-Z0-9_.%+-]+:[^\s/@:"']+@[a-zA-Z0-9_.-]+(?:\[\.\][a-zA-Z0-9_.-]+|\(\.\)[a-zA-Z0-9_.-]+)*(?::\d+)?(?:\/[^\s"']*)?/gi, label: 'SECRET' },
|
|
38
52
|
{ name: 'URL Query Parameter Secret', regex: /(?<=[?&](?:api_key|apikey|access_token|client_secret|auth_token|token|secret|password|private_key)=)[^\s"'\x26#)>]{8,}/gi, label: 'SECRET' },
|
|
53
|
+
{ name: 'CLI Credential Flag / Curl User Auth', regex: /(?:\bcurl\b[^\r\n]*?\s(?:-u|--user)\s+["']?[A-Za-z0-9_.+-]+:[^\s"']+|\b(?:--password|--api-key|--apikey|--access-token|--auth-token|--client-secret|--secret-key|--private-key)(?:=|\s+)["']?[^\s"']{6,}["']?)/gi, label: 'SECRET' },
|
|
54
|
+
{ name: 'HTTP API Key Header', regex: /\b(?:X-API-Key|X-Amz-Security-Token|X-Functions-Key|X-Shopify-Access-Token|X-Auth-Token|Api-Key)\s*:\s*["']?[A-Za-z0-9_.\-~+/=]{12,}["']?/gi, label: 'SECRET' },
|
|
55
|
+
{ name: 'Docker Registry Auth / K8s Secret Base64', regex: /(?<=["']?(?:auth|identitytoken|registrytoken|\.dockerconfigjson)["']?\s*:\s*["'])[A-Za-z0-9+/=]{16,}(?=["'])/gi, label: 'SECRET' },
|
|
39
56
|
{ name: 'Bearer / Auth Header Token', regex: /(?<=\b(?:Bearer|Token)\s+)[a-zA-Z0-9_\-\.~+/=]{16,}\b/gi, label: 'SECRET' },
|
|
40
|
-
{ name: 'Database/API Secret', regex: /\b(DB|POSTGRES|REDIS|MYSQL|AWS|SECRET|PASSWORD|TOKEN|API|KEY)[A-Z0-9_]*\s*[:=]\s*[^ \t\r\n"']{8,}
|
|
57
|
+
{ name: 'Database/API Secret', regex: /\b(DB|POSTGRES|REDIS|MYSQL|AWS|SECRET|PASSWORD|TOKEN|API|KEY)[A-Z0-9_]*\s*[:=]\s*[^ \t\r\n"']{8,}/gi, label: 'SECRET' },
|
|
41
58
|
{ name: 'Private Cryptographic Key (PEM Block)', regex: /-----BEGIN (?:RSA |EC |PGP |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY(?: BLOCK)?-----[\s\S]{10,8192}?-----END (?:RSA |EC |PGP |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY(?: BLOCK)?-----/g, label: 'SECRET' },
|
|
42
59
|
{ name: 'Private Cryptographic Key (Header)', regex: /-----BEGIN (?:RSA |EC |PGP |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY(?: BLOCK)?-----/g, label: 'SECRET' }
|
|
43
60
|
];
|
|
@@ -147,7 +164,7 @@ export function buildAuditTelemetry(currentTokenMap = {}) {
|
|
|
147
164
|
}
|
|
148
165
|
|
|
149
166
|
const ipHoldings = {
|
|
150
|
-
standard: 'IETF draft-sibiryakov-ztds-protocol-
|
|
167
|
+
standard: 'IETF draft-sibiryakov-ztds-protocol-01',
|
|
151
168
|
standardUrl: 'https://datatracker.ietf.org/doc/draft-sibiryakov-ztds-protocol/',
|
|
152
169
|
patent: "U.S. & Int'l Patents Pending (Paris Convention Priority)",
|
|
153
170
|
trademark: 'ZTDS™ Reg. #182655957 (ILPO Cl 9 & 42)'
|
|
@@ -157,27 +174,27 @@ export function buildAuditTelemetry(currentTokenMap = {}) {
|
|
|
157
174
|
{
|
|
158
175
|
statute: 'EU AI Act (Art. 50)',
|
|
159
176
|
scope: 'AI Transparency & Data Minimization',
|
|
160
|
-
rule: '
|
|
177
|
+
rule: 'Sanitization runs locally by design; reduces the content sent to external AI models. Informational mapping, not a compliance determination.'
|
|
161
178
|
},
|
|
162
179
|
{
|
|
163
180
|
statute: 'GDPR (Art. 4, 25, 28, 32)',
|
|
164
181
|
scope: 'Pseudonymization & Processor Liability',
|
|
165
|
-
rule: '
|
|
182
|
+
rule: 'Client-side pseudonymization is a technical measure supporting Art. 25 and 32. It does not by itself remove controller or processor obligations.'
|
|
166
183
|
},
|
|
167
184
|
{
|
|
168
185
|
statute: 'HIPAA 45 CFR § 164.514(b)',
|
|
169
186
|
scope: 'Safe Harbor De-Identification',
|
|
170
|
-
rule: '
|
|
187
|
+
rule: 'Pattern-based masking of direct identifiers supports Safe Harbor de-identification workflows. Review output for residual identifiers before sharing.'
|
|
171
188
|
},
|
|
172
189
|
{
|
|
173
190
|
statute: 'IRC § 7216 & IRS Pub. 1075',
|
|
174
191
|
scope: 'Tax Return Information Confidentiality',
|
|
175
|
-
rule: '
|
|
192
|
+
rule: 'Masking identifiers before AI use can reduce exposure of tax return information. Informational mapping, not legal advice.'
|
|
176
193
|
},
|
|
177
194
|
{
|
|
178
195
|
statute: 'FRE 502',
|
|
179
196
|
scope: 'Attorney-Client Privilege Preservation',
|
|
180
|
-
rule: '
|
|
197
|
+
rule: 'In-memory redaction can reduce disclosure of privileged details in AI workflows. Privilege determinations remain a legal judgment.'
|
|
181
198
|
}
|
|
182
199
|
];
|
|
183
200
|
|
|
@@ -545,6 +562,10 @@ export class PrivacyScrubberEngine {
|
|
|
545
562
|
delete this.sessionMap[key];
|
|
546
563
|
}
|
|
547
564
|
}
|
|
565
|
+
|
|
566
|
+
createStreamRestorer(options = {}) {
|
|
567
|
+
return createStreamTokenRestorer(this.sessionMap, options);
|
|
568
|
+
}
|
|
548
569
|
}
|
|
549
570
|
|
|
550
571
|
/**
|
|
@@ -576,13 +597,13 @@ export function buildStreamReplacer(tokenMap = {}, options = {}) {
|
|
|
576
597
|
|
|
577
598
|
for (const k of sortedKeys) {
|
|
578
599
|
const val = tokenMap[k];
|
|
579
|
-
const inner = k.replace(/^\[|<|\{\{
|
|
600
|
+
const inner = k.replace(/^\[|<|\{\{?|__|\]|>|\}\}?|__/g, '');
|
|
580
601
|
const match = inner.match(/^([A-Za-z_0-9]+?)[-_ ]?(\d+)$/);
|
|
581
602
|
|
|
582
603
|
if (match) {
|
|
583
|
-
const label = escapeRegex(match[1]);
|
|
604
|
+
const label = escapeRegex(match[1]).replace(/[_-]/g, '\\\\?[_-]');
|
|
584
605
|
const idx = match[2];
|
|
585
|
-
const patternStr = '(?:\\[\\s*' + label + '[-_
|
|
606
|
+
const patternStr = '(?:(?:\\[|\\\\u005[Bb]|%5[Bb]|&(?:amp;)?#0*91;|&(?:amp;)?#[xX]0*5[Bb];|&(?:amp;)?lbrack;|【|[|〔|「|『)\\s*[*`~]*' + label + '[-_\\\\\\s]*0*' + idx + '[*`~]*[.,;:!?]?\\s*(?:\\]|\\\\u005[Dd]|%5[Dd]|&(?:amp;)?#0*93;|&(?:amp;)?#[xX]0*5[Dd];|&(?:amp;)?rbrack;|】|]|〕|」|』)|<\\s*' + label + '[-_\\\\\\s]*0*' + idx + '[.,;:!?]?\\s*>|\\{\\{?\\s*' + label + '[-_\\\\\\s]*0*' + idx + '[.,;:!?]?\\s*\\}\\}?|__\\s*' + label + '[-_\\\\\\s]*0*' + idx + '\\s*__|\\b' + label + '[-_\\s]+0*' + idx + '\\b)';
|
|
586
607
|
patterns.push(patternStr);
|
|
587
608
|
const normKey = (match[1] + '_' + parseInt(idx, 10)).toUpperCase();
|
|
588
609
|
lookup.set(normKey, val);
|
|
@@ -611,10 +632,14 @@ export function buildStreamReplacer(tokenMap = {}, options = {}) {
|
|
|
611
632
|
} else if (lookup.has(coreMatch.toUpperCase())) {
|
|
612
633
|
restored = lookup.get(coreMatch.toUpperCase());
|
|
613
634
|
} else {
|
|
614
|
-
const inner = coreMatch
|
|
615
|
-
|
|
635
|
+
const inner = coreMatch
|
|
636
|
+
.replace(/^(?:\[|\\u005[Bb]|%5[Bb]|&(?:amp;)?#0*91;|&(?:amp;)?#[xX]0*5[Bb];|&(?:amp;)?lbrack;|【|[|〔|「|『|<|\{\{?|__)|(?:\]|\\u005[Dd]|%5[Dd]|&(?:amp;)?#0*93;|&(?:amp;)?#[xX]0*5[Dd];|&(?:amp;)?rbrack;|】|]|〕|」|』|>|\}\}?|__)$/gi, '')
|
|
637
|
+
.replace(/^[*`~\s]+|[.,;:!?*`~\s]+$/g, '')
|
|
638
|
+
.trim();
|
|
639
|
+
const m = inner.match(/^([A-Za-z_0-9]+?)[-_\\\s]*(\d+)$/);
|
|
616
640
|
if (m) {
|
|
617
|
-
const
|
|
641
|
+
const cleanLabel = m[1].replace(/\\([_-])/g, '$1');
|
|
642
|
+
const normKey = (cleanLabel + '_' + parseInt(m[2], 10)).toUpperCase();
|
|
618
643
|
if (lookup.has(normKey)) {
|
|
619
644
|
restored = lookup.get(normKey);
|
|
620
645
|
}
|
|
@@ -720,18 +745,17 @@ export class StreamingDetokenizer {
|
|
|
720
745
|
this.pendingBuffer += text;
|
|
721
746
|
|
|
722
747
|
// Candidate unclosed token at tail of buffer (inspect tail window up to 160 chars)
|
|
723
|
-
// Matches incomplete tokens starting with [, <, {
|
|
748
|
+
// Matches incomplete tokens starting with [, 【, [, <, {, __, %5B, \u005b, or HTML entity [ / [
|
|
724
749
|
const tailLen = 160;
|
|
725
750
|
const tail = this.pendingBuffer.length > tailLen ? this.pendingBuffer.slice(-tailLen) : this.pendingBuffer;
|
|
726
751
|
const offset = this.pendingBuffer.length > tailLen ? this.pendingBuffer.length - tailLen : 0;
|
|
727
|
-
const m = tail.match(/(?:\[
|
|
752
|
+
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)?)?)?)?)?)?)$/);
|
|
728
753
|
|
|
729
754
|
if (m) {
|
|
730
755
|
const candidate = m[0];
|
|
731
|
-
//
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
if (isClosedUnderscoreToken && this.replacer(candidate) !== candidate) {
|
|
756
|
+
// Closed double-underscore (__NAME_1__) or JSON Unicode (\u005bNAME_1\u005d) tokens match character class and can be emitted immediately.
|
|
757
|
+
const isClosedUnderscoreOrUnicode = (candidate.length >= 4 && candidate.startsWith('__') && candidate.endsWith('__')) || /\\u005[Dd]/.test(candidate);
|
|
758
|
+
if (isClosedUnderscoreOrUnicode && this.replacer(candidate) !== candidate) {
|
|
735
759
|
const toEmit = this.pendingBuffer;
|
|
736
760
|
this.pendingBuffer = '';
|
|
737
761
|
return this.replacer(toEmit);
|
|
@@ -773,6 +797,49 @@ export class StreamingDetokenizer {
|
|
|
773
797
|
this.pendingBuffer = '';
|
|
774
798
|
return this.replacer(remaining);
|
|
775
799
|
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Wraps an async iterable (e.g. LLM streaming response) yielding restored chunks.
|
|
803
|
+
* @param {AsyncIterable<any>} asyncIterable
|
|
804
|
+
* @returns {AsyncGenerator<any>}
|
|
805
|
+
*/
|
|
806
|
+
async* wrapStream(asyncIterable) {
|
|
807
|
+
for await (const chunk of asyncIterable) {
|
|
808
|
+
if (chunk === null || chunk === undefined) continue;
|
|
809
|
+
if (chunk.choices && Array.isArray(chunk.choices) && chunk.choices[0]?.delta?.content !== undefined) {
|
|
810
|
+
const delta = chunk.choices[0].delta.content;
|
|
811
|
+
if (typeof delta === 'string') {
|
|
812
|
+
const restored = this.feed(delta);
|
|
813
|
+
if (restored) {
|
|
814
|
+
chunk.choices[0].delta.content = restored;
|
|
815
|
+
yield chunk;
|
|
816
|
+
}
|
|
817
|
+
} else {
|
|
818
|
+
yield chunk;
|
|
819
|
+
}
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
let text = '';
|
|
824
|
+
if (typeof chunk === 'string') {
|
|
825
|
+
text = chunk;
|
|
826
|
+
} else if (typeof chunk?.text === 'string') {
|
|
827
|
+
text = chunk.text;
|
|
828
|
+
} else if (typeof chunk?.content === 'string') {
|
|
829
|
+
text = chunk.content;
|
|
830
|
+
} else if (chunk instanceof Uint8Array || (typeof Buffer !== 'undefined' && Buffer.isBuffer(chunk))) {
|
|
831
|
+
text = chunk;
|
|
832
|
+
} else {
|
|
833
|
+
text = String(chunk);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
const emitted = this.feed(text);
|
|
837
|
+
if (emitted) yield emitted;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
const finalChunk = this.flush();
|
|
841
|
+
if (finalChunk) yield finalChunk;
|
|
842
|
+
}
|
|
776
843
|
}
|
|
777
844
|
|
|
778
845
|
/**
|
|
@@ -803,33 +870,198 @@ export async function* wrapAiStream(stream, tokenMap = {}, options = {}) {
|
|
|
803
870
|
|
|
804
871
|
export function createStreamTokenRestorer(tokenMap = {}, options = {}) {
|
|
805
872
|
const detok = new StreamingDetokenizer(tokenMap, options);
|
|
873
|
+
let stream;
|
|
806
874
|
if (typeof TransformStream !== 'undefined') {
|
|
807
|
-
|
|
875
|
+
stream = new TransformStream({
|
|
808
876
|
transform(chunk, controller) {
|
|
809
|
-
const
|
|
810
|
-
const out = detok.process(str);
|
|
877
|
+
const out = detok.feed(chunk);
|
|
811
878
|
if (out) controller.enqueue(out);
|
|
812
879
|
},
|
|
813
880
|
flush(controller) {
|
|
814
|
-
const
|
|
815
|
-
if (
|
|
881
|
+
const out = detok.flush();
|
|
882
|
+
if (out) controller.enqueue(out);
|
|
816
883
|
}
|
|
817
884
|
});
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
ts.detokenizer = detok;
|
|
821
|
-
return ts;
|
|
885
|
+
} else {
|
|
886
|
+
stream = {};
|
|
822
887
|
}
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
888
|
+
stream.process = (chunk) => detok.feed(chunk);
|
|
889
|
+
stream.feed = (chunk) => detok.feed(chunk);
|
|
890
|
+
stream.flush = () => detok.flush();
|
|
891
|
+
stream.wrapStream = (asyncIterable) => detok.wrapStream(asyncIterable);
|
|
892
|
+
stream.detokenizer = detok;
|
|
893
|
+
return stream;
|
|
828
894
|
}
|
|
829
895
|
|
|
830
896
|
export const scrubText = scrubberPkg ? scrubberPkg.scrubText : null;
|
|
831
897
|
export const unscrubText = scrubberPkg ? scrubberPkg.unscrubText : null;
|
|
832
898
|
|
|
899
|
+
// ── PSR-2 Receipt Integrity (real SHA-256; parity with src/modules/receipt-integrity.js) ──
|
|
900
|
+
export const PSR2_VERSION = 'PSR-2';
|
|
901
|
+
export const PSR2_STATEMENTS = {
|
|
902
|
+
measured: [
|
|
903
|
+
'Token Manifest SHA-256: digest of the sorted token labels held in this session (original values are never hashed or exported).',
|
|
904
|
+
'Entity counts per type.',
|
|
905
|
+
'Receipt SHA-256: digest of the canonical field string, so edits to this receipt are detectable.'
|
|
906
|
+
],
|
|
907
|
+
byDesign: [
|
|
908
|
+
'The sanitization engine runs locally in process memory and does not transmit prompt content. This is an architectural property and is not measured by this receipt.',
|
|
909
|
+
'Token-to-original mappings are held in volatile memory for the session. This receipt does not record their contents.'
|
|
910
|
+
],
|
|
911
|
+
limits: [
|
|
912
|
+
'This receipt is not a digital signature and does not prove who generated it or when.',
|
|
913
|
+
'No final output digest is recorded by this export (Output SHA-256: not recorded).',
|
|
914
|
+
'Detection is pattern-based. Free-text narrative can contain indirect identifiers that remain after sanitization; review output before sharing.',
|
|
915
|
+
'Regulatory references are informational technical-safeguard mappings, not a legal determination of compliance.'
|
|
916
|
+
]
|
|
917
|
+
};
|
|
918
|
+
function psr2Sha256(input) {
|
|
919
|
+
const bytes = new TextEncoder().encode(String(input == null ? '' : input));
|
|
920
|
+
const K = [
|
|
921
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
922
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
923
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
924
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
925
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
926
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
927
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
928
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
|
929
|
+
];
|
|
930
|
+
const H = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19];
|
|
931
|
+
const bitLen = bytes.length * 8;
|
|
932
|
+
const totalLen = (((bytes.length + 9 + 63) >> 6) << 6);
|
|
933
|
+
const msg = new Uint8Array(totalLen);
|
|
934
|
+
msg.set(bytes);
|
|
935
|
+
msg[bytes.length] = 0x80;
|
|
936
|
+
const view = new DataView(msg.buffer);
|
|
937
|
+
view.setUint32(totalLen - 8, Math.floor(bitLen / 0x100000000), false);
|
|
938
|
+
view.setUint32(totalLen - 4, bitLen >>> 0, false);
|
|
939
|
+
const W = new Uint32Array(64);
|
|
940
|
+
const rotr = (x, n) => (x >>> n) | (x << (32 - n));
|
|
941
|
+
for (let off = 0; off < totalLen; off += 64) {
|
|
942
|
+
for (let i = 0; i < 16; i++) W[i] = view.getUint32(off + i * 4, false);
|
|
943
|
+
for (let i = 16; i < 64; i++) {
|
|
944
|
+
const s0 = rotr(W[i - 15], 7) ^ rotr(W[i - 15], 18) ^ (W[i - 15] >>> 3);
|
|
945
|
+
const s1 = rotr(W[i - 2], 17) ^ rotr(W[i - 2], 19) ^ (W[i - 2] >>> 10);
|
|
946
|
+
W[i] = (W[i - 16] + s0 + W[i - 7] + s1) >>> 0;
|
|
947
|
+
}
|
|
948
|
+
let [a, b, c, d, e, f, g, h] = H;
|
|
949
|
+
for (let i = 0; i < 64; i++) {
|
|
950
|
+
const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
|
|
951
|
+
const ch = (e & f) ^ ((~e) & g);
|
|
952
|
+
const temp1 = (h + S1 + ch + K[i] + W[i]) >>> 0;
|
|
953
|
+
const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
|
|
954
|
+
const maj = (a & b) ^ (a & c) ^ (b & c);
|
|
955
|
+
const temp2 = (S0 + maj) >>> 0;
|
|
956
|
+
h = g; g = f; f = e; e = (d + temp1) >>> 0;
|
|
957
|
+
d = c; c = b; b = a; a = (temp1 + temp2) >>> 0;
|
|
958
|
+
}
|
|
959
|
+
H[0] = (H[0] + a) >>> 0; H[1] = (H[1] + b) >>> 0; H[2] = (H[2] + c) >>> 0; H[3] = (H[3] + d) >>> 0;
|
|
960
|
+
H[4] = (H[4] + e) >>> 0; H[5] = (H[5] + f) >>> 0; H[6] = (H[6] + g) >>> 0; H[7] = (H[7] + h) >>> 0;
|
|
961
|
+
}
|
|
962
|
+
return H.map(x => x.toString(16).padStart(8, '0')).join('');
|
|
963
|
+
}
|
|
964
|
+
export function buildPsr2Receipt(tokenMap, timestamp = new Date().toISOString()) {
|
|
965
|
+
const labels = (tokenMap && typeof tokenMap === 'object' ? Object.keys(tokenMap) : []).slice().sort();
|
|
966
|
+
const counts = {};
|
|
967
|
+
for (const t of labels) {
|
|
968
|
+
const m = t.match(/\[([A-Z_]+)_\d+\]/);
|
|
969
|
+
const type = m ? m[1] : 'CUSTOM';
|
|
970
|
+
counts[type] = (counts[type] || 0) + 1;
|
|
971
|
+
}
|
|
972
|
+
const entityCounts = Object.keys(counts).sort().map(k => `${k}=${counts[k]}`).join(',') || 'none';
|
|
973
|
+
const manifestSha256 = psr2Sha256(labels.join('\n'));
|
|
974
|
+
const canonical = [PSR2_VERSION, timestamp, 'none', manifestSha256, entityCounts, 'na'].join('|');
|
|
975
|
+
return { version: PSR2_VERSION, timestamp, outputSha256: null, manifestSha256, entityCounts, tokensTotal: labels.length, tokensPresentInOutput: null, canonical, receiptSha256: psr2Sha256(canonical) };
|
|
976
|
+
}
|
|
977
|
+
export function psr2Lines(r) {
|
|
978
|
+
return [
|
|
979
|
+
`Receipt Version: ${r.version}`,
|
|
980
|
+
`Timestamp (UTC): ${r.timestamp}`,
|
|
981
|
+
`Output SHA-256: not recorded`,
|
|
982
|
+
`Token Manifest SHA-256: ${r.manifestSha256}`,
|
|
983
|
+
`Entity Counts: ${r.entityCounts}`,
|
|
984
|
+
`Tokens In Final Output: not recorded`,
|
|
985
|
+
`Canonical: ${r.canonical}`,
|
|
986
|
+
`Receipt SHA-256: ${r.receiptSha256}`
|
|
987
|
+
];
|
|
988
|
+
}
|
|
989
|
+
function formatPsr2Report({ format, receipt, telemetry, company_name, department, utmMedium }) {
|
|
990
|
+
const entitySummary = Object.entries(telemetry.entities).map(([t, count]) => `[${t}]: ${count}`).join(', ') || 'None (Clean)';
|
|
991
|
+
const teamsUrl = `https://privacyscrubber.com/teams?tier=teams&utm_source=sdk_audit_report&utm_medium=${utmMedium}&utm_campaign=teams_upgrade`;
|
|
992
|
+
const sdkUrl = `https://privacyscrubber.com/sdk/?tier=sdk&utm_source=sdk_audit_report&utm_medium=${utmMedium}&utm_campaign=sdk_pipeline`;
|
|
993
|
+
if (format === 'json') {
|
|
994
|
+
return JSON.stringify({
|
|
995
|
+
protocol: 'Zero-Trust Data Sanitization (ZTDS)',
|
|
996
|
+
receipt_version: receipt.version,
|
|
997
|
+
company: company_name,
|
|
998
|
+
department,
|
|
999
|
+
timestamp: receipt.timestamp,
|
|
1000
|
+
output_sha256: null,
|
|
1001
|
+
token_manifest_sha256: receipt.manifestSha256,
|
|
1002
|
+
entity_counts: receipt.entityCounts,
|
|
1003
|
+
canonical: receipt.canonical,
|
|
1004
|
+
receipt_sha256: receipt.receiptSha256,
|
|
1005
|
+
risk_level: telemetry.riskLevel,
|
|
1006
|
+
frameworks_referenced: telemetry.frameworksList,
|
|
1007
|
+
total_masked_tokens: telemetry.totalCount,
|
|
1008
|
+
entities_breakdown: telemetry.entities,
|
|
1009
|
+
scope: { measured: PSR2_STATEMENTS.measured, by_design_not_measured: PSR2_STATEMENTS.byDesign, limits: PSR2_STATEMENTS.limits },
|
|
1010
|
+
verification_url: 'https://privacyscrubber.com/features/audit-receipt/#verify',
|
|
1011
|
+
licensing_and_governance: { teams_governance_url: teamsUrl, sdk_pipeline_url: sdkUrl }
|
|
1012
|
+
}, null, 2);
|
|
1013
|
+
}
|
|
1014
|
+
if (format === 'summary') {
|
|
1015
|
+
return `[PrivacyScrubber SHA-256 Receipt] ${receipt.version} | Receipt SHA-256: ${receipt.receiptSha256.substring(0, 16)}... | Organization: ${company_name} | Masked Tokens: ${telemetry.totalCount} | Risk: ${telemetry.riskLevel} | Frameworks (informational): ${telemetry.frameworksList.join(', ')}`;
|
|
1016
|
+
}
|
|
1017
|
+
return `# Zero-Trust Data Sanitization Receipt
|
|
1018
|
+
**Organization:** ${company_name} (${department})
|
|
1019
|
+
**Timestamp:** ${receipt.timestamp}
|
|
1020
|
+
**Receipt Format:** ${receipt.version} (SHA-256 digests; not a digital signature)
|
|
1021
|
+
|
|
1022
|
+
---
|
|
1023
|
+
|
|
1024
|
+
### Sanitization Metrics & Risk Assessment
|
|
1025
|
+
* **Overall Risk Rating:** **${telemetry.riskLevel}**
|
|
1026
|
+
* **Total Sensitive Entities Masked:** \`${telemetry.totalCount}\`
|
|
1027
|
+
* **Entity Breakdown:** ${entitySummary}
|
|
1028
|
+
|
|
1029
|
+
### Informational Framework References
|
|
1030
|
+
${telemetry.frameworksList.map(f => `- ${f}`).join('\n')}
|
|
1031
|
+
|
|
1032
|
+
These are technical-safeguard mappings, not a legal determination of compliance.
|
|
1033
|
+
|
|
1034
|
+
### Receipt Integrity (${receipt.version})
|
|
1035
|
+
\`\`\`text
|
|
1036
|
+
${psr2Lines(receipt).join('\n')}
|
|
1037
|
+
\`\`\`
|
|
1038
|
+
|
|
1039
|
+
### Scope of This Receipt
|
|
1040
|
+
**Measured:** ${PSR2_STATEMENTS.measured.join(' ')}
|
|
1041
|
+
|
|
1042
|
+
**Stated by design, not measured:** ${PSR2_STATEMENTS.byDesign.join(' ')}
|
|
1043
|
+
|
|
1044
|
+
**Limits:** ${PSR2_STATEMENTS.limits.join(' ')}
|
|
1045
|
+
|
|
1046
|
+
### Enterprise & Team Governance
|
|
1047
|
+
* **Team Governance & Encrypted Handoff:** Centrally deploy redaction rules and encrypted session handoff with **PrivacyScrubber TEAMS** ($99/mo flat): [privacyscrubber.com/teams](${teamsUrl})
|
|
1048
|
+
* **Backend Microservices & RAG Pipelines:** Integrate in-memory sanitization (<1ms) into your Node.js/Python infrastructure with **Developer SDK** ($299/mo): [privacyscrubber.com/sdk](${sdkUrl})
|
|
1049
|
+
|
|
1050
|
+
*Generated locally by PrivacyScrubber Core SDK*
|
|
1051
|
+
*Verify locally (paste this receipt): https://privacyscrubber.com/features/audit-receipt/#verify*`;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
export function generateAuditReport(sessionMap = {}, options = {}) {
|
|
1055
|
+
const { companyName = 'PrivacyScrubber Client', department = 'SecOps / Compliance', format = 'markdown' } = options;
|
|
1056
|
+
const telemetry = buildAuditTelemetry(sessionMap);
|
|
1057
|
+
const timestamp = new Date().toISOString();
|
|
1058
|
+
const receipt = buildPsr2Receipt(sessionMap, timestamp);
|
|
1059
|
+
if (format === 'json') {
|
|
1060
|
+
return JSON.parse(formatPsr2Report({ format: 'json', receipt, telemetry, company_name: companyName, department, utmMedium: 'json' }));
|
|
1061
|
+
}
|
|
1062
|
+
return formatPsr2Report({ format: format === 'summary' ? 'summary' : 'markdown', receipt, telemetry, company_name: companyName, department, utmMedium: 'markdown' });
|
|
1063
|
+
}
|
|
1064
|
+
|
|
833
1065
|
// Vision & Multimodal exports
|
|
834
1066
|
export {
|
|
835
1067
|
stripExifMetadata,
|
|
@@ -855,6 +1087,11 @@ export default {
|
|
|
855
1087
|
buildAuditTelemetry,
|
|
856
1088
|
buildCisoAuditTelemetry,
|
|
857
1089
|
formatAuditReceipt,
|
|
1090
|
+
generateAuditReport,
|
|
1091
|
+
PSR2_VERSION,
|
|
1092
|
+
PSR2_STATEMENTS,
|
|
1093
|
+
buildPsr2Receipt,
|
|
1094
|
+
psr2Lines,
|
|
858
1095
|
PrivacyScrubberEngine,
|
|
859
1096
|
PrivacyScrubberLicenseError,
|
|
860
1097
|
StreamingDetokenizer,
|
package/cli.js
CHANGED
|
@@ -23,7 +23,7 @@ const __dirname = path.dirname(__filename);
|
|
|
23
23
|
|
|
24
24
|
const args = process.argv.slice(2);
|
|
25
25
|
|
|
26
|
-
let sdkVersion = '2.4.
|
|
26
|
+
let sdkVersion = '2.4.1';
|
|
27
27
|
try {
|
|
28
28
|
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'));
|
|
29
29
|
sdkVersion = pkg.version || sdkVersion;
|
|
@@ -24,6 +24,7 @@ const candidatePaths = [
|
|
|
24
24
|
process.env.PRIVACYSCRUBBER_SDK_PATH,
|
|
25
25
|
path.resolve(process.cwd(), 'npm-sdk/index.cjs'),
|
|
26
26
|
path.resolve(process.cwd(), 'node_modules/@privacyscrubber/sdk'),
|
|
27
|
+
path.resolve(__dirname, '../../../index.cjs'),
|
|
27
28
|
path.resolve(__dirname, '../../index.cjs'),
|
|
28
29
|
path.resolve(__dirname, '../index.cjs'),
|
|
29
30
|
path.resolve(__dirname, './index.cjs'),
|
|
@@ -115,7 +116,7 @@ rl.on('line', (line) => {
|
|
|
115
116
|
sendResponse(id, {
|
|
116
117
|
pong: true,
|
|
117
118
|
engine: 'PrivacyScrubber ZTDS',
|
|
118
|
-
version: '2.4.
|
|
119
|
+
version: '2.4.1',
|
|
119
120
|
uptime: process.uptime()
|
|
120
121
|
});
|
|
121
122
|
break;
|
|
@@ -171,7 +172,6 @@ rl.on('line', (line) => {
|
|
|
171
172
|
};
|
|
172
173
|
|
|
173
174
|
const res = sdk.sanitizeObject(data, options);
|
|
174
|
-
// Also merge into engine sessionMap if requested
|
|
175
175
|
if (req.persist_session !== false && res.tokenMap) {
|
|
176
176
|
Object.assign(engine.sessionMap, res.tokenMap);
|
|
177
177
|
}
|
|
@@ -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
|