@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 +1 -1
- package/browser.js +5 -3
- package/cli.js +3 -3
- 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 +46 -24
- package/package.json +10 -2
- package/ps-license-manager.js +1 -1
- package/ps-pii-engine.cjs +1 -1
- package/scrubber-core.cjs +1 -1
- package/vision.js +1 -1
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/
|
|
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
|
-
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
-
|
|
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
|
|
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)
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
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
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
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
|
-
|
|
1291
|
-
|
|
1292
|
-
const
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
return
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
|
|
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.
|
|
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"
|