@privacyscrubber/mcp-server 2.4.6 → 2.4.7
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/index.js +37 -17
- package/install.js +1 -1
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/pii-masking-run.js +227 -8
- package/ps-license-manager.cjs +1 -1
- package/ps-pii-engine.cjs +1 -1
- package/scrubber-core.cjs +1 -1
- package/server.json +3 -3
package/README.md
CHANGED
|
@@ -485,7 +485,7 @@ Returns a visual dashboard showing your current tier, session request count, act
|
|
|
485
485
|
* **Response Example (Free Tier):**
|
|
486
486
|
```
|
|
487
487
|
╔══════════════════════════════════════════════════╗
|
|
488
|
-
║ PrivacyScrubber MCP Server v2.4.
|
|
488
|
+
║ PrivacyScrubber MCP Server v2.4.7 ║
|
|
489
489
|
╠══════════════════════════════════════════════════╣
|
|
490
490
|
║ 🔓 Tier: FREE ║
|
|
491
491
|
║ 📊 Session requests: 5 ║
|
package/index.js
CHANGED
|
@@ -43,6 +43,7 @@ if (rawCliArgs.includes('--help') || rawCliArgs.includes('-h') || rawCliArgs.inc
|
|
|
43
43
|
console.log(`PrivacyScrubber MCP Server v${MCP_VERSION} — Zero-Trust Data Sanitization (ZTDS)`);
|
|
44
44
|
console.log('\nUsage:');
|
|
45
45
|
console.log(' npx @privacyscrubber/mcp-server Start MCP stdio server');
|
|
46
|
+
console.log(' npx @privacyscrubber/mcp-server --wrap <cmd> Transparent ZTDS proxy for any 3rd-party MCP server');
|
|
46
47
|
console.log(' npx @privacyscrubber/mcp-server init Auto-configure Claude, Cursor, Windsurf, Cline');
|
|
47
48
|
console.log(' npx @privacyscrubber/mcp-server uninstall Remove from IDE configurations');
|
|
48
49
|
console.log(' npx @privacyscrubber/mcp-server trial Request 7-day B2B TEAMS/SDK evaluation key');
|
|
@@ -82,8 +83,9 @@ if (rawCliArgs.includes('--memo') || rawCliArgs.includes('memo')) {
|
|
|
82
83
|
console.log(generateProcurementMemo({ targetTier: tierArg, format: 'text' }));
|
|
83
84
|
process.exit(0);
|
|
84
85
|
}
|
|
85
|
-
if (rawCliArgs.includes('--hook')) {
|
|
86
|
+
if (rawCliArgs.includes('--hook') || rawCliArgs.includes('--wrap')) {
|
|
86
87
|
await import('./pii-masking-run.js');
|
|
88
|
+
await new Promise(() => {});
|
|
87
89
|
}
|
|
88
90
|
if (rawCliArgs.some(arg => ['init', 'install', '--install', 'setup', '--setup', 'uninstall', '--uninstall', '--remove'].includes(arg))) {
|
|
89
91
|
const { runInstaller } = await import('./install.js');
|
|
@@ -751,11 +753,27 @@ function buildCisoAuditTelemetry(currentTokenMap = {}) {
|
|
|
751
753
|
trademark: 'ZTDS™ Reg. #182655957 (ILPO Cl 9 & 42)'
|
|
752
754
|
};
|
|
753
755
|
|
|
756
|
+
const agenticProtocol = {
|
|
757
|
+
protocolsSupported: ['MCP-1.0 (stdio/SSE)', 'A2A-1.0 (Agent2Agent)', 'ZTDS-RFC-v1.0'],
|
|
758
|
+
agentCardUrl: 'https://privacyscrubber.com/.well-known/agent-card.json',
|
|
759
|
+
ztdsStandardCardUrl: 'https://ztds.ai/.well-known/agent-card.json',
|
|
760
|
+
regulatorySafeHarbor: [
|
|
761
|
+
'GDPR-Art-4(5)-Pseudonymization',
|
|
762
|
+
'GDPR-Art-25-Privacy-by-Design',
|
|
763
|
+
'GDPR-Art-32-TOMs',
|
|
764
|
+
'EU-AI-Act-Art-50',
|
|
765
|
+
'EU-AI-Act-Art-17-EN-18286-QMS',
|
|
766
|
+
'CNIL-ICO-Agentic-Memory-Compartmentalization',
|
|
767
|
+
'IL-PPL-Amendment-13-Sec-17'
|
|
768
|
+
],
|
|
769
|
+
zeroCleartextEgress: true
|
|
770
|
+
};
|
|
771
|
+
|
|
754
772
|
const statutoryCitations = [
|
|
755
773
|
{
|
|
756
|
-
statute: 'EU AI Act (Art. 50)',
|
|
757
|
-
scope: 'AI Transparency &
|
|
758
|
-
rule: 'Sanitization runs locally by design; reduces the content sent to external AI models. Informational mapping, not a compliance determination.'
|
|
774
|
+
statute: 'EU AI Act (Art. 50 & Art. 17 / EN 18286:2026 QMS)',
|
|
775
|
+
scope: 'AI Transparency, Data Minimization & Harmonized QMS Governance',
|
|
776
|
+
rule: 'Sanitization runs locally by design; reduces the content sent to external AI models and supports Article 17 Quality Management System (EN 18286:2026) data governance controls. Informational mapping, not a compliance determination.'
|
|
759
777
|
},
|
|
760
778
|
{
|
|
761
779
|
statute: 'GDPR (Art. 4, 25, 28, 32)',
|
|
@@ -787,6 +805,7 @@ function buildCisoAuditTelemetry(currentTokenMap = {}) {
|
|
|
787
805
|
frameworksList: Array.from(frameworksSet),
|
|
788
806
|
statutoryCitations,
|
|
789
807
|
ipHoldings,
|
|
808
|
+
agenticProtocol,
|
|
790
809
|
multimodalEgress: '0.00 Bytes (Solid Blackout & Burn-in Verified)'
|
|
791
810
|
};
|
|
792
811
|
}
|
|
@@ -2489,10 +2508,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2489
2508
|
|
|
2490
2509
|
const charLimit = (isAdvanced && !isAutoElevated && !license.isPro) ? 5000 : 15000;
|
|
2491
2510
|
if (isAdvanced && !license.isPro) {
|
|
2492
|
-
mcpLog(`${colors.yellowBold}[PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char trial quota).${colors.reset}\n${colors.cyan}[Upgrade]
|
|
2511
|
+
mcpLog(`${colors.yellowBold}[PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char trial quota).${colors.reset}\n${colors.cyan}[Upgrade] 7-Day B2B Trial / TEAMS ($99/mo flat) & Developer SDK ($299/mo): https://privacyscrubber.com/pricing?tier=teams&trial=true&profile=${encodeURIComponent(targetProfile)}&utm_source=mcp&utm_medium=profile_quota&utm_campaign=teams_upgrade (or run: npx @privacyscrubber/mcp-server trial)${colors.reset}\n`);
|
|
2493
2512
|
}
|
|
2494
2513
|
|
|
2495
|
-
const { processedText, wasTruncated } = truncateIfFree(text, license.isPro, charLimit);
|
|
2514
|
+
const { processedText, wasTruncated } = truncateIfFree(text, license.isPro, charLimit, finalProfile);
|
|
2496
2515
|
|
|
2497
2516
|
const { scrubbedText, newTokens } = performSanitization(processedText, finalProfile, currentIgnoreList, currentSession);
|
|
2498
2517
|
|
|
@@ -2851,10 +2870,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2851
2870
|
|
|
2852
2871
|
const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
|
|
2853
2872
|
if (isAdvanced && !license.isPro) {
|
|
2854
|
-
mcpLog(`${colors.yellowBold}[PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char trial quota).${colors.reset}\n${colors.cyan}[Upgrade]
|
|
2873
|
+
mcpLog(`${colors.yellowBold}[PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char trial quota).${colors.reset}\n${colors.cyan}[Upgrade] 7-Day B2B Trial / TEAMS ($99/mo flat) & Developer SDK ($299/mo): https://privacyscrubber.com/pricing?tier=teams&trial=true&profile=${encodeURIComponent(targetProfile)}&utm_source=mcp&utm_medium=profile_quota&utm_campaign=teams_upgrade (or run: npx @privacyscrubber/mcp-server trial)${colors.reset}\n`);
|
|
2855
2874
|
}
|
|
2856
2875
|
|
|
2857
|
-
const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro, charLimit);
|
|
2876
|
+
const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro, charLimit, finalProfile);
|
|
2858
2877
|
|
|
2859
2878
|
const sanitized = performSanitization(processedContent, finalProfile, targetIgnoreList, targetSession);
|
|
2860
2879
|
const { scrubbedText, newTokens } = sanitized;
|
|
@@ -3082,10 +3101,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3082
3101
|
|
|
3083
3102
|
const charLimit = (isAdvanced && !isAutoElevated && !license.isPro) ? 5000 : 15000;
|
|
3084
3103
|
if (isAdvanced && !license.isPro) {
|
|
3085
|
-
mcpLog(`${colors.yellowBold}[PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char trial quota).${colors.reset}\n${colors.cyan}[Upgrade]
|
|
3104
|
+
mcpLog(`${colors.yellowBold}[PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char trial quota).${colors.reset}\n${colors.cyan}[Upgrade] 7-Day B2B Trial / TEAMS ($99/mo flat) & Developer SDK ($299/mo): https://privacyscrubber.com/pricing?tier=teams&trial=true&profile=${encodeURIComponent(targetProfile)}&utm_source=mcp&utm_medium=profile_quota&utm_campaign=teams_upgrade (or run: npx @privacyscrubber/mcp-server trial)${colors.reset}\n`);
|
|
3086
3105
|
}
|
|
3087
3106
|
|
|
3088
|
-
const { processedText: processedContent2, wasTruncated } = truncateIfFree(content, license.isPro, charLimit);
|
|
3107
|
+
const { processedText: processedContent2, wasTruncated } = truncateIfFree(content, license.isPro, charLimit, finalProfile);
|
|
3089
3108
|
|
|
3090
3109
|
const { scrubbedText, newTokens } = performSanitization(processedContent2, finalProfile, targetIgnoreList, targetSession);
|
|
3091
3110
|
const telemetry = buildCisoAuditTelemetry(newTokens);
|
|
@@ -3581,8 +3600,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3581
3600
|
const isAdvanced = targetProfile.toLowerCase() !== "general";
|
|
3582
3601
|
const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
|
|
3583
3602
|
|
|
3584
|
-
const { processedText: cleanStdout, wasTruncated: truncOut } = truncateIfFree(stdout, license.isPro, charLimit);
|
|
3585
|
-
const { processedText: cleanStderr, wasTruncated: truncErr } = truncateIfFree(stderr, license.isPro, charLimit);
|
|
3603
|
+
const { processedText: cleanStdout, wasTruncated: truncOut } = truncateIfFree(stdout, license.isPro, charLimit, targetProfile);
|
|
3604
|
+
const { processedText: cleanStderr, wasTruncated: truncErr } = truncateIfFree(stderr, license.isPro, charLimit, targetProfile);
|
|
3586
3605
|
|
|
3587
3606
|
const resCommand = performSanitization(command, targetProfile, targetIgnoreList, targetSession);
|
|
3588
3607
|
const resStdout = performSanitization(cleanStdout, targetProfile, targetIgnoreList, targetSession);
|
|
@@ -3751,7 +3770,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3751
3770
|
const isAdvanced = targetProfile.toLowerCase() !== "general";
|
|
3752
3771
|
const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
|
|
3753
3772
|
|
|
3754
|
-
const { processedText, wasTruncated: wasCharTruncated } = truncateIfFree(capped, license.isPro, charLimit);
|
|
3773
|
+
const { processedText, wasTruncated: wasCharTruncated } = truncateIfFree(capped, license.isPro, charLimit, targetProfile);
|
|
3755
3774
|
const currentSession = getSessionMap(session_id);
|
|
3756
3775
|
const currentIgnoreList = getSessionIgnoreList(session_id);
|
|
3757
3776
|
const { scrubbedText, newTokens } = performSanitization(processedText, targetProfile, currentIgnoreList, currentSession);
|
|
@@ -3815,7 +3834,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3815
3834
|
const isAdvanced = targetProfile.toLowerCase() !== "general";
|
|
3816
3835
|
const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
|
|
3817
3836
|
|
|
3818
|
-
const { processedText, wasTruncated } = truncateIfFree(diffOutput, license.isPro, charLimit);
|
|
3837
|
+
const { processedText, wasTruncated } = truncateIfFree(diffOutput, license.isPro, charLimit, targetProfile);
|
|
3819
3838
|
const { scrubbedText, newTokens } = performSanitization(processedText, targetProfile, targetIgnoreList, targetSession);
|
|
3820
3839
|
|
|
3821
3840
|
const telemetry = buildCisoAuditTelemetry(newTokens);
|
|
@@ -4621,10 +4640,11 @@ export function performSanitization(text, profile, ignoreList = null, customSess
|
|
|
4621
4640
|
return { scrubbedText: result.scrubbedText, newTokens };
|
|
4622
4641
|
}
|
|
4623
4642
|
|
|
4624
|
-
export function truncateIfFree(text, isPro, charLimit = 15000) {
|
|
4643
|
+
export function truncateIfFree(text, isPro, charLimit = 15000, profile = 'general') {
|
|
4625
4644
|
if (!isPro && text.length > charLimit) {
|
|
4626
|
-
|
|
4627
|
-
|
|
4645
|
+
const safeProf = encodeURIComponent(String(profile || 'general').toLowerCase());
|
|
4646
|
+
mcpLog(`${colors.yellowBold}[PrivacyScrubber] Input truncated to ${charLimit.toLocaleString()} characters (Free Tier Limit for profile '${profile || 'general'}').${colors.reset}\n${colors.cyan}[Upgrade] Set PRIVACYSCRUBBER_KEY to your PRO/TEAMS/SDK license key or start a 7-day B2B trial ('npx @privacyscrubber/mcp-server trial'): https://privacyscrubber.com/pricing?tier=teams&trial=true&profile=${safeProf}&utm_source=mcp&utm_medium=truncation&utm_campaign=teams_upgrade${colors.reset}\n`);
|
|
4647
|
+
const upsellNotice = `\n\n[PrivacyScrubber Free Tier: Payload truncated to ${charLimit.toLocaleString()} chars (${profile || 'general'}). Upgrade to PRO ($15/mo or $110 LT), TEAMS ($99/mo flat) or Developer SDK ($299/mo): https://privacyscrubber.com/pricing?tier=teams&trial=true&profile=${safeProf}&utm_source=mcp&utm_medium=truncation&utm_campaign=teams_upgrade | CLI Trial: npx @privacyscrubber/mcp-server trial]`;
|
|
4628
4648
|
return { processedText: text.substring(0, charLimit), wasTruncated: true, upsellNotice, charLimit };
|
|
4629
4649
|
}
|
|
4630
4650
|
return { processedText: text, wasTruncated: false, upsellNotice: "", charLimit };
|
package/install.js
CHANGED
|
@@ -496,7 +496,7 @@ Options:
|
|
|
496
496
|
console.log(`[SUMMARY] ${options.remove ? 'Cleaned' : 'Configured'} ${configuredCount} AI client(s) successfully.`);
|
|
497
497
|
if (!options.remove) {
|
|
498
498
|
console.log('Next step: Restart your AI IDE or Claude Desktop to activate PrivacyScrubber.');
|
|
499
|
-
console.log('\x1b[36m💼 B2B 7-Day Free Trial (TEAMS/SDK): npx @privacyscrubber/mcp-server trial\x1b[0m');
|
|
499
|
+
console.log('\x1b[36m💼 B2B 7-Day Free Trial (TEAMS $99/mo · SDK $299/mo): npx @privacyscrubber/mcp-server trial | https://privacyscrubber.com/pricing/?tier=teams&trial=true&utm_source=mcp_install\x1b[0m');
|
|
500
500
|
console.log('\x1b[36m📋 Generate CISO Procurement Memo: npx @privacyscrubber/mcp-server memo\x1b[0m');
|
|
501
501
|
console.log('\x1b[33m⭐ Enjoying zero-trust privacy? Star the repo on GitHub: https://github.com/moxno/privacyscrubber-mcp\x1b[0m');
|
|
502
502
|
console.log('\x1b[90m One-click CLI: gh repo star moxno/privacyscrubber-mcp && gh repo star moxno/ztds.ai\x1b[0m');
|
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.
|
|
4
|
+
"version": "2.4.7",
|
|
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.4.
|
|
3
|
+
"version": "2.4.7",
|
|
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.4.
|
|
30
|
+
let version = '2.4.7';
|
|
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);
|
|
@@ -36,6 +36,7 @@ if (args.includes("--version") || args.includes("-v")) {
|
|
|
36
36
|
const isDiffMode = args.includes("--diff");
|
|
37
37
|
const isRulesMode = args.includes("--rules");
|
|
38
38
|
const isHookMode = args.includes("--hook");
|
|
39
|
+
const isWrapMode = args.includes("--wrap");
|
|
39
40
|
const isJsonMode = args.includes("--json");
|
|
40
41
|
const isRestoreMode = args.includes("--restore") || args.includes("--unscrub");
|
|
41
42
|
let tokenMapInput = null;
|
|
@@ -48,19 +49,44 @@ const payloadIdx = args.indexOf("--payload");
|
|
|
48
49
|
if (payloadIdx !== -1 && args[payloadIdx + 1]) {
|
|
49
50
|
hookPayloadArg = args[payloadIdx + 1];
|
|
50
51
|
}
|
|
51
|
-
|
|
52
|
+
|
|
53
|
+
let wrapCommandArgs = [];
|
|
54
|
+
if (isWrapMode) {
|
|
55
|
+
if (cmdIndex !== -1 && args.slice(cmdIndex + 1).length > 0) {
|
|
56
|
+
wrapCommandArgs = args.slice(cmdIndex + 1);
|
|
57
|
+
} else {
|
|
58
|
+
const wrapIdx = args.indexOf("--wrap");
|
|
59
|
+
const afterWrap = args.slice(wrapIdx + 1);
|
|
60
|
+
for (let i = 0; i < afterWrap.length; i++) {
|
|
61
|
+
const a = afterWrap[i];
|
|
62
|
+
if ((a === "--profile" || a === "--key" || a === "--token-map") && afterWrap[i + 1]) {
|
|
63
|
+
i++;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (a === "--quiet" || a === "-q" || a === "--verbose" || a === "--json") {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
wrapCommandArgs = afterWrap.slice(i);
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const hasCommand = (cmdIndex !== -1 && args.slice(cmdIndex + 1).length > 0) || wrapCommandArgs.length > 0;
|
|
52
76
|
const hasStdin = !process.stdin.isTTY;
|
|
53
77
|
|
|
54
|
-
if (args.includes("-h") || args.includes("--help") || (!hasCommand && !isDiffMode && !isRulesMode && !isHookMode && !isRestoreMode && !hasStdin)) {
|
|
78
|
+
if (args.includes("-h") || args.includes("--help") || (!hasCommand && !isDiffMode && !isRulesMode && !isHookMode && !isWrapMode && !isRestoreMode && !hasStdin)) {
|
|
55
79
|
console.log("PrivacyScrubber CLI & Agentic Guard: ps-guard / pii-masking-run");
|
|
56
80
|
console.log("\nUsage:");
|
|
57
81
|
console.log(" npx ps-guard [options] -- <command> [args...]");
|
|
82
|
+
console.log(" npx ps-guard --wrap [options] [--] <mcp-server-cmd> [args...]");
|
|
58
83
|
console.log(" cat <file> | npx ps-guard [options]");
|
|
59
84
|
console.log(" npx ps-guard --diff [--staged] [options]");
|
|
60
85
|
console.log(" npx ps-guard --restore --token-map <file|json> [options]");
|
|
61
86
|
console.log(" npx ps-guard --rules [targets]");
|
|
62
87
|
console.log(" npx ps-guard --hook (Claude Code & Cursor IDE PreToolUse / beforeReadFile hook)");
|
|
63
88
|
console.log("\nOptions:");
|
|
89
|
+
console.log(" --wrap Transparent JSON-RPC 2.0 stdio proxy for any 3rd-party MCP server");
|
|
64
90
|
console.log(" --profile <name> Specify detection profile (e.g. General, Dev, Medical, Legal, HR)");
|
|
65
91
|
console.log(" --key <licKey> PrivacyScrubber PRO License Key");
|
|
66
92
|
console.log(" --json Output structured JSON (scrubbedText, tokenMap, count, telemetry)");
|
|
@@ -75,6 +101,7 @@ if (args.includes("-h") || args.includes("--help") || (!hasCommand && !isDiffMod
|
|
|
75
101
|
console.log(" -h, --help Show this help screen");
|
|
76
102
|
console.log("\nExamples:");
|
|
77
103
|
console.log(" npx ps-guard -- cat database-dump.sql");
|
|
104
|
+
console.log(" npx ps-guard --wrap -- npx -y @modelcontextprotocol/server-postgres postgresql://localhost/mydb");
|
|
78
105
|
console.log(" npx ps-guard --profile dev -- npm test");
|
|
79
106
|
console.log(" cat .env | npx ps-guard --profile dev --json");
|
|
80
107
|
console.log(" cat ai_reply.txt | npx ps-guard --restore --token-map map.json");
|
|
@@ -425,11 +452,27 @@ function buildCisoAuditTelemetry(currentTokenMap = {}) {
|
|
|
425
452
|
trademark: 'ZTDS™ Reg. #182655957 (ILPO Cl 9 & 42)'
|
|
426
453
|
};
|
|
427
454
|
|
|
455
|
+
const agenticProtocol = {
|
|
456
|
+
protocolsSupported: ['MCP-1.0 (stdio/SSE)', 'A2A-1.0 (Agent2Agent)', 'ZTDS-RFC-v1.0'],
|
|
457
|
+
agentCardUrl: 'https://privacyscrubber.com/.well-known/agent-card.json',
|
|
458
|
+
ztdsStandardCardUrl: 'https://ztds.ai/.well-known/agent-card.json',
|
|
459
|
+
regulatorySafeHarbor: [
|
|
460
|
+
'GDPR-Art-4(5)-Pseudonymization',
|
|
461
|
+
'GDPR-Art-25-Privacy-by-Design',
|
|
462
|
+
'GDPR-Art-32-TOMs',
|
|
463
|
+
'EU-AI-Act-Art-50',
|
|
464
|
+
'EU-AI-Act-Art-17-EN-18286-QMS',
|
|
465
|
+
'CNIL-ICO-Agentic-Memory-Compartmentalization',
|
|
466
|
+
'IL-PPL-Amendment-13-Sec-17'
|
|
467
|
+
],
|
|
468
|
+
zeroCleartextEgress: true
|
|
469
|
+
};
|
|
470
|
+
|
|
428
471
|
const statutoryCitations = [
|
|
429
472
|
{
|
|
430
|
-
statute: 'EU AI Act (Art. 50)',
|
|
431
|
-
scope: 'AI Transparency &
|
|
432
|
-
rule: 'Sanitization runs locally by design; reduces the content sent to external AI models. Informational mapping, not a compliance determination.'
|
|
473
|
+
statute: 'EU AI Act (Art. 50 & Art. 17 / EN 18286:2026 QMS)',
|
|
474
|
+
scope: 'AI Transparency, Data Minimization & Harmonized QMS Governance',
|
|
475
|
+
rule: 'Sanitization runs locally by design; reduces the content sent to external AI models and supports Article 17 Quality Management System (EN 18286:2026) data governance controls. Informational mapping, not a compliance determination.'
|
|
433
476
|
},
|
|
434
477
|
{
|
|
435
478
|
statute: 'GDPR (Art. 4, 25, 28, 32)',
|
|
@@ -461,6 +504,7 @@ function buildCisoAuditTelemetry(currentTokenMap = {}) {
|
|
|
461
504
|
frameworksList: Array.from(frameworksSet),
|
|
462
505
|
statutoryCitations,
|
|
463
506
|
ipHoldings,
|
|
507
|
+
agenticProtocol,
|
|
464
508
|
multimodalEgress: '0.00 Bytes (Solid Blackout & Burn-in Verified)'
|
|
465
509
|
};
|
|
466
510
|
}
|
|
@@ -775,8 +819,183 @@ if (isDiffMode) {
|
|
|
775
819
|
}
|
|
776
820
|
}
|
|
777
821
|
|
|
778
|
-
// Mode
|
|
779
|
-
if (
|
|
822
|
+
// Mode 2.5: Transparent MCP JSON-RPC 2.0 Stdio Proxy Wrapper (--wrap)
|
|
823
|
+
if (isWrapMode) {
|
|
824
|
+
if (wrapCommandArgs.length === 0) {
|
|
825
|
+
console.error('Error: --wrap requires an upstream MCP server command (e.g. npx ps-guard --wrap -- npx -y @modelcontextprotocol/server-postgres ...)');
|
|
826
|
+
process.exit(1);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
const SKIP_SANITIZE_KEYS = new Set([
|
|
830
|
+
'jsonrpc', 'id', 'method', 'type', 'mimeType', 'uri', 'name', 'role',
|
|
831
|
+
'protocolVersion', 'cursor', 'nextCursor', 'isError', 'version'
|
|
832
|
+
]);
|
|
833
|
+
|
|
834
|
+
const deepDetokenizeParams = (node, depth = 0) => {
|
|
835
|
+
if (depth > 20 || node === null || node === undefined) return node;
|
|
836
|
+
if (typeof node === 'string') {
|
|
837
|
+
if (Object.keys(localSessionMap).length === 0) return node;
|
|
838
|
+
const res = PrivacyScrubberCore.unscrubText(node, localSessionMap);
|
|
839
|
+
return (res && typeof res.restoredText === 'string') ? res.restoredText : node;
|
|
840
|
+
}
|
|
841
|
+
if (Array.isArray(node)) {
|
|
842
|
+
return node.map(item => deepDetokenizeParams(item, depth + 1));
|
|
843
|
+
}
|
|
844
|
+
if (typeof node === 'object') {
|
|
845
|
+
const out = {};
|
|
846
|
+
for (const [k, v] of Object.entries(node)) {
|
|
847
|
+
if (k === '__proto__' || k === 'constructor' || k === 'prototype') continue;
|
|
848
|
+
out[k] = deepDetokenizeParams(v, depth + 1);
|
|
849
|
+
}
|
|
850
|
+
return out;
|
|
851
|
+
}
|
|
852
|
+
return node;
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
const deepSanitizeResult = (node, keyName = '', depth = 0) => {
|
|
856
|
+
if (depth > 20 || node === null || node === undefined) return node;
|
|
857
|
+
if (typeof node === 'string') {
|
|
858
|
+
if (SKIP_SANITIZE_KEYS.has(keyName) || node.length < 4) return node;
|
|
859
|
+
return performSanitization(node);
|
|
860
|
+
}
|
|
861
|
+
if (Array.isArray(node)) {
|
|
862
|
+
return node.map(item => deepSanitizeResult(item, keyName, depth + 1));
|
|
863
|
+
}
|
|
864
|
+
if (typeof node === 'object') {
|
|
865
|
+
const out = {};
|
|
866
|
+
for (const [k, v] of Object.entries(node)) {
|
|
867
|
+
if (k === '__proto__' || k === 'constructor' || k === 'prototype') continue;
|
|
868
|
+
if (k === 'inputSchema' || k === 'outputSchema' || k === 'capabilities' || k === 'serverInfo' || k === '_meta') {
|
|
869
|
+
out[k] = v;
|
|
870
|
+
} else {
|
|
871
|
+
out[k] = deepSanitizeResult(v, k, depth + 1);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return out;
|
|
875
|
+
}
|
|
876
|
+
return node;
|
|
877
|
+
};
|
|
878
|
+
|
|
879
|
+
const zeroizeSessionMap = () => {
|
|
880
|
+
for (const k of Object.keys(localSessionMap)) {
|
|
881
|
+
localSessionMap[k] = '';
|
|
882
|
+
delete localSessionMap[k];
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
|
|
886
|
+
const [cmdBin, ...cmdRest] = wrapCommandArgs;
|
|
887
|
+
const child = spawn(cmdBin, cmdRest, {
|
|
888
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
889
|
+
shell: process.platform === 'win32'
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
// Intercept client -> upstream MCP server (JSON-RPC requests on stdin)
|
|
893
|
+
const rlClientIn = readline.createInterface({
|
|
894
|
+
input: process.stdin,
|
|
895
|
+
terminal: false
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
rlClientIn.on('line', (line) => {
|
|
899
|
+
if (!child.stdin || child.stdin.destroyed) return;
|
|
900
|
+
const trimmed = line.trim();
|
|
901
|
+
if (!trimmed.startsWith('{')) {
|
|
902
|
+
child.stdin.write(line + '\n');
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
try {
|
|
906
|
+
const msg = JSON.parse(trimmed);
|
|
907
|
+
if (msg && typeof msg === 'object' && msg.params && (msg.method === 'tools/call' || msg.method === 'prompts/get' || msg.method === 'resources/read' || msg.method === 'sampling/createMessage')) {
|
|
908
|
+
msg.params = deepDetokenizeParams(msg.params);
|
|
909
|
+
child.stdin.write(JSON.stringify(msg) + '\n');
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
} catch (_) {}
|
|
913
|
+
child.stdin.write(line + '\n');
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
rlClientIn.on('close', () => {
|
|
917
|
+
if (child.stdin && !child.stdin.destroyed) {
|
|
918
|
+
child.stdin.end();
|
|
919
|
+
}
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
// Intercept upstream MCP server -> client (JSON-RPC responses/notifications on stdout)
|
|
923
|
+
const rlUpstreamOut = readline.createInterface({
|
|
924
|
+
input: child.stdout,
|
|
925
|
+
terminal: false
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
rlUpstreamOut.on('line', (line) => {
|
|
929
|
+
const trimmed = line.trim();
|
|
930
|
+
if (!trimmed.startsWith('{')) {
|
|
931
|
+
process.stdout.write(performSanitization(line) + '\n');
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
try {
|
|
935
|
+
const msg = JSON.parse(trimmed);
|
|
936
|
+
if (msg && typeof msg === 'object') {
|
|
937
|
+
if (msg.result && typeof msg.result === 'object') {
|
|
938
|
+
const isSchemaListing = Array.isArray(msg.result.tools) || Array.isArray(msg.result.prompts) || Array.isArray(msg.result.resourceTemplates) || Boolean(msg.result.protocolVersion);
|
|
939
|
+
if (!isSchemaListing) {
|
|
940
|
+
msg.result = deepSanitizeResult(msg.result);
|
|
941
|
+
const receipt = buildPsr2Receipt(localSessionMap);
|
|
942
|
+
const telemetry = buildCisoAuditTelemetry(localSessionMap);
|
|
943
|
+
msg.result._meta = Object.assign({}, msg.result._meta || {}, {
|
|
944
|
+
ztds_receipt: {
|
|
945
|
+
version: receipt.version,
|
|
946
|
+
timestamp: receipt.timestamp,
|
|
947
|
+
manifestSha256: receipt.manifestSha256,
|
|
948
|
+
receiptSha256: receipt.receiptSha256,
|
|
949
|
+
entityCounts: receipt.entityCounts,
|
|
950
|
+
tokensTotal: receipt.tokensTotal,
|
|
951
|
+
riskLevel: telemetry.riskLevel,
|
|
952
|
+
regulatorySafeHarbor: telemetry.agenticProtocol ? telemetry.agenticProtocol.regulatorySafeHarbor : [],
|
|
953
|
+
zero_cleartext_egress: true
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
} else if (msg.params && typeof msg.params === 'object') {
|
|
958
|
+
msg.params = deepSanitizeResult(msg.params);
|
|
959
|
+
}
|
|
960
|
+
process.stdout.write(JSON.stringify(msg) + '\n');
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
} catch (_) {}
|
|
964
|
+
process.stdout.write(performSanitization(line) + '\n');
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
const rlUpstreamErr = readline.createInterface({
|
|
968
|
+
input: child.stderr,
|
|
969
|
+
terminal: false
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
rlUpstreamErr.on('line', (line) => {
|
|
973
|
+
process.stderr.write(performSanitization(line) + '\n');
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
const handleSignal = (sig) => {
|
|
977
|
+
zeroizeSessionMap();
|
|
978
|
+
if (!child.killed) {
|
|
979
|
+
try { child.kill(sig); } catch (_) {}
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
process.on('SIGINT', () => handleSignal('SIGINT'));
|
|
983
|
+
process.on('SIGTERM', () => handleSignal('SIGTERM'));
|
|
984
|
+
process.on('exit', zeroizeSessionMap);
|
|
985
|
+
|
|
986
|
+
child.on('close', (code) => {
|
|
987
|
+
emitProofFeedback();
|
|
988
|
+
zeroizeSessionMap();
|
|
989
|
+
process.exit(code || 0);
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
child.on('error', (err) => {
|
|
993
|
+
zeroizeSessionMap();
|
|
994
|
+
console.error(`Error spawning wrapped MCP server: ${err.message}`);
|
|
995
|
+
process.exit(1);
|
|
996
|
+
});
|
|
997
|
+
} else if (hasStdin && !hasCommand && !isRestoreMode && !isDiffMode && !isRulesMode) {
|
|
998
|
+
// Mode 3: Stdin Stream Mode (pipe)
|
|
780
999
|
if (isJsonMode) {
|
|
781
1000
|
let rawInput = '';
|
|
782
1001
|
process.stdin.setEncoding('utf8');
|