@privacyscrubber/sdk 2.2.10 → 2.3.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/browser.js CHANGED
@@ -125,29 +125,77 @@ export function buildAuditTelemetry(currentTokenMap = {}) {
125
125
  frameworksSet.add('GDPR (Art. 4)');
126
126
  frameworksSet.add('CCPA/CPRA');
127
127
  }
128
- if (types.includes('ID') || types.includes('SSN') || types.includes('PASSPORT')) {
128
+ if (types.includes('ID') || types.includes('SSN') || types.includes('PASSPORT') || types.includes('NATIONAL_ID')) {
129
129
  frameworksSet.add('SOC 2 Type II');
130
130
  frameworksSet.add('ISO 27001 (A.8.11)');
131
131
  }
132
- if (types.some(t => ['CREDIT_CARD', 'BANK', 'IBAN', 'FINANCIAL', 'CARD', 'STRIPE_KEY'].includes(t))) {
132
+ if (types.some(t => ['CREDIT_CARD', 'BANK', 'IBAN', 'FINANCIAL', 'CARD', 'STRIPE_KEY', 'TAX_ID', 'EIN', 'W2', '1099', 'SALARY', 'WAGE'].includes(t))) {
133
133
  frameworksSet.add('PCI DSS v4.0');
134
+ frameworksSet.add('IRC § 7216 & GLBA');
134
135
  }
135
- if (types.some(t => ['MRN', 'HEALTH', 'MEDICAL', 'PATIENT'].includes(t))) {
136
+ if (types.some(t => ['MRN', 'HEALTH', 'MEDICAL', 'PATIENT', 'DIAGNOSIS', 'NPI', 'DEA'].includes(t))) {
136
137
  frameworksSet.add('HIPAA §164.514');
137
138
  }
138
- if (types.some(t => ['API_KEY', 'SECRET', 'PASSWORD', 'TOKEN', 'KEY', 'AWS_KEY', 'JWT_TOKEN', 'API_TOKEN'].includes(t))) {
139
+ if (types.some(t => ['CASE_NUMBER', 'DOCKET', 'PRIVILEGED', 'LEGAL', 'ATTORNEY', 'COURT', 'CONTRACT', 'NDA'].includes(t))) {
140
+ frameworksSet.add('FRE 502 (Privilege)');
141
+ }
142
+ if (types.some(t => ['API_KEY', 'SECRET', 'PASSWORD', 'TOKEN', 'KEY', 'AWS_KEY', 'JWT_TOKEN', 'API_TOKEN', 'CREDENTIAL'].includes(t))) {
139
143
  frameworksSet.add('NIST SP 800-53');
140
144
  }
145
+ if (totalCount > 0) {
146
+ frameworksSet.add('EU AI Act (Art. 50)');
147
+ }
148
+
149
+ const ipHoldings = {
150
+ standard: 'IETF draft-sibiryakov-ztds-protocol-00',
151
+ standardUrl: 'https://datatracker.ietf.org/doc/draft-sibiryakov-ztds-protocol/',
152
+ patent: 'IL 331905 (Tracking: 94221)',
153
+ wipoDas: 'B17B',
154
+ trademark: 'ZTDS™ Reg. #182655957 (ILPO Cl 9 & 42)'
155
+ };
156
+
157
+ const statutoryCitations = [
158
+ {
159
+ statute: 'EU AI Act (Art. 50)',
160
+ scope: 'AI Transparency & Data Minimization',
161
+ rule: '0-byte external network transmission; prevents unauthorized ingestion into frontier models.'
162
+ },
163
+ {
164
+ statute: 'GDPR (Art. 4, 25, 28, 32)',
165
+ scope: 'Pseudonymization & Processor Liability',
166
+ rule: 'Volatile client-side tokenization eliminates GDPR Article 28 data processor liability.'
167
+ },
168
+ {
169
+ statute: 'HIPAA 45 CFR § 164.514(b)',
170
+ scope: 'Safe Harbor De-Identification',
171
+ rule: 'Deterministic removal of all 18 PHI identifiers prior to AI analysis.'
172
+ },
173
+ {
174
+ statute: 'IRC § 7216 & IRS Pub. 1075',
175
+ scope: 'Tax Return Information Confidentiality',
176
+ rule: 'Strict criminal penalty safeguard against unauthorized disclosure of tax records to cloud LLMs.'
177
+ },
178
+ {
179
+ statute: 'FRE 502',
180
+ scope: 'Attorney-Client Privilege Preservation',
181
+ rule: 'Automated in-memory redaction prevents inadvertent waiver of legal privilege in AI workflows.'
182
+ }
183
+ ];
141
184
 
142
185
  return {
143
186
  totalCount,
144
187
  entities,
145
188
  types,
146
189
  riskLevel,
147
- frameworksList: Array.from(frameworksSet)
190
+ frameworksList: Array.from(frameworksSet),
191
+ statutoryCitations,
192
+ ipHoldings,
193
+ multimodalEgress: '0.00 Bytes (Solid Blackout & Burn-in Verified)'
148
194
  };
149
195
  }
150
196
 
197
+ export const buildCisoAuditTelemetry = buildAuditTelemetry;
198
+
151
199
  export function formatAuditReceipt(telemetry) {
152
200
  if (!telemetry || telemetry.totalCount === 0) {
153
201
  return "> 🛡️ **PrivacyScrubber Audit Receipt**: CLEAN (ZERO PII DETECTED)\n" +
@@ -665,6 +713,7 @@ export default {
665
713
  validateLicense,
666
714
  validateLicenseKey,
667
715
  buildAuditTelemetry,
716
+ buildCisoAuditTelemetry,
668
717
  formatAuditReceipt,
669
718
  PrivacyScrubberEngine,
670
719
  PrivacyScrubberLicenseError,
package/cli.js CHANGED
@@ -14,7 +14,7 @@ import path from 'node:path';
14
14
  import crypto from 'node:crypto';
15
15
  import http from 'node:http';
16
16
  import { fileURLToPath } from 'node:url';
17
- import { sanitize, restore, generateProcurementMemo, StreamingDetokenizer } from './index.js';
17
+ import { sanitize, restore, validateLicense, generateProcurementMemo, StreamingDetokenizer } from './index.js';
18
18
 
19
19
  const __filename = fileURLToPath(import.meta.url);
20
20
  const __dirname = path.dirname(__filename);
@@ -61,17 +61,24 @@ if (args.includes('sidecar') || args.includes('--sidecar') || args.includes('ser
61
61
  if (portIdx !== -1 && args[portIdx + 1]) {
62
62
  port = parseInt(args[portIdx + 1], 10);
63
63
  }
64
+ let host = process.env.HOST || '127.0.0.1';
65
+ const hostIdx = args.indexOf('--host');
66
+ if (hostIdx !== -1 && args[hostIdx + 1]) {
67
+ host = args[hostIdx + 1];
68
+ }
64
69
  let socketPath = null;
65
70
  const sockIdx = args.indexOf('--socket');
66
71
  if (sockIdx !== -1 && args[sockIdx + 1]) {
67
72
  socketPath = args[sockIdx + 1];
68
73
  }
69
74
 
75
+ const defaultEnvKey = process.env.PRIVACYSCRUBBER_KEY || process.env.PRIVACYSCRUBBER_LICENSE_KEY || process.env.ZTDS_LICENSE_KEY || '';
76
+
70
77
  const server = http.createServer((req, res) => {
71
- // CORS headers for local loopback
78
+ // CORS headers for local loopback & container IPC
72
79
  res.setHeader('Access-Control-Allow-Origin', '*');
73
80
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
74
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
81
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-License-Key');
75
82
 
76
83
  if (req.method === 'OPTIONS') {
77
84
  res.writeHead(204);
@@ -79,9 +86,46 @@ if (args.includes('sidecar') || args.includes('--sidecar') || args.includes('ser
79
86
  return;
80
87
  }
81
88
 
82
- if (req.method === 'GET' && (req.url === '/' || req.url === '/health')) {
89
+ const reqUrl = req.url ? req.url.split('?')[0] : '/';
90
+
91
+ // Extract authorization key from header if present
92
+ let headerKey = '';
93
+ const authHeader = req.headers['authorization'] || '';
94
+ if (authHeader.startsWith('Bearer ')) {
95
+ headerKey = authHeader.slice(7).trim();
96
+ } else if (authHeader) {
97
+ headerKey = authHeader.trim();
98
+ }
99
+ const xKey = req.headers['x-license-key'] ? String(req.headers['x-license-key']).trim() : '';
100
+ const effectiveServerKey = headerKey || xKey || defaultEnvKey;
101
+
102
+ if (req.method === 'GET' && (reqUrl === '/' || reqUrl === '/health' || reqUrl === '/v1/health')) {
103
+ const lic = validateLicense(effectiveServerKey);
83
104
  res.writeHead(200, { 'Content-Type': 'application/json' });
84
- res.end(JSON.stringify({ status: 'ok', engine: 'ZTDS', tier: 'Developer SDK', version: '2.2.6' }));
105
+ res.end(JSON.stringify({
106
+ status: 'ok',
107
+ engine: 'ZTDS',
108
+ tier: lic.valid ? (lic.tier || 'SDK') : 'Community (Free Tier)',
109
+ licensed: !!lic.valid,
110
+ version: '2.2.6',
111
+ networkEgress: '0 bytes (100% in-memory RAM)',
112
+ uptimeSec: Math.floor(process.uptime()),
113
+ memoryMb: (process.memoryUsage().heapUsed / (1024 * 1024)).toFixed(2)
114
+ }));
115
+ return;
116
+ }
117
+
118
+ if (req.method === 'GET' && (reqUrl === '/metrics' || reqUrl === '/v1/metrics')) {
119
+ const mem = process.memoryUsage();
120
+ res.writeHead(200, { 'Content-Type': 'text/plain; version=0.0.4' });
121
+ res.end(
122
+ `# HELP ztds_uptime_seconds Process uptime\n` +
123
+ `# TYPE ztds_uptime_seconds counter\n` +
124
+ `ztds_uptime_seconds ${Math.floor(process.uptime())}\n` +
125
+ `# HELP ztds_memory_heap_bytes Process heap usage\n` +
126
+ `# TYPE ztds_memory_heap_bytes gauge\n` +
127
+ `ztds_memory_heap_bytes ${mem.heapUsed}\n`
128
+ );
85
129
  return;
86
130
  }
87
131
 
@@ -99,9 +143,16 @@ if (args.includes('sidecar') || args.includes('--sidecar') || args.includes('ser
99
143
  req.on('end', () => {
100
144
  try {
101
145
  const payload = bodyStr ? JSON.parse(bodyStr) : {};
102
- if (req.url === '/scrub' || req.url === '/sanitize') {
146
+ const isScrub = reqUrl === '/scrub' || reqUrl === '/sanitize' || reqUrl === '/v1/scrub' || reqUrl === '/v1/sanitize';
147
+ const isUnscrub = reqUrl === '/unscrub' || reqUrl === '/restore' || reqUrl === '/v1/unscrub' || reqUrl === '/v1/restore';
148
+
149
+ if (isScrub) {
103
150
  const text = payload.text || '';
104
151
  const opts = payload.options || { profile: payload.profile || 'General', detectSecrets: true };
152
+ if (!opts.licenseKey) {
153
+ opts.licenseKey = payload.licenseKey || effectiveServerKey;
154
+ }
155
+
105
156
  const start = process.hrtime.bigint();
106
157
  const result = sanitize(text, opts);
107
158
  const end = process.hrtime.bigint();
@@ -111,6 +162,7 @@ if (args.includes('sidecar') || args.includes('--sidecar') || args.includes('ser
111
162
  res.end(JSON.stringify({
112
163
  sanitizedText: result.scrubbedText,
113
164
  tokenMap: result.tokenMap,
165
+ license: result.license,
114
166
  telemetry: {
115
167
  ...result.telemetry,
116
168
  latencyUs: durationUs,
@@ -120,7 +172,7 @@ if (args.includes('sidecar') || args.includes('--sidecar') || args.includes('ser
120
172
  return;
121
173
  }
122
174
 
123
- if (req.url === '/unscrub' || req.url === '/restore') {
175
+ if (isUnscrub) {
124
176
  const text = payload.text || '';
125
177
  const tokenMap = payload.tokenMap || {};
126
178
  const { restoredText, restoredCount } = restore(text, tokenMap);
@@ -130,10 +182,10 @@ if (args.includes('sidecar') || args.includes('--sidecar') || args.includes('ser
130
182
  }
131
183
 
132
184
  res.writeHead(404, { 'Content-Type': 'application/json' });
133
- res.end(JSON.stringify({ error: 'Endpoint Not Found. Supported: /scrub, /unscrub, /health' }));
185
+ res.end(JSON.stringify({ error: 'Endpoint Not Found. Supported: /v1/scrub, /v1/unscrub, /v1/health, /v1/metrics' }));
134
186
  } catch (err) {
135
187
  res.writeHead(400, { 'Content-Type': 'application/json' });
136
- res.end(JSON.stringify({ error: 'Invalid JSON payload', message: err.message }));
188
+ res.end(JSON.stringify({ error: 'Invalid JSON payload or processing error', message: err.message }));
137
189
  }
138
190
  });
139
191
  return;
@@ -152,9 +204,9 @@ if (args.includes('sidecar') || args.includes('--sidecar') || args.includes('ser
152
204
  console.log(`[ZTDS Sidecar] Ready for Python/Go local IPC with <1ms in-memory execution.`);
153
205
  });
154
206
  } else {
155
- server.listen(port, '127.0.0.1', () => {
156
- console.log(`[ZTDS Sidecar] HTTP Daemon listening on http://127.0.0.1:${port}`);
157
- console.log(`[ZTDS Sidecar] Endpoints: POST /scrub | POST /unscrub | GET /health`);
207
+ server.listen(port, host, () => {
208
+ console.log(`[ZTDS Sidecar] HTTP Daemon listening on http://${host}:${port}`);
209
+ console.log(`[ZTDS Sidecar] Endpoints: POST /v1/scrub | POST /v1/unscrub | GET /v1/health | GET /v1/metrics`);
158
210
  console.log(`[ZTDS Sidecar] Latency: <1.2ms (Zero network hops, local loopback)`);
159
211
  });
160
212
  }
package/index.d.ts CHANGED
@@ -103,12 +103,28 @@ export interface LicenseMetadata {
103
103
  recommendation?: string;
104
104
  }
105
105
 
106
+ export interface StatutoryCitation {
107
+ statute: string;
108
+ scope: string;
109
+ rule: string;
110
+ }
111
+
112
+ export interface IpHoldings {
113
+ standard: string;
114
+ standardUrl: string;
115
+ patent: string;
116
+ wipoDas: string;
117
+ trademark: string;
118
+ }
119
+
106
120
  export interface AuditTelemetry {
107
121
  totalCount: number;
108
122
  entities: Record<string, number>;
109
123
  types: string[];
110
124
  riskLevel: 'CLEAN (ZERO PII)' | 'LOW EXPOSURE' | 'MODERATE EXPOSURE' | 'CRITICAL (HIGH EXPOSURE)';
111
125
  frameworksList: string[];
126
+ statutoryCitations?: StatutoryCitation[];
127
+ ipHoldings?: IpHoldings;
112
128
  }
113
129
 
114
130
  export interface SanitizeResult {
@@ -160,6 +176,7 @@ export function validateLicense(key: string): LicenseValidationResult;
160
176
  export function validateLicenseKey(key: string): 'PRO' | 'TEAMS' | 'SDK' | 'ENTERPRISE' | 'OEM' | null;
161
177
 
162
178
  export function buildAuditTelemetry(currentTokenMap?: Record<string, string> | string[]): AuditTelemetry;
179
+ export const buildCisoAuditTelemetry: typeof buildAuditTelemetry;
163
180
  export function formatAuditReceipt(telemetry: AuditTelemetry): string;
164
181
 
165
182
  export interface AuditEventData {
package/index.js CHANGED
@@ -142,29 +142,80 @@ export function buildAuditTelemetry(currentTokenMap = {}) {
142
142
  frameworksSet.add('GDPR (Art. 4)');
143
143
  frameworksSet.add('CCPA/CPRA');
144
144
  }
145
- if (types.includes('ID') || types.includes('SSN') || types.includes('PASSPORT')) {
145
+ if (types.includes('ID') || types.includes('SSN') || types.includes('PASSPORT') || types.includes('NATIONAL_ID')) {
146
146
  frameworksSet.add('SOC 2 Type II');
147
147
  frameworksSet.add('ISO 27001 (A.8.11)');
148
148
  }
149
- if (types.some(t => ['CREDIT_CARD', 'BANK', 'IBAN', 'FINANCIAL', 'CARD', 'STRIPE_KEY'].includes(t))) {
149
+ if (types.some(t => ['CREDIT_CARD', 'BANK', 'IBAN', 'FINANCIAL', 'CARD', 'STRIPE_KEY', 'TAX_ID', 'EIN', 'W2', '1099', 'SALARY', 'WAGE'].includes(t))) {
150
150
  frameworksSet.add('PCI DSS v4.0');
151
+ frameworksSet.add('IRC § 7216 & GLBA');
151
152
  }
152
- if (types.some(t => ['MRN', 'HEALTH', 'MEDICAL', 'PATIENT'].includes(t))) {
153
+ if (types.some(t => ['MRN', 'HEALTH', 'MEDICAL', 'PATIENT', 'DIAGNOSIS', 'NPI', 'DEA'].includes(t))) {
153
154
  frameworksSet.add('HIPAA §164.514');
154
155
  }
155
- if (types.some(t => ['API_KEY', 'SECRET', 'PASSWORD', 'TOKEN', 'KEY', 'AWS_KEY', 'JWT_TOKEN', 'API_TOKEN'].includes(t))) {
156
+ if (types.some(t => ['CASE_NUMBER', 'DOCKET', 'PRIVILEGED', 'LEGAL', 'ATTORNEY', 'COURT', 'CONTRACT', 'NDA'].includes(t))) {
157
+ frameworksSet.add('FRE 502 (Privilege)');
158
+ }
159
+ if (types.some(t => ['API_KEY', 'SECRET', 'PASSWORD', 'TOKEN', 'KEY', 'AWS_KEY', 'JWT_TOKEN', 'API_TOKEN', 'CREDENTIAL'].includes(t))) {
156
160
  frameworksSet.add('NIST SP 800-53');
157
161
  }
162
+ if (totalCount > 0) {
163
+ frameworksSet.add('EU AI Act (Art. 50)');
164
+ }
165
+
166
+ const ipHoldings = {
167
+ standard: 'IETF draft-sibiryakov-ztds-protocol-00',
168
+ standardUrl: 'https://datatracker.ietf.org/doc/draft-sibiryakov-ztds-protocol/',
169
+ patent: 'IL 331905 (Tracking: 94221)',
170
+ wipoDas: 'B17B',
171
+ trademark: 'ZTDS™ Reg. #182655957 (ILPO Cl 9 & 42)'
172
+ };
173
+
174
+ const statutoryCitations = [
175
+ {
176
+ statute: 'EU AI Act (Art. 50)',
177
+ scope: 'AI Transparency & Data Minimization',
178
+ rule: '0-byte external network transmission; prevents unauthorized ingestion into frontier models.'
179
+ },
180
+ {
181
+ statute: 'GDPR (Art. 4, 25, 28, 32)',
182
+ scope: 'Pseudonymization & Processor Liability',
183
+ rule: 'Volatile client-side tokenization eliminates GDPR Article 28 data processor liability.'
184
+ },
185
+ {
186
+ statute: 'HIPAA 45 CFR § 164.514(b)',
187
+ scope: 'Safe Harbor De-Identification',
188
+ rule: 'Deterministic removal of all 18 PHI identifiers prior to AI analysis.'
189
+ },
190
+ {
191
+ statute: 'IRC § 7216 & IRS Pub. 1075',
192
+ scope: 'Tax Return Information Confidentiality',
193
+ rule: 'Strict criminal penalty safeguard against unauthorized disclosure of tax records to cloud LLMs.'
194
+ },
195
+ {
196
+ statute: 'FRE 502',
197
+ scope: 'Attorney-Client Privilege Preservation',
198
+ rule: 'Automated in-memory redaction prevents inadvertent waiver of legal privilege in AI workflows.'
199
+ }
200
+ ];
158
201
 
159
202
  return {
160
203
  totalCount,
161
204
  entities,
162
205
  types,
163
206
  riskLevel,
164
- frameworksList: Array.from(frameworksSet)
207
+ frameworksList: Array.from(frameworksSet),
208
+ statutoryCitations,
209
+ ipHoldings,
210
+ multimodalEgress: '0.00 Bytes (Solid Blackout & Burn-in Verified)'
165
211
  };
166
212
  }
167
213
 
214
+ /**
215
+ * SSOT Alias for cross-plane parity with Web, Extension, and MCP.
216
+ */
217
+ export const buildCisoAuditTelemetry = buildAuditTelemetry;
218
+
168
219
  /**
169
220
  * Formats structured audit telemetry into a human-readable Markdown Audit Receipt.
170
221
  * @param {object} telemetry
@@ -716,6 +767,7 @@ export class PrivacyScrubberEngine {
716
767
  this.generateReceipt = config.generateReceipt !== false;
717
768
  this.sessionMap = { ...(config.initialSessionMap || {}) };
718
769
  this.ignoreList = new Set(config.ignoreList || []);
770
+ this.maxSessionTokens = config.maxSessionTokens || 10000;
719
771
 
720
772
  this.license = validateLicense(this.licenseKey);
721
773
  }
@@ -758,6 +810,14 @@ export class PrivacyScrubberEngine {
758
810
  const result = sanitize(text, mergedOptions);
759
811
  if (options.persistSession !== false) {
760
812
  Object.assign(this.sessionMap, result.tokenMap);
813
+ const maxTokens = options.maxSessionTokens || this.maxSessionTokens || 10000;
814
+ const keys = Object.keys(this.sessionMap);
815
+ if (keys.length > maxTokens) {
816
+ const pruneCount = keys.length - maxTokens;
817
+ for (let i = 0; i < pruneCount; i++) {
818
+ delete this.sessionMap[keys[i]];
819
+ }
820
+ }
761
821
  }
762
822
  return result;
763
823
  }
@@ -1359,9 +1419,30 @@ export class StreamingDetokenizer {
1359
1419
  this.replacer = buildStreamReplacer(this.tokenMap);
1360
1420
  this.pendingBuffer = '';
1361
1421
  this.hasTokens = Object.keys(this.tokenMap).length > 0;
1422
+ this._lastTokenCount = Object.keys(this.tokenMap).length;
1362
1423
  this.decoder = new TextDecoder('utf-8', { fatal: false });
1363
1424
  }
1364
1425
 
1426
+ /**
1427
+ * Dynamically updates the active token map for this streaming session.
1428
+ * @param {Record<string, string>} newMap
1429
+ */
1430
+ updateTokenMap(newMap) {
1431
+ if (newMap && typeof newMap === 'object') {
1432
+ this.tokenMap = newMap;
1433
+ this.replacer = buildStreamReplacer(this.tokenMap);
1434
+ this.hasTokens = Object.keys(this.tokenMap).length > 0;
1435
+ this._lastTokenCount = Object.keys(this.tokenMap).length;
1436
+ }
1437
+ }
1438
+
1439
+ /**
1440
+ * Resets the internal sliding window buffer.
1441
+ */
1442
+ reset() {
1443
+ this.pendingBuffer = '';
1444
+ }
1445
+
1365
1446
  /**
1366
1447
  * Feeds an incoming chunk (string, Uint8Array, or Buffer) and returns de-tokenized text.
1367
1448
  * Buffers any unclosed token candidates at chunk boundaries until closed or flushed.
@@ -1380,6 +1461,15 @@ export class StreamingDetokenizer {
1380
1461
  }
1381
1462
 
1382
1463
  if (!text) return '';
1464
+
1465
+ // Dynamically recompile if tokens were added to the referenced map
1466
+ const currentTokenCount = Object.keys(this.tokenMap).length;
1467
+ if (currentTokenCount !== this._lastTokenCount) {
1468
+ this.replacer = buildStreamReplacer(this.tokenMap);
1469
+ this.hasTokens = currentTokenCount > 0;
1470
+ this._lastTokenCount = currentTokenCount;
1471
+ }
1472
+
1383
1473
  if (!this.hasTokens) return text;
1384
1474
 
1385
1475
  this.pendingBuffer += text;
@@ -1431,7 +1521,7 @@ export class StreamingDetokenizer {
1431
1521
  */
1432
1522
  toNodeTransform() {
1433
1523
  const self = this;
1434
- return new Transform({
1524
+ const transformStream = new Transform({
1435
1525
  transform(chunk, encoding, callback) {
1436
1526
  try {
1437
1527
  const out = self.feed(chunk);
@@ -1449,8 +1539,14 @@ export class StreamingDetokenizer {
1449
1539
  } catch (err) {
1450
1540
  callback(err);
1451
1541
  }
1542
+ },
1543
+ destroy(err, callback) {
1544
+ self.reset();
1545
+ callback(err);
1452
1546
  }
1453
1547
  });
1548
+ transformStream.detokenizer = self;
1549
+ return transformStream;
1454
1550
  }
1455
1551
 
1456
1552
  /**
@@ -2014,6 +2110,8 @@ ${telemetry.frameworksList.map(f => `- **${f}**`).join('\n')}
2014
2110
  */
2015
2111
  export function createSanitizeStream(options = {}) {
2016
2112
  const engine = options.engine || new PrivacyScrubberEngine(options);
2113
+ const persistSession = options.persistSession !== false;
2114
+ const maxSessionTokens = options.maxSessionTokens || 10000;
2017
2115
  let tail = '';
2018
2116
 
2019
2117
  const stream = new Transform({
@@ -2024,7 +2122,12 @@ export function createSanitizeStream(options = {}) {
2024
2122
  tail = lines.pop() || '';
2025
2123
 
2026
2124
  for (let i = 0; i < lines.length; i++) {
2027
- const sanitized = engine.sanitize(lines[i], { persistSession: true, generateReceipt: false });
2125
+ const sanitized = engine.sanitize(lines[i], {
2126
+ ...options,
2127
+ persistSession,
2128
+ maxSessionTokens,
2129
+ generateReceipt: false
2130
+ });
2028
2131
  this.push(sanitized.scrubbedText + '\n');
2029
2132
  }
2030
2133
  callback();
@@ -2035,7 +2138,12 @@ export function createSanitizeStream(options = {}) {
2035
2138
  flush(callback) {
2036
2139
  try {
2037
2140
  if (tail.length > 0) {
2038
- const sanitized = engine.sanitize(tail, { persistSession: true, generateReceipt: false });
2141
+ const sanitized = engine.sanitize(tail, {
2142
+ ...options,
2143
+ persistSession,
2144
+ maxSessionTokens,
2145
+ generateReceipt: false
2146
+ });
2039
2147
  this.push(sanitized.scrubbedText);
2040
2148
  tail = '';
2041
2149
  }
@@ -2043,6 +2151,10 @@ export function createSanitizeStream(options = {}) {
2043
2151
  } catch (err) {
2044
2152
  callback(err);
2045
2153
  }
2154
+ },
2155
+ destroy(err, callback) {
2156
+ tail = '';
2157
+ callback(err);
2046
2158
  }
2047
2159
  });
2048
2160
 
@@ -2380,6 +2492,7 @@ export default {
2380
2492
  validateLicense,
2381
2493
  validateLicenseKey,
2382
2494
  buildAuditTelemetry,
2495
+ buildCisoAuditTelemetry,
2383
2496
  formatAuditReceipt,
2384
2497
  generateAuditReport,
2385
2498
  formatCefEvent,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@privacyscrubber/sdk",
3
- "version": "2.2.10",
3
+ "version": "2.3.1",
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.js",
6
6
  "browser": "browser.js",