@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 +1 -0
- package/browser.js +31 -6
- package/cli.js +2 -2
- package/fastmcp.cjs +273 -0
- package/fastmcp.d.ts +65 -0
- package/fastmcp.js +27 -0
- package/index.d.ts +26 -0
- package/index.js +38 -7
- package/package.json +9 -1
- package/ps-license-manager.js +1 -1
- package/ps-pii-engine.cjs +1 -1
- package/scrubber-core.cjs +1 -1
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
[](https://opensource.org/licenses/MIT)
|
|
9
9
|
[-06b6d4.svg)](https://privacyscrubber.com/patents/)
|
|
10
10
|
[](https://datatracker.ietf.org/doc/draft-sibiryakov-ztds-protocol/)
|
|
11
|
+
[](https://ztds.ai/registry/#privacyscrubber-sdk)
|
|
11
12
|
[](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
|
|
200
|
-
|
|
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
|
|
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
|
-
...(
|
|
273
|
-
upgradeUrl: 'https://privacyscrubber.com/pricing?
|
|
274
|
-
recommendation:
|
|
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
|
|
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
|
|
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
|
|
416
|
-
|
|
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
|
|
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
|
-
...(
|
|
494
|
-
upgradeUrl: 'https://privacyscrubber.com/pricing?
|
|
495
|
-
recommendation:
|
|
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.
|
|
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",
|