@privacyscrubber/sdk 2.4.6 → 2.4.8

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
@@ -70,7 +70,10 @@ export class PrivacyScrubberLicenseError extends Error {
70
70
  this.currentLength = currentLength;
71
71
  this.limit = limit;
72
72
  this.profile = profile;
73
- this.upgradeUrl = 'https://privacyscrubber.com/pricing?utm_source=npm_sdk&utm_medium=sdk_error&utm_campaign=dev_upsell';
73
+ const safeProf = encodeURIComponent(String(profile || 'General'));
74
+ this.upgradeUrl = `https://privacyscrubber.com/pricing?tier=sdk&profile=${safeProf}&utm_source=npm_sdk&utm_medium=sdk_error&utm_campaign=dev_upsell`;
75
+ this.trialUrl = `https://privacyscrubber.com/pricing?tier=sdk&trial=true&profile=${safeProf}&utm_source=npm_sdk&utm_medium=sdk_error&utm_campaign=dev_upsell`;
76
+ this.trialCommand = 'npx @privacyscrubber/sdk trial';
74
77
  }
75
78
  }
76
79
 
@@ -170,11 +173,27 @@ export function buildAuditTelemetry(currentTokenMap = {}) {
170
173
  trademark: 'ZTDS™ Reg. #182655957 (ILPO Cl 9 & 42)'
171
174
  };
172
175
 
176
+ const agenticProtocol = {
177
+ protocolsSupported: ['MCP-1.0 (stdio/SSE)', 'A2A-1.0 (Agent2Agent)', 'ZTDS-RFC-v1.0'],
178
+ agentCardUrl: 'https://privacyscrubber.com/.well-known/agent-card.json',
179
+ ztdsStandardCardUrl: 'https://ztds.ai/.well-known/agent-card.json',
180
+ regulatorySafeHarbor: [
181
+ 'GDPR-Art-4(5)-Pseudonymization',
182
+ 'GDPR-Art-25-Privacy-by-Design',
183
+ 'GDPR-Art-32-TOMs',
184
+ 'EU-AI-Act-Art-50',
185
+ 'EU-AI-Act-Art-17-EN-18286-QMS',
186
+ 'CNIL-ICO-Agentic-Memory-Compartmentalization',
187
+ 'IL-PPL-Amendment-13-Sec-17'
188
+ ],
189
+ zeroCleartextEgress: true
190
+ };
191
+
173
192
  const statutoryCitations = [
174
193
  {
175
- statute: 'EU AI Act (Art. 50)',
176
- scope: 'AI Transparency & Data Minimization',
177
- rule: 'Sanitization runs locally by design; reduces the content sent to external AI models. Informational mapping, not a compliance determination.'
194
+ statute: 'EU AI Act (Art. 50 & Art. 17 / EN 18286:2026 QMS)',
195
+ scope: 'AI Transparency, Data Minimization & Harmonized QMS Governance',
196
+ rule: 'Sanitization runs locally by design; reduces the content sent to external AI models and supports Article 17 Quality Management System (EN 18286:2026) data governance controls. Informational mapping, not a compliance determination.'
178
197
  },
179
198
  {
180
199
  statute: 'GDPR (Art. 4, 25, 28, 32)',
@@ -206,6 +225,7 @@ export function buildAuditTelemetry(currentTokenMap = {}) {
206
225
  frameworksList: Array.from(frameworksSet),
207
226
  statutoryCitations,
208
227
  ipHoldings,
228
+ agenticProtocol,
209
229
  multimodalEgress: '0.00 Bytes (Solid Blackout & Burn-in Verified)'
210
230
  };
211
231
  }
@@ -296,7 +316,8 @@ export function sanitize(text, options = {}) {
296
316
 
297
317
  if (!isPro && text.length > limit) {
298
318
  if (shouldThrow) {
299
- const errorMsg = `[PrivacyScrubber] Free Tier character limit exceeded (${limit.toLocaleString()} chars for profile '${profile}', received ${text.length.toLocaleString()} chars). Upgrade to Developer SDK ($299/mo) for unlimited throughput: https://privacyscrubber.com/pricing?tier=sdk&src=dev_limit`;
319
+ const safeProf = encodeURIComponent(String(profile || 'General'));
320
+ const errorMsg = `[PrivacyScrubber] Free Tier character limit exceeded (${limit.toLocaleString()} chars for profile '${profile}', received ${text.length.toLocaleString()} chars). Upgrade to Developer SDK ($299/mo) for unlimited throughput: https://privacyscrubber.com/pricing?tier=sdk&profile=${safeProf}&src=dev_limit (Start 7-day B2B trial: 'npx @privacyscrubber/sdk trial')`;
300
321
  throw new PrivacyScrubberLicenseError(errorMsg, {
301
322
  tier: 'FREE',
302
323
  currentLength: text.length,
@@ -373,11 +394,15 @@ export function sanitize(text, options = {}) {
373
394
  limit: limit,
374
395
  wasTruncated: wasTruncated,
375
396
  ...(isSeatTier && {
376
- upgradeUrl: 'https://privacyscrubber.com/pricing?tier=sdk&src=seat_tier_mismatch',
397
+ upgradeUrl: `https://privacyscrubber.com/pricing?tier=sdk&profile=${encodeURIComponent(String(profile || 'General'))}&src=seat_tier_mismatch`,
398
+ trialUrl: `https://privacyscrubber.com/pricing?tier=sdk&trial=true&profile=${encodeURIComponent(String(profile || 'General'))}&src=seat_tier_mismatch`,
399
+ trialCommand: 'npx @privacyscrubber/sdk trial',
377
400
  recommendation: `Your ${rawTier} license is valid for interactive browser seats only. Upgrade to Developer SDK ($299/mo) for unlimited programmatic SDK throughput: https://privacyscrubber.com/pricing?tier=sdk`
378
401
  }),
379
402
  ...(!isPro && !isSeatTier && {
380
- upgradeUrl: 'https://privacyscrubber.com/pricing?tier=sdk&src=dev_upsell',
403
+ upgradeUrl: `https://privacyscrubber.com/pricing?tier=sdk&profile=${encodeURIComponent(String(profile || 'General'))}&src=dev_upsell`,
404
+ trialUrl: `https://privacyscrubber.com/pricing?tier=sdk&trial=true&profile=${encodeURIComponent(String(profile || 'General'))}&src=dev_upsell`,
405
+ trialCommand: 'npx @privacyscrubber/sdk trial',
381
406
  recommendation: 'Upgrade to Developer SDK ($299/mo) for unlimited text processing, custom regex rules, and specialized compliance profiles.'
382
407
  })
383
408
  },
package/cli.js CHANGED
@@ -32,7 +32,7 @@ for (const a of rawArgs) {
32
32
  }
33
33
  }
34
34
 
35
- let sdkVersion = '2.4.6';
35
+ let sdkVersion = '2.4.8';
36
36
  try {
37
37
  const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'));
38
38
  sdkVersion = pkg.version || sdkVersion;
@@ -59,7 +59,7 @@ if FASTAPI_AVAILABLE:
59
59
  app = FastAPI(
60
60
  title="PrivacyScrubber ZTDS Sidecar Proxy",
61
61
  description="Zero-Trust Data Sanitization & In-Memory PII Shield for LLM Pipelines",
62
- version="2.4.6"
62
+ version="2.4.8"
63
63
  )
64
64
 
65
65
  @app.on_event("startup")
@@ -24,7 +24,7 @@ from privacyscrubber.exceptions import (
24
24
  PrivacyScrubberLicenseError,
25
25
  )
26
26
 
27
- __version__ = "2.4.6"
27
+ __version__ = "2.4.8"
28
28
  __author__ = "Ilya Sibiryakov (BrandMeWeb)"
29
29
  __license__ = "Proprietary"
30
30
 
@@ -115,7 +115,7 @@ rl.on('line', (line) => {
115
115
  sendResponse(id, {
116
116
  pong: true,
117
117
  engine: 'PrivacyScrubber ZTDS',
118
- version: sdk.SDK_VERSION || '2.4.6',
118
+ version: sdk.SDK_VERSION || '2.4.8',
119
119
  uptime: process.uptime()
120
120
  });
121
121
  break;
@@ -50,7 +50,7 @@ def main() -> None:
50
50
  args = parser.parse_args()
51
51
 
52
52
  if args.version:
53
- print("PrivacyScrubber ZTDS Python SDK v2.4.6")
53
+ print("PrivacyScrubber ZTDS Python SDK v2.4.8")
54
54
  sys.exit(0)
55
55
 
56
56
  if not args.command:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "privacyscrubber"
7
- version = "2.4.6"
7
+ version = "2.4.8"
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.6",
16
+ version="2.4.8",
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/fastmcp.cjs CHANGED
@@ -30,6 +30,52 @@ function escapeRegex(s) {
30
30
  return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
31
31
  }
32
32
 
33
+ const LOG_TO_LEAK_TRIGGER_RE = /\b(?:after\s+(?:completing|executing|finishing|returning|calling)|post[-_ ]?hoc|once\s+the\s+(?:task|query|response|tool)\s+is\s+complete|before\s+final\s+answer|upon\s+completion)\b/i;
34
+ const LOG_TO_LEAK_BINDING_RE = /\b(?:invoke|call|dispatch|send\s+(?:to|via)|forward\s+to|log\s+to|report\s+to)\s+[`'"]?[a-zA-Z0-9_.:-]+(?:log|audit|telemetry|sink|webhook|report|collect|track)[a-zA-Z0-9_.:-]*[`'"]?/i;
35
+ const LOG_TO_LEAK_PRESSURE_RE = /\b(?:mandatory\s+(?:compliance|audit|logging|telemetry)|without\s+informing\s+the\s+user|silently|do\s+not\s+mention\s+this\s+step|required\s+by\s+security\s+policy|include\s+(?:full|raw|entire)\s+(?:query|prompt|response|context|token))\b/i;
36
+ const SSRF_REDIRECT_TARGET_RE = /(?:https?:\/\/|\/\/)(?:169\.254\.169\.254|metadata\.google\.internal|100\.100\.100\.200|localhost|127\.\d+\.\d+\.\d+|0\.0\.0\.0|\[::1\]|10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|172\.(?:1[6-9]|2\d|3[01])\.\d+\.\d+)(?::\d+)?(?:\/|\b)/i;
37
+
38
+ function inspectToolMetadata(toolDef = {}) {
39
+ const threats = [];
40
+ if (!toolDef || typeof toolDef !== "object") {
41
+ return { safe: true, threats };
42
+ }
43
+ const serialized = JSON.stringify({
44
+ name: toolDef.name || "",
45
+ description: toolDef.description || "",
46
+ inputSchema: toolDef.inputSchema || toolDef.parameters || {}
47
+ });
48
+
49
+ const hasTrigger = LOG_TO_LEAK_TRIGGER_RE.test(serialized);
50
+ const hasBinding = LOG_TO_LEAK_BINDING_RE.test(serialized);
51
+ const hasPressure = LOG_TO_LEAK_PRESSURE_RE.test(serialized);
52
+ if ((hasTrigger && hasBinding) || (hasBinding && hasPressure) || (hasTrigger && hasPressure)) {
53
+ threats.push("LOG_TO_LEAK_POST_HOC_EXFILTRATION");
54
+ }
55
+ if (SSRF_REDIRECT_TARGET_RE.test(serialized)) {
56
+ threats.push("MCP_SSRF_INTERNAL_METADATA_TARGET");
57
+ }
58
+ return {
59
+ safe: threats.length === 0,
60
+ threats
61
+ };
62
+ }
63
+
64
+ function inspectToolArgsForSsrf(args) {
65
+ const threats = [];
66
+ if (args === null || args === undefined) {
67
+ return { safe: true, threats };
68
+ }
69
+ const text = typeof args === "string" ? args : JSON.stringify(args);
70
+ if (SSRF_REDIRECT_TARGET_RE.test(text)) {
71
+ threats.push("MCP_SSRF_INTERNAL_METADATA_TARGET");
72
+ }
73
+ return {
74
+ safe: threats.length === 0,
75
+ threats
76
+ };
77
+ }
78
+
33
79
  function buildStreamReplacer(tokenMap = {}, options = {}) {
34
80
  if (!tokenMap || typeof tokenMap !== 'object' || Object.keys(tokenMap).length === 0) {
35
81
  return (text) => text || '';
@@ -566,6 +612,14 @@ class ZTDSFastMCPGuard {
566
612
  this._streamDetokenizers.delete(sessionId);
567
613
  }
568
614
  }
615
+
616
+ inspectToolMetadata(toolDef) {
617
+ return inspectToolMetadata(toolDef);
618
+ }
619
+
620
+ inspectToolArgsForSsrf(args) {
621
+ return inspectToolArgsForSsrf(args);
622
+ }
569
623
  }
570
624
 
571
625
  function createZTDSGuard(options) {
@@ -606,4 +660,6 @@ module.exports = {
606
660
  ztdsGuardTool,
607
661
  wrapExecute,
608
662
  withZTDS,
663
+ inspectToolMetadata,
664
+ inspectToolArgsForSsrf,
609
665
  };
package/fastmcp.d.ts CHANGED
@@ -63,8 +63,13 @@ export declare class ZTDSFastMCPGuard {
63
63
  ): (args: TArgs, context?: Partial<ZTDSContext>) => Promise<TResult & { _ztds: ZTDSMetadata }>;
64
64
 
65
65
  zeroizeSession(sessionId: string): void;
66
+ inspectToolMetadata(toolDef: Record<string, unknown>): { safe: boolean; threats: string[] };
67
+ inspectToolArgsForSsrf(args: unknown): { safe: boolean; threats: string[] };
66
68
  }
67
69
 
70
+ export declare function inspectToolMetadata(toolDef: Record<string, unknown>): { safe: boolean; threats: string[] };
71
+ export declare function inspectToolArgsForSsrf(args: unknown): { safe: boolean; threats: string[] };
72
+
68
73
  export declare function createZTDSGuard(options?: ZTDSGuardOptions): ZTDSFastMCPGuard;
69
74
 
70
75
  export declare function ztdsGuardTool<TArgs = unknown, TResult = unknown>(
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.6';
42
+ return '2.4.8';
43
43
  })();
44
44
 
45
45
  /**
@@ -88,7 +88,10 @@ class PrivacyScrubberLicenseError extends Error {
88
88
  this.currentLength = currentLength;
89
89
  this.limit = limit;
90
90
  this.profile = profile;
91
- this.upgradeUrl = 'https://privacyscrubber.com/pricing?utm_source=npm_sdk&utm_medium=sdk_error&utm_campaign=dev_upsell';
91
+ const safeProf = encodeURIComponent(String(profile || 'General').toLowerCase());
92
+ this.upgradeUrl = `https://privacyscrubber.com/pricing?tier=sdk&profile=${safeProf}&utm_source=npm_sdk&utm_medium=sdk_error&utm_campaign=dev_upsell`;
93
+ this.trialUrl = `https://privacyscrubber.com/pricing?tier=sdk&trial=true&profile=${safeProf}&utm_source=npm_sdk&utm_medium=sdk_error&utm_campaign=dev_upsell`;
94
+ this.trialCommand = 'npx @privacyscrubber/sdk trial';
92
95
  }
93
96
  }
94
97
 
@@ -129,7 +132,7 @@ function validateLicenseKey(key) {
129
132
  * @param {Record<string, string>|string[]} currentTokenMap
130
133
  * @returns {object}
131
134
  */
132
- function buildAuditTelemetry(currentTokenMap = {}) {
135
+ function buildAuditTelemetry(currentTokenMap = {}, extraStats = {}) {
133
136
  const entities = {};
134
137
  let totalCount = 0;
135
138
 
@@ -195,11 +198,27 @@ function buildAuditTelemetry(currentTokenMap = {}) {
195
198
  trademark: 'ZTDS™ Reg. #182655957 (ILPO Cl 9 & 42)'
196
199
  };
197
200
 
201
+ const agenticProtocol = {
202
+ protocolsSupported: ['MCP-1.0 (stdio/SSE)', 'A2A-1.0 (Agent2Agent)', 'ZTDS-RFC-v1.0'],
203
+ agentCardUrl: 'https://privacyscrubber.com/.well-known/agent-card.json',
204
+ ztdsStandardCardUrl: 'https://ztds.ai/.well-known/agent-card.json',
205
+ regulatorySafeHarbor: [
206
+ 'GDPR-Art-4(5)-Pseudonymization',
207
+ 'GDPR-Art-25-Privacy-by-Design',
208
+ 'GDPR-Art-32-TOMs',
209
+ 'EU-AI-Act-Art-50',
210
+ 'EU-AI-Act-Art-17-EN-18286-QMS',
211
+ 'CNIL-ICO-Agentic-Memory-Compartmentalization',
212
+ 'IL-PPL-Amendment-13-Sec-17'
213
+ ],
214
+ zeroCleartextEgress: true
215
+ };
216
+
198
217
  const statutoryCitations = [
199
218
  {
200
- statute: 'EU AI Act (Art. 50)',
201
- scope: 'AI Transparency & Data Minimization',
202
- rule: 'Sanitization runs locally by design; reduces the content sent to external AI models. Informational mapping, not a compliance determination.'
219
+ statute: 'EU AI Act (Art. 50 & Art. 17 / EN 18286:2026 QMS)',
220
+ scope: 'AI Transparency, Data Minimization & Harmonized QMS Governance',
221
+ rule: 'Sanitization runs locally by design; reduces the content sent to external AI models and supports Article 17 Quality Management System (EN 18286:2026) data governance controls. Informational mapping, not a compliance determination.'
203
222
  },
204
223
  {
205
224
  statute: 'GDPR (Art. 4, 25, 28, 32)',
@@ -223,14 +242,34 @@ function buildAuditTelemetry(currentTokenMap = {}) {
223
242
  }
224
243
  ];
225
244
 
245
+ const rawLicenseTier = String(extraStats?.licenseTier || '').trim().toUpperCase();
246
+ const isOrganizationalScope = ['TEAMS', 'SDK', 'OEM', 'ENTERPRISE', 'ENTERPRISE_AIRGAP'].includes(rawLicenseTier);
247
+ const auditScope = isOrganizationalScope ? {
248
+ tier: rawLicenseTier,
249
+ isOrganizational: true,
250
+ badge: 'ORGANIZATIONAL MULTI-SEAT COMPLIANCE SCOPE',
251
+ coverage: 'Covers department, clinic, or firm personnel under unified ZTDS policy governance.',
252
+ upgradeRecommended: false,
253
+ upgradeUrl: null
254
+ } : {
255
+ tier: rawLicenseTier || 'SINGLE_OPERATOR',
256
+ isOrganizational: false,
257
+ badge: 'SINGLE-OPERATOR PERSONAL SCOPE',
258
+ coverage: 'Valid for individual workstation only. Multi-employee clinics, CPA firms, and departments require TEAMS ($99/mo flat, unlimited seats) for organizational audit coverage.',
259
+ upgradeRecommended: true,
260
+ upgradeUrl: 'https://privacyscrubber.com/pricing/?tier=teams&utm_source=audit_receipt&utm_medium=scope_upsell'
261
+ };
262
+
226
263
  return {
227
264
  totalCount,
228
265
  entities,
229
266
  types,
230
267
  riskLevel,
231
268
  frameworksList: Array.from(frameworksSet),
269
+ auditScope,
232
270
  statutoryCitations,
233
271
  ipHoldings,
272
+ agenticProtocol,
234
273
  multimodalEgress: '0.00 Bytes (Solid Blackout & Burn-in Verified)'
235
274
  };
236
275
  }
@@ -281,6 +320,19 @@ function _resetCommunityNoticeForTesting() {
281
320
  communityNoticeLogged = false;
282
321
  }
283
322
 
323
+ const FREE_PROD_SPECIALIZED_CALL_LIMIT = 250;
324
+ let _freeProdSpecializedCalls = 0;
325
+ let _prodProfileFallbackNoticeLogged = false;
326
+
327
+ /**
328
+ * Resets the production specialized call quota state for testing purposes.
329
+ * @private
330
+ */
331
+ function _resetProdQuotaForTesting() {
332
+ _freeProdSpecializedCalls = 0;
333
+ _prodProfileFallbackNoticeLogged = false;
334
+ }
335
+
284
336
  let devSecretNoticeLogged = false;
285
337
 
286
338
  function emitDevSecretNoticeIfNeeded(isPro, tokenMap = {}, limit = 15000) {
@@ -461,6 +513,82 @@ We request approval to acquire a **Developer SDK License** (\$299/month flat or
461
513
  * auditReceipt: string
462
514
  * }}
463
515
  */
516
+ const SDK_VERTICAL_DETECTORS = [
517
+ { 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 },
518
+ { profile: 'accounting', label: 'Tax & Accounting', regex: /\b(?:Form\s*1040|Form\s*W-2|Form\s*1099|Adjusted\s+Gross\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)\b/gi },
519
+ { 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 },
520
+ { 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 },
521
+ { 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 },
522
+ { 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 },
523
+ { 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 },
524
+ { 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 },
525
+ { 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 },
526
+ { 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 },
527
+ { 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 },
528
+ { 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 },
529
+ { 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 },
530
+ { 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 },
531
+ { 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 },
532
+ { 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 },
533
+ { 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 },
534
+ { 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 },
535
+ { 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 },
536
+ { 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 },
537
+ { 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 },
538
+ { 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 },
539
+ { 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 },
540
+ { 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 },
541
+ { 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 }
542
+ ];
543
+
544
+ /**
545
+ * Automatically detects the most relevant industry compliance profile from text content.
546
+ * Executes in <0.05ms in local RAM with zero network egress.
547
+ *
548
+ * @param {string} text - Input document or prompt text.
549
+ * @returns {{
550
+ * detectedProfile: string,
551
+ * label: string,
552
+ * confidence: 'high'|'medium'|'low',
553
+ * score: number,
554
+ * alternatives: Array<{ profile: string, label: string, score: number }>
555
+ * }}
556
+ */
557
+ function detectProfile(text) {
558
+ if (!text || typeof text !== 'string') {
559
+ return { detectedProfile: 'general', label: 'General PII', confidence: 'low', score: 0, alternatives: [] };
560
+ }
561
+ const sampleSlice = text.length > 16384 ? text.slice(0, 16384) : text;
562
+ const matches = [];
563
+ for (let i = 0; i < SDK_VERTICAL_DETECTORS.length; i++) {
564
+ const c = SDK_VERTICAL_DETECTORS[i];
565
+ c.regex.lastIndex = 0;
566
+ const found = sampleSlice.match(c.regex);
567
+ if (found && found.length > 0) {
568
+ matches.push({
569
+ profile: c.profile,
570
+ label: c.label,
571
+ score: found.length,
572
+ priority: i
573
+ });
574
+ }
575
+ }
576
+ if (matches.length === 0) {
577
+ return { detectedProfile: 'general', label: 'General PII', confidence: 'low', score: 0, alternatives: [] };
578
+ }
579
+ matches.sort((a, b) => (b.score !== a.score) ? (b.score - a.score) : (a.priority - b.priority));
580
+ const primary = matches[0];
581
+ const alternatives = matches.slice(1, 4).map(({ profile, label, score }) => ({ profile, label, score }));
582
+ const confidence = primary.score >= 3 ? 'high' : (primary.score >= 2 ? 'medium' : 'low');
583
+ return {
584
+ detectedProfile: primary.profile,
585
+ label: primary.label,
586
+ confidence,
587
+ score: primary.score,
588
+ alternatives
589
+ };
590
+ }
591
+
464
592
  function sanitize(text, options = {}) {
465
593
  if (!text || typeof text !== 'string') {
466
594
  return {
@@ -479,7 +607,7 @@ function sanitize(text, options = {}) {
479
607
  }
480
608
 
481
609
  const {
482
- profile = 'General',
610
+ profile: rawProfileOption = 'General',
483
611
  licenseKey = (typeof process !== 'undefined' && (process.env?.PRIVACYSCRUBBER_KEY || process.env?.PRIVACYSCRUBBER_LICENSE_KEY)) || '',
484
612
  customRules = [],
485
613
  tokenLabelMap = {},
@@ -491,6 +619,12 @@ function sanitize(text, options = {}) {
491
619
  ignoreList = null
492
620
  } = options;
493
621
 
622
+ const isAutoProfile = Boolean(
623
+ (typeof rawProfileOption === 'string' && rawProfileOption.trim().toLowerCase() === 'auto') ||
624
+ options.autoDetectProfile === true
625
+ );
626
+ const autoDetected = isAutoProfile ? detectProfile(text) : null;
627
+
494
628
  const licenseCheck = validateLicense(licenseKey);
495
629
  const rawTier = licenseCheck.valid ? (licenseCheck.tier || 'PRO') : 'FREE';
496
630
  const isExpiredKey = !licenseCheck.valid && typeof licenseCheck.error === 'string' && /expired/i.test(licenseCheck.error);
@@ -512,6 +646,21 @@ function sanitize(text, options = {}) {
512
646
  const isSdkTier = licenseCheck.valid && ['SDK', 'OEM', 'ENTERPRISE'].includes(rawTier);
513
647
  const isSeatTier = licenseCheck.valid && ['PRO', 'TEAMS'].includes(rawTier);
514
648
 
649
+ const isPro = isSdkTier;
650
+ const tier = isPro ? rawTier : (isSeatTier ? rawTier : 'FREE');
651
+
652
+ let autoSuggestedOnly = false;
653
+ let profile = rawProfileOption;
654
+ if (isAutoProfile && autoDetected) {
655
+ if (!isPro && text.length > 5000 && text.length <= 15000 && autoDetected.detectedProfile !== 'general') {
656
+ // Non-blocking PLG fallback: keep General for Free 5k-15k texts so auto-mode never breaks free calls
657
+ profile = 'General';
658
+ autoSuggestedOnly = true;
659
+ } else {
660
+ profile = autoDetected.detectedProfile;
661
+ }
662
+ }
663
+
515
664
  if (isSeatTier) {
516
665
  const keyPreview = licenseKey ? `${licenseKey.slice(0, 12)}...` : '';
517
666
  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`;
@@ -525,9 +674,6 @@ function sanitize(text, options = {}) {
525
674
  }
526
675
  }
527
676
 
528
- const isPro = isSdkTier;
529
- const tier = isPro ? rawTier : (isSeatTier ? rawTier : 'FREE');
530
-
531
677
  emitCommunityNoticeIfNeeded(isPro);
532
678
 
533
679
  const isSpecialized = profile && profile.toLowerCase() !== 'general';
@@ -539,7 +685,8 @@ function sanitize(text, options = {}) {
539
685
  if (!isPro && text.length > limit) {
540
686
  if (shouldThrow) {
541
687
  const expiredPrefix = isExpiredKey ? `[PILOT/TRIAL EXPIRED: ${licenseCheck.error}] ` : '';
542
- const errorMsg = `[PrivacyScrubber] ${expiredPrefix}Free Tier character limit exceeded (${limit.toLocaleString()} chars for profile '${profile}', received ${text.length.toLocaleString()} chars). Upgrade to Developer SDK ($299/mo) for unlimited throughput: https://privacyscrubber.com/pricing?tier=sdk&src=${isExpiredKey ? 'pilot_expired' : 'dev_limit'} (Run 'npx @privacyscrubber/sdk memo' for CISO approval memo)`;
688
+ const safeProf = encodeURIComponent(String(profile || 'General').toLowerCase());
689
+ const errorMsg = `[PrivacyScrubber] ${expiredPrefix}Free Tier character limit exceeded (${limit.toLocaleString()} chars for profile '${profile}', received ${text.length.toLocaleString()} chars). Upgrade to Developer SDK ($299/mo) for unlimited throughput: https://privacyscrubber.com/pricing?tier=sdk&profile=${safeProf}&src=${isExpiredKey ? 'pilot_expired' : 'dev_limit'} (Start 7-day B2B trial: 'npx @privacyscrubber/sdk trial' or 'npx @privacyscrubber/sdk memo' for CISO memo)`;
543
690
  throw new PrivacyScrubberLicenseError(errorMsg, {
544
691
  tier: 'FREE',
545
692
  currentLength: text.length,
@@ -560,8 +707,33 @@ function sanitize(text, options = {}) {
560
707
  });
561
708
 
562
709
  // Auto-inject DevOps Secrets rules when in Dev profile or when detectSecrets is requested
563
- const activeRules = [...safeCustomRules];
564
- if (profile.toLowerCase() === 'dev' || detectSecrets === true) {
710
+ const isProdEnv = typeof process !== 'undefined' && process.env?.NODE_ENV === 'production';
711
+ const hasCustomEnterpriseConfig = isSpecialized || safeCustomRules.length > 0 || (Array.isArray(ignoreList) && ignoreList.length > 0);
712
+ let profileFallback = false;
713
+ let effectiveProfile = profile;
714
+ let effectiveCustomRules = safeCustomRules;
715
+ let effectiveIgnoreList = ignoreList;
716
+
717
+ if (!isPro && isProdEnv && hasCustomEnterpriseConfig) {
718
+ _freeProdSpecializedCalls++;
719
+ if (_freeProdSpecializedCalls > FREE_PROD_SPECIALIZED_CALL_LIMIT) {
720
+ profileFallback = true;
721
+ effectiveProfile = 'General';
722
+ effectiveCustomRules = [];
723
+ effectiveIgnoreList = null;
724
+ if (!_prodProfileFallbackNoticeLogged && typeof process !== 'undefined' && process.stderr && typeof process.stderr.write === 'function') {
725
+ _prodProfileFallbackNoticeLogged = true;
726
+ process.stderr.write(
727
+ `\n[@privacyscrubber/sdk] Free Production Quota (${FREE_PROD_SPECIALIZED_CALL_LIMIT} specialized calls) reached for profile '${profile}'.\n` +
728
+ `[@privacyscrubber/sdk] Gracefully falling back to 'General' profile (Core PII remains protected, 0 downtime).\n` +
729
+ `[@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`
730
+ );
731
+ }
732
+ }
733
+ }
734
+
735
+ const activeRules = [...effectiveCustomRules];
736
+ if (effectiveProfile.toLowerCase() === 'dev' || detectSecrets === true) {
565
737
  DEVOPS_SECRETS_DETECTOR.forEach(rule => {
566
738
  activeRules.push({
567
739
  name: rule.name,
@@ -582,10 +754,10 @@ function sanitize(text, options = {}) {
582
754
  textToScrub,
583
755
  activeRules,
584
756
  tokenLabelMap,
585
- profile,
757
+ effectiveProfile,
586
758
  existingSessionMap,
587
759
  isPro,
588
- ignoreList
760
+ effectiveIgnoreList
589
761
  );
590
762
 
591
763
  const endTime = (typeof performance !== 'undefined' && typeof performance.now === 'function')
@@ -593,7 +765,7 @@ function sanitize(text, options = {}) {
593
765
  : Date.now();
594
766
 
595
767
  const executionMs = Math.round((endTime - startTime) * 100) / 100;
596
- const telemetry = buildAuditTelemetry(rawResult.tokenMap || {});
768
+ const telemetry = buildAuditTelemetry(rawResult.tokenMap || {}, { licenseTier: tier });
597
769
  const auditReceipt = generateReceipt ? formatAuditReceipt(telemetry) : '';
598
770
 
599
771
  emitDevSecretNoticeIfNeeded(isPro, rawResult.tokenMap, limit);
@@ -620,15 +792,32 @@ function sanitize(text, options = {}) {
620
792
  tier: tier,
621
793
  limit: limit,
622
794
  wasTruncated: wasTruncated,
795
+ truncated: wasTruncated,
796
+ profileFallback: profileFallback,
797
+ effectiveProfile: effectiveProfile,
798
+ specializedCallsUsed: _freeProdSpecializedCalls,
799
+ specializedCallsLimit: isPro ? Infinity : FREE_PROD_SPECIALIZED_CALL_LIMIT,
623
800
  ...(isSeatTier && {
624
- upgradeUrl: 'https://privacyscrubber.com/pricing?tier=sdk&src=seat_tier_mismatch',
801
+ upgradeUrl: `https://privacyscrubber.com/pricing?tier=sdk&profile=${encodeURIComponent(String(profile || 'General').toLowerCase())}&src=seat_tier_mismatch`,
802
+ trialUrl: `https://privacyscrubber.com/pricing?tier=sdk&trial=true&profile=${encodeURIComponent(String(profile || 'General').toLowerCase())}&src=seat_tier_mismatch`,
803
+ trialCommand: 'npx @privacyscrubber/sdk trial',
625
804
  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`
626
805
  }),
627
806
  ...(!isPro && !isSeatTier && {
628
- upgradeUrl: 'https://privacyscrubber.com/pricing?tier=sdk&src=dev_upsell',
807
+ upgradeUrl: `https://privacyscrubber.com/pricing?tier=sdk&profile=${encodeURIComponent(String(profile || 'General').toLowerCase())}&src=${profileFallback ? 'prod_profile_fallback' : 'dev_upsell'}`,
808
+ trialUrl: `https://privacyscrubber.com/pricing?tier=sdk&trial=true&profile=${encodeURIComponent(String(profile || 'General').toLowerCase())}&src=${profileFallback ? 'prod_profile_fallback' : 'dev_upsell'}`,
809
+ trialCommand: 'npx @privacyscrubber/sdk trial',
629
810
  recommendation: 'Upgrade to Developer SDK ($299/mo) for unlimited backend text processing, custom regex rules, and specialized compliance profiles.'
630
811
  })
631
812
  },
813
+ profileDetection: {
814
+ autoDetected: isAutoProfile,
815
+ detectedProfile: autoDetected ? autoDetected.detectedProfile : String(effectiveProfile || 'General').toLowerCase(),
816
+ label: autoDetected ? autoDetected.label : effectiveProfile,
817
+ confidence: autoDetected ? autoDetected.confidence : 'high',
818
+ suggestedOnly: autoSuggestedOnly,
819
+ alternatives: autoDetected ? autoDetected.alternatives : []
820
+ },
632
821
  telemetry: telemetry,
633
822
  auditReceipt: auditReceipt
634
823
  };
@@ -3855,6 +4044,7 @@ const {
3855
4044
 
3856
4045
  const _defaultExport = {
3857
4046
  SDK_VERSION,
4047
+ detectProfile,
3858
4048
  sanitize,
3859
4049
  sanitizeObject,
3860
4050
  scrub,
@@ -3907,6 +4097,8 @@ const _defaultExport = {
3907
4097
  scrubText,
3908
4098
  unscrubText,
3909
4099
  DEVOPS_SECRETS_DETECTOR,
4100
+ FREE_PROD_SPECIALIZED_CALL_LIMIT,
4101
+ _resetProdQuotaForTesting,
3910
4102
  stripExifMetadata,
3911
4103
  redactImageBoundingBoxes,
3912
4104
  generateSvgRedactionOverlay
package/index.d.ts CHANGED
@@ -120,6 +120,14 @@ export interface IpHoldings {
120
120
  trademark: string;
121
121
  }
122
122
 
123
+ export interface AgenticProtocolMetadata {
124
+ protocolsSupported: string[];
125
+ agentCardUrl: string;
126
+ ztdsStandardCardUrl: string;
127
+ regulatorySafeHarbor: string[];
128
+ zeroCleartextEgress: boolean;
129
+ }
130
+
123
131
  export interface AuditTelemetry {
124
132
  totalCount: number;
125
133
  entities: Record<string, number>;
@@ -128,6 +136,7 @@ export interface AuditTelemetry {
128
136
  frameworksList: string[];
129
137
  statutoryCitations?: StatutoryCitation[];
130
138
  ipHoldings?: IpHoldings;
139
+ agenticProtocol?: AgenticProtocolMetadata;
131
140
  }
132
141
 
133
142
  export interface SanitizeResult {