@privacyscrubber/sdk 2.2.6 โ†’ 2.2.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/README.md CHANGED
@@ -648,7 +648,7 @@ To acquire a commercial Developer SDK license:
648
648
  - ๐ŸŒ **Web App**: [https://privacyscrubber.com](https://privacyscrubber.com)
649
649
  - ๐Ÿ”Œ **MCP Server**: [@privacyscrubber/mcp-server](https://www.npmjs.com/package/@privacyscrubber/mcp-server)
650
650
  - ๐Ÿงฉ **Chrome Extension**: [Chrome Web Store](https://chromewebstore.google.com/detail/privacyscrubber-%E2%80%94-zero-tr/pimoejgefeilajmmbpghifdmhdlkgjol)
651
- - ๐Ÿ“– **Developer SDK Specs**: [https://privacyscrubber.com/features/developer-sdk/](https://privacyscrubber.com/features/developer-sdk/)
651
+ - ๐Ÿ“– **Developer SDK Specs**: [https://privacyscrubber.com/sdk/](https://privacyscrubber.com/sdk/)
652
652
  - โญ **GitHub Repository**: [moxno/privacyscrubber-mcp](https://github.com/moxno/privacyscrubber-mcp) (Star us on GitHub!)
653
653
  - ๐Ÿงช **DLP Latency Benchmark**: [https://privacyscrubber.com/dlp-speed-test/](https://privacyscrubber.com/dlp-speed-test/)
654
654
 
package/browser.js CHANGED
@@ -94,7 +94,9 @@ export function buildAuditTelemetry(currentTokenMap = {}) {
94
94
  if (currentTokenMap && typeof currentTokenMap === 'object') {
95
95
  const keys = Array.isArray(currentTokenMap) ? currentTokenMap : Object.keys(currentTokenMap);
96
96
  for (const t of keys) {
97
- const tokenStr = typeof t === 'string' ? t : (t.token || t.mask || '');
97
+ if (!t) continue;
98
+ const tokenStr = typeof t === 'string' ? t : ((t && (t.token || t.mask)) ? String(t.token || t.mask) : '');
99
+ if (!tokenStr) continue;
98
100
  const match = tokenStr.match(/\[([A-Z_]+)_\d+\]/);
99
101
  const baseType = match ? match[1] : (tokenStr.replace(/\[|\]/g, '').replace(/_[0-9]+$/, '') || 'CUSTOM');
100
102
  entities[baseType] = (entities[baseType] || 0) + 1;
@@ -149,7 +151,7 @@ export function buildAuditTelemetry(currentTokenMap = {}) {
149
151
  export function formatAuditReceipt(telemetry) {
150
152
  if (!telemetry || telemetry.totalCount === 0) {
151
153
  return "> ๐Ÿ›ก๏ธ **PrivacyScrubber Audit Receipt**: CLEAN (ZERO PII DETECTED)\n" +
152
- "> * โญ **Star on GitHub:** [moxno/privacyscrubber-mcp](https://github.com/moxno/privacyscrubber-mcp) | **SDK & Enterprise:** [privacyscrubber.com/pricing](https://privacyscrubber.com/pricing)\n";
154
+ "> * โญ **Star on GitHub:** [moxno/privacyscrubber-mcp](https://github.com/moxno/privacyscrubber-mcp) | **SDK & Enterprise:** [privacyscrubber.com/sdk](https://privacyscrubber.com/sdk/)\n";
153
155
  }
154
156
  const entitiesList = Object.entries(telemetry.entities)
155
157
  .map(([type, count]) => `${count} ${type}`)
@@ -161,7 +163,7 @@ export function formatAuditReceipt(telemetry) {
161
163
  `> * **Risk Level:** ${icon} ${telemetry.riskLevel}\n` +
162
164
  `> * **Compliance Enforced:** ${telemetry.frameworksList.join(', ')}\n` +
163
165
  `> * **Tokens Masked:** ${telemetry.totalCount} (${entitiesList})\n` +
164
- `> * โญ **Star on GitHub:** [moxno/privacyscrubber-mcp](https://github.com/moxno/privacyscrubber-mcp) | **SDK & Enterprise:** [privacyscrubber.com/pricing](https://privacyscrubber.com/pricing)\n`;
166
+ `> * โญ **Star on GitHub:** [moxno/privacyscrubber-mcp](https://github.com/moxno/privacyscrubber-mcp) | **SDK & Enterprise:** [privacyscrubber.com/sdk](https://privacyscrubber.com/sdk/)\n`;
165
167
  }
166
168
 
167
169
  /**
package/cli.js CHANGED
@@ -206,7 +206,7 @@ ${res.scrubbedText.split('\n').map(l => `โ•‘ ${l.slice(0, 62).padEnd(64)}โ•‘`).
206
206
  โ•‘ * Copy CISO Approval Memo : npx @privacyscrubber/sdk memo โ•‘
207
207
  โ•‘ * CI/CD PR Stamp : cat prompt.txt | sdk --verify-ci โ•‘
208
208
  โ•‘ * Star on GitHub : github.com/moxno/privacyscrubber-mcpโ•‘
209
- โ•‘ * Unlimited SDK ($299/mo) : https://privacyscrubber.com/pricing โ•‘
209
+ โ•‘ * Unlimited SDK ($299/mo) : https://privacyscrubber.com/sdk/ โ•‘
210
210
  โ•š${border}โ•
211
211
  `);
212
212
  process.exit(0);
@@ -317,7 +317,7 @@ function handleExecution(text) {
317
317
 
318
318
  if (isVerifyCi) {
319
319
  if (!text || !text.trim()) {
320
- console.log(`> **Verified by PrivacyScrubber (ZTDS Standard)**\n> * **Status:** PASSED ยท 0 PII / 0 Secrets detected in prompt pipeline (Empty payload)\n> * **Execution:** Local RAM (<1ms) ยท 0 Network Egress Bytes\n> * **Compliance Spec:** [Zero-Trust Data Sanitization (ZTDS)](https://privacyscrubber.com/solutions/engineering/zero-trust-ai-data-sanitization?src=ci_stamp) | โญ [Star on GitHub](https://github.com/moxno/privacyscrubber-mcp) | [Developer SDK ($299/mo)](https://privacyscrubber.com/pricing?tier=sdk&src=ci_stamp)`);
320
+ console.log(`> **Verified by PrivacyScrubber (ZTDS Standard)**\n> * **Status:** PASSED ยท 0 PII / 0 Secrets detected in prompt pipeline (Empty payload)\n> * **Execution:** Local RAM (<1ms) ยท 0 Network Egress Bytes\n> * **Compliance Spec:** [Zero-Trust Data Sanitization (ZTDS)](https://privacyscrubber.com/solutions/engineering/zero-trust-ai-data-sanitization?src=ci_stamp) | โญ [Star on GitHub](https://github.com/moxno/privacyscrubber-mcp) | [Developer SDK ($299/mo)](https://privacyscrubber.com/sdk/?src=ci_stamp)`);
321
321
  process.exit(0);
322
322
  }
323
323
 
@@ -330,7 +330,7 @@ function handleExecution(text) {
330
330
  > * **Status:** PASSED ยท ${maskedCount} item(s) protected (${telemetry?.riskLevel || 'CLEAN'})
331
331
  > * **Execution:** Local RAM (<1ms) ยท 0 Network Egress Bytes
332
332
  > * **Audit Hash:** \`${hash}\` (Zero-Trust Verified)
333
- > * **Compliance Spec:** [Zero-Trust Data Sanitization (ZTDS)](https://privacyscrubber.com/solutions/engineering/zero-trust-ai-data-sanitization?src=ci_stamp) | โญ [Star on GitHub](https://github.com/moxno/privacyscrubber-mcp) | [Developer SDK ($299/mo)](https://privacyscrubber.com/pricing?tier=sdk&src=ci_stamp)
333
+ > * **Compliance Spec:** [Zero-Trust Data Sanitization (ZTDS)](https://privacyscrubber.com/solutions/engineering/zero-trust-ai-data-sanitization?src=ci_stamp) | โญ [Star on GitHub](https://github.com/moxno/privacyscrubber-mcp) | [Developer SDK ($299/mo)](https://privacyscrubber.com/sdk/?src=ci_stamp)
334
334
  `;
335
335
  console.log(stamp.trim());
336
336
  process.exit(0);
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
@@ -111,7 +111,9 @@ export function buildAuditTelemetry(currentTokenMap = {}) {
111
111
  if (currentTokenMap && typeof currentTokenMap === 'object') {
112
112
  const keys = Array.isArray(currentTokenMap) ? currentTokenMap : Object.keys(currentTokenMap);
113
113
  for (const t of keys) {
114
- const tokenStr = typeof t === 'string' ? t : (t.token || t.mask || '');
114
+ if (!t) continue;
115
+ const tokenStr = typeof t === 'string' ? t : ((t && (t.token || t.mask)) ? String(t.token || t.mask) : '');
116
+ if (!tokenStr) continue;
115
117
  const match = tokenStr.match(/\[([A-Z_]+)_\d+\]/);
116
118
  const baseType = match ? match[1] : (tokenStr.replace(/\[|\]/g, '').replace(/_[0-9]+$/, '') || 'CUSTOM');
117
119
  entities[baseType] = (entities[baseType] || 0) + 1;
@@ -478,9 +480,15 @@ export function sanitize(text, options = {}) {
478
480
  finalScrubbedText += `\n\n<!-- [PrivacyScrubber] Input truncated at free tier limit (${limit.toLocaleString()} chars). For unlimited throughput: https://privacyscrubber.com/pricing?tier=sdk&src=truncation -->`;
479
481
  }
480
482
 
483
+ const returnFullSessionMap = options.returnFullSessionMap === true;
484
+ const sessionMap = { ...(existingSessionMap || {}), ...(rawResult.tokenMap || {}) };
485
+
481
486
  return {
482
487
  scrubbedText: finalScrubbedText,
483
- tokenMap: rawResult.tokenMap || {},
488
+ tokenMap: returnFullSessionMap ? sessionMap : (rawResult.tokenMap || {}),
489
+ sessionMap: sessionMap,
490
+ matches: rawResult.matches || [],
491
+ spans: rawResult.matches || [],
484
492
  count: rawResult.count || 0,
485
493
  executionMs: executionMs,
486
494
  license: {
@@ -1258,7 +1266,8 @@ export function buildStreamReplacer(tokenMap = {}) {
1258
1266
  return (text) => text || '';
1259
1267
  }
1260
1268
 
1261
- const rules = [];
1269
+ const lookup = new Map();
1270
+ const patterns = [];
1262
1271
  const sortedKeys = Object.keys(tokenMap).sort((a, b) => b.length - a.length);
1263
1272
 
1264
1273
  for (const k of sortedKeys) {
@@ -1269,34 +1278,47 @@ export function buildStreamReplacer(tokenMap = {}) {
1269
1278
  if (match) {
1270
1279
  const label = escapeRegex(match[1]);
1271
1280
  const idx = match[2];
1272
- const patternStr = '(?:\\[\\s*' + label + '[-_\\s]*0*' + idx + '\\s*\\]|<\\s*' + label + '[-_\\s]*0*' + idx + '\\s*>|\\{\\{\\s*' + label + '[-_\\s]*0*' + idx + '\\s*\\}\\}|__\\s*' + label + '[-_\\s]*0*' + idx + '\\s*__|\\b' + label + '[-_\\s]+0*' + idx + '\\b)(?:\'s|โ€™s|s)?';
1273
- rules.push({
1274
- regex: new RegExp(patternStr, 'gi'),
1275
- val,
1276
- k
1277
- });
1281
+ const patternStr = '(?:\\[\\s*' + label + '[-_\\s]*0*' + idx + '\\s*\\]|<\\s*' + label + '[-_\\s]*0*' + idx + '\\s*>|\\{\\{\\s*' + label + '[-_\\s]*0*' + idx + '\\s*\\}\\}|__\\s*' + label + '[-_\\s]*0*' + idx + '\\s*__|\\b' + label + '[-_\\s]+0*' + idx + '\\b)';
1282
+ patterns.push(patternStr);
1283
+ const normKey = (match[1] + '_' + parseInt(idx, 10)).toUpperCase();
1284
+ lookup.set(normKey, val);
1285
+ lookup.set(k, val);
1286
+ lookup.set(k.toUpperCase(), val);
1278
1287
  } else {
1279
1288
  const safe = escapeRegex(k);
1280
- rules.push({
1281
- regex: new RegExp(safe + '(?:\'s|โ€™s|s)?', 'gi'),
1282
- val,
1283
- k
1284
- });
1289
+ patterns.push(safe);
1290
+ lookup.set(k, val);
1291
+ lookup.set(k.toUpperCase(), val);
1285
1292
  }
1286
1293
  }
1287
1294
 
1295
+ const unifiedRegex = new RegExp('(?:' + patterns.join('|') + ')(\'s|โ€™s|s)?', 'gi');
1296
+
1288
1297
  return function replaceTokens(text) {
1289
1298
  if (!text) return '';
1290
- let result = text;
1291
- for (let i = 0; i < rules.length; i++) {
1292
- const rule = rules[i];
1293
- rule.regex.lastIndex = 0;
1294
- result = result.replace(rule.regex, (m) => {
1295
- const isPossessive = /('s|โ€™s)$/i.test(m);
1296
- return rule.val + (isPossessive ? "'s" : '');
1297
- });
1298
- }
1299
- return result;
1299
+ unifiedRegex.lastIndex = 0;
1300
+ return text.replace(unifiedRegex, (match, suffix) => {
1301
+ const isPossessive = suffix && /('s|โ€™s)$/i.test(suffix);
1302
+ const coreMatch = suffix ? match.slice(0, match.length - suffix.length) : match;
1303
+
1304
+ if (lookup.has(coreMatch)) {
1305
+ return lookup.get(coreMatch) + (isPossessive ? "'s" : '');
1306
+ }
1307
+ if (lookup.has(coreMatch.toUpperCase())) {
1308
+ return lookup.get(coreMatch.toUpperCase()) + (isPossessive ? "'s" : '');
1309
+ }
1310
+
1311
+ const inner = coreMatch.replace(/^\[|<|\{\{|__|\]|>|\}\}|__/g, '').trim();
1312
+ const m = inner.match(/^([A-Za-z_0-9]+?)[-_ ]?(\d+)$/);
1313
+ if (m) {
1314
+ const normKey = (m[1] + '_' + parseInt(m[2], 10)).toUpperCase();
1315
+ if (lookup.has(normKey)) {
1316
+ return lookup.get(normKey) + (isPossessive ? "'s" : '');
1317
+ }
1318
+ }
1319
+
1320
+ return lookup.has(match) ? lookup.get(match) : match;
1321
+ });
1300
1322
  };
1301
1323
  }
1302
1324
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@privacyscrubber/sdk",
3
- "version": "2.2.6",
3
+ "version": "2.2.8",
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",
@@ -68,7 +76,7 @@
68
76
  ],
69
77
  "author": "Ilya Sibiryakov (BrandMeWeb)",
70
78
  "license": "SEE LICENSE IN LICENSE.md",
71
- "homepage": "https://privacyscrubber.com/?utm_source=npm&utm_medium=readme&utm_campaign=sdk",
79
+ "homepage": "https://privacyscrubber.com/sdk/?utm_source=npm&utm_medium=readme&utm_campaign=sdk",
72
80
  "repository": {
73
81
  "type": "git",
74
82
  "url": "git+https://github.com/moxno/privacyscrubber-mcp.git"