@privacyscrubber/sdk 2.2.7 → 2.2.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 CHANGED
@@ -8,6 +8,7 @@
8
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
9
9
  [![Patent Pending](https://img.shields.io/badge/Patent-Pending%20(ILPO%20331905)-06b6d4.svg)](https://privacyscrubber.com/patents/)
10
10
  [![IETF Specification](https://img.shields.io/badge/IETF-ZTDS--Protocol-0284c7.svg)](https://datatracker.ietf.org/doc/draft-sibiryakov-ztds-protocol/)
11
+ [![ZTDS Verified](https://ztds.ai/badge/privacyscrubber-sdk.svg)](https://ztds.ai/registry/#privacyscrubber-sdk)
11
12
  [![GitHub Stars](https://img.shields.io/github/stars/moxno/privacyscrubber-mcp?style=social)](https://github.com/moxno/privacyscrubber-mcp)
12
13
 
13
14
  > **In-Process Consumer Privacy Defense & Pre-Emptive Interception Engine**
package/browser.js CHANGED
@@ -196,8 +196,29 @@ export function sanitize(text, options = {}) {
196
196
  } = options;
197
197
 
198
198
  const licenseCheck = validateLicense(licenseKey);
199
- const isPro = licenseCheck.valid;
200
- const tier = isPro ? licenseCheck.tier : 'FREE';
199
+ const rawTier = licenseCheck.valid ? (licenseCheck.tier || 'PRO') : 'FREE';
200
+
201
+ // Anti-Cannibalization Barrier (SSOT):
202
+ // PRO ($15/mo) and TEAMS ($99/mo) licenses are strictly human-seat licenses for Web App & Chrome Extension.
203
+ // Programmatic SDK pipelines require Developer SDK, OEM, or ENTERPRISE ($299/mo).
204
+ const isSdkTier = licenseCheck.valid && ['SDK', 'OEM', 'ENTERPRISE'].includes(rawTier);
205
+ const isSeatTier = licenseCheck.valid && ['PRO', 'TEAMS'].includes(rawTier);
206
+
207
+ if (isSeatTier) {
208
+ const keyPreview = licenseKey ? `${licenseKey.slice(0, 12)}...` : '';
209
+ const errorMsg = `[PrivacyScrubber] License tier mismatch: ${rawTier} license (${keyPreview}) is licensed strictly for human seats (Web App & Chrome Extension). Programmatic SDK pipelines require a Developer SDK license ($299/mo). Upgrade: https://privacyscrubber.com/pricing?tier=sdk&src=seat_tier_mismatch`;
210
+ if (throwOnLimit !== false) {
211
+ throw new PrivacyScrubberLicenseError(errorMsg, {
212
+ tier: rawTier,
213
+ currentLength: text.length,
214
+ limit: 15000,
215
+ profile: profile
216
+ });
217
+ }
218
+ }
219
+
220
+ const isPro = isSdkTier;
221
+ const tier = isPro ? rawTier : (isSeatTier ? rawTier : 'FREE');
201
222
 
202
223
  const isSpecialized = profile && profile.toLowerCase() !== 'general';
203
224
  const limit = isPro ? Infinity : (isSpecialized ? 5000 : 15000);
@@ -207,7 +228,7 @@ export function sanitize(text, options = {}) {
207
228
 
208
229
  if (!isPro && text.length > limit) {
209
230
  if (shouldThrow) {
210
- const errorMsg = `[PrivacyScrubber] Free Tier character limit exceeded (${limit.toLocaleString()} chars for profile '${profile}', received ${text.length.toLocaleString()} chars). Upgrade to PRO, TEAMS, or Developer SDK for unlimited throughput: https://privacyscrubber.com/pricing?utm_source=npm_sdk&utm_medium=sdk_error&utm_campaign=dev_upsell`;
231
+ 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`;
211
232
  throw new PrivacyScrubberLicenseError(errorMsg, {
212
233
  tier: 'FREE',
213
234
  currentLength: text.length,
@@ -269,9 +290,13 @@ export function sanitize(text, options = {}) {
269
290
  tier: tier,
270
291
  limit: limit,
271
292
  wasTruncated: wasTruncated,
272
- ...(!isPro && {
273
- upgradeUrl: 'https://privacyscrubber.com/pricing?utm_source=npm_sdk&utm_medium=sdk_telemetry&utm_campaign=dev_upsell',
274
- recommendation: 'Upgrade to PRO, TEAMS, or Developer SDK for unlimited text processing, custom regex rules, and specialized compliance profiles.'
293
+ ...(isSeatTier && {
294
+ upgradeUrl: 'https://privacyscrubber.com/pricing?tier=sdk&src=seat_tier_mismatch',
295
+ 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`
296
+ }),
297
+ ...(!isPro && !isSeatTier && {
298
+ upgradeUrl: 'https://privacyscrubber.com/pricing?tier=sdk&src=dev_upsell',
299
+ recommendation: 'Upgrade to Developer SDK ($299/mo) for unlimited text processing, custom regex rules, and specialized compliance profiles.'
275
300
  })
276
301
  },
277
302
  telemetry: telemetry,
package/cli.js CHANGED
@@ -235,7 +235,7 @@ Options:
235
235
  --stream Stream de-tokenization chunks in real-time from stdin
236
236
  --token-map <path> Path to JSON file or inline JSON string of token mappings
237
237
  --profile <name> Specify detection profile (default: Dev, 30 profiles available)
238
- --key <licKey> PrivacyScrubber PRO/TEAMS/SDK license key
238
+ --key <licKey> PrivacyScrubber Developer SDK license key (PS-SDK-...)
239
239
  -v, --version Show SDK version
240
240
  -h, --help Show help screen
241
241
 
@@ -251,7 +251,7 @@ Examples:
251
251
 
252
252
  Commercial Licensing:
253
253
  Developer SDK ($299/mo flat): https://privacyscrubber.com/pricing?tier=sdk&src=cli_help
254
- Teams ($99/mo flat): https://privacyscrubber.com/pricing?tier=teams&src=cli_help
254
+ Teams ($99/mo, Web & Chrome): https://privacyscrubber.com/pricing?tier=teams&src=cli_help
255
255
  `);
256
256
  process.exit(0);
257
257
  }
package/fastmcp.cjs ADDED
@@ -0,0 +1,273 @@
1
+ /**
2
+ * @privacyscrubber/fastmcp-guard
3
+ * Zero-Trust Data Sanitization (ZTDS) In-Memory Middleware for FastMCP
4
+ * Conforms to IETF Standards Track: draft-sibiryakov-ztds-protocol-02
5
+ *
6
+ * Core Protocol Invariants:
7
+ * 1. Zero External Egress Prior to Sanitization
8
+ * 2. Deterministic Reversible Tokenization
9
+ * 3. In-Memory Reference Purge (Zeroization)
10
+ * 4. ReDoS-Resistant Pattern Scanning
11
+ * 5. Single-Pass O(N) Restoration (Zero Token Cascading)
12
+ */
13
+
14
+ const crypto = require("node:crypto");
15
+
16
+ const ZTDS_PATTERNS = {
17
+ API_SECRET: /\b(?:sk-(?:proj-)?[A-Za-z0-9_-]{20,}|(?:ghp|gho|ghu|ghs|ghr|github_pat_)[A-Za-z0-9_]{20,}|eyJ[a-zA-Z0-9_-]{20,}\.[a-zA-Z0-9_-]{20,}\.[a-zA-Z0-9_-]{20,})\b/g,
18
+ AWS_KEY: /\b(?:AKIA|ASIA|AGPA|AIDA|AROA|AIPA)[A-Z0-9]{16}\b/g,
19
+ CREDIT_CARD: /\b(?:\d{4}[-\s]?){3}\d{4}\b/g,
20
+ EMAIL: /[A-Za-z0-9._%+-]{1,64}@[A-Za-z0-9-]{1,63}(?:\.[A-Za-z0-9-]{1,63})*\.[A-Za-z]{2,24}/g,
21
+ IBAN: /\b[A-Z]{2}[0-9]{2}[A-Z0-9]{4}[0-9]{7}(?:[A-Z0-9]?){0,16}\b/g,
22
+ IPV4: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g,
23
+ PHONE: /(?:(?:\+?1\s*(?:[.-]\s*)?)?(?:\(\s*([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\s*\)|([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\s*(?:[.-]\s*)?)?([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8]{2})\s*(?:[.-]\s*)?([0-9]{4})\b/g,
24
+ SSN: /\b\d{3}-\d{2}-\d{4}\b/g,
25
+ };
26
+
27
+ const TOKEN_PATTERN = /\[([A-Z_]+)_TOKEN_([a-zA-Z0-9_-]+)\]/g;
28
+
29
+ class ZTDSFastMCPGuard {
30
+ constructor(options = {}) {
31
+ this.enabledEntities = options.enabledEntities || Object.keys(ZTDS_PATTERNS);
32
+ this.sanitizeInputs = options.sanitizeInputs !== false;
33
+ this.sanitizeOutputs = options.sanitizeOutputs !== false;
34
+ this.useCryptoTokens = options.useCryptoTokens !== false;
35
+ this.autoZeroize = options.autoZeroize !== false;
36
+ this._sessionMaps = new Map();
37
+ this._entityMaps = new Map();
38
+ }
39
+
40
+ _generateToken(entityType, tokenMap) {
41
+ if (this.useCryptoTokens) {
42
+ for (let i = 0; i < 100; i++) {
43
+ const hex = crypto.randomBytes(4).toString("hex");
44
+ const candidate = `[${entityType}_TOKEN_${hex}]`;
45
+ if (!tokenMap.has(candidate)) {
46
+ return candidate;
47
+ }
48
+ }
49
+ }
50
+ let count = 0;
51
+ for (const k of tokenMap.keys()) {
52
+ if (k.startsWith(`[${entityType}_TOKEN_`)) {
53
+ count++;
54
+ }
55
+ }
56
+ return `[${entityType}_TOKEN_${count + 1}]`;
57
+ }
58
+
59
+ sanitizeText(text, sessionId) {
60
+ if (typeof text !== "string") {
61
+ return { sanitized: text, tokenMap: {} };
62
+ }
63
+
64
+ if (!this._sessionMaps.has(sessionId)) {
65
+ this._sessionMaps.set(sessionId, new Map());
66
+ this._entityMaps.set(sessionId, new Map());
67
+ }
68
+
69
+ const tokenMap = this._sessionMaps.get(sessionId);
70
+ const entityMap = this._entityMaps.get(sessionId);
71
+ let sanitized = text;
72
+
73
+ for (const entityType of this.enabledEntities) {
74
+ const pattern = ZTDS_PATTERNS[entityType];
75
+ if (!pattern) continue;
76
+
77
+ if (entityType === "EMAIL" && !sanitized.includes("@")) {
78
+ continue;
79
+ }
80
+
81
+ const regex = new RegExp(`(\\[[A-Z_]+_TOKEN_[a-zA-Z0-9_-]+\\])|(?:${pattern.source})`, "g");
82
+ sanitized = sanitized.replace(regex, (match, existingToken) => {
83
+ if (existingToken) {
84
+ return existingToken;
85
+ }
86
+ if (entityMap.has(match)) {
87
+ return entityMap.get(match);
88
+ }
89
+ const token = this._generateToken(entityType, tokenMap);
90
+ tokenMap.set(token, match);
91
+ entityMap.set(match, token);
92
+ return token;
93
+ });
94
+ }
95
+
96
+ const exportedMap = {};
97
+ for (const [k, v] of tokenMap.entries()) {
98
+ exportedMap[k] = v;
99
+ }
100
+ return { sanitized, tokenMap: exportedMap };
101
+ }
102
+
103
+ restoreText(text, sessionId) {
104
+ if (typeof text !== "string") {
105
+ return text;
106
+ }
107
+ const tokenMap = this._sessionMaps.get(sessionId);
108
+ if (!tokenMap || tokenMap.size === 0) {
109
+ return text;
110
+ }
111
+
112
+ return text.replace(TOKEN_PATTERN, (match) => {
113
+ return tokenMap.get(match) || match;
114
+ });
115
+ }
116
+
117
+ sanitizeObject(val, sessionId) {
118
+ if (typeof val === "string") {
119
+ return this.sanitizeText(val, sessionId).sanitized;
120
+ }
121
+ if (Array.isArray(val)) {
122
+ return val.map((item) => this.sanitizeObject(item, sessionId));
123
+ }
124
+ if (val !== null && typeof val === "object") {
125
+ const result = {};
126
+ for (const [k, v] of Object.entries(val)) {
127
+ result[k] = this.sanitizeObject(v, sessionId);
128
+ }
129
+ return result;
130
+ }
131
+ return val;
132
+ }
133
+
134
+ restoreObject(val, sessionId) {
135
+ if (typeof val === "string") {
136
+ return this.restoreText(val, sessionId);
137
+ }
138
+ if (Array.isArray(val)) {
139
+ return val.map((item) => this.restoreObject(item, sessionId));
140
+ }
141
+ if (val !== null && typeof val === "object") {
142
+ const result = {};
143
+ for (const [k, v] of Object.entries(val)) {
144
+ result[k] = this.restoreObject(v, sessionId);
145
+ }
146
+ return result;
147
+ }
148
+ return val;
149
+ }
150
+
151
+ wrapTool(toolName, handler) {
152
+ return async (args, context = {}) => {
153
+ const sessionId =
154
+ context.sessionId ||
155
+ `mcp-${Date.now()}-${crypto.randomBytes(4).toString("hex")}`;
156
+
157
+ const enrichedContext = {
158
+ ...context,
159
+ sessionId,
160
+ unmask: (val) => this.restoreObject(val, sessionId),
161
+ };
162
+
163
+ try {
164
+ let processedArgs = args;
165
+ if (this.sanitizeInputs && args !== undefined && args !== null) {
166
+ processedArgs = this.sanitizeObject(args, sessionId);
167
+ }
168
+
169
+ const rawResult = await handler(processedArgs, enrichedContext);
170
+
171
+ let finalResult = rawResult;
172
+ if (this.sanitizeOutputs && rawResult !== undefined && rawResult !== null) {
173
+ if (typeof rawResult === "string") {
174
+ finalResult = this.sanitizeText(rawResult, sessionId).sanitized;
175
+ } else if (typeof rawResult === "object" && Array.isArray(rawResult.content)) {
176
+ finalResult = {
177
+ ...rawResult,
178
+ content: rawResult.content.map((block) => {
179
+ if (block && block.type === "text" && typeof block.text === "string") {
180
+ return {
181
+ ...block,
182
+ text: this.sanitizeText(block.text, sessionId).sanitized,
183
+ };
184
+ }
185
+ return block;
186
+ }),
187
+ };
188
+ } else {
189
+ finalResult = this.sanitizeObject(rawResult, sessionId);
190
+ }
191
+ }
192
+
193
+ const ztdsMeta = {
194
+ sessionId,
195
+ standard: "IETF draft-sibiryakov-ztds-protocol-02",
196
+ zeroEgress: true,
197
+ };
198
+
199
+ if (typeof finalResult === "string") {
200
+ return {
201
+ content: [{ type: "text", text: finalResult }],
202
+ _ztds: ztdsMeta,
203
+ };
204
+ }
205
+
206
+ if (finalResult !== null && typeof finalResult === "object" && !Array.isArray(finalResult)) {
207
+ return {
208
+ ...finalResult,
209
+ _ztds: ztdsMeta,
210
+ };
211
+ }
212
+
213
+ return {
214
+ result: finalResult,
215
+ _ztds: ztdsMeta,
216
+ };
217
+ } finally {
218
+ if (this.autoZeroize) {
219
+ this.zeroizeSession(sessionId);
220
+ }
221
+ }
222
+ };
223
+ }
224
+
225
+ zeroizeSession(sessionId) {
226
+ if (this._sessionMaps.has(sessionId)) {
227
+ this._sessionMaps.get(sessionId).clear();
228
+ this._sessionMaps.delete(sessionId);
229
+ }
230
+ if (this._entityMaps.has(sessionId)) {
231
+ this._entityMaps.get(sessionId).clear();
232
+ this._entityMaps.delete(sessionId);
233
+ }
234
+ }
235
+ }
236
+
237
+ function createZTDSGuard(options) {
238
+ return new ZTDSFastMCPGuard(options);
239
+ }
240
+
241
+ function ztdsGuardTool(handler, options) {
242
+ const guard = new ZTDSFastMCPGuard(options);
243
+ return guard.wrapTool("tool", handler);
244
+ }
245
+
246
+ function wrapExecute(handler, options) {
247
+ return ztdsGuardTool(handler, options);
248
+ }
249
+
250
+ function withZTDS(server, options = {}) {
251
+ const guard = new ZTDSFastMCPGuard(options);
252
+ if (server && typeof server.addTool === "function") {
253
+ const origAddTool = server.addTool.bind(server);
254
+ server.addTool = function(toolDef) {
255
+ if (toolDef && typeof toolDef.execute === "function") {
256
+ const origExecute = toolDef.execute;
257
+ toolDef.execute = guard.wrapTool(toolDef.name || "tool", origExecute);
258
+ }
259
+ return origAddTool(toolDef);
260
+ };
261
+ }
262
+ return server;
263
+ }
264
+
265
+ module.exports = {
266
+ ZTDSFastMCPGuard,
267
+ ZTDS_PATTERNS,
268
+ TOKEN_PATTERN,
269
+ createZTDSGuard,
270
+ ztdsGuardTool,
271
+ wrapExecute,
272
+ withZTDS,
273
+ };
package/fastmcp.d.ts ADDED
@@ -0,0 +1,65 @@
1
+ /**
2
+ * @privacyscrubber/fastmcp-guard
3
+ * TypeScript Definitions
4
+ */
5
+
6
+ export interface ZTDSMetadata {
7
+ sessionId: string;
8
+ standard: string;
9
+ zeroEgress: boolean;
10
+ }
11
+
12
+ export interface ZTDSGuardOptions {
13
+ enabledEntities?: string[];
14
+ sanitizeInputs?: boolean;
15
+ sanitizeOutputs?: boolean;
16
+ useCryptoTokens?: boolean;
17
+ autoZeroize?: boolean;
18
+ }
19
+
20
+ export interface ZTDSContext {
21
+ sessionId: string;
22
+ unmask: (val: unknown) => unknown;
23
+ [key: string]: unknown;
24
+ }
25
+
26
+ export declare const ZTDS_PATTERNS: Record<string, RegExp>;
27
+ export declare const TOKEN_PATTERN: RegExp;
28
+
29
+ export declare class ZTDSFastMCPGuard {
30
+ constructor(options?: ZTDSGuardOptions);
31
+ enabledEntities: string[];
32
+ sanitizeInputs: boolean;
33
+ sanitizeOutputs: boolean;
34
+ useCryptoTokens: boolean;
35
+ autoZeroize: boolean;
36
+
37
+ sanitizeText(text: string, sessionId: string): { sanitized: string; tokenMap: Record<string, string> };
38
+ restoreText(text: string, sessionId: string): string;
39
+ sanitizeObject(val: unknown, sessionId: string): unknown;
40
+ restoreObject(val: unknown, sessionId: string): unknown;
41
+
42
+ wrapTool<TArgs = unknown, TResult = unknown>(
43
+ toolName: string,
44
+ handler: (args: TArgs, context: ZTDSContext) => Promise<TResult>
45
+ ): (args: TArgs, context?: Partial<ZTDSContext>) => Promise<TResult & { _ztds: ZTDSMetadata }>;
46
+
47
+ zeroizeSession(sessionId: string): void;
48
+ }
49
+
50
+ export declare function createZTDSGuard(options?: ZTDSGuardOptions): ZTDSFastMCPGuard;
51
+
52
+ export declare function ztdsGuardTool<TArgs = unknown, TResult = unknown>(
53
+ handler: (args: TArgs, context: ZTDSContext) => Promise<TResult>,
54
+ options?: ZTDSGuardOptions
55
+ ): (args: TArgs, context?: Partial<ZTDSContext>) => Promise<TResult & { _ztds: ZTDSMetadata }>;
56
+
57
+ export declare function wrapExecute<TArgs = unknown, TResult = unknown>(
58
+ handler: (args: TArgs, context: ZTDSContext) => Promise<TResult>,
59
+ options?: ZTDSGuardOptions
60
+ ): (args: TArgs, context?: Partial<ZTDSContext>) => Promise<TResult & { _ztds: ZTDSMetadata }>;
61
+
62
+ export declare function withZTDS<TServer extends object = object>(
63
+ server: TServer,
64
+ options?: ZTDSGuardOptions
65
+ ): TServer;
package/fastmcp.js ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * @privacyscrubber/sdk/fastmcp
3
+ * Zero-Trust Data Sanitization (ZTDS) In-Memory Middleware for FastMCP
4
+ */
5
+
6
+ import { createRequire } from 'module';
7
+ const require = createRequire(import.meta.url);
8
+
9
+ const fastmcpGuard = require('./fastmcp.cjs');
10
+
11
+ export const ZTDSFastMCPGuard = fastmcpGuard.ZTDSFastMCPGuard;
12
+ export const ZTDS_PATTERNS = fastmcpGuard.ZTDS_PATTERNS;
13
+ export const TOKEN_PATTERN = fastmcpGuard.TOKEN_PATTERN;
14
+ export const createZTDSGuard = fastmcpGuard.createZTDSGuard;
15
+ export const ztdsGuardTool = fastmcpGuard.ztdsGuardTool;
16
+ export const wrapExecute = fastmcpGuard.wrapExecute;
17
+ export const withZTDS = fastmcpGuard.withZTDS;
18
+
19
+ export default {
20
+ ZTDSFastMCPGuard,
21
+ ZTDS_PATTERNS,
22
+ TOKEN_PATTERN,
23
+ createZTDSGuard,
24
+ ztdsGuardTool,
25
+ wrapExecute,
26
+ withZTDS,
27
+ };
package/index.d.ts CHANGED
@@ -7,6 +7,7 @@ export interface CustomRule {
7
7
  regex: RegExp;
8
8
  mask?: string;
9
9
  type?: string;
10
+ label?: string;
10
11
  }
11
12
 
12
13
  export type PiiProfile =
@@ -66,6 +67,10 @@ export interface SanitizeOptions {
66
67
  tokenLabelMap?: Record<string, string>;
67
68
  /** Pre-existing token session map for stateful conversation tracking. */
68
69
  existingSessionMap?: Record<string, string>;
70
+ /** Whether tokenMap should return the full cumulative session map when existingSessionMap is provided (Default: false). */
71
+ returnFullSessionMap?: boolean;
72
+ /** Set or array of strings or exact terms to preserve unmasked in cleartext. */
73
+ ignoreList?: string[] | Set<string>;
69
74
  /** Whether to generate a CISO Markdown audit receipt (Default: true). */
70
75
  generateReceipt?: boolean;
71
76
  /** Throw PrivacyScrubberLicenseError when free tier limits are reached (Default: true). */
@@ -74,6 +79,21 @@ export interface SanitizeOptions {
74
79
  onLimit?: 'throw' | 'truncate';
75
80
  }
76
81
 
82
+ export interface DetectedSpan {
83
+ /** 0-based character start index in original text. */
84
+ start: number;
85
+ /** 0-based character end index in original text. */
86
+ end: number;
87
+ /** Raw matched value before sanitization. */
88
+ value: string;
89
+ /** Detected entity type (NAME, EMAIL, PHONE, ID, FINANCIAL, etc.). */
90
+ type: string;
91
+ /** Deterministic masking token assigned (e.g. [NAME_1]). */
92
+ token?: string;
93
+ /** True if anchored by context keyword, honorific, or statutory checksum. */
94
+ isHighConfidence?: boolean;
95
+ }
96
+
77
97
  export interface LicenseMetadata {
78
98
  isPro: boolean;
79
99
  tier: 'PRO' | 'TEAMS' | 'SDK' | 'ENTERPRISE' | 'OEM' | 'FREE';
@@ -96,6 +116,12 @@ export interface SanitizeResult {
96
116
  scrubbedText: string;
97
117
  /** The volatile in-memory token map needed to restore data. */
98
118
  tokenMap: Record<string, string>;
119
+ /** Full cumulative session map when existingSessionMap was provided. */
120
+ sessionMap?: Record<string, string>;
121
+ /** Detailed array of detected entity spans and positions. */
122
+ spans: DetectedSpan[];
123
+ /** Alias for spans. */
124
+ matches: DetectedSpan[];
99
125
  /** Total number of tokens masked. */
100
126
  count: number;
101
127
  /** Execution duration in milliseconds. */
package/index.js CHANGED
@@ -412,8 +412,29 @@ export function sanitize(text, options = {}) {
412
412
  } = options;
413
413
 
414
414
  const licenseCheck = validateLicense(licenseKey);
415
- const isPro = licenseCheck.valid;
416
- const tier = isPro ? licenseCheck.tier : 'FREE';
415
+ const rawTier = licenseCheck.valid ? (licenseCheck.tier || 'PRO') : 'FREE';
416
+
417
+ // Anti-Cannibalization Barrier (SSOT):
418
+ // PRO ($15/mo) and TEAMS ($99/mo) licenses are strictly human-seat licenses for Web App & Chrome Extension.
419
+ // Backend automation, microservices, RAG pipelines, and headless SDK execution require Developer SDK, OEM, or ENTERPRISE ($299/mo).
420
+ const isSdkTier = licenseCheck.valid && ['SDK', 'OEM', 'ENTERPRISE'].includes(rawTier);
421
+ const isSeatTier = licenseCheck.valid && ['PRO', 'TEAMS'].includes(rawTier);
422
+
423
+ if (isSeatTier) {
424
+ const keyPreview = licenseKey ? `${licenseKey.slice(0, 12)}...` : '';
425
+ 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`;
426
+ if (throwOnLimit !== false) {
427
+ throw new PrivacyScrubberLicenseError(errorMsg, {
428
+ tier: rawTier,
429
+ currentLength: text.length,
430
+ limit: 15000,
431
+ profile: profile
432
+ });
433
+ }
434
+ }
435
+
436
+ const isPro = isSdkTier;
437
+ const tier = isPro ? rawTier : (isSeatTier ? rawTier : 'FREE');
417
438
 
418
439
  emitCommunityNoticeIfNeeded(isPro);
419
440
 
@@ -425,7 +446,7 @@ export function sanitize(text, options = {}) {
425
446
 
426
447
  if (!isPro && text.length > limit) {
427
448
  if (shouldThrow) {
428
- const errorMsg = `[PrivacyScrubber] Free Tier character limit exceeded (${limit.toLocaleString()} chars for profile '${profile}', received ${text.length.toLocaleString()} chars). Upgrade to PRO, TEAMS, or Developer SDK for unlimited throughput: https://privacyscrubber.com/pricing?utm_source=npm_sdk&utm_medium=sdk_error&utm_campaign=dev_upsell`;
449
+ 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`;
429
450
  throw new PrivacyScrubberLicenseError(errorMsg, {
430
451
  tier: 'FREE',
431
452
  currentLength: text.length,
@@ -480,9 +501,15 @@ export function sanitize(text, options = {}) {
480
501
  finalScrubbedText += `\n\n<!-- [PrivacyScrubber] Input truncated at free tier limit (${limit.toLocaleString()} chars). For unlimited throughput: https://privacyscrubber.com/pricing?tier=sdk&src=truncation -->`;
481
502
  }
482
503
 
504
+ const returnFullSessionMap = options.returnFullSessionMap === true;
505
+ const sessionMap = { ...(existingSessionMap || {}), ...(rawResult.tokenMap || {}) };
506
+
483
507
  return {
484
508
  scrubbedText: finalScrubbedText,
485
- tokenMap: rawResult.tokenMap || {},
509
+ tokenMap: returnFullSessionMap ? sessionMap : (rawResult.tokenMap || {}),
510
+ sessionMap: sessionMap,
511
+ matches: rawResult.matches || [],
512
+ spans: rawResult.matches || [],
486
513
  count: rawResult.count || 0,
487
514
  executionMs: executionMs,
488
515
  license: {
@@ -490,9 +517,13 @@ export function sanitize(text, options = {}) {
490
517
  tier: tier,
491
518
  limit: limit,
492
519
  wasTruncated: wasTruncated,
493
- ...(!isPro && {
494
- upgradeUrl: 'https://privacyscrubber.com/pricing?utm_source=npm_sdk&utm_medium=sdk_telemetry&utm_campaign=dev_upsell',
495
- recommendation: 'Upgrade to PRO, TEAMS, or Developer SDK for unlimited text processing, custom regex rules, and specialized compliance profiles.'
520
+ ...(isSeatTier && {
521
+ upgradeUrl: 'https://privacyscrubber.com/pricing?tier=sdk&src=seat_tier_mismatch',
522
+ 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`
523
+ }),
524
+ ...(!isPro && !isSeatTier && {
525
+ upgradeUrl: 'https://privacyscrubber.com/pricing?tier=sdk&src=dev_upsell',
526
+ recommendation: 'Upgrade to Developer SDK ($299/mo) for unlimited backend text processing, custom regex rules, and specialized compliance profiles.'
496
527
  })
497
528
  },
498
529
  telemetry: telemetry,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@privacyscrubber/sdk",
3
- "version": "2.2.7",
3
+ "version": "2.2.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.js",
6
6
  "browser": "browser.js",
@@ -16,6 +16,11 @@
16
16
  "types": "./vision.d.ts",
17
17
  "import": "./vision.js",
18
18
  "default": "./vision.js"
19
+ },
20
+ "./fastmcp": {
21
+ "types": "./fastmcp.d.ts",
22
+ "import": "./fastmcp.js",
23
+ "default": "./fastmcp.js"
19
24
  }
20
25
  },
21
26
  "publishConfig": {
@@ -26,6 +31,9 @@
26
31
  "browser.js",
27
32
  "vision.js",
28
33
  "vision.d.ts",
34
+ "fastmcp.js",
35
+ "fastmcp.cjs",
36
+ "fastmcp.d.ts",
29
37
  "index.d.ts",
30
38
  "cli.js",
31
39
  "polyfill.js",