@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/README.md
CHANGED
|
@@ -207,7 +207,13 @@ const { scrubbedText, tokenMap } = sanitize(rawCrmPayload, {
|
|
|
207
207
|
],
|
|
208
208
|
|
|
209
209
|
// Exclude specific domain terms or false-positive nouns (case-insensitive; also prevents multi-word NAME matches containing these terms):
|
|
210
|
-
ignoreList: ['Materialmangel', 'Störgrund', 'Prüfstand', 'Linie 3']
|
|
210
|
+
ignoreList: ['Materialmangel', 'Störgrund', 'Prüfstand', 'Linie 3'],
|
|
211
|
+
|
|
212
|
+
// Optional: Entity-type allowlist filter (e.g., ['NAME'] or ['PERSON'] to mask ONLY person names while keeping batch numbers, order IDs, and phone/date fields readable):
|
|
213
|
+
// entities: ['NAME'],
|
|
214
|
+
|
|
215
|
+
// Optional: Enable higher-recall person-name detection in free text:
|
|
216
|
+
// highRecallNames: true
|
|
211
217
|
});
|
|
212
218
|
```
|
|
213
219
|
|
package/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "privacyscrubber"
|
|
7
|
-
version = "2.4.
|
|
7
|
+
version = "2.4.9"
|
|
8
8
|
description = "Zero-Trust Data Sanitization & In-Memory PII Shield for Python AI Pipelines (ZTDS RFC v1.0)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -13,7 +13,7 @@ with open(os.path.join(here, "README.md"), encoding="utf-8") as f:
|
|
|
13
13
|
|
|
14
14
|
setup(
|
|
15
15
|
name="privacyscrubber",
|
|
16
|
-
version="2.4.
|
|
16
|
+
version="2.4.9",
|
|
17
17
|
description="Zero-Trust Data Sanitization & In-Memory PII Shield for Python AI Pipelines (ZTDS RFC v1.0)",
|
|
18
18
|
long_description=long_description,
|
|
19
19
|
long_description_content_type="text/markdown",
|
package/index.cjs
CHANGED
|
@@ -39,7 +39,7 @@ const SDK_VERSION = (() => {
|
|
|
39
39
|
return pkgJson.version;
|
|
40
40
|
}
|
|
41
41
|
} catch (_) {}
|
|
42
|
-
return '2.4.
|
|
42
|
+
return '2.4.9';
|
|
43
43
|
})();
|
|
44
44
|
|
|
45
45
|
/**
|
|
@@ -132,7 +132,7 @@ function validateLicenseKey(key) {
|
|
|
132
132
|
* @param {Record<string, string>|string[]} currentTokenMap
|
|
133
133
|
* @returns {object}
|
|
134
134
|
*/
|
|
135
|
-
function buildAuditTelemetry(currentTokenMap = {}) {
|
|
135
|
+
function buildAuditTelemetry(currentTokenMap = {}, extraStats = {}) {
|
|
136
136
|
const entities = {};
|
|
137
137
|
let totalCount = 0;
|
|
138
138
|
|
|
@@ -142,8 +142,9 @@ function buildAuditTelemetry(currentTokenMap = {}) {
|
|
|
142
142
|
if (!t) continue;
|
|
143
143
|
const tokenStr = typeof t === 'string' ? t : ((t && (t.token || t.mask)) ? String(t.token || t.mask) : '');
|
|
144
144
|
if (!tokenStr) continue;
|
|
145
|
-
const match = tokenStr.match(/\[([A-
|
|
146
|
-
const
|
|
145
|
+
const match = tokenStr.match(/\[([A-Za-z0-9_]+)_\d+\]/);
|
|
146
|
+
const rawBase = match ? match[1] : tokenStr.replace(/\[|\]|<|>|\{|\}|__/g, '').replace(/_[0-9]+$/, '');
|
|
147
|
+
const baseType = (rawBase.replace(/[^A-Za-z0-9_]/g, '') || 'CUSTOM').toUpperCase();
|
|
147
148
|
entities[baseType] = (entities[baseType] || 0) + 1;
|
|
148
149
|
totalCount++;
|
|
149
150
|
}
|
|
@@ -153,7 +154,7 @@ function buildAuditTelemetry(currentTokenMap = {}) {
|
|
|
153
154
|
let riskLevel = 'LOW EXPOSURE';
|
|
154
155
|
|
|
155
156
|
if (totalCount > 0) {
|
|
156
|
-
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));
|
|
157
|
+
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));
|
|
157
158
|
if (hasHighRisk || types.length >= 3 || totalCount >= 10) {
|
|
158
159
|
riskLevel = 'CRITICAL (HIGH EXPOSURE)';
|
|
159
160
|
} else if (types.length >= 2 || totalCount >= 3) {
|
|
@@ -178,10 +179,10 @@ function buildAuditTelemetry(currentTokenMap = {}) {
|
|
|
178
179
|
frameworksSet.add('PCI DSS v4.0');
|
|
179
180
|
frameworksSet.add('IRC § 7216 & GLBA');
|
|
180
181
|
}
|
|
181
|
-
if (types.some(t => ['MRN', 'HEALTH', 'MEDICAL', 'PATIENT', 'DIAGNOSIS', 'NPI', 'DEA'].includes(t))) {
|
|
182
|
+
if (types.some(t => ['PHI', 'MRN', 'HEALTH', 'MEDICAL', 'PATIENT', 'DIAGNOSIS', 'NPI', 'DEA'].includes(t))) {
|
|
182
183
|
frameworksSet.add('HIPAA §164.514');
|
|
183
184
|
}
|
|
184
|
-
if (types.some(t => ['CASE_NUMBER', 'DOCKET', 'PRIVILEGED', 'LEGAL', 'ATTORNEY', 'COURT', 'CONTRACT', 'NDA'].includes(t))) {
|
|
185
|
+
if (types.some(t => ['PRIVILEGE', 'CASE_NUMBER', 'DOCKET', 'PRIVILEGED', 'LEGAL', 'ATTORNEY', 'COURT', 'CONTRACT', 'NDA'].includes(t))) {
|
|
185
186
|
frameworksSet.add('FRE 502 (Privilege)');
|
|
186
187
|
}
|
|
187
188
|
if (types.some(t => ['API_KEY', 'SECRET', 'PASSWORD', 'TOKEN', 'KEY', 'AWS_KEY', 'JWT_TOKEN', 'API_TOKEN', 'CREDENTIAL'].includes(t))) {
|
|
@@ -191,6 +192,26 @@ function buildAuditTelemetry(currentTokenMap = {}) {
|
|
|
191
192
|
frameworksSet.add('EU AI Act (Art. 50)');
|
|
192
193
|
}
|
|
193
194
|
|
|
195
|
+
const frameworksList = Array.from(frameworksSet);
|
|
196
|
+
const getFrameworkSlug = (name) => {
|
|
197
|
+
const lower = name.toLowerCase();
|
|
198
|
+
if (lower.includes('gdpr')) return 'gdpr';
|
|
199
|
+
if (lower.includes('ccpa')) return 'ccpa';
|
|
200
|
+
if (lower.includes('soc')) return 'soc2';
|
|
201
|
+
if (lower.includes('iso')) return 'iso27001';
|
|
202
|
+
if (lower.includes('pci')) return 'pci';
|
|
203
|
+
if (lower.includes('hipaa')) return 'hipaa';
|
|
204
|
+
if (lower.includes('nist')) return 'nist';
|
|
205
|
+
if (lower.includes('irc') || lower.includes('7216')) return 'irc7216';
|
|
206
|
+
if (lower.includes('fre') || lower.includes('502')) return 'fre502';
|
|
207
|
+
if (lower.includes('ai act')) return 'euaiact';
|
|
208
|
+
return lower.replace(/[^a-z0-9]/g, '');
|
|
209
|
+
};
|
|
210
|
+
const triggeredFrameworks = frameworksList.map(name => ({
|
|
211
|
+
name,
|
|
212
|
+
id: getFrameworkSlug(name)
|
|
213
|
+
}));
|
|
214
|
+
|
|
194
215
|
const ipHoldings = {
|
|
195
216
|
standard: 'IETF draft-sibiryakov-ztds-protocol-01',
|
|
196
217
|
standardUrl: 'https://datatracker.ietf.org/doc/draft-sibiryakov-ztds-protocol/',
|
|
@@ -242,12 +263,33 @@ function buildAuditTelemetry(currentTokenMap = {}) {
|
|
|
242
263
|
}
|
|
243
264
|
];
|
|
244
265
|
|
|
266
|
+
const rawLicenseTier = String(extraStats?.licenseTier || '').trim().toUpperCase();
|
|
267
|
+
const isOrganizationalScope = ['TEAMS', 'SDK', 'OEM', 'ENTERPRISE', 'ENTERPRISE_AIRGAP'].includes(rawLicenseTier);
|
|
268
|
+
const auditScope = isOrganizationalScope ? {
|
|
269
|
+
tier: rawLicenseTier,
|
|
270
|
+
isOrganizational: true,
|
|
271
|
+
badge: 'ORGANIZATIONAL MULTI-SEAT COMPLIANCE SCOPE',
|
|
272
|
+
coverage: 'Covers department, clinic, or firm personnel under unified ZTDS policy governance.',
|
|
273
|
+
upgradeRecommended: false,
|
|
274
|
+
upgradeUrl: null
|
|
275
|
+
} : {
|
|
276
|
+
tier: rawLicenseTier || 'SINGLE_OPERATOR',
|
|
277
|
+
isOrganizational: false,
|
|
278
|
+
badge: 'SINGLE-OPERATOR PERSONAL SCOPE',
|
|
279
|
+
coverage: 'Valid for individual workstation only. Multi-employee clinics, CPA firms, and departments require TEAMS ($99/mo flat, unlimited seats) for organizational audit coverage.',
|
|
280
|
+
upgradeRecommended: true,
|
|
281
|
+
upgradeUrl: 'https://privacyscrubber.com/pricing/?tier=teams&utm_source=audit_receipt&utm_medium=scope_upsell'
|
|
282
|
+
};
|
|
283
|
+
|
|
245
284
|
return {
|
|
246
285
|
totalCount,
|
|
286
|
+
totalDetected: totalCount,
|
|
247
287
|
entities,
|
|
248
288
|
types,
|
|
249
289
|
riskLevel,
|
|
250
|
-
frameworksList
|
|
290
|
+
frameworksList,
|
|
291
|
+
triggeredFrameworks,
|
|
292
|
+
auditScope,
|
|
251
293
|
statutoryCitations,
|
|
252
294
|
ipHoldings,
|
|
253
295
|
agenticProtocol,
|
|
@@ -301,6 +343,19 @@ function _resetCommunityNoticeForTesting() {
|
|
|
301
343
|
communityNoticeLogged = false;
|
|
302
344
|
}
|
|
303
345
|
|
|
346
|
+
const FREE_PROD_SPECIALIZED_CALL_LIMIT = 250;
|
|
347
|
+
let _freeProdSpecializedCalls = 0;
|
|
348
|
+
let _prodProfileFallbackNoticeLogged = false;
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Resets the production specialized call quota state for testing purposes.
|
|
352
|
+
* @private
|
|
353
|
+
*/
|
|
354
|
+
function _resetProdQuotaForTesting() {
|
|
355
|
+
_freeProdSpecializedCalls = 0;
|
|
356
|
+
_prodProfileFallbackNoticeLogged = false;
|
|
357
|
+
}
|
|
358
|
+
|
|
304
359
|
let devSecretNoticeLogged = false;
|
|
305
360
|
|
|
306
361
|
function emitDevSecretNoticeIfNeeded(isPro, tokenMap = {}, limit = 15000) {
|
|
@@ -481,6 +536,82 @@ We request approval to acquire a **Developer SDK License** (\$299/month flat or
|
|
|
481
536
|
* auditReceipt: string
|
|
482
537
|
* }}
|
|
483
538
|
*/
|
|
539
|
+
const SDK_VERTICAL_DETECTORS = [
|
|
540
|
+
{ 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 },
|
|
541
|
+
{ 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 },
|
|
542
|
+
{ 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 },
|
|
543
|
+
{ 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 },
|
|
544
|
+
{ 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 },
|
|
545
|
+
{ 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 },
|
|
546
|
+
{ 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 },
|
|
547
|
+
{ 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 },
|
|
548
|
+
{ 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 },
|
|
549
|
+
{ 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 },
|
|
550
|
+
{ 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 },
|
|
551
|
+
{ 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 },
|
|
552
|
+
{ 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 },
|
|
553
|
+
{ 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 },
|
|
554
|
+
{ 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 },
|
|
555
|
+
{ 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 },
|
|
556
|
+
{ 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 },
|
|
557
|
+
{ 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 },
|
|
558
|
+
{ 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 },
|
|
559
|
+
{ 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 },
|
|
560
|
+
{ 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 },
|
|
561
|
+
{ 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 },
|
|
562
|
+
{ 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 },
|
|
563
|
+
{ 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 },
|
|
564
|
+
{ 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 }
|
|
565
|
+
];
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Automatically detects the most relevant industry compliance profile from text content.
|
|
569
|
+
* Executes in <0.05ms in local RAM with zero network egress.
|
|
570
|
+
*
|
|
571
|
+
* @param {string} text - Input document or prompt text.
|
|
572
|
+
* @returns {{
|
|
573
|
+
* detectedProfile: string,
|
|
574
|
+
* label: string,
|
|
575
|
+
* confidence: 'high'|'medium'|'low',
|
|
576
|
+
* score: number,
|
|
577
|
+
* alternatives: Array<{ profile: string, label: string, score: number }>
|
|
578
|
+
* }}
|
|
579
|
+
*/
|
|
580
|
+
function detectProfile(text) {
|
|
581
|
+
if (!text || typeof text !== 'string') {
|
|
582
|
+
return { detectedProfile: 'general', label: 'General PII', confidence: 'low', score: 0, alternatives: [] };
|
|
583
|
+
}
|
|
584
|
+
const sampleSlice = text.length > 16384 ? text.slice(0, 16384) : text;
|
|
585
|
+
const matches = [];
|
|
586
|
+
for (let i = 0; i < SDK_VERTICAL_DETECTORS.length; i++) {
|
|
587
|
+
const c = SDK_VERTICAL_DETECTORS[i];
|
|
588
|
+
c.regex.lastIndex = 0;
|
|
589
|
+
const found = sampleSlice.match(c.regex);
|
|
590
|
+
if (found && found.length > 0) {
|
|
591
|
+
matches.push({
|
|
592
|
+
profile: c.profile,
|
|
593
|
+
label: c.label,
|
|
594
|
+
score: found.length,
|
|
595
|
+
priority: i
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
if (matches.length === 0) {
|
|
600
|
+
return { detectedProfile: 'general', label: 'General PII', confidence: 'low', score: 0, alternatives: [] };
|
|
601
|
+
}
|
|
602
|
+
matches.sort((a, b) => (b.score !== a.score) ? (b.score - a.score) : (a.priority - b.priority));
|
|
603
|
+
const primary = matches[0];
|
|
604
|
+
const alternatives = matches.slice(1, 4).map(({ profile, label, score }) => ({ profile, label, score }));
|
|
605
|
+
const confidence = primary.score >= 3 ? 'high' : (primary.score >= 2 ? 'medium' : 'low');
|
|
606
|
+
return {
|
|
607
|
+
detectedProfile: primary.profile,
|
|
608
|
+
label: primary.label,
|
|
609
|
+
confidence,
|
|
610
|
+
score: primary.score,
|
|
611
|
+
alternatives
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
484
615
|
function sanitize(text, options = {}) {
|
|
485
616
|
if (!text || typeof text !== 'string') {
|
|
486
617
|
return {
|
|
@@ -499,7 +630,7 @@ function sanitize(text, options = {}) {
|
|
|
499
630
|
}
|
|
500
631
|
|
|
501
632
|
const {
|
|
502
|
-
profile = 'General',
|
|
633
|
+
profile: rawProfileOption = 'General',
|
|
503
634
|
licenseKey = (typeof process !== 'undefined' && (process.env?.PRIVACYSCRUBBER_KEY || process.env?.PRIVACYSCRUBBER_LICENSE_KEY)) || '',
|
|
504
635
|
customRules = [],
|
|
505
636
|
tokenLabelMap = {},
|
|
@@ -508,9 +639,17 @@ function sanitize(text, options = {}) {
|
|
|
508
639
|
throwOnLimit = true,
|
|
509
640
|
onLimit = 'throw',
|
|
510
641
|
detectSecrets = false,
|
|
511
|
-
ignoreList = null
|
|
642
|
+
ignoreList = null,
|
|
643
|
+
entities = options.entityTypes || options.enabledEntities || null,
|
|
644
|
+
highRecallNames = Boolean(options.highRecallNames || options.nameRecall === 'high')
|
|
512
645
|
} = options;
|
|
513
646
|
|
|
647
|
+
const isAutoProfile = Boolean(
|
|
648
|
+
(typeof rawProfileOption === 'string' && rawProfileOption.trim().toLowerCase() === 'auto') ||
|
|
649
|
+
options.autoDetectProfile === true
|
|
650
|
+
);
|
|
651
|
+
const autoDetected = isAutoProfile ? detectProfile(text) : null;
|
|
652
|
+
|
|
514
653
|
const licenseCheck = validateLicense(licenseKey);
|
|
515
654
|
const rawTier = licenseCheck.valid ? (licenseCheck.tier || 'PRO') : 'FREE';
|
|
516
655
|
const isExpiredKey = !licenseCheck.valid && typeof licenseCheck.error === 'string' && /expired/i.test(licenseCheck.error);
|
|
@@ -532,6 +671,21 @@ function sanitize(text, options = {}) {
|
|
|
532
671
|
const isSdkTier = licenseCheck.valid && ['SDK', 'OEM', 'ENTERPRISE'].includes(rawTier);
|
|
533
672
|
const isSeatTier = licenseCheck.valid && ['PRO', 'TEAMS'].includes(rawTier);
|
|
534
673
|
|
|
674
|
+
const isPro = isSdkTier;
|
|
675
|
+
const tier = isPro ? rawTier : (isSeatTier ? rawTier : 'FREE');
|
|
676
|
+
|
|
677
|
+
let autoSuggestedOnly = false;
|
|
678
|
+
let profile = rawProfileOption;
|
|
679
|
+
if (isAutoProfile && autoDetected) {
|
|
680
|
+
if (!isPro && text.length > 5000 && text.length <= 15000 && autoDetected.detectedProfile !== 'general') {
|
|
681
|
+
// Non-blocking PLG fallback: keep General for Free 5k-15k texts so auto-mode never breaks free calls
|
|
682
|
+
profile = 'General';
|
|
683
|
+
autoSuggestedOnly = true;
|
|
684
|
+
} else {
|
|
685
|
+
profile = autoDetected.detectedProfile;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
535
689
|
if (isSeatTier) {
|
|
536
690
|
const keyPreview = licenseKey ? `${licenseKey.slice(0, 12)}...` : '';
|
|
537
691
|
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`;
|
|
@@ -545,9 +699,6 @@ function sanitize(text, options = {}) {
|
|
|
545
699
|
}
|
|
546
700
|
}
|
|
547
701
|
|
|
548
|
-
const isPro = isSdkTier;
|
|
549
|
-
const tier = isPro ? rawTier : (isSeatTier ? rawTier : 'FREE');
|
|
550
|
-
|
|
551
702
|
emitCommunityNoticeIfNeeded(isPro);
|
|
552
703
|
|
|
553
704
|
const isSpecialized = profile && profile.toLowerCase() !== 'general';
|
|
@@ -581,8 +732,48 @@ function sanitize(text, options = {}) {
|
|
|
581
732
|
});
|
|
582
733
|
|
|
583
734
|
// Auto-inject DevOps Secrets rules when in Dev profile or when detectSecrets is requested
|
|
584
|
-
const
|
|
585
|
-
|
|
735
|
+
const isProdEnv = typeof process !== 'undefined' && process.env?.NODE_ENV === 'production';
|
|
736
|
+
const hasCustomEnterpriseConfig = isSpecialized || safeCustomRules.length > 0 || (Array.isArray(ignoreList) && ignoreList.length > 0);
|
|
737
|
+
let profileFallback = false;
|
|
738
|
+
let effectiveProfile = profile;
|
|
739
|
+
let effectiveCustomRules = safeCustomRules;
|
|
740
|
+
let effectiveIgnoreList = ignoreList;
|
|
741
|
+
|
|
742
|
+
if (!isPro && isProdEnv && hasCustomEnterpriseConfig) {
|
|
743
|
+
_freeProdSpecializedCalls++;
|
|
744
|
+
if (_freeProdSpecializedCalls > FREE_PROD_SPECIALIZED_CALL_LIMIT) {
|
|
745
|
+
profileFallback = true;
|
|
746
|
+
effectiveProfile = 'General';
|
|
747
|
+
effectiveCustomRules = [];
|
|
748
|
+
effectiveIgnoreList = null;
|
|
749
|
+
if (!_prodProfileFallbackNoticeLogged && typeof process !== 'undefined' && process.stderr && typeof process.stderr.write === 'function') {
|
|
750
|
+
_prodProfileFallbackNoticeLogged = true;
|
|
751
|
+
process.stderr.write(
|
|
752
|
+
`\n[@privacyscrubber/sdk] Free Production Quota (${FREE_PROD_SPECIALIZED_CALL_LIMIT} specialized calls) reached for profile '${profile}'.\n` +
|
|
753
|
+
`[@privacyscrubber/sdk] Gracefully falling back to 'General' profile (Core PII remains protected, 0 downtime).\n` +
|
|
754
|
+
`[@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`
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
let normalizedEntities = null;
|
|
761
|
+
if (entities) {
|
|
762
|
+
const rawArr = entities instanceof Set ? Array.from(entities) : (Array.isArray(entities) ? entities : [entities]);
|
|
763
|
+
normalizedEntities = rawArr.filter(Boolean).map(e => {
|
|
764
|
+
const u = String(e).trim().toUpperCase();
|
|
765
|
+
if (u === 'PERSON' || u === 'PERSONS' || u === 'PEOPLE' || u === 'NAMES') return 'NAME';
|
|
766
|
+
if (u === 'EMAILS') return 'EMAIL';
|
|
767
|
+
if (u === 'PHONES' || u === 'TELEPHONE') return 'PHONE';
|
|
768
|
+
if (u === 'IDS' || u === 'IDENTIFIER' || u === 'IDENTIFIERS') return 'ID';
|
|
769
|
+
if (u === 'ADDRESSES' || u === 'LOCATION' || u === 'LOCATIONS') return 'ADDRESS';
|
|
770
|
+
return u;
|
|
771
|
+
});
|
|
772
|
+
if (normalizedEntities.length === 0) normalizedEntities = null;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
const activeRules = [...effectiveCustomRules];
|
|
776
|
+
if ((effectiveProfile.toLowerCase() === 'dev' || detectSecrets === true) && (!normalizedEntities || normalizedEntities.includes('SECRET') || normalizedEntities.includes('CUSTOM'))) {
|
|
586
777
|
DEVOPS_SECRETS_DETECTOR.forEach(rule => {
|
|
587
778
|
activeRules.push({
|
|
588
779
|
name: rule.name,
|
|
@@ -603,10 +794,12 @@ function sanitize(text, options = {}) {
|
|
|
603
794
|
textToScrub,
|
|
604
795
|
activeRules,
|
|
605
796
|
tokenLabelMap,
|
|
606
|
-
|
|
797
|
+
effectiveProfile,
|
|
607
798
|
existingSessionMap,
|
|
608
799
|
isPro,
|
|
609
|
-
|
|
800
|
+
effectiveIgnoreList,
|
|
801
|
+
normalizedEntities,
|
|
802
|
+
Boolean(highRecallNames)
|
|
610
803
|
);
|
|
611
804
|
|
|
612
805
|
const endTime = (typeof performance !== 'undefined' && typeof performance.now === 'function')
|
|
@@ -614,7 +807,7 @@ function sanitize(text, options = {}) {
|
|
|
614
807
|
: Date.now();
|
|
615
808
|
|
|
616
809
|
const executionMs = Math.round((endTime - startTime) * 100) / 100;
|
|
617
|
-
const telemetry = buildAuditTelemetry(rawResult.tokenMap || {});
|
|
810
|
+
const telemetry = buildAuditTelemetry(rawResult.tokenMap || {}, { licenseTier: tier });
|
|
618
811
|
const auditReceipt = generateReceipt ? formatAuditReceipt(telemetry) : '';
|
|
619
812
|
|
|
620
813
|
emitDevSecretNoticeIfNeeded(isPro, rawResult.tokenMap, limit);
|
|
@@ -642,6 +835,10 @@ function sanitize(text, options = {}) {
|
|
|
642
835
|
limit: limit,
|
|
643
836
|
wasTruncated: wasTruncated,
|
|
644
837
|
truncated: wasTruncated,
|
|
838
|
+
profileFallback: profileFallback,
|
|
839
|
+
effectiveProfile: effectiveProfile,
|
|
840
|
+
specializedCallsUsed: _freeProdSpecializedCalls,
|
|
841
|
+
specializedCallsLimit: isPro ? Infinity : FREE_PROD_SPECIALIZED_CALL_LIMIT,
|
|
645
842
|
...(isSeatTier && {
|
|
646
843
|
upgradeUrl: `https://privacyscrubber.com/pricing?tier=sdk&profile=${encodeURIComponent(String(profile || 'General').toLowerCase())}&src=seat_tier_mismatch`,
|
|
647
844
|
trialUrl: `https://privacyscrubber.com/pricing?tier=sdk&trial=true&profile=${encodeURIComponent(String(profile || 'General').toLowerCase())}&src=seat_tier_mismatch`,
|
|
@@ -649,12 +846,20 @@ function sanitize(text, options = {}) {
|
|
|
649
846
|
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`
|
|
650
847
|
}),
|
|
651
848
|
...(!isPro && !isSeatTier && {
|
|
652
|
-
upgradeUrl: `https://privacyscrubber.com/pricing?tier=sdk&profile=${encodeURIComponent(String(profile || 'General').toLowerCase())}&src
|
|
653
|
-
trialUrl: `https://privacyscrubber.com/pricing?tier=sdk&trial=true&profile=${encodeURIComponent(String(profile || 'General').toLowerCase())}&src
|
|
849
|
+
upgradeUrl: `https://privacyscrubber.com/pricing?tier=sdk&profile=${encodeURIComponent(String(profile || 'General').toLowerCase())}&src=${profileFallback ? 'prod_profile_fallback' : 'dev_upsell'}`,
|
|
850
|
+
trialUrl: `https://privacyscrubber.com/pricing?tier=sdk&trial=true&profile=${encodeURIComponent(String(profile || 'General').toLowerCase())}&src=${profileFallback ? 'prod_profile_fallback' : 'dev_upsell'}`,
|
|
654
851
|
trialCommand: 'npx @privacyscrubber/sdk trial',
|
|
655
852
|
recommendation: 'Upgrade to Developer SDK ($299/mo) for unlimited backend text processing, custom regex rules, and specialized compliance profiles.'
|
|
656
853
|
})
|
|
657
854
|
},
|
|
855
|
+
profileDetection: {
|
|
856
|
+
autoDetected: isAutoProfile,
|
|
857
|
+
detectedProfile: autoDetected ? autoDetected.detectedProfile : String(effectiveProfile || 'General').toLowerCase(),
|
|
858
|
+
label: autoDetected ? autoDetected.label : effectiveProfile,
|
|
859
|
+
confidence: autoDetected ? autoDetected.confidence : 'high',
|
|
860
|
+
suggestedOnly: autoSuggestedOnly,
|
|
861
|
+
alternatives: autoDetected ? autoDetected.alternatives : []
|
|
862
|
+
},
|
|
658
863
|
telemetry: telemetry,
|
|
659
864
|
auditReceipt: auditReceipt
|
|
660
865
|
};
|
|
@@ -922,6 +1127,8 @@ class PrivacyScrubberEngine {
|
|
|
922
1127
|
this.generateReceipt = config.generateReceipt !== false;
|
|
923
1128
|
this.sessionMap = { ...(config.initialSessionMap || {}) };
|
|
924
1129
|
this.ignoreList = new Set(config.ignoreList || []);
|
|
1130
|
+
this.entities = config.entities || config.entityTypes || config.enabledEntities || null;
|
|
1131
|
+
this.highRecallNames = Boolean(config.highRecallNames || config.nameRecall === 'high');
|
|
925
1132
|
this.maxSessionTokens = config.maxSessionTokens || 10000;
|
|
926
1133
|
|
|
927
1134
|
this.license = validateLicense(this.licenseKey);
|
|
@@ -963,7 +1170,9 @@ class PrivacyScrubberEngine {
|
|
|
963
1170
|
generateReceipt: options.generateReceipt !== undefined ? options.generateReceipt : this.generateReceipt,
|
|
964
1171
|
throwOnLimit: options.throwOnLimit !== undefined ? options.throwOnLimit : true,
|
|
965
1172
|
detectSecrets: options.detectSecrets !== undefined ? options.detectSecrets : false,
|
|
966
|
-
ignoreList: mergedIgnoreList.size > 0 ? mergedIgnoreList : null
|
|
1173
|
+
ignoreList: mergedIgnoreList.size > 0 ? mergedIgnoreList : null,
|
|
1174
|
+
entities: options.entities || options.entityTypes || options.enabledEntities || this.entities,
|
|
1175
|
+
highRecallNames: options.highRecallNames !== undefined ? Boolean(options.highRecallNames) : (options.nameRecall === 'high' ? true : this.highRecallNames)
|
|
967
1176
|
};
|
|
968
1177
|
|
|
969
1178
|
const result = sanitize(text, mergedOptions);
|
|
@@ -2503,13 +2712,13 @@ function buildStreamReplacer(tokenMap = {}, options = {}) {
|
|
|
2503
2712
|
|
|
2504
2713
|
for (const k of sortedKeys) {
|
|
2505
2714
|
const val = tokenMap[k];
|
|
2506
|
-
const inner = k.replace(
|
|
2715
|
+
const inner = k.replace(/^(?:\[|<|\{\{?|__|【|[|〔|「|『)|(?:\]|>|\}\}?|__|】|]|〕|」|』)$/g, '');
|
|
2507
2716
|
const match = inner.match(/^([A-Za-z_0-9]+?)[-_ ]?(\d+)$/);
|
|
2508
2717
|
|
|
2509
2718
|
if (match) {
|
|
2510
2719
|
const label = escapeRegex(match[1]).replace(/[_-]/g, '\\\\?[_-]');
|
|
2511
2720
|
const idx = match[2];
|
|
2512
|
-
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)';
|
|
2721
|
+
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)';
|
|
2513
2722
|
patterns.push(patternStr);
|
|
2514
2723
|
const normKey = (match[1] + '_' + parseInt(idx, 10)).toUpperCase();
|
|
2515
2724
|
lookup.set(normKey, val);
|
|
@@ -2539,8 +2748,8 @@ function buildStreamReplacer(tokenMap = {}, options = {}) {
|
|
|
2539
2748
|
restored = lookup.get(coreMatch.toUpperCase());
|
|
2540
2749
|
} else {
|
|
2541
2750
|
const inner = coreMatch
|
|
2542
|
-
.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, '')
|
|
2543
|
-
.replace(/^[
|
|
2751
|
+
.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, '')
|
|
2752
|
+
.replace(/^[*`_~\s]+|[.,;:!?*`_~\s]+$/g, '')
|
|
2544
2753
|
.trim();
|
|
2545
2754
|
const m = inner.match(/^([A-Za-z_0-9]+?)[-_\\\s]*(\d+)$/);
|
|
2546
2755
|
if (m) {
|
|
@@ -2653,18 +2862,37 @@ class StreamingDetokenizer {
|
|
|
2653
2862
|
// Candidate unclosed token at tail of buffer (inspect tail window up to 160 chars)
|
|
2654
2863
|
// Matches incomplete tokens starting with [, 【, [, <, {, __, %5B, \u005b, or HTML entity [ / [
|
|
2655
2864
|
const tailLen = 160;
|
|
2656
|
-
const
|
|
2657
|
-
|
|
2658
|
-
|
|
2865
|
+
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)?)?)?)?)?)?|_)$/;
|
|
2866
|
+
let tail = this.pendingBuffer.length > tailLen ? this.pendingBuffer.slice(-tailLen) : this.pendingBuffer;
|
|
2867
|
+
let offset = this.pendingBuffer.length > tailLen ? this.pendingBuffer.length - tailLen : 0;
|
|
2868
|
+
let m = tail.match(tailRegex);
|
|
2659
2869
|
|
|
2660
2870
|
if (m) {
|
|
2661
2871
|
const candidate = m[0];
|
|
2662
2872
|
// Closed double-underscore (__NAME_1__) or JSON Unicode (\u005bNAME_1\u005d) tokens match character class and can be emitted immediately.
|
|
2663
|
-
const
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2873
|
+
const uClose = candidate.startsWith('__') ? candidate.indexOf('__', 2) : -1;
|
|
2874
|
+
const uniMatch = candidate.match(/\\u005[Dd]/);
|
|
2875
|
+
const closeLen = uClose !== -1 ? uClose + 2 : (uniMatch ? uniMatch.index + 6 : -1);
|
|
2876
|
+
if (closeLen !== -1) {
|
|
2877
|
+
const closedToken = candidate.slice(0, closeLen);
|
|
2878
|
+
if (this.replacer(closedToken) !== closedToken) {
|
|
2879
|
+
const splitAfterToken = offset + m.index + closeLen;
|
|
2880
|
+
const prefixEmitted = this.replacer(this.pendingBuffer.slice(0, splitAfterToken));
|
|
2881
|
+
this.pendingBuffer = this.pendingBuffer.slice(splitAfterToken);
|
|
2882
|
+
if (!this.pendingBuffer) return prefixEmitted;
|
|
2883
|
+
tail = this.pendingBuffer.length > tailLen ? this.pendingBuffer.slice(-tailLen) : this.pendingBuffer;
|
|
2884
|
+
offset = this.pendingBuffer.length > tailLen ? this.pendingBuffer.length - tailLen : 0;
|
|
2885
|
+
m = tail.match(tailRegex);
|
|
2886
|
+
if (!m) {
|
|
2887
|
+
const rest = this.replacer(this.pendingBuffer);
|
|
2888
|
+
this.pendingBuffer = '';
|
|
2889
|
+
return prefixEmitted + rest;
|
|
2890
|
+
}
|
|
2891
|
+
const sIdx = offset + m.index;
|
|
2892
|
+
const safe = this.pendingBuffer.slice(0, sIdx);
|
|
2893
|
+
this.pendingBuffer = this.pendingBuffer.slice(sIdx);
|
|
2894
|
+
return prefixEmitted + (safe ? this.replacer(safe) : '');
|
|
2895
|
+
}
|
|
2668
2896
|
}
|
|
2669
2897
|
const splitIdx = offset + m.index;
|
|
2670
2898
|
const safeToEmit = this.pendingBuffer.slice(0, splitIdx);
|
|
@@ -3881,6 +4109,7 @@ const {
|
|
|
3881
4109
|
|
|
3882
4110
|
const _defaultExport = {
|
|
3883
4111
|
SDK_VERSION,
|
|
4112
|
+
detectProfile,
|
|
3884
4113
|
sanitize,
|
|
3885
4114
|
sanitizeObject,
|
|
3886
4115
|
scrub,
|
|
@@ -3933,6 +4162,8 @@ const _defaultExport = {
|
|
|
3933
4162
|
scrubText,
|
|
3934
4163
|
unscrubText,
|
|
3935
4164
|
DEVOPS_SECRETS_DETECTOR,
|
|
4165
|
+
FREE_PROD_SPECIALIZED_CALL_LIMIT,
|
|
4166
|
+
_resetProdQuotaForTesting,
|
|
3936
4167
|
stripExifMetadata,
|
|
3937
4168
|
redactImageBoundingBoxes,
|
|
3938
4169
|
generateSvgRedactionOverlay
|
package/index.d.ts
CHANGED
|
@@ -74,6 +74,14 @@ export interface SanitizeOptions {
|
|
|
74
74
|
returnFullSessionMap?: boolean;
|
|
75
75
|
/** Set or array of strings or exact terms to preserve unmasked in cleartext. */
|
|
76
76
|
ignoreList?: string[] | Set<string>;
|
|
77
|
+
/** Optional entity-type allowlist filter (e.g. ['NAME'] or ['PERSON'] to mask only person names while keeping batch/order numbers, dates, and IDs readable). */
|
|
78
|
+
entities?: string[];
|
|
79
|
+
/** Alias for entities. */
|
|
80
|
+
entityTypes?: string[];
|
|
81
|
+
/** Enable higher-recall person-name detection in free text (Default: false). */
|
|
82
|
+
highRecallNames?: boolean;
|
|
83
|
+
/** Alias for highRecallNames ('default' | 'high'). */
|
|
84
|
+
nameRecall?: 'default' | 'high';
|
|
77
85
|
/** Whether to generate a CISO Markdown audit receipt (Default: true). */
|
|
78
86
|
generateReceipt?: boolean;
|
|
79
87
|
/** Throw PrivacyScrubberLicenseError when free tier limits are reached (Default: true). */
|