@privacyscrubber/sdk 2.4.7 → 2.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/cli.js +1 -1
- 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/pyproject.toml +1 -1
- package/examples/python-bridge/setup.py +1 -1
- package/index.cjs +264 -33
- package/index.d.ts +8 -0
- package/index.js +264 -33
- package/package.json +1 -1
- package/ps-license-manager.js +1 -1
- package/ps-pii-engine.cjs +1 -1
- package/sarif.cjs +2 -2
- package/scrubber-core.cjs +1 -1
package/index.js
CHANGED
|
@@ -41,7 +41,7 @@ export const SDK_VERSION = (() => {
|
|
|
41
41
|
return pkgJson.version;
|
|
42
42
|
}
|
|
43
43
|
} catch (_) {}
|
|
44
|
-
return '2.4.
|
|
44
|
+
return '2.4.9';
|
|
45
45
|
})();
|
|
46
46
|
|
|
47
47
|
/**
|
|
@@ -134,7 +134,7 @@ export function validateLicenseKey(key) {
|
|
|
134
134
|
* @param {Record<string, string>|string[]} currentTokenMap
|
|
135
135
|
* @returns {object}
|
|
136
136
|
*/
|
|
137
|
-
export function buildAuditTelemetry(currentTokenMap = {}) {
|
|
137
|
+
export function buildAuditTelemetry(currentTokenMap = {}, extraStats = {}) {
|
|
138
138
|
const entities = {};
|
|
139
139
|
let totalCount = 0;
|
|
140
140
|
|
|
@@ -144,8 +144,9 @@ export function buildAuditTelemetry(currentTokenMap = {}) {
|
|
|
144
144
|
if (!t) continue;
|
|
145
145
|
const tokenStr = typeof t === 'string' ? t : ((t && (t.token || t.mask)) ? String(t.token || t.mask) : '');
|
|
146
146
|
if (!tokenStr) continue;
|
|
147
|
-
const match = tokenStr.match(/\[([A-
|
|
148
|
-
const
|
|
147
|
+
const match = tokenStr.match(/\[([A-Za-z0-9_]+)_\d+\]/);
|
|
148
|
+
const rawBase = match ? match[1] : tokenStr.replace(/\[|\]|<|>|\{|\}|__/g, '').replace(/_[0-9]+$/, '');
|
|
149
|
+
const baseType = (rawBase.replace(/[^A-Za-z0-9_]/g, '') || 'CUSTOM').toUpperCase();
|
|
149
150
|
entities[baseType] = (entities[baseType] || 0) + 1;
|
|
150
151
|
totalCount++;
|
|
151
152
|
}
|
|
@@ -155,7 +156,7 @@ export function buildAuditTelemetry(currentTokenMap = {}) {
|
|
|
155
156
|
let riskLevel = 'LOW EXPOSURE';
|
|
156
157
|
|
|
157
158
|
if (totalCount > 0) {
|
|
158
|
-
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));
|
|
159
|
+
const hasHighRisk = types.some(t => ['ID', 'SSN', 'CREDIT_CARD', 'PASSPORT', 'BANK', 'API_KEY', 'SECRET', 'PASSWORD', 'MRN', 'KEY', 'PHI', 'PRIVILEGE', 'AWS_KEY', 'STRIPE_KEY', 'JWT_TOKEN', 'API_TOKEN'].includes(t));
|
|
159
160
|
if (hasHighRisk || types.length >= 3 || totalCount >= 10) {
|
|
160
161
|
riskLevel = 'CRITICAL (HIGH EXPOSURE)';
|
|
161
162
|
} else if (types.length >= 2 || totalCount >= 3) {
|
|
@@ -180,10 +181,10 @@ export function buildAuditTelemetry(currentTokenMap = {}) {
|
|
|
180
181
|
frameworksSet.add('PCI DSS v4.0');
|
|
181
182
|
frameworksSet.add('IRC § 7216 & GLBA');
|
|
182
183
|
}
|
|
183
|
-
if (types.some(t => ['MRN', 'HEALTH', 'MEDICAL', 'PATIENT', 'DIAGNOSIS', 'NPI', 'DEA'].includes(t))) {
|
|
184
|
+
if (types.some(t => ['PHI', 'MRN', 'HEALTH', 'MEDICAL', 'PATIENT', 'DIAGNOSIS', 'NPI', 'DEA'].includes(t))) {
|
|
184
185
|
frameworksSet.add('HIPAA §164.514');
|
|
185
186
|
}
|
|
186
|
-
if (types.some(t => ['CASE_NUMBER', 'DOCKET', 'PRIVILEGED', 'LEGAL', 'ATTORNEY', 'COURT', 'CONTRACT', 'NDA'].includes(t))) {
|
|
187
|
+
if (types.some(t => ['PRIVILEGE', 'CASE_NUMBER', 'DOCKET', 'PRIVILEGED', 'LEGAL', 'ATTORNEY', 'COURT', 'CONTRACT', 'NDA'].includes(t))) {
|
|
187
188
|
frameworksSet.add('FRE 502 (Privilege)');
|
|
188
189
|
}
|
|
189
190
|
if (types.some(t => ['API_KEY', 'SECRET', 'PASSWORD', 'TOKEN', 'KEY', 'AWS_KEY', 'JWT_TOKEN', 'API_TOKEN', 'CREDENTIAL'].includes(t))) {
|
|
@@ -193,6 +194,26 @@ export function buildAuditTelemetry(currentTokenMap = {}) {
|
|
|
193
194
|
frameworksSet.add('EU AI Act (Art. 50)');
|
|
194
195
|
}
|
|
195
196
|
|
|
197
|
+
const frameworksList = Array.from(frameworksSet);
|
|
198
|
+
const getFrameworkSlug = (name) => {
|
|
199
|
+
const lower = name.toLowerCase();
|
|
200
|
+
if (lower.includes('gdpr')) return 'gdpr';
|
|
201
|
+
if (lower.includes('ccpa')) return 'ccpa';
|
|
202
|
+
if (lower.includes('soc')) return 'soc2';
|
|
203
|
+
if (lower.includes('iso')) return 'iso27001';
|
|
204
|
+
if (lower.includes('pci')) return 'pci';
|
|
205
|
+
if (lower.includes('hipaa')) return 'hipaa';
|
|
206
|
+
if (lower.includes('nist')) return 'nist';
|
|
207
|
+
if (lower.includes('irc') || lower.includes('7216')) return 'irc7216';
|
|
208
|
+
if (lower.includes('fre') || lower.includes('502')) return 'fre502';
|
|
209
|
+
if (lower.includes('ai act')) return 'euaiact';
|
|
210
|
+
return lower.replace(/[^a-z0-9]/g, '');
|
|
211
|
+
};
|
|
212
|
+
const triggeredFrameworks = frameworksList.map(name => ({
|
|
213
|
+
name,
|
|
214
|
+
id: getFrameworkSlug(name)
|
|
215
|
+
}));
|
|
216
|
+
|
|
196
217
|
const ipHoldings = {
|
|
197
218
|
standard: 'IETF draft-sibiryakov-ztds-protocol-01',
|
|
198
219
|
standardUrl: 'https://datatracker.ietf.org/doc/draft-sibiryakov-ztds-protocol/',
|
|
@@ -244,12 +265,33 @@ export function buildAuditTelemetry(currentTokenMap = {}) {
|
|
|
244
265
|
}
|
|
245
266
|
];
|
|
246
267
|
|
|
268
|
+
const rawLicenseTier = String(extraStats?.licenseTier || '').trim().toUpperCase();
|
|
269
|
+
const isOrganizationalScope = ['TEAMS', 'SDK', 'OEM', 'ENTERPRISE', 'ENTERPRISE_AIRGAP'].includes(rawLicenseTier);
|
|
270
|
+
const auditScope = isOrganizationalScope ? {
|
|
271
|
+
tier: rawLicenseTier,
|
|
272
|
+
isOrganizational: true,
|
|
273
|
+
badge: 'ORGANIZATIONAL MULTI-SEAT COMPLIANCE SCOPE',
|
|
274
|
+
coverage: 'Covers department, clinic, or firm personnel under unified ZTDS policy governance.',
|
|
275
|
+
upgradeRecommended: false,
|
|
276
|
+
upgradeUrl: null
|
|
277
|
+
} : {
|
|
278
|
+
tier: rawLicenseTier || 'SINGLE_OPERATOR',
|
|
279
|
+
isOrganizational: false,
|
|
280
|
+
badge: 'SINGLE-OPERATOR PERSONAL SCOPE',
|
|
281
|
+
coverage: 'Valid for individual workstation only. Multi-employee clinics, CPA firms, and departments require TEAMS ($99/mo flat, unlimited seats) for organizational audit coverage.',
|
|
282
|
+
upgradeRecommended: true,
|
|
283
|
+
upgradeUrl: 'https://privacyscrubber.com/pricing/?tier=teams&utm_source=audit_receipt&utm_medium=scope_upsell'
|
|
284
|
+
};
|
|
285
|
+
|
|
247
286
|
return {
|
|
248
287
|
totalCount,
|
|
288
|
+
totalDetected: totalCount,
|
|
249
289
|
entities,
|
|
250
290
|
types,
|
|
251
291
|
riskLevel,
|
|
252
|
-
frameworksList
|
|
292
|
+
frameworksList,
|
|
293
|
+
triggeredFrameworks,
|
|
294
|
+
auditScope,
|
|
253
295
|
statutoryCitations,
|
|
254
296
|
ipHoldings,
|
|
255
297
|
agenticProtocol,
|
|
@@ -303,6 +345,19 @@ export function _resetCommunityNoticeForTesting() {
|
|
|
303
345
|
communityNoticeLogged = false;
|
|
304
346
|
}
|
|
305
347
|
|
|
348
|
+
export const FREE_PROD_SPECIALIZED_CALL_LIMIT = 250;
|
|
349
|
+
let _freeProdSpecializedCalls = 0;
|
|
350
|
+
let _prodProfileFallbackNoticeLogged = false;
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Resets the production specialized call quota state for testing purposes.
|
|
354
|
+
* @private
|
|
355
|
+
*/
|
|
356
|
+
export function _resetProdQuotaForTesting() {
|
|
357
|
+
_freeProdSpecializedCalls = 0;
|
|
358
|
+
_prodProfileFallbackNoticeLogged = false;
|
|
359
|
+
}
|
|
360
|
+
|
|
306
361
|
let devSecretNoticeLogged = false;
|
|
307
362
|
|
|
308
363
|
function emitDevSecretNoticeIfNeeded(isPro, tokenMap = {}, limit = 15000) {
|
|
@@ -483,6 +538,82 @@ We request approval to acquire a **Developer SDK License** (\$299/month flat or
|
|
|
483
538
|
* auditReceipt: string
|
|
484
539
|
* }}
|
|
485
540
|
*/
|
|
541
|
+
const SDK_VERTICAL_DETECTORS = [
|
|
542
|
+
{ profile: 'medical', label: 'Medical PHI (HIPAA)', regex: /\b(?:Patient\s*(?:Name|ID|DOB|Record)|MRN\b|Medical\s+Record|Attending\s+Physician|Chief\s+Complaint|Diagnosis\s*:|ICD-10|HIPAA|Clinical\s+Intake|Discharge\s+Summary|MedPod|NPI\s*(?:#|No|:)?\s*\d{10}|Medicare\s*(?:ID|Beneficiary|Claim)|Medicaid\s*ID|Prescription|Rx\s*#|Blood\s+Type|Vital\s+Signs|Radiology|Lab\s+Results|EHR\b|EMR\b)\b/gi },
|
|
543
|
+
{ profile: 'accounting', label: 'Tax & Accounting', regex: /\b(?:Form\s*1040|Form\s*W-2|Form\s*1099|Adjusted\s+Gross\s+Income|Assessable\s+Income|Employer\s+Identification\s+Number|Schedule\s+C\b|Steuer-ID|USt-IdNr|Tax\s+Return|General\s+Ledger|Withholding\s+Allowance|Total\s+Tax\s+Liability|ABN\b|TFN\b|Tax\s+File\s+Number|Australian\s+Business\s+Number|Business\s+Activity\s+Statement|PAYG\s+(?:Withholding|Instalment)|Franking\s+Credits?)\b/gi },
|
|
544
|
+
{ profile: 'wealthmgmt', label: 'Wealth Management', regex: /\b(?:Revocable\s+Living\s+Trust|Irrevocable\s+(?:Generation-Skipping\s+)?Trust|Grantor\s*:|Settlor\s*:|Trustee\s*:|Beneficiary\s+Designation|Roth\s+IRA|Asset\s+Allocation|FBO\b|CUSIP\s*:|Grantor\s+Retained\s+Annuity\s+Trust|AUM\s*:)\b/gi },
|
|
545
|
+
{ profile: 'legal', label: 'Legal & Contracts', regex: /\b(?:Non-Disclosure\s+Agreement|Attorney-Client\s+Privilege|Docket\s+No|Hereinafter\s+referred|Indemnification\s+Clause|Governing\s+Law\s+and\s+Jurisdiction|Plaintiff\b|Defendant\b|Subpoena\b|Deposition\b|State\s+Bar\s*#)\b/gi },
|
|
546
|
+
{ profile: 'finance', label: 'Finance & Banking', regex: /\b(?:Wire\s+Transfer\s+Authorization|SWIFT\s*\/?\s*BIC\s*:|IBAN\s*:\s*[A-Z]{2}\d{2}|ABA\s+Routing|ACH\s+Transfer|PCI-DSS|Primary\s+Account\s+Number)\b/gi },
|
|
547
|
+
{ profile: 'hr', label: 'HR & Resumes', regex: /\b(?:Employee\s*ID|Performance\s+Improvement\s+Plan|Payroll\s+Register|Gross\s+Pay\b|Net\s+Pay\b|Candidate\s+Evaluation|Compensation\s+Band|Sozialversicherungsnummer|Personalnummer|EEID\s*:)\b/gi },
|
|
548
|
+
{ profile: 'realestate', label: 'Real Estate', regex: /\b(?:Assessor\s+Parcel\s+Number|APN\s*:|MLS\s*#|Closing\s+Disclosure|Escrow\s+(?:Account|Number|Officer)|Property\s+Deed|Title\s+Insurance|Lease\s+Application)\b/gi },
|
|
549
|
+
{ profile: 'dev', label: 'API & Dev Logs', regex: /\b(?:BEGIN\s+(?:RSA|OPENSSH|EC|PRIVATE)\s+PRIVATE\s+KEY|AWS_SECRET_ACCESS_KEY|DATABASE_URL\s*=|GITHUB_TOKEN\s*=|Bearer\s+eyJ[A-Za-z0-9_-]+)\b/g },
|
|
550
|
+
{ profile: 'sales', label: 'Sales & CRM', regex: /\b(?:CRM\s+(?:Opportunity|Export)|HubSpot\b|Salesforce\b|MEDDPICC\b|Primary\s+Buyer\s*:|MSA\s+Redlines|Deal\s+Stage|Pipeline\s+Forecast|Opportunity\s+ID|Account\s+Executive)\b/gi },
|
|
551
|
+
{ profile: 'engineering', label: 'DevOps & Infra', regex: /\b(?:Incident\s+Postmortem|Kubeconfig|Kubernetes\s+Cluster|Helm\s+Chart|Terraform\s+State|DMAIC\b|Bill\s+of\s+Materials|BOM\s+Spec|CNC\s+Machining|Shop\s+Floor\s+Batch)\b/gi },
|
|
552
|
+
{ profile: 'insurance', label: 'Insurance & Claims', regex: /\b(?:Claim\s+Settlement|Policyholder\s*:|Policy\s*#?POL-|Claim\s+Number|Adjuster\s*:|Loss\s+Location|Loss\s+Run|Subrogation\b|FNOL\b|Deductible\s*:)\b/gi },
|
|
553
|
+
{ profile: 'pharma', label: 'Pharma & Trials', regex: /\b(?:Clinical\s+Trial(?:\s+Protocol)?|NCT\d{8}\b|IRB\s+Protocol|FDA\s+1572|Adverse\s+Event|Pharmacovigilance|Principal\s+Investigator|Cohort\s+[A-Z]\b|Blinded\s+Study|CDISC\b|IND\s*(?:#|Protocol))\b/gi },
|
|
554
|
+
{ profile: 'underwriting', label: 'Loan Underwriting & Income', regex: /\b(?:Loan\s+Underwriting|Mortgage\s+Appraisal|Loan\s+Application\s+#?LOS|FICO\s*:\s*\d{3}|Appraised\s+Value\s*:|80%\s+LTV|Monthly\s+Qualifying\s*:|YTD\s+Earnings|Borrower\s*:)\b/gi },
|
|
555
|
+
{ profile: 'security', label: 'Security & SIEM', regex: /\b(?:SIEM\s+Alert|CVE-\d{4}-\d{4,7}\b|Threat\s*:\s*CRITICAL|Unauthorized\s+S3\s+bucket|SOC\s+Incident|Firewall\s+Rule\s+Violation|Intrusion\s+Detection)\b/gi },
|
|
556
|
+
{ profile: 'support', label: 'Support & Tickets', regex: /\b(?:Support\s+Ticket|Ticket\s*#INC-|Zendesk\b|Intercom\b|Billing\s+Discrepancy|SLA\s+Escalation|CSAT\s+Score|Customer\s+Support\s+Escalation)\b/gi },
|
|
557
|
+
{ profile: 'compliance', label: 'Compliance Audit', regex: /\b(?:Suspicious\s+Activity\s+Report|FinCEN\s+SAR|BSA\s+Officer|31\s+CFR\b|ROPA\s+Register|Data\s+Protection\s+Impact\s+Assessment|SOC\s*2\s+Type\s+II|ISO\s*27001\s+Audit|CMMC\s+Level|ITAR\s+Controlled)\b/gi },
|
|
558
|
+
{ profile: 'ccpa', label: 'CCPA / CPRA Privacy', regex: /\b(?:CCPA\s*\/?\s*CPRA|1798\.105|Right\s+to\s+Delete|Opt-out\s+of\s+Sale|Ticket\s*#?DSAR-)\b/gi },
|
|
559
|
+
{ profile: 'bizops', label: 'Biz Ops & M&A', regex: /\b(?:Vendor\s+Master\s+Agreement|Bill\s+of\s+Lading|Customs\s+Declaration|Purchase\s+Order\s*#?PO-|Supply\s+Chain\s+Manifest)\b/gi },
|
|
560
|
+
{ profile: 'biotech', label: 'Biotech & Genomics', regex: /\b(?:Sequencing\s+Platform|Illumina\s+NovaSeq|Flowcell\s*:|FastQ\s+Sample|ClinVar\b|HGVS\s+Variant|Genomic\s+Sequencing|Bioinformatics\s+Intake|BRCA[12]\s+c\.)\b/gi },
|
|
561
|
+
{ profile: 'automotive', label: 'Automotive & EV', regex: /\b(?:Connected\s+Vehicle\s+Telematics|ECU\s+Serial|Telematics\s+Device|ASIL-D\b|DTC\s+P\d{4}\b|CAN-Bus\s+Log)\b/gi },
|
|
562
|
+
{ profile: 'energy', label: 'Energy & Smart Grid', regex: /\b(?:Critical\s+Infrastructure\s+SCADA|Substation\s+ID|Feeder\s+ID|Smart\s+Meter\s*:|ESI\s+ID\s*:|NERC\s+CIP|BES\s+Cyber\s+Asset)\b/gi },
|
|
563
|
+
{ profile: 'hospitality', label: 'Hospitality & Travel', regex: /\b(?:Airline\s+Passenger\s+Dossier|PNR\s*:\s*[A-Z0-9]{6}|e-Ticket\s*:|Boarding\s+Pass\s*:|Known\s+Traveler\s+KTN|Guest\s+Folio|Marriott\s+Bonvoy|SkyMiles)\b/gi },
|
|
564
|
+
{ profile: 'telecom', label: 'Telecom & Call Center', regex: /\b(?:Telecom\s+Subscriber\s+CDR|MSISDN\s*:|IMSI\s*:\s*\d{14,15}|IMEI\s*:\s*\d{14,16}|Cell\s+Tower\s+ID|SIP\s+Signaling)\b/gi },
|
|
565
|
+
{ profile: 'academic', label: 'Academic & Research', regex: /\b(?:FERPA\s+Student\s+Record|Student\s+ID\s*:\s*#?STU-|Disciplinary\s+Record|IEP\s+Evaluation|IRB\s+Approval)\b/gi },
|
|
566
|
+
{ profile: 'agents', label: 'AI Agents & Prompts', regex: /\b(?:Autonomous\s+Financial\s+Procurement\s+Agent|AgentID\s*:\s*agt_|Encrypted\s+Tool\s+Token|Agent\s+Memory\s*>)\b/gi }
|
|
567
|
+
];
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Automatically detects the most relevant industry compliance profile from text content.
|
|
571
|
+
* Executes in <0.05ms in local RAM with zero network egress.
|
|
572
|
+
*
|
|
573
|
+
* @param {string} text - Input document or prompt text.
|
|
574
|
+
* @returns {{
|
|
575
|
+
* detectedProfile: string,
|
|
576
|
+
* label: string,
|
|
577
|
+
* confidence: 'high'|'medium'|'low',
|
|
578
|
+
* score: number,
|
|
579
|
+
* alternatives: Array<{ profile: string, label: string, score: number }>
|
|
580
|
+
* }}
|
|
581
|
+
*/
|
|
582
|
+
export function detectProfile(text) {
|
|
583
|
+
if (!text || typeof text !== 'string') {
|
|
584
|
+
return { detectedProfile: 'general', label: 'General PII', confidence: 'low', score: 0, alternatives: [] };
|
|
585
|
+
}
|
|
586
|
+
const sampleSlice = text.length > 16384 ? text.slice(0, 16384) : text;
|
|
587
|
+
const matches = [];
|
|
588
|
+
for (let i = 0; i < SDK_VERTICAL_DETECTORS.length; i++) {
|
|
589
|
+
const c = SDK_VERTICAL_DETECTORS[i];
|
|
590
|
+
c.regex.lastIndex = 0;
|
|
591
|
+
const found = sampleSlice.match(c.regex);
|
|
592
|
+
if (found && found.length > 0) {
|
|
593
|
+
matches.push({
|
|
594
|
+
profile: c.profile,
|
|
595
|
+
label: c.label,
|
|
596
|
+
score: found.length,
|
|
597
|
+
priority: i
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
if (matches.length === 0) {
|
|
602
|
+
return { detectedProfile: 'general', label: 'General PII', confidence: 'low', score: 0, alternatives: [] };
|
|
603
|
+
}
|
|
604
|
+
matches.sort((a, b) => (b.score !== a.score) ? (b.score - a.score) : (a.priority - b.priority));
|
|
605
|
+
const primary = matches[0];
|
|
606
|
+
const alternatives = matches.slice(1, 4).map(({ profile, label, score }) => ({ profile, label, score }));
|
|
607
|
+
const confidence = primary.score >= 3 ? 'high' : (primary.score >= 2 ? 'medium' : 'low');
|
|
608
|
+
return {
|
|
609
|
+
detectedProfile: primary.profile,
|
|
610
|
+
label: primary.label,
|
|
611
|
+
confidence,
|
|
612
|
+
score: primary.score,
|
|
613
|
+
alternatives
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
486
617
|
export function sanitize(text, options = {}) {
|
|
487
618
|
if (!text || typeof text !== 'string') {
|
|
488
619
|
return {
|
|
@@ -501,7 +632,7 @@ export function sanitize(text, options = {}) {
|
|
|
501
632
|
}
|
|
502
633
|
|
|
503
634
|
const {
|
|
504
|
-
profile = 'General',
|
|
635
|
+
profile: rawProfileOption = 'General',
|
|
505
636
|
licenseKey = (typeof process !== 'undefined' && (process.env?.PRIVACYSCRUBBER_KEY || process.env?.PRIVACYSCRUBBER_LICENSE_KEY)) || '',
|
|
506
637
|
customRules = [],
|
|
507
638
|
tokenLabelMap = {},
|
|
@@ -510,9 +641,17 @@ export function sanitize(text, options = {}) {
|
|
|
510
641
|
throwOnLimit = true,
|
|
511
642
|
onLimit = 'throw',
|
|
512
643
|
detectSecrets = false,
|
|
513
|
-
ignoreList = null
|
|
644
|
+
ignoreList = null,
|
|
645
|
+
entities = options.entityTypes || options.enabledEntities || null,
|
|
646
|
+
highRecallNames = Boolean(options.highRecallNames || options.nameRecall === 'high')
|
|
514
647
|
} = options;
|
|
515
648
|
|
|
649
|
+
const isAutoProfile = Boolean(
|
|
650
|
+
(typeof rawProfileOption === 'string' && rawProfileOption.trim().toLowerCase() === 'auto') ||
|
|
651
|
+
options.autoDetectProfile === true
|
|
652
|
+
);
|
|
653
|
+
const autoDetected = isAutoProfile ? detectProfile(text) : null;
|
|
654
|
+
|
|
516
655
|
const licenseCheck = validateLicense(licenseKey);
|
|
517
656
|
const rawTier = licenseCheck.valid ? (licenseCheck.tier || 'PRO') : 'FREE';
|
|
518
657
|
const isExpiredKey = !licenseCheck.valid && typeof licenseCheck.error === 'string' && /expired/i.test(licenseCheck.error);
|
|
@@ -534,6 +673,21 @@ export function sanitize(text, options = {}) {
|
|
|
534
673
|
const isSdkTier = licenseCheck.valid && ['SDK', 'OEM', 'ENTERPRISE'].includes(rawTier);
|
|
535
674
|
const isSeatTier = licenseCheck.valid && ['PRO', 'TEAMS'].includes(rawTier);
|
|
536
675
|
|
|
676
|
+
const isPro = isSdkTier;
|
|
677
|
+
const tier = isPro ? rawTier : (isSeatTier ? rawTier : 'FREE');
|
|
678
|
+
|
|
679
|
+
let autoSuggestedOnly = false;
|
|
680
|
+
let profile = rawProfileOption;
|
|
681
|
+
if (isAutoProfile && autoDetected) {
|
|
682
|
+
if (!isPro && text.length > 5000 && text.length <= 15000 && autoDetected.detectedProfile !== 'general') {
|
|
683
|
+
// Non-blocking PLG fallback: keep General for Free 5k-15k texts so auto-mode never breaks free calls
|
|
684
|
+
profile = 'General';
|
|
685
|
+
autoSuggestedOnly = true;
|
|
686
|
+
} else {
|
|
687
|
+
profile = autoDetected.detectedProfile;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
537
691
|
if (isSeatTier) {
|
|
538
692
|
const keyPreview = licenseKey ? `${licenseKey.slice(0, 12)}...` : '';
|
|
539
693
|
const errorMsg = `[PrivacyScrubber] License tier mismatch: ${rawTier} license (${keyPreview}) is licensed strictly for human seats (Web App & Chrome Extension). Backend automation, microservices, and CRM pipelines require a Developer SDK license ($299/mo). Upgrade: https://privacyscrubber.com/pricing?tier=sdk&src=seat_tier_mismatch`;
|
|
@@ -547,9 +701,6 @@ export function sanitize(text, options = {}) {
|
|
|
547
701
|
}
|
|
548
702
|
}
|
|
549
703
|
|
|
550
|
-
const isPro = isSdkTier;
|
|
551
|
-
const tier = isPro ? rawTier : (isSeatTier ? rawTier : 'FREE');
|
|
552
|
-
|
|
553
704
|
emitCommunityNoticeIfNeeded(isPro);
|
|
554
705
|
|
|
555
706
|
const isSpecialized = profile && profile.toLowerCase() !== 'general';
|
|
@@ -583,8 +734,48 @@ export function sanitize(text, options = {}) {
|
|
|
583
734
|
});
|
|
584
735
|
|
|
585
736
|
// Auto-inject DevOps Secrets rules when in Dev profile or when detectSecrets is requested
|
|
586
|
-
const
|
|
587
|
-
|
|
737
|
+
const isProdEnv = typeof process !== 'undefined' && process.env?.NODE_ENV === 'production';
|
|
738
|
+
const hasCustomEnterpriseConfig = isSpecialized || safeCustomRules.length > 0 || (Array.isArray(ignoreList) && ignoreList.length > 0);
|
|
739
|
+
let profileFallback = false;
|
|
740
|
+
let effectiveProfile = profile;
|
|
741
|
+
let effectiveCustomRules = safeCustomRules;
|
|
742
|
+
let effectiveIgnoreList = ignoreList;
|
|
743
|
+
|
|
744
|
+
if (!isPro && isProdEnv && hasCustomEnterpriseConfig) {
|
|
745
|
+
_freeProdSpecializedCalls++;
|
|
746
|
+
if (_freeProdSpecializedCalls > FREE_PROD_SPECIALIZED_CALL_LIMIT) {
|
|
747
|
+
profileFallback = true;
|
|
748
|
+
effectiveProfile = 'General';
|
|
749
|
+
effectiveCustomRules = [];
|
|
750
|
+
effectiveIgnoreList = null;
|
|
751
|
+
if (!_prodProfileFallbackNoticeLogged && typeof process !== 'undefined' && process.stderr && typeof process.stderr.write === 'function') {
|
|
752
|
+
_prodProfileFallbackNoticeLogged = true;
|
|
753
|
+
process.stderr.write(
|
|
754
|
+
`\n[@privacyscrubber/sdk] Free Production Quota (${FREE_PROD_SPECIALIZED_CALL_LIMIT} specialized calls) reached for profile '${profile}'.\n` +
|
|
755
|
+
`[@privacyscrubber/sdk] Gracefully falling back to 'General' profile (Core PII remains protected, 0 downtime).\n` +
|
|
756
|
+
`[@privacyscrubber/sdk] Unlock unlimited specialized profiles, custom rules & ignoreLists in production ($299/mo): https://privacyscrubber.com/pricing?tier=sdk&src=prod_profile_fallback (7-Day Trial: npx @privacyscrubber/sdk trial)\n\n`
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
let normalizedEntities = null;
|
|
763
|
+
if (entities) {
|
|
764
|
+
const rawArr = entities instanceof Set ? Array.from(entities) : (Array.isArray(entities) ? entities : [entities]);
|
|
765
|
+
normalizedEntities = rawArr.filter(Boolean).map(e => {
|
|
766
|
+
const u = String(e).trim().toUpperCase();
|
|
767
|
+
if (u === 'PERSON' || u === 'PERSONS' || u === 'PEOPLE' || u === 'NAMES') return 'NAME';
|
|
768
|
+
if (u === 'EMAILS') return 'EMAIL';
|
|
769
|
+
if (u === 'PHONES' || u === 'TELEPHONE') return 'PHONE';
|
|
770
|
+
if (u === 'IDS' || u === 'IDENTIFIER' || u === 'IDENTIFIERS') return 'ID';
|
|
771
|
+
if (u === 'ADDRESSES' || u === 'LOCATION' || u === 'LOCATIONS') return 'ADDRESS';
|
|
772
|
+
return u;
|
|
773
|
+
});
|
|
774
|
+
if (normalizedEntities.length === 0) normalizedEntities = null;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
const activeRules = [...effectiveCustomRules];
|
|
778
|
+
if ((effectiveProfile.toLowerCase() === 'dev' || detectSecrets === true) && (!normalizedEntities || normalizedEntities.includes('SECRET') || normalizedEntities.includes('CUSTOM'))) {
|
|
588
779
|
DEVOPS_SECRETS_DETECTOR.forEach(rule => {
|
|
589
780
|
activeRules.push({
|
|
590
781
|
name: rule.name,
|
|
@@ -605,10 +796,12 @@ export function sanitize(text, options = {}) {
|
|
|
605
796
|
textToScrub,
|
|
606
797
|
activeRules,
|
|
607
798
|
tokenLabelMap,
|
|
608
|
-
|
|
799
|
+
effectiveProfile,
|
|
609
800
|
existingSessionMap,
|
|
610
801
|
isPro,
|
|
611
|
-
|
|
802
|
+
effectiveIgnoreList,
|
|
803
|
+
normalizedEntities,
|
|
804
|
+
Boolean(highRecallNames)
|
|
612
805
|
);
|
|
613
806
|
|
|
614
807
|
const endTime = (typeof performance !== 'undefined' && typeof performance.now === 'function')
|
|
@@ -616,7 +809,7 @@ export function sanitize(text, options = {}) {
|
|
|
616
809
|
: Date.now();
|
|
617
810
|
|
|
618
811
|
const executionMs = Math.round((endTime - startTime) * 100) / 100;
|
|
619
|
-
const telemetry = buildAuditTelemetry(rawResult.tokenMap || {});
|
|
812
|
+
const telemetry = buildAuditTelemetry(rawResult.tokenMap || {}, { licenseTier: tier });
|
|
620
813
|
const auditReceipt = generateReceipt ? formatAuditReceipt(telemetry) : '';
|
|
621
814
|
|
|
622
815
|
emitDevSecretNoticeIfNeeded(isPro, rawResult.tokenMap, limit);
|
|
@@ -644,6 +837,10 @@ export function sanitize(text, options = {}) {
|
|
|
644
837
|
limit: limit,
|
|
645
838
|
wasTruncated: wasTruncated,
|
|
646
839
|
truncated: wasTruncated,
|
|
840
|
+
profileFallback: profileFallback,
|
|
841
|
+
effectiveProfile: effectiveProfile,
|
|
842
|
+
specializedCallsUsed: _freeProdSpecializedCalls,
|
|
843
|
+
specializedCallsLimit: isPro ? Infinity : FREE_PROD_SPECIALIZED_CALL_LIMIT,
|
|
647
844
|
...(isSeatTier && {
|
|
648
845
|
upgradeUrl: `https://privacyscrubber.com/pricing?tier=sdk&profile=${encodeURIComponent(String(profile || 'General').toLowerCase())}&src=seat_tier_mismatch`,
|
|
649
846
|
trialUrl: `https://privacyscrubber.com/pricing?tier=sdk&trial=true&profile=${encodeURIComponent(String(profile || 'General').toLowerCase())}&src=seat_tier_mismatch`,
|
|
@@ -651,12 +848,20 @@ export function sanitize(text, options = {}) {
|
|
|
651
848
|
recommendation: `Your ${rawTier} license is valid for interactive browser seats only. Upgrade to Developer SDK ($299/mo) for unlimited backend server throughput: https://privacyscrubber.com/pricing?tier=sdk`
|
|
652
849
|
}),
|
|
653
850
|
...(!isPro && !isSeatTier && {
|
|
654
|
-
upgradeUrl: `https://privacyscrubber.com/pricing?tier=sdk&profile=${encodeURIComponent(String(profile || 'General').toLowerCase())}&src
|
|
655
|
-
trialUrl: `https://privacyscrubber.com/pricing?tier=sdk&trial=true&profile=${encodeURIComponent(String(profile || 'General').toLowerCase())}&src
|
|
851
|
+
upgradeUrl: `https://privacyscrubber.com/pricing?tier=sdk&profile=${encodeURIComponent(String(profile || 'General').toLowerCase())}&src=${profileFallback ? 'prod_profile_fallback' : 'dev_upsell'}`,
|
|
852
|
+
trialUrl: `https://privacyscrubber.com/pricing?tier=sdk&trial=true&profile=${encodeURIComponent(String(profile || 'General').toLowerCase())}&src=${profileFallback ? 'prod_profile_fallback' : 'dev_upsell'}`,
|
|
656
853
|
trialCommand: 'npx @privacyscrubber/sdk trial',
|
|
657
854
|
recommendation: 'Upgrade to Developer SDK ($299/mo) for unlimited backend text processing, custom regex rules, and specialized compliance profiles.'
|
|
658
855
|
})
|
|
659
856
|
},
|
|
857
|
+
profileDetection: {
|
|
858
|
+
autoDetected: isAutoProfile,
|
|
859
|
+
detectedProfile: autoDetected ? autoDetected.detectedProfile : String(effectiveProfile || 'General').toLowerCase(),
|
|
860
|
+
label: autoDetected ? autoDetected.label : effectiveProfile,
|
|
861
|
+
confidence: autoDetected ? autoDetected.confidence : 'high',
|
|
862
|
+
suggestedOnly: autoSuggestedOnly,
|
|
863
|
+
alternatives: autoDetected ? autoDetected.alternatives : []
|
|
864
|
+
},
|
|
660
865
|
telemetry: telemetry,
|
|
661
866
|
auditReceipt: auditReceipt
|
|
662
867
|
};
|
|
@@ -924,6 +1129,8 @@ export class PrivacyScrubberEngine {
|
|
|
924
1129
|
this.generateReceipt = config.generateReceipt !== false;
|
|
925
1130
|
this.sessionMap = { ...(config.initialSessionMap || {}) };
|
|
926
1131
|
this.ignoreList = new Set(config.ignoreList || []);
|
|
1132
|
+
this.entities = config.entities || config.entityTypes || config.enabledEntities || null;
|
|
1133
|
+
this.highRecallNames = Boolean(config.highRecallNames || config.nameRecall === 'high');
|
|
927
1134
|
this.maxSessionTokens = config.maxSessionTokens || 10000;
|
|
928
1135
|
|
|
929
1136
|
this.license = validateLicense(this.licenseKey);
|
|
@@ -965,7 +1172,9 @@ export class PrivacyScrubberEngine {
|
|
|
965
1172
|
generateReceipt: options.generateReceipt !== undefined ? options.generateReceipt : this.generateReceipt,
|
|
966
1173
|
throwOnLimit: options.throwOnLimit !== undefined ? options.throwOnLimit : true,
|
|
967
1174
|
detectSecrets: options.detectSecrets !== undefined ? options.detectSecrets : false,
|
|
968
|
-
ignoreList: mergedIgnoreList.size > 0 ? mergedIgnoreList : null
|
|
1175
|
+
ignoreList: mergedIgnoreList.size > 0 ? mergedIgnoreList : null,
|
|
1176
|
+
entities: options.entities || options.entityTypes || options.enabledEntities || this.entities,
|
|
1177
|
+
highRecallNames: options.highRecallNames !== undefined ? Boolean(options.highRecallNames) : (options.nameRecall === 'high' ? true : this.highRecallNames)
|
|
969
1178
|
};
|
|
970
1179
|
|
|
971
1180
|
const result = sanitize(text, mergedOptions);
|
|
@@ -2505,13 +2714,13 @@ export function buildStreamReplacer(tokenMap = {}, options = {}) {
|
|
|
2505
2714
|
|
|
2506
2715
|
for (const k of sortedKeys) {
|
|
2507
2716
|
const val = tokenMap[k];
|
|
2508
|
-
const inner = k.replace(
|
|
2717
|
+
const inner = k.replace(/^(?:\[|<|\{\{?|__|【|[|〔|「|『)|(?:\]|>|\}\}?|__|】|]|〕|」|』)$/g, '');
|
|
2509
2718
|
const match = inner.match(/^([A-Za-z_0-9]+?)[-_ ]?(\d+)$/);
|
|
2510
2719
|
|
|
2511
2720
|
if (match) {
|
|
2512
2721
|
const label = escapeRegex(match[1]).replace(/[_-]/g, '\\\\?[_-]');
|
|
2513
2722
|
const idx = match[2];
|
|
2514
|
-
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)';
|
|
2723
|
+
const patternStr = '(?:(?:\\[|\\\\u005[Bb]|%5[Bb]|&(?:amp;)?#0*91;|&(?:amp;)?#[xX]0*5[Bb];|&(?:amp;)?(?:lbrack|lsqb);|【|[|〔|「|『)\\s*[*`~]*' + label + '[-_\\\\\\s]*0*' + idx + '[*`~]*[.,;:!?]?\\s*(?:\\]|\\\\u005[Dd]|%5[Dd]|&(?:amp;)?#0*93;|&(?:amp;)?#[xX]0*5[Dd];|&(?:amp;)?(?:rbrack|rsqb);|】|]|〕|」|』)|<\\s*' + label + '[-_\\\\\\s]*0*' + idx + '[.,;:!?]?\\s*>|\\{\\{?\\s*' + label + '[-_\\\\\\s]*0*' + idx + '[.,;:!?]?\\s*\\}\\}?|__\\s*' + label + '[-_\\\\\\s]*0*' + idx + '\\s*__|\\b' + label + '[-_\\s]+0*' + idx + '\\b)';
|
|
2515
2724
|
patterns.push(patternStr);
|
|
2516
2725
|
const normKey = (match[1] + '_' + parseInt(idx, 10)).toUpperCase();
|
|
2517
2726
|
lookup.set(normKey, val);
|
|
@@ -2541,8 +2750,8 @@ export function buildStreamReplacer(tokenMap = {}, options = {}) {
|
|
|
2541
2750
|
restored = lookup.get(coreMatch.toUpperCase());
|
|
2542
2751
|
} else {
|
|
2543
2752
|
const inner = coreMatch
|
|
2544
|
-
.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, '')
|
|
2545
|
-
.replace(/^[
|
|
2753
|
+
.replace(/^(?:\[|\\u005[Bb]|%5[Bb]|&(?:amp;)?#0*91;|&(?:amp;)?#[xX]0*5[Bb];|&(?:amp;)?(?:lbrack|lsqb);|【|[|〔|「|『|<|\{\{?|__)|(?:\]|\\u005[Dd]|%5[Dd]|&(?:amp;)?#0*93;|&(?:amp;)?#[xX]0*5[Dd];|&(?:amp;)?(?:rbrack|rsqb);|】|]|〕|」|』|>|\}\}?|__)$/gi, '')
|
|
2754
|
+
.replace(/^[*`_~\s]+|[.,;:!?*`_~\s]+$/g, '')
|
|
2546
2755
|
.trim();
|
|
2547
2756
|
const m = inner.match(/^([A-Za-z_0-9]+?)[-_\\\s]*(\d+)$/);
|
|
2548
2757
|
if (m) {
|
|
@@ -2655,18 +2864,37 @@ export class StreamingDetokenizer {
|
|
|
2655
2864
|
// Candidate unclosed token at tail of buffer (inspect tail window up to 160 chars)
|
|
2656
2865
|
// Matches incomplete tokens starting with [, 【, [, <, {, __, %5B, \u005b, or HTML entity [ / [
|
|
2657
2866
|
const tailLen = 160;
|
|
2658
|
-
const
|
|
2659
|
-
|
|
2660
|
-
|
|
2867
|
+
const tailRegex = /(?:(?:\[|【|[|〔|「|『|<|\{\{?|__|%5[Bb]|\\u005[Bb]|&(?:amp;)?(?:#0*91;|#[xX]0*5[Bb];|lbrack;))[A-Za-z0-9_\-\u0410-\u044F\u0401\u0451 *`~.\\]{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)?)?)?)?)?)?|_)$/;
|
|
2868
|
+
let tail = this.pendingBuffer.length > tailLen ? this.pendingBuffer.slice(-tailLen) : this.pendingBuffer;
|
|
2869
|
+
let offset = this.pendingBuffer.length > tailLen ? this.pendingBuffer.length - tailLen : 0;
|
|
2870
|
+
let m = tail.match(tailRegex);
|
|
2661
2871
|
|
|
2662
2872
|
if (m) {
|
|
2663
2873
|
const candidate = m[0];
|
|
2664
2874
|
// Closed double-underscore (__NAME_1__) or JSON Unicode (\u005bNAME_1\u005d) tokens match character class and can be emitted immediately.
|
|
2665
|
-
const
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2875
|
+
const uClose = candidate.startsWith('__') ? candidate.indexOf('__', 2) : -1;
|
|
2876
|
+
const uniMatch = candidate.match(/\\u005[Dd]/);
|
|
2877
|
+
const closeLen = uClose !== -1 ? uClose + 2 : (uniMatch ? uniMatch.index + 6 : -1);
|
|
2878
|
+
if (closeLen !== -1) {
|
|
2879
|
+
const closedToken = candidate.slice(0, closeLen);
|
|
2880
|
+
if (this.replacer(closedToken) !== closedToken) {
|
|
2881
|
+
const splitAfterToken = offset + m.index + closeLen;
|
|
2882
|
+
const prefixEmitted = this.replacer(this.pendingBuffer.slice(0, splitAfterToken));
|
|
2883
|
+
this.pendingBuffer = this.pendingBuffer.slice(splitAfterToken);
|
|
2884
|
+
if (!this.pendingBuffer) return prefixEmitted;
|
|
2885
|
+
tail = this.pendingBuffer.length > tailLen ? this.pendingBuffer.slice(-tailLen) : this.pendingBuffer;
|
|
2886
|
+
offset = this.pendingBuffer.length > tailLen ? this.pendingBuffer.length - tailLen : 0;
|
|
2887
|
+
m = tail.match(tailRegex);
|
|
2888
|
+
if (!m) {
|
|
2889
|
+
const rest = this.replacer(this.pendingBuffer);
|
|
2890
|
+
this.pendingBuffer = '';
|
|
2891
|
+
return prefixEmitted + rest;
|
|
2892
|
+
}
|
|
2893
|
+
const sIdx = offset + m.index;
|
|
2894
|
+
const safe = this.pendingBuffer.slice(0, sIdx);
|
|
2895
|
+
this.pendingBuffer = this.pendingBuffer.slice(sIdx);
|
|
2896
|
+
return prefixEmitted + (safe ? this.replacer(safe) : '');
|
|
2897
|
+
}
|
|
2670
2898
|
}
|
|
2671
2899
|
const splitIdx = offset + m.index;
|
|
2672
2900
|
const safeToEmit = this.pendingBuffer.slice(0, splitIdx);
|
|
@@ -3888,6 +4116,7 @@ import {
|
|
|
3888
4116
|
|
|
3889
4117
|
export default {
|
|
3890
4118
|
SDK_VERSION,
|
|
4119
|
+
detectProfile,
|
|
3891
4120
|
sanitize,
|
|
3892
4121
|
sanitizeObject,
|
|
3893
4122
|
scrub,
|
|
@@ -3940,6 +4169,8 @@ export default {
|
|
|
3940
4169
|
scrubText,
|
|
3941
4170
|
unscrubText,
|
|
3942
4171
|
DEVOPS_SECRETS_DETECTOR,
|
|
4172
|
+
FREE_PROD_SPECIALIZED_CALL_LIMIT,
|
|
4173
|
+
_resetProdQuotaForTesting,
|
|
3943
4174
|
stripExifMetadata,
|
|
3944
4175
|
redactImageBoundingBoxes,
|
|
3945
4176
|
generateSvgRedactionOverlay
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@privacyscrubber/sdk",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.9",
|
|
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",
|