@privacyscrubber/mcp-server 2.2.6 โ 2.3.1
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 +5 -4
- package/index.js +127 -53
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/pii-masking-run.js +54 -7
- package/ps-license-manager.cjs +1 -1
- package/ps-license-manager.js +1 -1
- package/ps-pii-engine.cjs +1 -1
- package/ps-pii-engine.js +1 -1
- package/scrubber-core.cjs +1 -1
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
[](https://osf.io/5byjf/)
|
|
11
11
|
[](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=7335581)
|
|
12
12
|
[](https://datatracker.ietf.org/doc/draft-sibiryakov-ztds-protocol/)
|
|
13
|
+
[](https://ztds.ai/registry/#privacyscrubber-mcp)
|
|
13
14
|
[](https://smithery.ai/servers/privacyscrubber/pii-masking-mcp)
|
|
14
15
|
[](https://cursor.directory/plugins/privacyscrubber-mcp)
|
|
15
16
|
[](https://glama.ai/mcp/servers/moxno/privacyscrubber-mcp)
|
|
@@ -176,7 +177,7 @@ const guardedTools = createGuardedTools(engine);
|
|
|
176
177
|
```
|
|
177
178
|
|
|
178
179
|
> ๐ข **Enterprise & Production Licensing:**
|
|
179
|
-
> - **Self-Serve Developer SDK ($299/mo flat or $2,990/yr):** Unlimited internal backend nodes, microservices, and RAG pipelines. [
|
|
180
|
+
> - **Self-Serve Developer SDK ($299/mo flat or $2,990/yr):** Unlimited internal backend nodes, microservices, and RAG pipelines. [Developer SDK Specs & Pricing](https://privacyscrubber.com/sdk/?utm_source=mcp_readme)
|
|
180
181
|
> - **PrivacyScrubber TEAMS ($99/mo flat):** Unlimited team seats, centralized policy enforcement, encrypted session handoff. [Deploy TEAMS](https://privacyscrubber.com/teams?utm_source=mcp_readme)
|
|
181
182
|
> - **Enterprise Air-Gapped License:** On-premise source code distribution, zero-network custom models. [Contact Enterprise](https://privacyscrubber.com/enterprise?utm_source=mcp_readme)
|
|
182
183
|
|
|
@@ -375,7 +376,7 @@ Returns a visual dashboard showing your current tier, session request count, act
|
|
|
375
376
|
* **Response Example (Free Tier):**
|
|
376
377
|
```
|
|
377
378
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
378
|
-
โ PrivacyScrubber MCP Server v2.2.
|
|
379
|
+
โ PrivacyScrubber MCP Server v2.2.8 โ
|
|
379
380
|
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ
|
|
380
381
|
โ ๐ Tier: FREE โ
|
|
381
382
|
โ ๐ Session requests: 5 โ
|
|
@@ -390,7 +391,7 @@ Returns a visual dashboard showing your current tier, session request count, act
|
|
|
390
391
|
โ After purchase, add your key to MCP config: โ
|
|
391
392
|
โ "PRIVACYSCRUBBER_KEY": "<your-key-here>" โ
|
|
392
393
|
โ Full setup guide: โ
|
|
393
|
-
โ https://privacyscrubber.com/
|
|
394
|
+
โ https://privacyscrubber.com/pii-mcp/?utm_source=npm&utm_medium=readme&utm_campaign=mcp_server โ
|
|
394
395
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
395
396
|
```
|
|
396
397
|
|
|
@@ -510,7 +511,7 @@ Use the check_status tool from PrivacyScrubber MCP
|
|
|
510
511
|
## ๐ Ecosystem & Production Architecture Guides
|
|
511
512
|
|
|
512
513
|
- ๐ **Web App**: [https://privacyscrubber.com](https://privacyscrubber.com)
|
|
513
|
-
- ๐ฆ **Node.js / TypeScript SDK**: [@privacyscrubber/sdk](https://www.npmjs.com/package/@privacyscrubber/sdk)
|
|
514
|
+
- ๐ฆ **Node.js / TypeScript SDK**: [@privacyscrubber/sdk](https://privacyscrubber.com/sdk/) ([npm](https://www.npmjs.com/package/@privacyscrubber/sdk))
|
|
514
515
|
- ๐งฉ **Chrome Extension**: [Chrome Web Store](https://chromewebstore.google.com/detail/privacyscrubber-%E2%80%94-zero-tr/pimoejgefeilajmmbpghifdmhdlkgjol)
|
|
515
516
|
- ๐ก๏ธ **RAG & Vector Databases**: [Sanitizing PII Before Vector DB Ingestion (Pinecone, Chroma, Qdrant)](https://privacyscrubber.com/solutions/dev/rag-vector-database-pii-masking/)
|
|
516
517
|
- ๐ค **LangChain & LlamaIndex**: [In-Memory PII Middleware for AI Agent Pipelines](https://privacyscrubber.com/solutions/agents/langchain-llamaindex-pii-anonymizer/)
|
package/index.js
CHANGED
|
@@ -67,7 +67,7 @@ PrivacyScrubberCore.init();
|
|
|
67
67
|
const agentSessions = new Map();
|
|
68
68
|
const MAX_CONCURRENT_SESSIONS = 200;
|
|
69
69
|
|
|
70
|
-
function
|
|
70
|
+
function getSession(sessionId = 'default') {
|
|
71
71
|
const sid = (sessionId || 'default').toString().trim() || 'default';
|
|
72
72
|
let session = agentSessions.get(sid);
|
|
73
73
|
const now = Date.now();
|
|
@@ -76,18 +76,31 @@ function getSessionMap(sessionId = 'default') {
|
|
|
76
76
|
const oldestKey = agentSessions.keys().next().value;
|
|
77
77
|
agentSessions.delete(oldestKey);
|
|
78
78
|
}
|
|
79
|
-
session = { map: {}, createdAt: now, lastActive: now };
|
|
79
|
+
session = { map: {}, ignoreList: new Set(), createdAt: now, lastActive: now };
|
|
80
80
|
agentSessions.set(sid, session);
|
|
81
81
|
}
|
|
82
82
|
session.lastActive = now;
|
|
83
|
-
return session
|
|
83
|
+
return session;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getSessionMap(sessionId = 'default') {
|
|
87
|
+
return getSession(sessionId).map;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function getSessionIgnoreList(sessionId = 'default') {
|
|
91
|
+
return getSession(sessionId).ignoreList;
|
|
84
92
|
}
|
|
85
93
|
|
|
86
94
|
// Global default sessionMap maintains 100% backward compatibility
|
|
87
95
|
const sessionMap = getSessionMap('default');
|
|
96
|
+
const sessionIgnoreList = getSessionIgnoreList('default');
|
|
88
97
|
|
|
89
98
|
function reverseTokens(text, map) {
|
|
90
|
-
if (!text || typeof text !== 'string' || !map) return text;
|
|
99
|
+
if (!text || typeof text !== 'string' || !map || Object.keys(map).length === 0) return text;
|
|
100
|
+
if (PrivacyScrubberCore && typeof PrivacyScrubberCore.unscrubText === 'function') {
|
|
101
|
+
const res = PrivacyScrubberCore.unscrubText(text, map);
|
|
102
|
+
return (res && typeof res.restoredText === 'string') ? res.restoredText : (typeof res === 'string' ? res : text);
|
|
103
|
+
}
|
|
91
104
|
let result = text;
|
|
92
105
|
for (const [token, original] of Object.entries(map)) {
|
|
93
106
|
if (result.includes(token)) {
|
|
@@ -97,9 +110,6 @@ function reverseTokens(text, map) {
|
|
|
97
110
|
return result;
|
|
98
111
|
}
|
|
99
112
|
|
|
100
|
-
// Volatile in-memory false positive ignore list (values excluded from future scrubs)
|
|
101
|
-
const sessionIgnoreList = new Set();
|
|
102
|
-
|
|
103
113
|
// ANSI terminal color helpers
|
|
104
114
|
const colors = {
|
|
105
115
|
yellowBold: '\x1b[1;33m',
|
|
@@ -156,31 +166,35 @@ function getUsageFilePath() {
|
|
|
156
166
|
return path.resolve(homeDir, '.privacyscrubber-usage.json');
|
|
157
167
|
}
|
|
158
168
|
|
|
169
|
+
let cachedDailyUsage = null;
|
|
170
|
+
|
|
159
171
|
function getDailyUsage() {
|
|
160
|
-
const file = getUsageFilePath();
|
|
161
172
|
const today = new Date().toISOString().split('T')[0];
|
|
162
|
-
if (
|
|
173
|
+
if (cachedDailyUsage && cachedDailyUsage.date === today) {
|
|
174
|
+
return cachedDailyUsage.count;
|
|
175
|
+
}
|
|
176
|
+
const file = getUsageFilePath();
|
|
177
|
+
if (!fs.existsSync(file)) {
|
|
178
|
+
cachedDailyUsage = { date: today, count: 0 };
|
|
179
|
+
return 0;
|
|
180
|
+
}
|
|
163
181
|
try {
|
|
164
182
|
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
165
183
|
if (data.date === today) {
|
|
166
|
-
|
|
184
|
+
cachedDailyUsage = { date: today, count: data.count || 0 };
|
|
185
|
+
return cachedDailyUsage.count;
|
|
167
186
|
}
|
|
168
187
|
} catch (e) {}
|
|
188
|
+
cachedDailyUsage = { date: today, count: 0 };
|
|
169
189
|
return 0;
|
|
170
190
|
}
|
|
171
191
|
|
|
172
192
|
function incrementDailyUsage() {
|
|
173
|
-
const file = getUsageFilePath();
|
|
174
193
|
const today = new Date().toISOString().split('T')[0];
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
try {
|
|
178
|
-
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
179
|
-
if (data.date === today) count = data.count || 0;
|
|
180
|
-
} catch (e) {}
|
|
181
|
-
}
|
|
182
|
-
count++;
|
|
194
|
+
const count = getDailyUsage() + 1;
|
|
195
|
+
cachedDailyUsage = { date: today, count };
|
|
183
196
|
try {
|
|
197
|
+
const file = getUsageFilePath();
|
|
184
198
|
fs.writeFileSync(file, JSON.stringify({ date: today, count }), 'utf8');
|
|
185
199
|
} catch (e) {}
|
|
186
200
|
return count;
|
|
@@ -216,7 +230,8 @@ function buildCisoAuditTelemetry(currentTokenMap = {}) {
|
|
|
216
230
|
if (currentTokenMap && typeof currentTokenMap === 'object') {
|
|
217
231
|
const keys = Array.isArray(currentTokenMap) ? currentTokenMap : Object.keys(currentTokenMap);
|
|
218
232
|
for (const t of keys) {
|
|
219
|
-
const tokenStr = typeof t === 'string' ? t : (t.token || t.mask || ''
|
|
233
|
+
const tokenStr = typeof t === 'string' ? t : (t && (t.token || t.mask)) ? String(t.token || t.mask) : '';
|
|
234
|
+
if (!tokenStr) continue;
|
|
220
235
|
const match = tokenStr.match(/\[([A-Z_]+)_\d+\]/);
|
|
221
236
|
const baseType = match ? match[1] : (tokenStr.replace(/\[|\]/g, '').replace(/_[0-9]+$/, '') || 'CUSTOM');
|
|
222
237
|
entities[baseType] = (entities[baseType] || 0) + 1;
|
|
@@ -245,26 +260,72 @@ function buildCisoAuditTelemetry(currentTokenMap = {}) {
|
|
|
245
260
|
frameworksSet.add('GDPR (Art. 4)');
|
|
246
261
|
frameworksSet.add('CCPA/CPRA');
|
|
247
262
|
}
|
|
248
|
-
if (types.includes('ID') || types.includes('SSN') || types.includes('PASSPORT')) {
|
|
263
|
+
if (types.includes('ID') || types.includes('SSN') || types.includes('PASSPORT') || types.includes('NATIONAL_ID')) {
|
|
249
264
|
frameworksSet.add('SOC 2 Type II');
|
|
250
265
|
frameworksSet.add('ISO 27001 (A.8.11)');
|
|
251
266
|
}
|
|
252
|
-
if (types.some(t => ['CREDIT_CARD', 'BANK', 'IBAN', 'FINANCIAL', 'CARD'].includes(t))) {
|
|
267
|
+
if (types.some(t => ['CREDIT_CARD', 'BANK', 'IBAN', 'FINANCIAL', 'CARD', 'STRIPE_KEY', 'TAX_ID', 'EIN', 'W2', '1099', 'SALARY', 'WAGE'].includes(t))) {
|
|
253
268
|
frameworksSet.add('PCI DSS v4.0');
|
|
269
|
+
frameworksSet.add('IRC ยง 7216 & GLBA');
|
|
254
270
|
}
|
|
255
|
-
if (types.some(t => ['MRN', 'HEALTH', 'MEDICAL', 'PATIENT'].includes(t))) {
|
|
271
|
+
if (types.some(t => ['MRN', 'HEALTH', 'MEDICAL', 'PATIENT', 'DIAGNOSIS', 'NPI', 'DEA'].includes(t))) {
|
|
256
272
|
frameworksSet.add('HIPAA ยง164.514');
|
|
257
273
|
}
|
|
258
|
-
if (types.some(t => ['
|
|
274
|
+
if (types.some(t => ['CASE_NUMBER', 'DOCKET', 'PRIVILEGED', 'LEGAL', 'ATTORNEY', 'COURT', 'CONTRACT', 'NDA'].includes(t))) {
|
|
275
|
+
frameworksSet.add('FRE 502 (Privilege)');
|
|
276
|
+
}
|
|
277
|
+
if (types.some(t => ['API_KEY', 'SECRET', 'PASSWORD', 'TOKEN', 'KEY', 'AWS_KEY', 'JWT_TOKEN', 'API_TOKEN', 'CREDENTIAL'].includes(t))) {
|
|
259
278
|
frameworksSet.add('NIST SP 800-53');
|
|
260
279
|
}
|
|
280
|
+
if (totalCount > 0) {
|
|
281
|
+
frameworksSet.add('EU AI Act (Art. 50)');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const ipHoldings = {
|
|
285
|
+
standard: 'IETF draft-sibiryakov-ztds-protocol-00',
|
|
286
|
+
standardUrl: 'https://datatracker.ietf.org/doc/draft-sibiryakov-ztds-protocol/',
|
|
287
|
+
patent: 'IL 331905 (Tracking: 94221)',
|
|
288
|
+
wipoDas: 'B17B',
|
|
289
|
+
trademark: 'ZTDSโข Reg. #182655957 (ILPO Cl 9 & 42)'
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const statutoryCitations = [
|
|
293
|
+
{
|
|
294
|
+
statute: 'EU AI Act (Art. 50)',
|
|
295
|
+
scope: 'AI Transparency & Data Minimization',
|
|
296
|
+
rule: '0-byte external network transmission; prevents unauthorized ingestion into frontier models.'
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
statute: 'GDPR (Art. 4, 25, 28, 32)',
|
|
300
|
+
scope: 'Pseudonymization & Processor Liability',
|
|
301
|
+
rule: 'Volatile client-side tokenization eliminates GDPR Article 28 data processor liability.'
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
statute: 'HIPAA 45 CFR ยง 164.514(b)',
|
|
305
|
+
scope: 'Safe Harbor De-Identification',
|
|
306
|
+
rule: 'Deterministic removal of all 18 PHI identifiers prior to AI analysis.'
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
statute: 'IRC ยง 7216 & IRS Pub. 1075',
|
|
310
|
+
scope: 'Tax Return Information Confidentiality',
|
|
311
|
+
rule: 'Strict criminal penalty safeguard against unauthorized disclosure of tax records to cloud LLMs.'
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
statute: 'FRE 502',
|
|
315
|
+
scope: 'Attorney-Client Privilege Preservation',
|
|
316
|
+
rule: 'Automated in-memory redaction prevents inadvertent waiver of legal privilege in AI workflows.'
|
|
317
|
+
}
|
|
318
|
+
];
|
|
261
319
|
|
|
262
320
|
return {
|
|
263
321
|
totalCount,
|
|
264
322
|
entities,
|
|
265
323
|
types,
|
|
266
324
|
riskLevel,
|
|
267
|
-
frameworksList: Array.from(frameworksSet)
|
|
325
|
+
frameworksList: Array.from(frameworksSet),
|
|
326
|
+
statutoryCitations,
|
|
327
|
+
ipHoldings,
|
|
328
|
+
multimodalEgress: '0.00 Bytes (Solid Blackout & Burn-in Verified)'
|
|
268
329
|
};
|
|
269
330
|
}
|
|
270
331
|
|
|
@@ -288,7 +349,7 @@ function formatAuditReceipt(telemetry, compact = false, wasTruncated = false, ch
|
|
|
288
349
|
}
|
|
289
350
|
|
|
290
351
|
if (telemetry.totalCount === 0) {
|
|
291
|
-
return `\n\n> ๐ก๏ธ **PrivacyScrubber Audit Receipt**: CLEAN (ZERO PII DETECTED)\n${truncationLine}> * โญ **Star on GitHub:** [moxno/privacyscrubber-mcp](https://github.com/moxno/privacyscrubber-mcp) (\`gh repo star moxno/privacyscrubber-mcp\`) | **SDK & Enterprise:** [privacyscrubber.com/
|
|
352
|
+
return `\n\n> ๐ก๏ธ **PrivacyScrubber Audit Receipt**: CLEAN (ZERO PII DETECTED)\n${truncationLine}> * โญ **Star on GitHub:** [moxno/privacyscrubber-mcp](https://github.com/moxno/privacyscrubber-mcp) (\`gh repo star moxno/privacyscrubber-mcp\`) | **SDK & Enterprise:** [privacyscrubber.com/sdk](https://privacyscrubber.com/sdk/)\n`;
|
|
292
353
|
}
|
|
293
354
|
|
|
294
355
|
const entitiesList = Object.entries(telemetry.entities)
|
|
@@ -297,7 +358,7 @@ function formatAuditReceipt(telemetry, compact = false, wasTruncated = false, ch
|
|
|
297
358
|
|
|
298
359
|
const icon = telemetry.riskLevel.includes('CRITICAL') ? '๐ด' : (telemetry.riskLevel.includes('MODERATE') ? '๐ ' : '๐ข');
|
|
299
360
|
|
|
300
|
-
return `\n\n> ๐ก๏ธ **PrivacyScrubber Audit Receipt**\n> * **Risk Level:** ${icon} ${telemetry.riskLevel}\n> * **Compliance Enforced:** ${telemetry.frameworksList.join(', ')}\n> * **Tokens Masked:** ${telemetry.totalCount} (${entitiesList})\n${truncationLine}> * โญ **Star on GitHub:** [moxno/privacyscrubber-mcp](https://github.com/moxno/privacyscrubber-mcp) (\`gh repo star moxno/privacyscrubber-mcp\`) | **SDK & Enterprise:** [privacyscrubber.com/
|
|
361
|
+
return `\n\n> ๐ก๏ธ **PrivacyScrubber Audit Receipt**\n> * **Risk Level:** ${icon} ${telemetry.riskLevel}\n> * **Compliance Enforced:** ${telemetry.frameworksList.join(', ')}\n> * **Tokens Masked:** ${telemetry.totalCount} (${entitiesList})\n${truncationLine}> * โญ **Star on GitHub:** [moxno/privacyscrubber-mcp](https://github.com/moxno/privacyscrubber-mcp) (\`gh repo star moxno/privacyscrubber-mcp\`) | **SDK & Enterprise:** [privacyscrubber.com/sdk](https://privacyscrubber.com/sdk/)\n`;
|
|
301
362
|
}
|
|
302
363
|
|
|
303
364
|
function formatCefEvent(eventData = {}) {
|
|
@@ -652,6 +713,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
652
713
|
token: {
|
|
653
714
|
type: "string",
|
|
654
715
|
description: "The token to mark as false positive (e.g., '[NAME_1]', '[EMAIL_2]')."
|
|
716
|
+
},
|
|
717
|
+
session_id: {
|
|
718
|
+
type: "string",
|
|
719
|
+
description: "Optional session ID for multi-agent isolation. Defaults to 'default'."
|
|
655
720
|
}
|
|
656
721
|
},
|
|
657
722
|
required: ["token"]
|
|
@@ -1146,10 +1211,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1146
1211
|
if (name === "sanitize_text" || name === "scrub_text") {
|
|
1147
1212
|
const { text, profile = "General", ignore_list, compact = false, session_id = "default" } = args || {};
|
|
1148
1213
|
const currentSession = getSessionMap(session_id);
|
|
1214
|
+
const currentIgnoreList = getSessionIgnoreList(session_id);
|
|
1149
1215
|
|
|
1150
1216
|
// Merge per-call ignore_list into persistent sessionIgnoreList
|
|
1151
1217
|
if (Array.isArray(ignore_list)) {
|
|
1152
|
-
ignore_list.forEach(v => { if (typeof v === 'string' && v.trim())
|
|
1218
|
+
ignore_list.forEach(v => { if (typeof v === 'string' && v.trim()) currentIgnoreList.add(v.trim()); });
|
|
1153
1219
|
}
|
|
1154
1220
|
if (text === undefined || text === null) {
|
|
1155
1221
|
return {
|
|
@@ -1195,7 +1261,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1195
1261
|
|
|
1196
1262
|
const { processedText, wasTruncated } = truncateIfFree(text, license.isPro, charLimit);
|
|
1197
1263
|
|
|
1198
|
-
const { scrubbedText, newTokens } = performSanitization(processedText, finalProfile,
|
|
1264
|
+
const { scrubbedText, newTokens } = performSanitization(processedText, finalProfile, currentIgnoreList, currentSession);
|
|
1199
1265
|
|
|
1200
1266
|
const telemetry = buildCisoAuditTelemetry(newTokens);
|
|
1201
1267
|
const receiptMd = formatAuditReceipt(telemetry, compact, wasTruncated, charLimit);
|
|
@@ -1249,7 +1315,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1249
1315
|
}
|
|
1250
1316
|
|
|
1251
1317
|
if (name === "mark_false_positive") {
|
|
1252
|
-
const { token } = args || {};
|
|
1318
|
+
const { token, session_id = "default" } = args || {};
|
|
1253
1319
|
if (!token || typeof token !== 'string') {
|
|
1254
1320
|
return {
|
|
1255
1321
|
isError: true,
|
|
@@ -1257,25 +1323,28 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1257
1323
|
};
|
|
1258
1324
|
}
|
|
1259
1325
|
|
|
1260
|
-
const
|
|
1326
|
+
const currentSession = getSessionMap(session_id);
|
|
1327
|
+
const currentIgnoreList = getSessionIgnoreList(session_id);
|
|
1328
|
+
|
|
1329
|
+
const original = currentSession[token];
|
|
1261
1330
|
if (!original) {
|
|
1262
1331
|
return {
|
|
1263
1332
|
isError: true,
|
|
1264
|
-
content: [{ type: "text", text: `Error: Token '${token}' not found in current session map. Available tokens: ${Object.keys(
|
|
1333
|
+
content: [{ type: "text", text: `Error: Token '${token}' not found in current session map. Available tokens: ${Object.keys(currentSession).join(', ') || '(empty session)'}` }]
|
|
1265
1334
|
};
|
|
1266
1335
|
}
|
|
1267
1336
|
|
|
1268
1337
|
// Add the original plaintext to the ignore list
|
|
1269
|
-
|
|
1338
|
+
currentIgnoreList.add(original);
|
|
1270
1339
|
// Remove the token from the session map
|
|
1271
|
-
delete
|
|
1340
|
+
delete currentSession[token];
|
|
1272
1341
|
|
|
1273
1342
|
mcpLog(`${colors.yellow}๐ [PrivacyScrubber] False Positive: '${token}' โ '${original}' will be excluded from future scrubs.${colors.reset}\n`);
|
|
1274
1343
|
|
|
1275
1344
|
return {
|
|
1276
1345
|
content: [{
|
|
1277
1346
|
type: "text",
|
|
1278
|
-
text: `โ
Marked '${token}' as false positive.\n\n**Restored value:** ${original}\n**Session ignore list size:** ${
|
|
1347
|
+
text: `โ
Marked '${token}' as false positive.\n\n**Restored value:** ${original}\n**Session ignore list size:** ${currentIgnoreList.size}\n\nThis value will be excluded from all future \`sanitize_text\` calls in this session.`
|
|
1279
1348
|
}]
|
|
1280
1349
|
};
|
|
1281
1350
|
}
|
|
@@ -1883,7 +1952,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1883
1952
|
verification_url: `https://privacyscrubber.com/features/audit-receipt/#verify?hash=${sessionHash.substring(0, 16)}`,
|
|
1884
1953
|
licensing_and_governance: {
|
|
1885
1954
|
teams_governance_url: "https://privacyscrubber.com/teams?src=mcp_compliance_json",
|
|
1886
|
-
sdk_pipeline_url: "https://privacyscrubber.com/
|
|
1955
|
+
sdk_pipeline_url: "https://privacyscrubber.com/sdk/?src=mcp_compliance_json"
|
|
1887
1956
|
}
|
|
1888
1957
|
};
|
|
1889
1958
|
|
|
@@ -1925,7 +1994,7 @@ ${telemetry.frameworksList.map(f => `- **${f}**`).join('\n')}
|
|
|
1925
1994
|
|
|
1926
1995
|
### ๐ข Enterprise & Team Governance
|
|
1927
1996
|
* **Team Governance & Encrypted Handoff:** Centrally deploy redaction rules, audit logs, and zero-knowledge session handoff with **PrivacyScrubber TEAMS** ($99/mo flat): [privacyscrubber.com/teams](https://privacyscrubber.com/teams?src=mcp_compliance_report)
|
|
1928
|
-
* **Backend Microservices & RAG Pipelines:** Integrate in-memory sanitization (<1ms) into your Node.js/Python infrastructure with **Developer SDK** ($299/mo): [privacyscrubber.com/
|
|
1997
|
+
* **Backend Microservices & RAG Pipelines:** Integrate in-memory sanitization (<1ms) into your Node.js/Python infrastructure with **Developer SDK** ($299/mo): [privacyscrubber.com/sdk](https://privacyscrubber.com/sdk/?src=mcp_compliance_report)
|
|
1929
1998
|
|
|
1930
1999
|
*Certified Offline by PrivacyScrubber Engine v${MCP_VERSION}*
|
|
1931
2000
|
*Verify at: https://privacyscrubber.com/features/audit-receipt/* | โญ *Star on GitHub: https://github.com/moxno/privacyscrubber-mcp*`;
|
|
@@ -1953,6 +2022,7 @@ ${telemetry.frameworksList.map(f => `- **${f}**`).join('\n')}
|
|
|
1953
2022
|
}
|
|
1954
2023
|
|
|
1955
2024
|
const targetSession = getSessionMap(session_id);
|
|
2025
|
+
const targetIgnoreList = getSessionIgnoreList(session_id);
|
|
1956
2026
|
// Transparent in-memory unmasking of any token placeholders before local execution
|
|
1957
2027
|
const rawExecutableCommand = reverseTokens(command, targetSession);
|
|
1958
2028
|
|
|
@@ -1982,9 +2052,9 @@ ${telemetry.frameworksList.map(f => `- **${f}**`).join('\n')}
|
|
|
1982
2052
|
const { processedText: cleanStdout, wasTruncated: truncOut } = truncateIfFree(stdout, license.isPro, charLimit);
|
|
1983
2053
|
const { processedText: cleanStderr, wasTruncated: truncErr } = truncateIfFree(stderr, license.isPro, charLimit);
|
|
1984
2054
|
|
|
1985
|
-
const resCommand = performSanitization(command, targetProfile,
|
|
1986
|
-
const resStdout = performSanitization(cleanStdout, targetProfile,
|
|
1987
|
-
const resStderr = performSanitization(cleanStderr, targetProfile,
|
|
2055
|
+
const resCommand = performSanitization(command, targetProfile, targetIgnoreList, targetSession);
|
|
2056
|
+
const resStdout = performSanitization(cleanStdout, targetProfile, targetIgnoreList, targetSession);
|
|
2057
|
+
const resStderr = performSanitization(cleanStderr, targetProfile, targetIgnoreList, targetSession);
|
|
1988
2058
|
|
|
1989
2059
|
const allNewTokens = { ...resCommand.newTokens, ...resStdout.newTokens, ...resStderr.newTokens };
|
|
1990
2060
|
const tokenCount = Object.keys(allNewTokens).length;
|
|
@@ -2005,7 +2075,7 @@ ${telemetry.frameworksList.map(f => `- **${f}**`).join('\n')}
|
|
|
2005
2075
|
}
|
|
2006
2076
|
|
|
2007
2077
|
if (name === "guard_read_file") {
|
|
2008
|
-
const { file_path, profile = "Dev", max_lines = 500 } = args || {};
|
|
2078
|
+
const { file_path, profile = "Dev", max_lines = 500, session_id = "default" } = args || {};
|
|
2009
2079
|
if (!file_path || typeof file_path !== "string") {
|
|
2010
2080
|
return {
|
|
2011
2081
|
isError: true,
|
|
@@ -2045,7 +2115,9 @@ ${telemetry.frameworksList.map(f => `- **${f}**`).join('\n')}
|
|
|
2045
2115
|
const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
|
|
2046
2116
|
|
|
2047
2117
|
const { processedText, wasTruncated: wasCharTruncated } = truncateIfFree(capped, license.isPro, charLimit);
|
|
2048
|
-
const
|
|
2118
|
+
const currentSession = getSessionMap(session_id);
|
|
2119
|
+
const currentIgnoreList = getSessionIgnoreList(session_id);
|
|
2120
|
+
const { scrubbedText, newTokens } = performSanitization(processedText, targetProfile, currentIgnoreList, currentSession);
|
|
2049
2121
|
|
|
2050
2122
|
const telemetry = buildCisoAuditTelemetry(newTokens);
|
|
2051
2123
|
const receiptMd = formatAuditReceipt(telemetry, false, (wasLineTruncated || wasCharTruncated), charLimit);
|
|
@@ -2448,18 +2520,20 @@ Before reading sensitive files, running terminal commands that may print credent
|
|
|
2448
2520
|
for (let i = 0; i < results.length; i++) {
|
|
2449
2521
|
const item = results[i];
|
|
2450
2522
|
if (typeof item === 'string') {
|
|
2451
|
-
const
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2523
|
+
const res = (PrivacyScrubberCore && typeof PrivacyScrubberCore.unscrubText === 'function')
|
|
2524
|
+
? PrivacyScrubberCore.unscrubText(item, targetSession)
|
|
2525
|
+
: { restoredText: reverseTokens(item, targetSession), restoredCount: 0 };
|
|
2526
|
+
const restored = (res && typeof res.restoredText === 'string') ? res.restoredText : (typeof res === 'string' ? res : item);
|
|
2527
|
+
totalTokensRestored += (res.restoredCount || 0);
|
|
2455
2528
|
restoredResults.push(restored);
|
|
2456
2529
|
} else if (item && typeof item === 'object') {
|
|
2457
2530
|
const copy = { ...item };
|
|
2458
2531
|
const rawText = copy.text || copy.pageContent || copy.document || '';
|
|
2459
|
-
const
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2532
|
+
const res = (PrivacyScrubberCore && typeof PrivacyScrubberCore.unscrubText === 'function')
|
|
2533
|
+
? PrivacyScrubberCore.unscrubText(rawText, targetSession)
|
|
2534
|
+
: { restoredText: reverseTokens(rawText, targetSession), restoredCount: 0 };
|
|
2535
|
+
const restored = (res && typeof res.restoredText === 'string') ? res.restoredText : (typeof res === 'string' ? res : rawText);
|
|
2536
|
+
totalTokensRestored += (res.restoredCount || 0);
|
|
2463
2537
|
|
|
2464
2538
|
if (copy.text !== undefined) copy.text = restored;
|
|
2465
2539
|
if (copy.pageContent !== undefined) copy.pageContent = restored;
|
|
@@ -2549,7 +2623,7 @@ Before reading sensitive files, running terminal commands that may print credent
|
|
|
2549
2623
|
verification_url: `https://privacyscrubber.com/features/audit-receipt/#verify?hash=${sessionHash.substring(0, 16)}`,
|
|
2550
2624
|
licensing_and_governance: {
|
|
2551
2625
|
teams_governance_url: "https://privacyscrubber.com/teams?src=mcp_audit_json",
|
|
2552
|
-
sdk_pipeline_url: "https://privacyscrubber.com/
|
|
2626
|
+
sdk_pipeline_url: "https://privacyscrubber.com/sdk/?src=mcp_audit_json"
|
|
2553
2627
|
}
|
|
2554
2628
|
}, null, 2);
|
|
2555
2629
|
} else if (format === "markdown") {
|
|
@@ -2581,7 +2655,7 @@ ${telemetry.frameworksList.map(f => `- **${f}**`).join('\n')}
|
|
|
2581
2655
|
|
|
2582
2656
|
### ๐ข Enterprise & Team Governance
|
|
2583
2657
|
* **Team Governance & Encrypted Handoff:** Centrally deploy redaction rules, audit logs, and zero-knowledge session handoff with **PrivacyScrubber TEAMS** ($99/mo flat): [privacyscrubber.com/teams](https://privacyscrubber.com/teams?src=mcp_audit_export)
|
|
2584
|
-
* **Backend Microservices & RAG Pipelines:** Integrate in-memory sanitization (<1ms) into your Node.js/Python infrastructure with **Developer SDK** ($299/mo): [privacyscrubber.com/
|
|
2658
|
+
* **Backend Microservices & RAG Pipelines:** Integrate in-memory sanitization (<1ms) into your Node.js/Python infrastructure with **Developer SDK** ($299/mo): [privacyscrubber.com/sdk](https://privacyscrubber.com/sdk/?src=mcp_audit_export)
|
|
2585
2659
|
|
|
2586
2660
|
*Certified Offline by PrivacyScrubber Engine v${MCP_VERSION}*
|
|
2587
2661
|
*Verify at: https://privacyscrubber.com/features/audit-receipt/* | โญ *Star on GitHub: https://github.com/moxno/privacyscrubber-mcp*`;
|
|
@@ -2740,7 +2814,7 @@ server.connect(transport).then(() => {
|
|
|
2740
2814
|
mcpLog(`${colors.greenBold}โ
PrivacyScrubber ZTDS MCP Server v${MCP_VERSION} started successfully.${colors.reset}\n`);
|
|
2741
2815
|
mcpLog(`${colors.cyan}๐ฆ Need programmatic in-code redaction? Try: npm install @privacyscrubber/sdk${colors.reset}\n`);
|
|
2742
2816
|
mcpLog(`${colors.yellowBold}โญ Star us on GitHub: https://github.com/moxno/privacyscrubber-mcp${colors.reset}\n`);
|
|
2743
|
-
mcpLog(`${colors.cyan}๐ Developer SDK & Enterprise: https://privacyscrubber.com/
|
|
2817
|
+
mcpLog(`${colors.cyan}๐ Developer SDK & Enterprise: https://privacyscrubber.com/sdk/${colors.reset}\n`);
|
|
2744
2818
|
}).catch((error) => {
|
|
2745
2819
|
console.error("Failed to connect MCP server transport:", error);
|
|
2746
2820
|
process.exit(1);
|
package/manifest.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": "0.3",
|
|
3
3
|
"name": "privacyscrubber/pii-masking-mcp",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.3.1",
|
|
5
5
|
"description": "CISO-Approved Zero-Trust PII & Secrets Redaction MCP Server for Cursor, Windsurf, and Claude Desktop. Prevent API leaks and comply with HIPAA/SOC 2 locally.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "PrivacyScrubber",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@privacyscrubber/mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"mcpName": "io.github.moxno/privacyscrubber-mcp",
|
|
5
5
|
"description": "CISO-Approved Zero-Trust PII & Secrets Redaction MCP Server for Cursor, Windsurf, and Claude Desktop. Prevent API leaks and comply with HIPAA/SOC 2 locally.",
|
|
6
6
|
"main": "index.js",
|
package/pii-masking-run.js
CHANGED
|
@@ -27,7 +27,7 @@ const cmdIndex = args.indexOf("--");
|
|
|
27
27
|
|
|
28
28
|
if (args.includes("--version") || args.includes("-v")) {
|
|
29
29
|
const pkgPath = path.resolve(__dirname, './package.json');
|
|
30
|
-
let version = '2.2.
|
|
30
|
+
let version = '2.2.10';
|
|
31
31
|
try { version = JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version; } catch (_) {}
|
|
32
32
|
console.log(`ps-guard / pii-masking-run v${version} (PrivacyScrubber MCP)`);
|
|
33
33
|
process.exit(0);
|
|
@@ -278,25 +278,72 @@ function buildCisoAuditTelemetry(currentTokenMap = {}) {
|
|
|
278
278
|
frameworksSet.add('GDPR (Art. 4)');
|
|
279
279
|
frameworksSet.add('CCPA/CPRA');
|
|
280
280
|
}
|
|
281
|
-
if (types.includes('ID') || types.includes('SSN') || types.includes('PASSPORT')) {
|
|
281
|
+
if (types.includes('ID') || types.includes('SSN') || types.includes('PASSPORT') || types.includes('NATIONAL_ID')) {
|
|
282
282
|
frameworksSet.add('SOC 2 Type II');
|
|
283
283
|
frameworksSet.add('ISO 27001 (A.8.11)');
|
|
284
284
|
}
|
|
285
|
-
if (types.some(t => ['CREDIT_CARD', 'BANK', 'IBAN', 'FINANCIAL', 'CARD'].includes(t))) {
|
|
285
|
+
if (types.some(t => ['CREDIT_CARD', 'BANK', 'IBAN', 'FINANCIAL', 'CARD', 'STRIPE_KEY', 'TAX_ID', 'EIN', 'W2', '1099', 'SALARY', 'WAGE'].includes(t))) {
|
|
286
286
|
frameworksSet.add('PCI DSS v4.0');
|
|
287
|
+
frameworksSet.add('IRC ยง 7216 & GLBA');
|
|
287
288
|
}
|
|
288
|
-
if (types.some(t => ['MRN', 'HEALTH', 'MEDICAL', 'PATIENT'].includes(t))) {
|
|
289
|
+
if (types.some(t => ['MRN', 'HEALTH', 'MEDICAL', 'PATIENT', 'DIAGNOSIS', 'NPI', 'DEA'].includes(t))) {
|
|
289
290
|
frameworksSet.add('HIPAA ยง164.514');
|
|
290
291
|
}
|
|
291
|
-
if (types.some(t => ['
|
|
292
|
+
if (types.some(t => ['CASE_NUMBER', 'DOCKET', 'PRIVILEGED', 'LEGAL', 'ATTORNEY', 'COURT', 'CONTRACT', 'NDA'].includes(t))) {
|
|
293
|
+
frameworksSet.add('FRE 502 (Privilege)');
|
|
294
|
+
}
|
|
295
|
+
if (types.some(t => ['API_KEY', 'SECRET', 'PASSWORD', 'TOKEN', 'KEY', 'AWS_KEY', 'JWT_TOKEN', 'API_TOKEN', 'CREDENTIAL'].includes(t))) {
|
|
292
296
|
frameworksSet.add('NIST SP 800-53');
|
|
293
297
|
}
|
|
298
|
+
if (totalCount > 0) {
|
|
299
|
+
frameworksSet.add('EU AI Act (Art. 50)');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const ipHoldings = {
|
|
303
|
+
standard: 'IETF draft-sibiryakov-ztds-protocol-00',
|
|
304
|
+
standardUrl: 'https://datatracker.ietf.org/doc/draft-sibiryakov-ztds-protocol/',
|
|
305
|
+
patent: 'IL 331905 (Tracking: 94221)',
|
|
306
|
+
wipoDas: 'B17B',
|
|
307
|
+
trademark: 'ZTDSโข Reg. #182655957 (ILPO Cl 9 & 42)'
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const statutoryCitations = [
|
|
311
|
+
{
|
|
312
|
+
statute: 'EU AI Act (Art. 50)',
|
|
313
|
+
scope: 'AI Transparency & Data Minimization',
|
|
314
|
+
rule: '0-byte external network transmission; prevents unauthorized ingestion into frontier models.'
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
statute: 'GDPR (Art. 4, 25, 28, 32)',
|
|
318
|
+
scope: 'Pseudonymization & Processor Liability',
|
|
319
|
+
rule: 'Volatile client-side tokenization eliminates GDPR Article 28 data processor liability.'
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
statute: 'HIPAA 45 CFR ยง 164.514(b)',
|
|
323
|
+
scope: 'Safe Harbor De-Identification',
|
|
324
|
+
rule: 'Deterministic removal of all 18 PHI identifiers prior to AI analysis.'
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
statute: 'IRC ยง 7216 & IRS Pub. 1075',
|
|
328
|
+
scope: 'Tax Return Information Confidentiality',
|
|
329
|
+
rule: 'Strict criminal penalty safeguard against unauthorized disclosure of tax records to cloud LLMs.'
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
statute: 'FRE 502',
|
|
333
|
+
scope: 'Attorney-Client Privilege Preservation',
|
|
334
|
+
rule: 'Automated in-memory redaction prevents inadvertent waiver of legal privilege in AI workflows.'
|
|
335
|
+
}
|
|
336
|
+
];
|
|
337
|
+
|
|
294
338
|
return {
|
|
295
339
|
totalCount,
|
|
296
340
|
entities,
|
|
297
341
|
types,
|
|
298
342
|
riskLevel,
|
|
299
|
-
frameworksList: Array.from(frameworksSet)
|
|
343
|
+
frameworksList: Array.from(frameworksSet),
|
|
344
|
+
statutoryCitations,
|
|
345
|
+
ipHoldings,
|
|
346
|
+
multimodalEgress: '0.00 Bytes (Solid Blackout & Burn-in Verified)'
|
|
300
347
|
};
|
|
301
348
|
}
|
|
302
349
|
|
|
@@ -437,7 +484,7 @@ Before reading sensitive files, running terminal commands that may print credent
|
|
|
437
484
|
generatedFiles.forEach(f => console.log(` - ${f}`));
|
|
438
485
|
console.log(`\nSupport open-source Zero-Trust AI tools:`);
|
|
439
486
|
console.log(` GitHub Star: https://github.com/moxno/privacyscrubber-mcp (or: gh repo star moxno/privacyscrubber-mcp)`);
|
|
440
|
-
console.log(` Backend SDK: npm install @privacyscrubber/sdk (https://privacyscrubber.com/
|
|
487
|
+
console.log(` Backend SDK: npm install @privacyscrubber/sdk (https://privacyscrubber.com/sdk/)`);
|
|
441
488
|
process.exit(0);
|
|
442
489
|
}
|
|
443
490
|
|