@privacyscrubber/mcp-server 2.0.2 → 2.0.3
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 +41 -5
- package/index.js +17 -134
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/pii-masking-run.js +10 -28
- package/ps-pii-engine.cjs +130 -42
- package/ps-pii-engine.js +130 -42
- package/server.json +3 -3
package/README.md
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@privacyscrubber/mcp-server)
|
|
4
4
|
[](https://www.npmjs.com/package/@privacyscrubber/mcp-server)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://zenodo.org/records/22058770)
|
|
7
|
+
[](https://osf.io/5byjf/)
|
|
8
|
+
[](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=7335581)
|
|
6
9
|
[](https://smithery.ai/servers/privacyscrubber/pii-masking-mcp)
|
|
7
10
|
[](https://privacyscrubber.com)
|
|
8
11
|
[](https://privacyscrubber.com)
|
|
@@ -148,7 +151,7 @@ Reads a local file, extracts text, sanitizes it, and returns the redacted templa
|
|
|
148
151
|
|
|
149
152
|
Looking for real-time protection directly inside your web browser?
|
|
150
153
|
* **Chrome Extension:** Get the [PrivacyScrubber Chrome Extension](https://chromewebstore.google.com/detail/privacyscrubber-%E2%80%94-pii-red/pimoejgefeilajmmbpghifdmhdlkgjol) to sanitize prompts directly inside ChatGPT, Claude, and Gemini in real-time.
|
|
151
|
-
* **Web Sandbox:** Use the zero-server browser sanitization tools at [PrivacyScrubber Homepage](https://privacyscrubber.com
|
|
154
|
+
* **Web Sandbox:** Use the zero-server browser sanitization tools at [PrivacyScrubber Homepage](https://privacyscrubber.com/?utm_source=npm&utm_medium=readme&utm_campaign=mcp_server).
|
|
152
155
|
|
|
153
156
|
## 📄 License & Commercial Upgrade
|
|
154
157
|
|
|
@@ -166,7 +169,7 @@ By default, the server runs under the **Free Tier** (restricted to 50,000 charac
|
|
|
166
169
|
| **Team Rules Sync (GPO)** | ❌ No | ❌ No | ✅ Yes (Shared Link) |
|
|
167
170
|
| **Licensing Cost** | $0 | **$110 Lifetime** | **$99/mo Flat Rate** |
|
|
168
171
|
|
|
169
|
-
👉 **[Acquire a PRO / TEAMS License Key at privacyscrubber.com/pricing](https://privacyscrubber.com/pricing)**
|
|
172
|
+
👉 **[Acquire a PRO / TEAMS License Key at privacyscrubber.com/pricing](https://privacyscrubber.com/pricing?utm_source=npm&utm_medium=readme&utm_campaign=mcp_server)**
|
|
170
173
|
|
|
171
174
|
---
|
|
172
175
|
|
|
@@ -195,12 +198,12 @@ Returns a visual dashboard showing your current tier, session request count, act
|
|
|
195
198
|
║ 📋 Custom rules: 🔒 Locked — requires PRO ║
|
|
196
199
|
╠══════════════════════════════════════════════════╣
|
|
197
200
|
║ 💳 Upgrade to PRO — $110 Lifetime ║
|
|
198
|
-
║ https://privacyscrubber.com/pricing ║
|
|
201
|
+
║ https://privacyscrubber.com/pricing?utm_source=npm&utm_medium=readme&utm_campaign=mcp_server ║
|
|
199
202
|
╠══════════════════════════════════════════════════╣
|
|
200
203
|
║ After purchase, add your key to MCP config: ║
|
|
201
204
|
║ "PRIVACYSCRUBBER_KEY": "<your-key-here>" ║
|
|
202
205
|
║ Full setup guide: ║
|
|
203
|
-
║ https://privacyscrubber.com/features/mcp
|
|
206
|
+
║ https://privacyscrubber.com/features/mcp/?utm_source=npm&utm_medium=readme&utm_campaign=mcp_server ║
|
|
204
207
|
╚══════════════════════════════════════════════════╝
|
|
205
208
|
```
|
|
206
209
|
|
|
@@ -208,7 +211,7 @@ Returns a visual dashboard showing your current tier, session request count, act
|
|
|
208
211
|
|
|
209
212
|
## 🔐 After Purchase: Activate PRO in Your MCP Client
|
|
210
213
|
|
|
211
|
-
After purchasing a PRO license at [privacyscrubber.com/pricing](https://privacyscrubber.com/pricing), you will receive a license key. Add it to your MCP client config as an environment variable: `PRIVACYSCRUBBER_KEY`.
|
|
214
|
+
After purchasing a PRO license at [privacyscrubber.com/pricing](https://privacyscrubber.com/pricing?utm_source=npm&utm_medium=readme&utm_campaign=mcp_server), you will receive a license key. Add it to your MCP client config as an environment variable: `PRIVACYSCRUBBER_KEY`.
|
|
212
215
|
|
|
213
216
|
### Claude Desktop
|
|
214
217
|
|
|
@@ -271,3 +274,36 @@ Use the check_status tool from PrivacyScrubber MCP
|
|
|
271
274
|
|
|
272
275
|
The dashboard should show **Tier: PRO** and all profiles unlocked.
|
|
273
276
|
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## 📚 Academic Foundations & Regulatory Verification
|
|
280
|
+
|
|
281
|
+
PrivacyScrubber and the Zero-Trust Data Sanitization (ZTDS) protocol are backed by published scientific, clinical, and legal treatises:
|
|
282
|
+
|
|
283
|
+
| Repository / Archive | DOI / Identifier | Focus Area | Regulatory & Compliance Scope |
|
|
284
|
+
|---|---|---|---|
|
|
285
|
+
| **Zenodo / CERN** | [`10.5281/zenodo.22058770`](https://zenodo.org/records/22058770) | Zero-Trust Data Sanitization (ZTDS) Protocol Foundation | Cross-Border AI Privacy, ISO 27001 A.8.11 |
|
|
286
|
+
| **OSF (Center for Open Science)** | [`10.17605/OSF.IO/5BYJF`](https://osf.io/5byjf/) | Empirical Latency Benchmark & Memory Profiling (<2ms RAM) | Performance vs Cloud DLP Proxies |
|
|
287
|
+
| **SSRN / Elsevier** | [`SSRN ID: 7335581`](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=7335581) | Enterprise Generative AI Governance | EU AI Act, UK GDPR, US State Privacy |
|
|
288
|
+
| **medRxiv (Cold Spring Harbor)** | [`MEDRXIV/2026/361661`](https://submit.medrxiv.org/) | Multi-Center Clinical Trial De-Identification | HIPAA Safe Harbor Section 164.514(b) |
|
|
289
|
+
| **Law Archive / OSF** | [`LawArchive ID: 4wc86`](https://osf.io/preprints/lawarchive/4wc86/) | Preserving Attorney-Client Privilege in AI Workflows | ABA Model Rules & Legal Ethics |
|
|
290
|
+
|
|
291
|
+
### Citing PrivacyScrubber in Research & Audits
|
|
292
|
+
```bibtex
|
|
293
|
+
@software{sibiryakov2026privacyscrubber,
|
|
294
|
+
author = {Sibiryakov, Ilya},
|
|
295
|
+
title = {PrivacyScrubber: Zero-Trust Data Sanitization (ZTDS) Engine & MCP Server},
|
|
296
|
+
year = {2026},
|
|
297
|
+
publisher = {Zenodo},
|
|
298
|
+
doi = {10.5281/zenodo.22058770},
|
|
299
|
+
url = {https://github.com/moxno/privacyscrubber-mcp}
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## 📄 License
|
|
306
|
+
|
|
307
|
+
MIT © [Ilya Sibiryakov](https://privacyscrubber.com) (BrandMeWeb)
|
|
308
|
+
|
|
309
|
+
|
package/index.js
CHANGED
|
@@ -97,6 +97,8 @@ function checkLicenseStatus() {
|
|
|
97
97
|
|
|
98
98
|
return { isPro: true, type: result.tier, error: null };
|
|
99
99
|
}
|
|
100
|
+
// ── Zero-Trust Air-Gapped Operation ──────────────────────────────────────────
|
|
101
|
+
// 100% local stdio execution in memory. Zero external network calls, zero telemetry.
|
|
100
102
|
|
|
101
103
|
// ── Free Tier Usage Tracking (Persistent) ─────────────────────────────
|
|
102
104
|
const FREE_TIER_DAILY_LIMIT = 10;
|
|
@@ -230,16 +232,6 @@ function formatAuditReceipt(telemetry) {
|
|
|
230
232
|
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`;
|
|
231
233
|
}
|
|
232
234
|
|
|
233
|
-
// Build a clean upsell content block (Disabled to prevent agent-visible marketing content)
|
|
234
|
-
function buildUpsellBlock(reason) {
|
|
235
|
-
return null;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Decide if we should attach a soft periodic nudge (Disabled to maintain clean MCP output)
|
|
239
|
-
function shouldNudge(isPro) {
|
|
240
|
-
return false;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
235
|
// Create the MCP server
|
|
244
236
|
const server = new Server(
|
|
245
237
|
{
|
|
@@ -540,6 +532,7 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
540
532
|
// Handle tool execution calls
|
|
541
533
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
542
534
|
const { name, arguments: args } = request.params;
|
|
535
|
+
const toolStart = Date.now();
|
|
543
536
|
|
|
544
537
|
try {
|
|
545
538
|
if (name === "sanitize_text") {
|
|
@@ -567,7 +560,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
567
560
|
const isAdvanced = targetProfile.toLowerCase() !== "general";
|
|
568
561
|
const license = checkLicenseStatus();
|
|
569
562
|
let finalProfile = targetProfile;
|
|
570
|
-
const extraBlocks = [];
|
|
571
563
|
|
|
572
564
|
const limitStatus = checkFreeTierLimit(license.isPro);
|
|
573
565
|
if (limitStatus.blocked) {
|
|
@@ -579,22 +571,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
579
571
|
|
|
580
572
|
const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
|
|
581
573
|
if (isAdvanced && !license.isPro) {
|
|
582
|
-
mcpLog(`${colors.yellowBold}⚠️ [PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char limit).${colors.reset}\n
|
|
583
|
-
extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' active (Free Tier limit: 5,000 chars). Upgrade to PRO for unlimited text.`));
|
|
574
|
+
mcpLog(`${colors.yellowBold}⚠️ [PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char limit).${colors.reset}\n`);
|
|
584
575
|
}
|
|
585
576
|
|
|
586
|
-
const { processedText
|
|
587
|
-
if (wasTruncated) {
|
|
588
|
-
extraBlocks.push(buildUpsellBlock(`Input was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
if (!license.isPro) {
|
|
592
|
-
const detected = detectSecrets(processedText);
|
|
593
|
-
if (detected.length > 0) {
|
|
594
|
-
mcpLog(`${colors.cyan}🔒 [PrivacyScrubber] DevOps Secret / API Key (${detected.join(', ')}) sanitized locally in RAM.${colors.reset}\n${colors.yellowBold}👉 Upgrade to PRO for 25 industry profiles & batch sanitization: https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal${colors.reset}\n`);
|
|
595
|
-
extraBlocks.push(buildUpsellBlock(`DevOps Secret / API Key (${detected.join(', ')}) sanitized. Upgrade to PRO for 25 specialized industry profiles & batch directory sanitization.`));
|
|
596
|
-
}
|
|
597
|
-
}
|
|
577
|
+
const { processedText } = truncateIfFree(text, license.isPro, charLimit);
|
|
598
578
|
|
|
599
579
|
const { scrubbedText, newTokens } = performSanitization(processedText, finalProfile, sessionIgnoreList);
|
|
600
580
|
|
|
@@ -603,8 +583,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
603
583
|
|
|
604
584
|
return {
|
|
605
585
|
content: [
|
|
606
|
-
{ type: "text", text: scrubbedText + receiptMd }
|
|
607
|
-
...extraBlocks.filter(Boolean)
|
|
586
|
+
{ type: "text", text: scrubbedText + receiptMd }
|
|
608
587
|
]
|
|
609
588
|
};
|
|
610
589
|
}
|
|
@@ -745,9 +724,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
745
724
|
const targetProfile = profile.trim();
|
|
746
725
|
const isAdvanced = targetProfile.toLowerCase() !== "general";
|
|
747
726
|
const license = checkLicenseStatus();
|
|
748
|
-
|
|
749
727
|
let finalProfile = targetProfile;
|
|
750
|
-
const extraBlocks = [];
|
|
751
728
|
|
|
752
729
|
const limitStatus = checkFreeTierLimit(license.isPro);
|
|
753
730
|
if (limitStatus.blocked) {
|
|
@@ -759,23 +736,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
759
736
|
|
|
760
737
|
const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
|
|
761
738
|
if (isAdvanced && !license.isPro) {
|
|
762
|
-
mcpLog(`${colors.yellowBold}⚠️ [PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char limit).${colors.reset}\n
|
|
763
|
-
extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' active (Free Tier limit: 5,000 chars). Upgrade to PRO for unlimited text.`));
|
|
739
|
+
mcpLog(`${colors.yellowBold}⚠️ [PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char limit).${colors.reset}\n`);
|
|
764
740
|
}
|
|
765
741
|
|
|
766
|
-
const { processedText: processedContent
|
|
767
|
-
if (wasTruncated) extraBlocks.push(buildUpsellBlock(`File content was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
|
|
768
|
-
|
|
769
|
-
if (!license.isPro) {
|
|
770
|
-
const detected = detectSecrets(processedContent);
|
|
771
|
-
if (detected.length > 0) {
|
|
772
|
-
mcpLog(`${colors.redBold}🚫 [PrivacyScrubber] API Key / Secret (${detected.join(', ')}) detected! Request BLOCKED to protect your data (Requires PRO Profile).${colors.reset}\n${colors.cyan}👉 Upgrade at: https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal${colors.reset}\n`);
|
|
773
|
-
return {
|
|
774
|
-
isError: true,
|
|
775
|
-
content: [{ type: "text", text: `Error: API Key / Secret (${detected.join(', ')}) detected! Request BLOCKED to protect your data. Sanitizing DevOps secrets requires a PRO license. Get a key at: https://privacyscrubber.com/pricing` }]
|
|
776
|
-
};
|
|
777
|
-
}
|
|
778
|
-
}
|
|
742
|
+
const { processedText: processedContent } = truncateIfFree(content, license.isPro, charLimit);
|
|
779
743
|
|
|
780
744
|
const sanitized = performSanitization(processedContent, finalProfile, sessionIgnoreList);
|
|
781
745
|
const { scrubbedText, newTokens } = sanitized;
|
|
@@ -785,8 +749,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
785
749
|
|
|
786
750
|
return {
|
|
787
751
|
content: [
|
|
788
|
-
{ type: "text", text: combinedOutput }
|
|
789
|
-
...extraBlocks.filter(Boolean)
|
|
752
|
+
{ type: "text", text: combinedOutput }
|
|
790
753
|
]
|
|
791
754
|
};
|
|
792
755
|
} catch (docxError) {
|
|
@@ -810,7 +773,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
810
773
|
content: [
|
|
811
774
|
{
|
|
812
775
|
type: "text",
|
|
813
|
-
text:
|
|
776
|
+
text: `🔒 [PrivacyScrubber PRO] Sanitizing ${isPdf ? 'PDF' : 'Excel/XLSX'} files is a PRO feature.\n\n👉 Set your PRIVACYSCRUBBER_KEY environment variable to a valid PRO license key.\n👉 Upgrade at: https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal\n👉 Alternatively, sanitize plain text, code, CSV, and DOCX files for free.`
|
|
814
777
|
}
|
|
815
778
|
]
|
|
816
779
|
};
|
|
@@ -822,40 +785,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
822
785
|
const data = await pdf(buffer);
|
|
823
786
|
const content = data.text;
|
|
824
787
|
const targetProfile = profile.trim();
|
|
825
|
-
const isAdvanced = targetProfile.toLowerCase() !== "general";
|
|
826
|
-
|
|
827
788
|
let finalProfile = targetProfile;
|
|
828
|
-
const extraBlocks = [];
|
|
829
|
-
|
|
830
|
-
const limitStatus = checkFreeTierLimit(license.isPro);
|
|
831
|
-
if (limitStatus.blocked) {
|
|
832
|
-
return {
|
|
833
|
-
isError: true,
|
|
834
|
-
content: [{ type: "text", text: `Error: Free tier daily limit exhausted (${FREE_TIER_DAILY_LIMIT} requests). Please set your PRO license key to continue. Get a key at: https://privacyscrubber.com/pricing` }]
|
|
835
|
-
};
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
|
|
839
|
-
if (isAdvanced && !license.isPro) {
|
|
840
|
-
mcpLog(`${colors.yellowBold}⚠️ [PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char limit).${colors.reset}\n${colors.cyan}👉 Get a PRO key for unlimited use: https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal${colors.reset}\n`);
|
|
841
|
-
extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' active (Free Tier limit: 5,000 chars). Upgrade to PRO for unlimited text.`));
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro, charLimit);
|
|
845
|
-
if (wasTruncated) extraBlocks.push(buildUpsellBlock(`File content was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
|
|
846
|
-
|
|
847
|
-
if (!license.isPro) {
|
|
848
|
-
const detected = detectSecrets(processedContent);
|
|
849
|
-
if (detected.length > 0) {
|
|
850
|
-
mcpLog(`${colors.redBold}🚫 [PrivacyScrubber] API Key / Secret (${detected.join(', ')}) detected! Request BLOCKED to protect your data (Requires PRO Profile).${colors.reset}\n${colors.cyan}👉 Upgrade at: https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal${colors.reset}\n`);
|
|
851
|
-
return {
|
|
852
|
-
isError: true,
|
|
853
|
-
content: [{ type: "text", text: `Error: API Key / Secret (${detected.join(', ')}) detected! Request BLOCKED to protect your data. Sanitizing DevOps secrets requires a PRO license. Get a key at: https://privacyscrubber.com/pricing` }]
|
|
854
|
-
};
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
789
|
|
|
858
|
-
const sanitized = performSanitization(
|
|
790
|
+
const sanitized = performSanitization(content, finalProfile, sessionIgnoreList);
|
|
859
791
|
const { scrubbedText, newTokens } = sanitized;
|
|
860
792
|
const telemetry = buildCisoAuditTelemetry(newTokens);
|
|
861
793
|
const receiptMarkdown = formatAuditReceipt(telemetry);
|
|
@@ -863,8 +795,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
863
795
|
|
|
864
796
|
return {
|
|
865
797
|
content: [
|
|
866
|
-
{ type: "text", text: combinedOutput }
|
|
867
|
-
...extraBlocks.filter(Boolean)
|
|
798
|
+
{ type: "text", text: combinedOutput }
|
|
868
799
|
]
|
|
869
800
|
};
|
|
870
801
|
} catch (pdfError) {
|
|
@@ -887,40 +818,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
887
818
|
});
|
|
888
819
|
|
|
889
820
|
const targetProfile = profile.trim();
|
|
890
|
-
const isAdvanced = targetProfile.toLowerCase() !== "general";
|
|
891
|
-
|
|
892
821
|
let finalProfile = targetProfile;
|
|
893
|
-
const extraBlocks = [];
|
|
894
|
-
|
|
895
|
-
const limitStatus = checkFreeTierLimit(license.isPro);
|
|
896
|
-
if (limitStatus.blocked) {
|
|
897
|
-
return {
|
|
898
|
-
isError: true,
|
|
899
|
-
content: [{ type: "text", text: `Error: Free tier daily limit exhausted (${FREE_TIER_DAILY_LIMIT} requests). Please set your PRO license key to continue. Get a key at: https://privacyscrubber.com/pricing` }]
|
|
900
|
-
};
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
|
|
904
|
-
if (isAdvanced && !license.isPro) {
|
|
905
|
-
mcpLog(`${colors.yellowBold}⚠️ [PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char limit).${colors.reset}\n${colors.cyan}👉 Get a PRO key for unlimited use: https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal${colors.reset}\n`);
|
|
906
|
-
extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' active (Free Tier limit: 5,000 chars). Upgrade to PRO for unlimited text.`));
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro, charLimit);
|
|
910
|
-
if (wasTruncated) extraBlocks.push(buildUpsellBlock(`File content was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
|
|
911
|
-
|
|
912
|
-
if (!license.isPro) {
|
|
913
|
-
const detected = detectSecrets(processedContent);
|
|
914
|
-
if (detected.length > 0) {
|
|
915
|
-
mcpLog(`${colors.redBold}🚫 [PrivacyScrubber] API Key / Secret (${detected.join(', ')}) detected! Request BLOCKED to protect your data (Requires PRO Profile).${colors.reset}\n${colors.cyan}👉 Upgrade at: https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal${colors.reset}\n`);
|
|
916
|
-
return {
|
|
917
|
-
isError: true,
|
|
918
|
-
content: [{ type: "text", text: `Error: API Key / Secret (${detected.join(', ')}) detected! Request BLOCKED to protect your data. Sanitizing DevOps secrets requires a PRO license. Get a key at: https://privacyscrubber.com/pricing` }]
|
|
919
|
-
};
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
822
|
|
|
923
|
-
const sanitized = performSanitization(
|
|
823
|
+
const sanitized = performSanitization(content, finalProfile, sessionIgnoreList);
|
|
924
824
|
const { scrubbedText, newTokens } = sanitized;
|
|
925
825
|
const telemetry = buildCisoAuditTelemetry(newTokens);
|
|
926
826
|
const receiptMarkdown = formatAuditReceipt(telemetry);
|
|
@@ -928,8 +828,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
928
828
|
|
|
929
829
|
return {
|
|
930
830
|
content: [
|
|
931
|
-
{ type: "text", text: combinedOutput }
|
|
932
|
-
...extraBlocks.filter(Boolean)
|
|
831
|
+
{ type: "text", text: combinedOutput }
|
|
933
832
|
]
|
|
934
833
|
};
|
|
935
834
|
} catch (xlsxError) {
|
|
@@ -967,9 +866,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
967
866
|
const targetProfile = profile.trim();
|
|
968
867
|
const isAdvanced = targetProfile.toLowerCase() !== "general";
|
|
969
868
|
const license = checkLicenseStatus();
|
|
970
|
-
|
|
971
869
|
let finalProfile = targetProfile;
|
|
972
|
-
const extraBlocks = [];
|
|
973
870
|
|
|
974
871
|
const limitStatus = checkFreeTierLimit(license.isPro);
|
|
975
872
|
if (limitStatus.blocked) {
|
|
@@ -981,23 +878,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
981
878
|
|
|
982
879
|
const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
|
|
983
880
|
if (isAdvanced && !license.isPro) {
|
|
984
|
-
mcpLog(`${colors.yellowBold}⚠️ [PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char limit).${colors.reset}\n
|
|
985
|
-
extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' active (Free Tier limit: 5,000 chars). Upgrade to PRO for unlimited text.`));
|
|
881
|
+
mcpLog(`${colors.yellowBold}⚠️ [PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char limit).${colors.reset}\n`);
|
|
986
882
|
}
|
|
987
883
|
|
|
988
|
-
const { processedText: processedContent2
|
|
989
|
-
if (wasTruncated2) extraBlocks.push(buildUpsellBlock(`File content was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
|
|
990
|
-
|
|
991
|
-
if (!license.isPro) {
|
|
992
|
-
const detected = detectSecrets(processedContent2);
|
|
993
|
-
if (detected.length > 0) {
|
|
994
|
-
mcpLog(`${colors.redBold}🚫 [PrivacyScrubber] API Key / Secret (${detected.join(', ')}) detected! Request BLOCKED to protect your data (Requires PRO Profile).${colors.reset}\n${colors.cyan}👉 Upgrade at: https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal${colors.reset}\n`);
|
|
995
|
-
return {
|
|
996
|
-
isError: true,
|
|
997
|
-
content: [{ type: "text", text: `Error: API Key / Secret (${detected.join(', ')}) detected! Request BLOCKED to protect your data. Sanitizing DevOps secrets requires a PRO license. Get a key at: https://privacyscrubber.com/pricing` }]
|
|
998
|
-
};
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
884
|
+
const { processedText: processedContent2 } = truncateIfFree(content, license.isPro, charLimit);
|
|
1001
885
|
|
|
1002
886
|
const { scrubbedText, newTokens } = performSanitization(processedContent2, finalProfile, sessionIgnoreList);
|
|
1003
887
|
const telemetry = buildCisoAuditTelemetry(newTokens);
|
|
@@ -1005,8 +889,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1005
889
|
|
|
1006
890
|
return {
|
|
1007
891
|
content: [
|
|
1008
|
-
{ type: "text", text: scrubbedText + receiptMd }
|
|
1009
|
-
...extraBlocks.filter(Boolean)
|
|
892
|
+
{ type: "text", text: scrubbedText + receiptMd }
|
|
1010
893
|
]
|
|
1011
894
|
};
|
|
1012
895
|
}
|
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": "
|
|
4
|
+
"version": "2.0.3",
|
|
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.0.
|
|
3
|
+
"version": "2.0.3",
|
|
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
|
@@ -69,38 +69,20 @@ const DEVOPS_SECRETS_DETECTOR = [
|
|
|
69
69
|
{ name: 'Database/API Secret', regex: /\b(DB|POSTGRES|REDIS|MYSQL|AWS|SECRET|PASSWORD|TOKEN|API|KEY)[A-Z0-9_]*\s*[:=]\s*[^ \t\r\n"']{8,}\b/gi }
|
|
70
70
|
];
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
TRhSHUeL7ReltWlCHwz9xf5m2FrKtxr2VBlEoyPjsFzalHMey1EX+yXe81zM7IIi
|
|
77
|
-
t1Z8agLzo7WIfNBAIWmRlerTplaFFZrQgdF5g/Y0n8IIMZOtadgoY8E855psDNZV
|
|
78
|
-
7wIDAQAB
|
|
79
|
-
-----END PUBLIC KEY-----`;
|
|
72
|
+
let LicenseManager = require('./ps-license-manager.js');
|
|
73
|
+
if (!LicenseManager || typeof LicenseManager.validate !== 'function') {
|
|
74
|
+
LicenseManager = global.LicenseManager;
|
|
75
|
+
}
|
|
80
76
|
|
|
81
77
|
function checkLicenseStatus() {
|
|
82
78
|
const key = (process.env.PRIVACYSCRUBBER_KEY || "").trim();
|
|
83
|
-
if (!key) return { isPro: false, error: "No license key provided." };
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
verifier.update(payloadBase64);
|
|
89
|
-
const isVerified = verifier.verify(PUBLIC_KEY, signatureBase64, 'base64');
|
|
90
|
-
|
|
91
|
-
if (!isVerified) return { isPro: false, error: "Signature verification failed." };
|
|
92
|
-
|
|
93
|
-
const payload = JSON.parse(Buffer.from(payloadBase64, 'base64').toString('utf8'));
|
|
94
|
-
if (payload.expires && payload.expires < Math.floor(Date.now() / 1000)) {
|
|
95
|
-
return {
|
|
96
|
-
isPro: false,
|
|
97
|
-
error: `License expired on ${new Date(payload.expires * 1000).toLocaleDateString()}`
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
return { isPro: true, error: null };
|
|
101
|
-
} catch (e) {
|
|
102
|
-
return { isPro: false, error: "Error parsing license: " + e.message };
|
|
79
|
+
if (!key) return { isPro: false, type: null, error: "No license key provided." };
|
|
80
|
+
|
|
81
|
+
const result = LicenseManager.validate(key);
|
|
82
|
+
if (!result.valid) {
|
|
83
|
+
return { isPro: false, type: null, error: result.reason || "Invalid license format or signature." };
|
|
103
84
|
}
|
|
85
|
+
return { isPro: true, type: result.tier, error: null };
|
|
104
86
|
}
|
|
105
87
|
|
|
106
88
|
function resolveConfigPath() {
|