@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 CHANGED
@@ -10,6 +10,7 @@
10
10
  [![OSF DOI](https://img.shields.io/badge/OSF%20DOI-10.17605%2FOSF.IO%2F5BYJF-blue.svg)](https://osf.io/5byjf/)
11
11
  [![SSRN](https://img.shields.io/badge/SSRN-7335581-darkred.svg)](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=7335581)
12
12
  [![IETF Specification](https://img.shields.io/badge/IETF-ZTDS--Protocol-0284c7.svg)](https://datatracker.ietf.org/doc/draft-sibiryakov-ztds-protocol/)
13
+ [![ZTDS Verified](https://ztds.ai/badge/privacyscrubber-sdk.svg)](https://ztds.ai/registry/#privacyscrubber-mcp)
13
14
  [![smithery badge](https://smithery.ai/badge/privacyscrubber/pii-masking-mcp)](https://smithery.ai/servers/privacyscrubber/pii-masking-mcp)
14
15
  [![Cursor Directory](https://img.shields.io/badge/Cursor%20Directory-Verified%20Plugin-000000.svg)](https://cursor.directory/plugins/privacyscrubber-mcp)
15
16
  [![Glama.ai](https://glama.ai/mcp/servers/moxno/privacyscrubber-mcp/badge)](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. [Get SDK License](https://privacyscrubber.com/pricing?tier=sdk&utm_source=mcp_readme)
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.4 โ•‘
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/features/mcp/?utm_source=npm&utm_medium=readme&utm_campaign=mcp_server โ•‘
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 getSessionMap(sessionId = 'default') {
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.map;
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 (!fs.existsSync(file)) return 0;
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
- return data.count || 0;
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
- let count = 0;
176
- if (fs.existsSync(file)) {
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 => ['API_KEY', 'SECRET', 'PASSWORD', 'TOKEN', 'KEY'].includes(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/pricing](https://privacyscrubber.com/pricing)\n`;
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/pricing](https://privacyscrubber.com/pricing)\n`;
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()) sessionIgnoreList.add(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, sessionIgnoreList, currentSession);
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 original = sessionMap[token];
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(sessionMap).join(', ') || '(empty session)'}` }]
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
- sessionIgnoreList.add(original);
1338
+ currentIgnoreList.add(original);
1270
1339
  // Remove the token from the session map
1271
- delete sessionMap[token];
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:** ${sessionIgnoreList.size}\n\nThis value will be excluded from all future \`sanitize_text\` calls in this session.`
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/pricing?tier=sdk&src=mcp_compliance_json"
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/pricing](https://privacyscrubber.com/pricing?tier=sdk&src=mcp_compliance_report)
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, sessionIgnoreList, targetSession);
1986
- const resStdout = performSanitization(cleanStdout, targetProfile, sessionIgnoreList, targetSession);
1987
- const resStderr = performSanitization(cleanStderr, targetProfile, sessionIgnoreList, targetSession);
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 { scrubbedText, newTokens } = performSanitization(processedText, targetProfile, sessionIgnoreList);
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 restored = reverseTokens(item, targetSession);
2452
- for (const token of Object.keys(targetSession)) {
2453
- if (item.includes(token)) totalTokensRestored++;
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 restored = reverseTokens(rawText, targetSession);
2460
- for (const token of Object.keys(targetSession)) {
2461
- if (rawText.includes(token)) totalTokensRestored++;
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/pricing?tier=sdk&src=mcp_audit_json"
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/pricing](https://privacyscrubber.com/pricing?tier=sdk&src=mcp_audit_export)
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/pricing${colors.reset}\n`);
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.2.6",
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.2.6",
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",
@@ -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.4';
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 => ['API_KEY', 'SECRET', 'PASSWORD', 'TOKEN', 'KEY'].includes(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/pricing)`);
487
+ console.log(` Backend SDK: npm install @privacyscrubber/sdk (https://privacyscrubber.com/sdk/)`);
441
488
  process.exit(0);
442
489
  }
443
490