@privacyscrubber/sdk 2.0.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/index.js ADDED
@@ -0,0 +1,581 @@
1
+ /**
2
+ * PrivacyScrubber Developer SDK (@privacyscrubber/sdk)
3
+ * Zero-Trust Data Sanitization & PII Redaction Engine.
4
+ *
5
+ * 100% Client-Side / Server-Side in-memory execution.
6
+ * Zero telemetry network requests. Zero data leakage.
7
+ *
8
+ * https://privacyscrubber.com
9
+ */
10
+
11
+ import { createRequire } from 'module';
12
+ const require = createRequire(import.meta.url);
13
+
14
+ import './polyfill.js';
15
+ import './ps-license-manager.js';
16
+
17
+ // Access LicenseManager from global scope
18
+ const LicenseManager = (typeof globalThis !== 'undefined' && globalThis.LicenseManager)
19
+ || (typeof global !== 'undefined' && global.LicenseManager)
20
+ || (typeof window !== 'undefined' && window.LicenseManager);
21
+
22
+ // Synchronously load the production CJS core engine
23
+ require('./scrubber-core.cjs');
24
+ const scrubberPkg = (typeof globalThis !== 'undefined' && globalThis.PrivacyScrubberCore)
25
+ || (typeof global !== 'undefined' && global.PrivacyScrubberCore)
26
+ || (typeof window !== 'undefined' && window.PrivacyScrubberCore);
27
+
28
+ if (scrubberPkg && typeof scrubberPkg.init === 'function') {
29
+ scrubberPkg.init();
30
+ }
31
+
32
+ /**
33
+ * Built-in patterns for DevOps & Cloud Secrets detection.
34
+ */
35
+ export const DEVOPS_SECRETS_DETECTOR = [
36
+ { name: 'AWS Credentials', regex: /\b(?:AKIA|ASIA|AGPA|AIDA|AROA|AIPA)[A-Z0-9]{16}\b/g, label: 'AWS_KEY' },
37
+ { name: 'JSON Web Token (JWT)', regex: /\beyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\b/g, label: 'JWT_TOKEN' },
38
+ { 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' },
39
+ { name: 'Stripe API Key', regex: /\b(?:[rs]k)_(?:test|live)_[a-zA-Z0-9]{24,}\b/g, label: 'STRIPE_KEY' },
40
+ { name: 'OpenAI Project API Key', regex: /\b(?:sk|pk)-(?:proj-)?[a-zA-Z0-9_-]{16,}\b/gi, label: 'OPENAI_KEY' },
41
+ { name: 'Database Connection URI', regex: /\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqp|mssql):\/\/[^\s"']+/gi, label: 'DB_URI' },
42
+ { 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,}\b/gi, label: 'SECRET' }
43
+ ];
44
+
45
+ /**
46
+ * Custom Error thrown when Free Tier limits are exceeded.
47
+ */
48
+ export class PrivacyScrubberLicenseError extends Error {
49
+ constructor(message, { tier = 'FREE', currentLength = 0, limit = 15000, profile = 'General' } = {}) {
50
+ super(message);
51
+ this.name = 'PrivacyScrubberLicenseError';
52
+ this.tier = tier;
53
+ this.currentLength = currentLength;
54
+ this.limit = limit;
55
+ this.profile = profile;
56
+ this.upgradeUrl = 'https://privacyscrubber.com/pricing?utm_source=npm_sdk&utm_medium=sdk_error&utm_campaign=dev_upsell';
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Validates a PrivacyScrubber license key (RS256 JWT or Cryptographic Checksum).
62
+ * @param {string} key - License key string.
63
+ * @returns {{ valid: boolean, tier: 'PRO'|'TEAMS'|null, expires: number|null, error: string|null }}
64
+ */
65
+ export function validateLicense(key) {
66
+ if (!key || typeof key !== 'string') {
67
+ return { valid: false, tier: null, expires: null, error: 'No license key provided.' };
68
+ }
69
+ const cleanKey = key.trim();
70
+ if (!LicenseManager || typeof LicenseManager.validate !== 'function') {
71
+ return { valid: false, tier: null, expires: null, error: 'License manager engine unavailable.' };
72
+ }
73
+ const result = LicenseManager.validate(cleanKey);
74
+ return {
75
+ valid: !!result.valid,
76
+ tier: result.valid ? (result.tier || 'PRO') : null,
77
+ expires: result.expires || null,
78
+ error: result.valid ? null : (result.reason || 'Invalid license key.')
79
+ };
80
+ }
81
+
82
+ /**
83
+ * Legacy validator helper returning active tier or null.
84
+ * @param {string} key
85
+ * @returns {'PRO'|'TEAMS'|null}
86
+ */
87
+ export function validateLicenseKey(key) {
88
+ const res = validateLicense(key);
89
+ return res.valid ? res.tier : null;
90
+ }
91
+
92
+ /**
93
+ * Builds CISO-grade compliance audit telemetry from masked tokens.
94
+ * @param {Record<string, string>|string[]} currentTokenMap
95
+ * @returns {object}
96
+ */
97
+ export function buildAuditTelemetry(currentTokenMap = {}) {
98
+ const entities = {};
99
+ let totalCount = 0;
100
+
101
+ if (currentTokenMap && typeof currentTokenMap === 'object') {
102
+ const keys = Array.isArray(currentTokenMap) ? currentTokenMap : Object.keys(currentTokenMap);
103
+ for (const t of keys) {
104
+ const tokenStr = typeof t === 'string' ? t : (t.token || t.mask || '');
105
+ const match = tokenStr.match(/\[([A-Z_]+)_\d+\]/);
106
+ const baseType = match ? match[1] : (tokenStr.replace(/\[|\]/g, '').replace(/_[0-9]+$/, '') || 'CUSTOM');
107
+ entities[baseType] = (entities[baseType] || 0) + 1;
108
+ totalCount++;
109
+ }
110
+ }
111
+
112
+ const types = Object.keys(entities);
113
+ let riskLevel = 'LOW EXPOSURE';
114
+
115
+ if (totalCount > 0) {
116
+ const hasHighRisk = types.some(t => ['ID', 'SSN', 'CREDIT_CARD', 'PASSPORT', 'BANK', 'API_KEY', 'SECRET', 'PASSWORD', 'MRN', 'KEY', 'AWS_KEY', 'STRIPE_KEY', 'JWT_TOKEN', 'API_TOKEN'].includes(t));
117
+ if (hasHighRisk || types.length >= 3 || totalCount >= 10) {
118
+ riskLevel = 'CRITICAL (HIGH EXPOSURE)';
119
+ } else if (types.length >= 2 || totalCount >= 3) {
120
+ riskLevel = 'MODERATE EXPOSURE';
121
+ } else {
122
+ riskLevel = 'LOW EXPOSURE';
123
+ }
124
+ } else {
125
+ riskLevel = 'CLEAN (ZERO PII)';
126
+ }
127
+
128
+ const frameworksSet = new Set(['ZTDS Standard']);
129
+ if (types.includes('NAME') || types.includes('EMAIL') || types.includes('PHONE')) {
130
+ frameworksSet.add('GDPR (Art. 4)');
131
+ frameworksSet.add('CCPA/CPRA');
132
+ }
133
+ if (types.includes('ID') || types.includes('SSN') || types.includes('PASSPORT')) {
134
+ frameworksSet.add('SOC 2 Type II');
135
+ frameworksSet.add('ISO 27001 (A.8.11)');
136
+ }
137
+ if (types.some(t => ['CREDIT_CARD', 'BANK', 'IBAN', 'FINANCIAL', 'CARD', 'STRIPE_KEY'].includes(t))) {
138
+ frameworksSet.add('PCI DSS v4.0');
139
+ }
140
+ if (types.some(t => ['MRN', 'HEALTH', 'MEDICAL', 'PATIENT'].includes(t))) {
141
+ frameworksSet.add('HIPAA ยง164.514');
142
+ }
143
+ if (types.some(t => ['API_KEY', 'SECRET', 'PASSWORD', 'TOKEN', 'KEY', 'AWS_KEY', 'JWT_TOKEN', 'API_TOKEN'].includes(t))) {
144
+ frameworksSet.add('NIST SP 800-53');
145
+ }
146
+
147
+ return {
148
+ totalCount,
149
+ entities,
150
+ types,
151
+ riskLevel,
152
+ frameworksList: Array.from(frameworksSet)
153
+ };
154
+ }
155
+
156
+ /**
157
+ * Formats structured audit telemetry into a human-readable Markdown Audit Receipt.
158
+ * @param {object} telemetry
159
+ * @returns {string}
160
+ */
161
+ export function formatAuditReceipt(telemetry) {
162
+ if (!telemetry || telemetry.totalCount === 0) {
163
+ return "> ๐Ÿ›ก๏ธ **PrivacyScrubber Audit Receipt**: CLEAN (ZERO PII DETECTED)\n";
164
+ }
165
+ const entitiesList = Object.entries(telemetry.entities)
166
+ .map(([type, count]) => `${count} ${type}`)
167
+ .join(', ');
168
+
169
+ const icon = telemetry.riskLevel.includes('CRITICAL') ? '๐Ÿ”ด' : (telemetry.riskLevel.includes('MODERATE') ? '๐ŸŸ ' : '๐ŸŸข');
170
+
171
+ return `> ๐Ÿ›ก๏ธ **PrivacyScrubber Audit Receipt**\n` +
172
+ `> * **Risk Level:** ${icon} ${telemetry.riskLevel}\n` +
173
+ `> * **Compliance Enforced:** ${telemetry.frameworksList.join(', ')}\n` +
174
+ `> * **Tokens Masked:** ${telemetry.totalCount} (${entitiesList})\n`;
175
+ }
176
+
177
+ /**
178
+ * Primary High-Level Function: Sanitize / Scrub text of PII and secrets.
179
+ *
180
+ * @param {string} text - Raw input text containing potential PII/secrets.
181
+ * @param {object} [options={}] - Configuration options.
182
+ * @param {string} [options.profile='General'] - PII Profile (General, Dev, Medical, Financial, Legal, HR, etc.).
183
+ * @param {string} [options.licenseKey] - Optional license key. Falls back to process.env.PRIVACYSCRUBBER_KEY.
184
+ * @param {Array} [options.customRules=[]] - Custom regex rules array.
185
+ * @param {Record<string, string>} [options.tokenLabelMap={}] - Custom label overrides.
186
+ * @param {Record<string, string>} [options.existingSessionMap={}] - Session map to maintain consistency across turns.
187
+ * @param {boolean} [options.generateReceipt=true] - Whether to generate Markdown audit receipt.
188
+ * @param {boolean} [options.throwOnLimit=true] - Throw PrivacyScrubberLicenseError when free tier limit is hit.
189
+ * @param {boolean} [options.detectSecrets=false] - Auto-inject DevOps & Cloud secrets detection rules.
190
+ * @returns {{
191
+ * scrubbedText: string,
192
+ * tokenMap: Record<string, string>,
193
+ * count: number,
194
+ * executionMs: number,
195
+ * license: { isPro: boolean, tier: 'PRO'|'TEAMS'|'FREE', limit: number, upgradeUrl?: string },
196
+ * telemetry: object,
197
+ * auditReceipt: string
198
+ * }}
199
+ */
200
+ export function sanitize(text, options = {}) {
201
+ if (!text || typeof text !== 'string') {
202
+ return {
203
+ scrubbedText: '',
204
+ tokenMap: {},
205
+ count: 0,
206
+ executionMs: 0,
207
+ license: { isPro: false, tier: 'FREE', limit: 15000 },
208
+ telemetry: buildAuditTelemetry({}),
209
+ auditReceipt: formatAuditReceipt(buildAuditTelemetry({}))
210
+ };
211
+ }
212
+
213
+ const {
214
+ profile = 'General',
215
+ licenseKey = (typeof process !== 'undefined' && process.env?.PRIVACYSCRUBBER_KEY) || '',
216
+ customRules = [],
217
+ tokenLabelMap = {},
218
+ existingSessionMap = {},
219
+ generateReceipt = true,
220
+ throwOnLimit = true,
221
+ detectSecrets = false,
222
+ ignoreList = null
223
+ } = options;
224
+
225
+ const licenseCheck = validateLicense(licenseKey);
226
+ const isPro = licenseCheck.valid;
227
+ const tier = isPro ? licenseCheck.tier : 'FREE';
228
+
229
+ const isSpecialized = profile && profile.toLowerCase() !== 'general';
230
+ const limit = isPro ? Infinity : (isSpecialized ? 5000 : 15000);
231
+
232
+ if (!isPro && text.length > limit) {
233
+ const errorMsg = `[PrivacyScrubber] Free Tier character limit exceeded (${limit.toLocaleString()} chars for profile '${profile}', received ${text.length.toLocaleString()} chars). Upgrade to PRO, TEAMS, or Developer SDK for unlimited throughput: https://privacyscrubber.com/pricing?utm_source=npm_sdk&utm_medium=sdk_error&utm_campaign=dev_upsell`;
234
+ if (throwOnLimit) {
235
+ throw new PrivacyScrubberLicenseError(errorMsg, {
236
+ tier: 'FREE',
237
+ currentLength: text.length,
238
+ limit: limit,
239
+ profile: profile
240
+ });
241
+ }
242
+ }
243
+
244
+ // Auto-inject DevOps Secrets rules when in Dev profile or when detectSecrets is requested
245
+ const activeRules = [...customRules];
246
+ if (profile.toLowerCase() === 'dev' || detectSecrets === true) {
247
+ DEVOPS_SECRETS_DETECTOR.forEach(rule => {
248
+ activeRules.push({
249
+ name: rule.name,
250
+ pattern: rule.regex.source,
251
+ regex: new RegExp(rule.regex.source, rule.regex.flags),
252
+ label: rule.label || 'SECRET'
253
+ });
254
+ });
255
+ }
256
+
257
+ const startTime = (typeof performance !== 'undefined' && typeof performance.now === 'function')
258
+ ? performance.now()
259
+ : Date.now();
260
+
261
+ const textToScrub = (!isPro && text.length > limit) ? text.slice(0, limit) : text;
262
+
263
+ const rawResult = scrubberPkg.scrubText(
264
+ textToScrub,
265
+ activeRules,
266
+ tokenLabelMap,
267
+ profile,
268
+ existingSessionMap,
269
+ isPro,
270
+ ignoreList
271
+ );
272
+
273
+ const endTime = (typeof performance !== 'undefined' && typeof performance.now === 'function')
274
+ ? performance.now()
275
+ : Date.now();
276
+
277
+ const executionMs = Math.round((endTime - startTime) * 100) / 100;
278
+ const telemetry = buildAuditTelemetry(rawResult.tokenMap || {});
279
+ const auditReceipt = generateReceipt ? formatAuditReceipt(telemetry) : '';
280
+
281
+ return {
282
+ scrubbedText: rawResult.scrubbedText,
283
+ tokenMap: rawResult.tokenMap || {},
284
+ count: rawResult.count || 0,
285
+ executionMs: executionMs,
286
+ license: {
287
+ isPro: isPro,
288
+ tier: tier,
289
+ limit: limit,
290
+ ...(!isPro && {
291
+ upgradeUrl: 'https://privacyscrubber.com/pricing?utm_source=npm_sdk&utm_medium=sdk_telemetry&utm_campaign=dev_upsell',
292
+ recommendation: 'Upgrade to PRO, TEAMS, or Developer SDK for unlimited text processing, custom regex rules, and specialized compliance profiles.'
293
+ })
294
+ },
295
+ telemetry: telemetry,
296
+ auditReceipt: auditReceipt
297
+ };
298
+ }
299
+
300
+ /**
301
+ * Alias for sanitize.
302
+ */
303
+ export const scrub = sanitize;
304
+
305
+ /**
306
+ * Primary High-Level Function: Restore / Unscrub AI response back to original entities in-memory.
307
+ *
308
+ * @param {string} aiResponse - Text containing masked tokens (e.g. [EMAIL_1], [NAME_1]).
309
+ * @param {Record<string, string>} tokenMap - Volatile token map returned from sanitize().
310
+ * @returns {{ restoredText: string, restoredCount: number }}
311
+ */
312
+ export function restore(aiResponse, tokenMap = {}) {
313
+ if (!aiResponse || typeof aiResponse !== 'string') {
314
+ return { restoredText: '', restoredCount: 0 };
315
+ }
316
+ const result = scrubberPkg.unscrubText(aiResponse, tokenMap);
317
+ return {
318
+ restoredText: result.restoredText,
319
+ restoredCount: result.restoredCount || 0
320
+ };
321
+ }
322
+
323
+ /**
324
+ * Alias for restore.
325
+ */
326
+ export const unscrub = restore;
327
+
328
+ /**
329
+ * Stateful PrivacyScrubber Engine class for backend servers & conversational pipelines.
330
+ */
331
+ export class PrivacyScrubberEngine {
332
+ constructor(config = {}) {
333
+ this.licenseKey = config.licenseKey || config.apiKey || (typeof process !== 'undefined' && process.env?.PRIVACYSCRUBBER_KEY) || '';
334
+ this.defaultProfile = config.defaultProfile || config.profile || 'General';
335
+ this.customRules = config.customRules || [];
336
+ this.tokenLabelMap = config.tokenLabelMap || {};
337
+ this.generateReceipt = config.generateReceipt !== false;
338
+ this.sessionMap = { ...(config.initialSessionMap || {}) };
339
+ this.ignoreList = new Set(config.ignoreList || []);
340
+
341
+ this.license = validateLicense(this.licenseKey);
342
+ }
343
+
344
+ /**
345
+ * Updates active license key.
346
+ * @param {string} key
347
+ */
348
+ setLicenseKey(key) {
349
+ this.licenseKey = key;
350
+ this.license = validateLicense(this.licenseKey);
351
+ return this.license;
352
+ }
353
+
354
+ /**
355
+ * Sanitizes input text, maintaining stateful session memory if requested.
356
+ * @param {string} text
357
+ * @param {object} [options={}]
358
+ */
359
+ sanitize(text, options = {}) {
360
+ // Merge instance ignoreList with per-call ignoreList
361
+ const mergedIgnoreList = new Set(this.ignoreList);
362
+ if (options.ignoreList) {
363
+ (Array.isArray(options.ignoreList) ? options.ignoreList : [...options.ignoreList])
364
+ .forEach(v => mergedIgnoreList.add(v));
365
+ }
366
+
367
+ const mergedOptions = {
368
+ profile: options.profile || this.defaultProfile,
369
+ licenseKey: options.licenseKey || this.licenseKey,
370
+ customRules: [...this.customRules, ...(options.customRules || [])],
371
+ tokenLabelMap: { ...this.tokenLabelMap, ...(options.tokenLabelMap || {}) },
372
+ existingSessionMap: options.persistSession !== false ? this.sessionMap : (options.existingSessionMap || {}),
373
+ generateReceipt: options.generateReceipt !== undefined ? options.generateReceipt : this.generateReceipt,
374
+ throwOnLimit: options.throwOnLimit !== undefined ? options.throwOnLimit : true,
375
+ detectSecrets: options.detectSecrets !== undefined ? options.detectSecrets : false,
376
+ ignoreList: mergedIgnoreList.size > 0 ? mergedIgnoreList : null
377
+ };
378
+
379
+ const result = sanitize(text, mergedOptions);
380
+ if (options.persistSession !== false) {
381
+ Object.assign(this.sessionMap, result.tokenMap);
382
+ }
383
+ return result;
384
+ }
385
+
386
+ /**
387
+ * Restores AI response using the engine's internal session map or an explicit tokenMap.
388
+ * @param {string} aiResponse
389
+ * @param {Record<string, string>} [tokenMap]
390
+ */
391
+ restore(aiResponse, tokenMap = null) {
392
+ const activeMap = tokenMap || this.sessionMap;
393
+ return restore(aiResponse, activeMap);
394
+ }
395
+
396
+ /**
397
+ * Resets the persistent session token map.
398
+ */
399
+ resetSession() {
400
+ for (const key of Object.keys(this.sessionMap)) {
401
+ delete this.sessionMap[key];
402
+ }
403
+ }
404
+
405
+ /**
406
+ * Marks a token as a false positive. The original plaintext value will be
407
+ * excluded from all future sanitize() calls on this engine instance.
408
+ * @param {string} token - The token to mark (e.g., '[NAME_1]').
409
+ * @returns {{ success: boolean, token?: string, original?: string, ignoreListSize?: number, reason?: string }}
410
+ */
411
+ markFalsePositive(token) {
412
+ const original = this.sessionMap[token];
413
+ if (!original) {
414
+ return { success: false, reason: `Token '${token}' not found in session map` };
415
+ }
416
+ this.ignoreList.add(original);
417
+ delete this.sessionMap[token];
418
+ return { success: true, token, original, ignoreListSize: this.ignoreList.size };
419
+ }
420
+
421
+ /**
422
+ * Clears the false positive ignore list.
423
+ */
424
+ clearIgnoreList() {
425
+ this.ignoreList.clear();
426
+ }
427
+ }
428
+
429
+ /**
430
+ * Transparent Zero-Trust Wrapper for OpenAI Client.
431
+ * Automatically sanitizes outgoing messages before transmission to OpenAI,
432
+ * and restores completion tokens in the assistant response before returning!
433
+ *
434
+ * @param {object} openaiClient - Instance of OpenAI SDK (e.g. new OpenAI()).
435
+ * @param {object} [options={}] - PrivacyScrubber configuration.
436
+ * @returns {object} Wrapped OpenAI client.
437
+ */
438
+ export function wrapOpenAI(openaiClient, options = {}) {
439
+ if (!openaiClient || !openaiClient.chat || !openaiClient.chat.completions) {
440
+ throw new Error('[PrivacyScrubber] Invalid OpenAI client instance passed to wrapOpenAI.');
441
+ }
442
+
443
+ const engine = new PrivacyScrubberEngine(options);
444
+ const originalCreate = openaiClient.chat.completions.create.bind(openaiClient.chat.completions);
445
+
446
+ openaiClient.chat.completions.create = async function(params, requestOptions) {
447
+ if (!params || !Array.isArray(params.messages)) {
448
+ return originalCreate(params, requestOptions);
449
+ }
450
+
451
+ const sessionTokenMap = {};
452
+ const sanitizedMessages = params.messages.map(msg => {
453
+ if (typeof msg.content === 'string') {
454
+ const res = engine.sanitize(msg.content, { existingSessionMap: sessionTokenMap });
455
+ Object.assign(sessionTokenMap, res.tokenMap);
456
+ return { ...msg, content: res.scrubbedText };
457
+ }
458
+ return msg;
459
+ });
460
+
461
+ const modifiedParams = { ...params, messages: sanitizedMessages };
462
+ const response = await originalCreate(modifiedParams, requestOptions);
463
+
464
+ if (response && Array.isArray(response.choices)) {
465
+ response.choices.forEach(choice => {
466
+ if (choice.message && typeof choice.message.content === 'string') {
467
+ choice.message.content = engine.restore(choice.message.content, sessionTokenMap).restoredText;
468
+ }
469
+ });
470
+ }
471
+
472
+ return response;
473
+ };
474
+
475
+ return openaiClient;
476
+ }
477
+
478
+ /**
479
+ * Framework middleware helper for LangChain / LlamaIndex pipelines.
480
+ */
481
+ export function createLangChainTransform(options = {}) {
482
+ const engine = new PrivacyScrubberEngine(options);
483
+ return {
484
+ preprocess: (input) => engine.sanitize(input),
485
+ postprocess: (output, tokenMap) => engine.restore(output, tokenMap)
486
+ };
487
+ }
488
+
489
+ /**
490
+ * Generates an official Zero-Trust Compliance Audit Report.
491
+ * @param {Record<string, string>} sessionMap - Map of token bindings.
492
+ * @param {object} [options] - Options: companyName, department, format ('markdown'|'json'|'summary').
493
+ * @returns {object|string}
494
+ */
495
+ export function generateAuditReport(sessionMap = {}, options = {}) {
496
+ const { companyName = 'PrivacyScrubber Client', department = 'SecOps / Compliance', format = 'markdown' } = options;
497
+ const telemetry = buildAuditTelemetry(sessionMap);
498
+
499
+ let sessionHash = '';
500
+ try {
501
+ const cryptoMod = require('crypto');
502
+ sessionHash = cryptoMod.createHash('sha256').update(JSON.stringify(sessionMap) + Date.now().toString()).digest('hex');
503
+ } catch {
504
+ sessionHash = Array.from(crypto.getRandomValues(new Uint8Array(16))).map(b => b.toString(16).padStart(2, '0')).join('');
505
+ }
506
+
507
+ const timestamp = new Date().toISOString();
508
+ const entitySummary = Object.entries(telemetry.entities).map(([t, count]) => `[${t}]: ${count}`).join(', ') || 'None (Clean)';
509
+
510
+ if (format === 'json') {
511
+ return {
512
+ protocol: "Zero-Trust Data Sanitization (ZTDS)",
513
+ certificateId: `ZTDS-CERT-${sessionHash.substring(0, 16).toUpperCase()}`,
514
+ organization: companyName,
515
+ department: department,
516
+ timestamp: timestamp,
517
+ sessionHash: sessionHash,
518
+ verificationMode: "100% Offline (In-Memory RAM)",
519
+ complianceStatus: "VERIFIED PASS",
520
+ riskLevel: telemetry.riskLevel,
521
+ frameworksEnforced: telemetry.frameworksList,
522
+ totalMaskedTokens: telemetry.totalCount,
523
+ entitiesBreakdown: telemetry.entities,
524
+ zeroEgressVerified: true,
525
+ verificationUrl: `https://privacyscrubber.com/features/audit-receipt/#verify?hash=${sessionHash.substring(0, 16)}`
526
+ };
527
+ }
528
+
529
+ if (format === 'summary') {
530
+ return `[PrivacyScrubber Compliance Certificate] ID: ZTDS-${sessionHash.substring(0, 8).toUpperCase()} | Organization: ${companyName} | Masked Tokens: ${telemetry.totalCount} | Risk: ${telemetry.riskLevel} | Frameworks: ${telemetry.frameworksList.join(', ')} | Status: PASS (100% Air-Gapped)`;
531
+ }
532
+
533
+ return `# ๐Ÿ›ก๏ธ Zero-Trust Data Sanitization Compliance Certificate
534
+ **Certificate ID:** \`ZTDS-CERT-${sessionHash.substring(0, 16).toUpperCase()}\`
535
+ **Organization:** ${companyName} (${department})
536
+ **Timestamp:** ${timestamp}
537
+ **Verification Mode:** 100% Local In-Memory Processing (Air-Gapped)
538
+ **Status:** **VERIFIED PASS** (Zero Network Egress)
539
+
540
+ ---
541
+
542
+ ### ๐Ÿ“Š Sanitization Metrics & Risk Assessment
543
+ * **Overall Risk Rating:** **${telemetry.riskLevel}**
544
+ * **Total Sensitive Entities Masked:** \`${telemetry.totalCount}\`
545
+ * **Entity Breakdown:** ${entitySummary}
546
+ * **Network Data Transmitted:** \`0.00 KB (Zero-Trust Local RAM)\`
547
+
548
+ ### ๐Ÿ“œ Regulatory Frameworks Enforced
549
+ ${telemetry.frameworksList.map(f => `- **${f}**`).join('\n')}
550
+
551
+ ### ๐Ÿ”’ CISO Compliance Declaration
552
+ 1. **EU AI Act (Art. 50) & GDPR (Art. 25 & 32):** Data minimization and local pseudonymization enforced prior to model interaction.
553
+ 2. **HIPAA Safe Harbor (ยง164.514) / SOC 2 Type II:** All direct and indirect identifiers sanitized locally without cloud processor liability.
554
+ 3. **Cryptographic Verification:** Tamper-evident session verification hash: \`${sessionHash}\`
555
+
556
+ *Certified Offline by PrivacyScrubber Core SDK*
557
+ *Verify at: https://privacyscrubber.com/features/audit-receipt/*`;
558
+ }
559
+
560
+ // Low-level backwards-compatible exports
561
+ export const scrubText = scrubberPkg.scrubText;
562
+ export const unscrubText = scrubberPkg.unscrubText;
563
+
564
+ export default {
565
+ sanitize,
566
+ scrub,
567
+ restore,
568
+ unscrub,
569
+ validateLicense,
570
+ validateLicenseKey,
571
+ buildAuditTelemetry,
572
+ formatAuditReceipt,
573
+ generateAuditReport,
574
+ PrivacyScrubberEngine,
575
+ PrivacyScrubberLicenseError,
576
+ wrapOpenAI,
577
+ createLangChainTransform,
578
+ scrubText,
579
+ unscrubText,
580
+ DEVOPS_SECRETS_DETECTOR
581
+ };
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@privacyscrubber/sdk",
3
+ "version": "2.0.2",
4
+ "description": "Zero-Trust Data Sanitization & PII Redaction Engine. 100% Client-Side / Server-Side in-memory execution. Zero external dependencies.",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "type": "module",
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "files": [
12
+ "index.js",
13
+ "index.d.ts",
14
+ "polyfill.js",
15
+ "ps-license-manager.js",
16
+ "ps-pii-engine.cjs",
17
+ "ps-pii-engine.js",
18
+ "scrubber-core.cjs",
19
+ "shared-ui.js",
20
+ "ui-modals.js",
21
+ "README.md"
22
+ ],
23
+ "scripts": {
24
+ "test": "node test-sdk.js"
25
+ },
26
+ "keywords": [
27
+ "privacy",
28
+ "pii",
29
+ "redaction",
30
+ "sanitization",
31
+ "security",
32
+ "zero-trust",
33
+ "compliance",
34
+ "hipaa",
35
+ "soc2",
36
+ "gdpr",
37
+ "llm-privacy",
38
+ "openai",
39
+ "langchain"
40
+ ],
41
+ "author": "Ilya Sibiryakov (BrandMeWeb)",
42
+ "license": "MIT",
43
+ "homepage": "https://privacyscrubber.com/?utm_source=npm&utm_medium=readme&utm_campaign=sdk",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "git+https://github.com/moxno/privacyscrubber-mcp.git"
47
+ },
48
+ "engines": {
49
+ "node": ">=18.0.0"
50
+ }
51
+ }
package/polyfill.js ADDED
@@ -0,0 +1,33 @@
1
+ import crypto from 'crypto';
2
+
3
+ if (typeof global !== 'undefined') {
4
+ if (typeof global.window === 'undefined') {
5
+ global.window = {
6
+ crypto: {
7
+ getRandomValues: (arr) => crypto.randomFillSync(arr)
8
+ },
9
+ addEventListener: () => {},
10
+ location: { search: '', hash: '' }
11
+ };
12
+ }
13
+ if (typeof global.document === 'undefined') {
14
+ global.document = {
15
+ readyState: 'complete',
16
+ getElementById: () => null,
17
+ addEventListener: () => {}
18
+ };
19
+ }
20
+ if (typeof global.localStorage === 'undefined') {
21
+ global.localStorage = {
22
+ getItem: () => null,
23
+ setItem: () => {},
24
+ removeItem: () => {}
25
+ };
26
+ }
27
+ if (typeof global.atob === 'undefined') {
28
+ global.atob = (str) => Buffer.from(str, 'base64').toString('binary');
29
+ }
30
+ if (typeof global.btoa === 'undefined') {
31
+ global.btoa = (str) => Buffer.from(str, 'binary').toString('base64');
32
+ }
33
+ }