@privacyscrubber/mcp-server 2.0.0 → 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 +19 -139
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/pii-masking-run.js +10 -28
- package/ps-pii-engine.cjs +185 -67
- package/ps-pii-engine.js +185 -67
- package/server.json +3 -3
- package/bundle.mcpb +0 -0
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,25 +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.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`);
|
|
595
|
-
return {
|
|
596
|
-
isError: true,
|
|
597
|
-
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` }]
|
|
598
|
-
};
|
|
599
|
-
}
|
|
600
|
-
}
|
|
577
|
+
const { processedText } = truncateIfFree(text, license.isPro, charLimit);
|
|
601
578
|
|
|
602
579
|
const { scrubbedText, newTokens } = performSanitization(processedText, finalProfile, sessionIgnoreList);
|
|
603
580
|
|
|
@@ -606,8 +583,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
606
583
|
|
|
607
584
|
return {
|
|
608
585
|
content: [
|
|
609
|
-
{ type: "text", text: scrubbedText + receiptMd }
|
|
610
|
-
...extraBlocks.filter(Boolean)
|
|
586
|
+
{ type: "text", text: scrubbedText + receiptMd }
|
|
611
587
|
]
|
|
612
588
|
};
|
|
613
589
|
}
|
|
@@ -748,9 +724,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
748
724
|
const targetProfile = profile.trim();
|
|
749
725
|
const isAdvanced = targetProfile.toLowerCase() !== "general";
|
|
750
726
|
const license = checkLicenseStatus();
|
|
751
|
-
|
|
752
727
|
let finalProfile = targetProfile;
|
|
753
|
-
const extraBlocks = [];
|
|
754
728
|
|
|
755
729
|
const limitStatus = checkFreeTierLimit(license.isPro);
|
|
756
730
|
if (limitStatus.blocked) {
|
|
@@ -762,23 +736,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
762
736
|
|
|
763
737
|
const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
|
|
764
738
|
if (isAdvanced && !license.isPro) {
|
|
765
|
-
mcpLog(`${colors.yellowBold}⚠️ [PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char limit).${colors.reset}\n
|
|
766
|
-
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`);
|
|
767
740
|
}
|
|
768
741
|
|
|
769
|
-
const { processedText: processedContent
|
|
770
|
-
if (wasTruncated) extraBlocks.push(buildUpsellBlock(`File content was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
|
|
771
|
-
|
|
772
|
-
if (!license.isPro) {
|
|
773
|
-
const detected = detectSecrets(processedContent);
|
|
774
|
-
if (detected.length > 0) {
|
|
775
|
-
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`);
|
|
776
|
-
return {
|
|
777
|
-
isError: true,
|
|
778
|
-
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` }]
|
|
779
|
-
};
|
|
780
|
-
}
|
|
781
|
-
}
|
|
742
|
+
const { processedText: processedContent } = truncateIfFree(content, license.isPro, charLimit);
|
|
782
743
|
|
|
783
744
|
const sanitized = performSanitization(processedContent, finalProfile, sessionIgnoreList);
|
|
784
745
|
const { scrubbedText, newTokens } = sanitized;
|
|
@@ -788,8 +749,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
788
749
|
|
|
789
750
|
return {
|
|
790
751
|
content: [
|
|
791
|
-
{ type: "text", text: combinedOutput }
|
|
792
|
-
...extraBlocks.filter(Boolean)
|
|
752
|
+
{ type: "text", text: combinedOutput }
|
|
793
753
|
]
|
|
794
754
|
};
|
|
795
755
|
} catch (docxError) {
|
|
@@ -813,7 +773,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
813
773
|
content: [
|
|
814
774
|
{
|
|
815
775
|
type: "text",
|
|
816
|
-
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.`
|
|
817
777
|
}
|
|
818
778
|
]
|
|
819
779
|
};
|
|
@@ -825,40 +785,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
825
785
|
const data = await pdf(buffer);
|
|
826
786
|
const content = data.text;
|
|
827
787
|
const targetProfile = profile.trim();
|
|
828
|
-
const isAdvanced = targetProfile.toLowerCase() !== "general";
|
|
829
|
-
|
|
830
788
|
let finalProfile = targetProfile;
|
|
831
|
-
const extraBlocks = [];
|
|
832
|
-
|
|
833
|
-
const limitStatus = checkFreeTierLimit(license.isPro);
|
|
834
|
-
if (limitStatus.blocked) {
|
|
835
|
-
return {
|
|
836
|
-
isError: true,
|
|
837
|
-
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` }]
|
|
838
|
-
};
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
|
|
842
|
-
if (isAdvanced && !license.isPro) {
|
|
843
|
-
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`);
|
|
844
|
-
extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' active (Free Tier limit: 5,000 chars). Upgrade to PRO for unlimited text.`));
|
|
845
|
-
}
|
|
846
789
|
|
|
847
|
-
const
|
|
848
|
-
if (wasTruncated) extraBlocks.push(buildUpsellBlock(`File content was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
|
|
849
|
-
|
|
850
|
-
if (!license.isPro) {
|
|
851
|
-
const detected = detectSecrets(processedContent);
|
|
852
|
-
if (detected.length > 0) {
|
|
853
|
-
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`);
|
|
854
|
-
return {
|
|
855
|
-
isError: true,
|
|
856
|
-
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` }]
|
|
857
|
-
};
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
const sanitized = performSanitization(processedContent, finalProfile, sessionIgnoreList);
|
|
790
|
+
const sanitized = performSanitization(content, finalProfile, sessionIgnoreList);
|
|
862
791
|
const { scrubbedText, newTokens } = sanitized;
|
|
863
792
|
const telemetry = buildCisoAuditTelemetry(newTokens);
|
|
864
793
|
const receiptMarkdown = formatAuditReceipt(telemetry);
|
|
@@ -866,8 +795,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
866
795
|
|
|
867
796
|
return {
|
|
868
797
|
content: [
|
|
869
|
-
{ type: "text", text: combinedOutput }
|
|
870
|
-
...extraBlocks.filter(Boolean)
|
|
798
|
+
{ type: "text", text: combinedOutput }
|
|
871
799
|
]
|
|
872
800
|
};
|
|
873
801
|
} catch (pdfError) {
|
|
@@ -890,40 +818,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
890
818
|
});
|
|
891
819
|
|
|
892
820
|
const targetProfile = profile.trim();
|
|
893
|
-
const isAdvanced = targetProfile.toLowerCase() !== "general";
|
|
894
|
-
|
|
895
821
|
let finalProfile = targetProfile;
|
|
896
|
-
const extraBlocks = [];
|
|
897
|
-
|
|
898
|
-
const limitStatus = checkFreeTierLimit(license.isPro);
|
|
899
|
-
if (limitStatus.blocked) {
|
|
900
|
-
return {
|
|
901
|
-
isError: true,
|
|
902
|
-
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` }]
|
|
903
|
-
};
|
|
904
|
-
}
|
|
905
822
|
|
|
906
|
-
const
|
|
907
|
-
if (isAdvanced && !license.isPro) {
|
|
908
|
-
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`);
|
|
909
|
-
extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' active (Free Tier limit: 5,000 chars). Upgrade to PRO for unlimited text.`));
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro, charLimit);
|
|
913
|
-
if (wasTruncated) extraBlocks.push(buildUpsellBlock(`File content was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
|
|
914
|
-
|
|
915
|
-
if (!license.isPro) {
|
|
916
|
-
const detected = detectSecrets(processedContent);
|
|
917
|
-
if (detected.length > 0) {
|
|
918
|
-
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`);
|
|
919
|
-
return {
|
|
920
|
-
isError: true,
|
|
921
|
-
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` }]
|
|
922
|
-
};
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
const sanitized = performSanitization(processedContent, finalProfile, sessionIgnoreList);
|
|
823
|
+
const sanitized = performSanitization(content, finalProfile, sessionIgnoreList);
|
|
927
824
|
const { scrubbedText, newTokens } = sanitized;
|
|
928
825
|
const telemetry = buildCisoAuditTelemetry(newTokens);
|
|
929
826
|
const receiptMarkdown = formatAuditReceipt(telemetry);
|
|
@@ -931,8 +828,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
931
828
|
|
|
932
829
|
return {
|
|
933
830
|
content: [
|
|
934
|
-
{ type: "text", text: combinedOutput }
|
|
935
|
-
...extraBlocks.filter(Boolean)
|
|
831
|
+
{ type: "text", text: combinedOutput }
|
|
936
832
|
]
|
|
937
833
|
};
|
|
938
834
|
} catch (xlsxError) {
|
|
@@ -970,9 +866,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
970
866
|
const targetProfile = profile.trim();
|
|
971
867
|
const isAdvanced = targetProfile.toLowerCase() !== "general";
|
|
972
868
|
const license = checkLicenseStatus();
|
|
973
|
-
|
|
974
869
|
let finalProfile = targetProfile;
|
|
975
|
-
const extraBlocks = [];
|
|
976
870
|
|
|
977
871
|
const limitStatus = checkFreeTierLimit(license.isPro);
|
|
978
872
|
if (limitStatus.blocked) {
|
|
@@ -984,23 +878,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
984
878
|
|
|
985
879
|
const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
|
|
986
880
|
if (isAdvanced && !license.isPro) {
|
|
987
|
-
mcpLog(`${colors.yellowBold}⚠️ [PrivacyScrubber] Profile '${targetProfile}' active on Free Tier (5,000 char limit).${colors.reset}\n
|
|
988
|
-
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`);
|
|
989
882
|
}
|
|
990
883
|
|
|
991
|
-
const { processedText: processedContent2
|
|
992
|
-
if (wasTruncated2) extraBlocks.push(buildUpsellBlock(`File content was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
|
|
993
|
-
|
|
994
|
-
if (!license.isPro) {
|
|
995
|
-
const detected = detectSecrets(processedContent2);
|
|
996
|
-
if (detected.length > 0) {
|
|
997
|
-
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`);
|
|
998
|
-
return {
|
|
999
|
-
isError: true,
|
|
1000
|
-
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` }]
|
|
1001
|
-
};
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
884
|
+
const { processedText: processedContent2 } = truncateIfFree(content, license.isPro, charLimit);
|
|
1004
885
|
|
|
1005
886
|
const { scrubbedText, newTokens } = performSanitization(processedContent2, finalProfile, sessionIgnoreList);
|
|
1006
887
|
const telemetry = buildCisoAuditTelemetry(newTokens);
|
|
@@ -1008,8 +889,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1008
889
|
|
|
1009
890
|
return {
|
|
1010
891
|
content: [
|
|
1011
|
-
{ type: "text", text: scrubbedText + receiptMd }
|
|
1012
|
-
...extraBlocks.filter(Boolean)
|
|
892
|
+
{ type: "text", text: scrubbedText + receiptMd }
|
|
1013
893
|
]
|
|
1014
894
|
};
|
|
1015
895
|
}
|
|
@@ -1159,8 +1039,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1159
1039
|
const tier = license.isPro ? 'PRO' : 'FREE';
|
|
1160
1040
|
const tierIcon = license.isPro ? '✅' : '🔓';
|
|
1161
1041
|
const profileList = license.isPro
|
|
1162
|
-
? 'All
|
|
1163
|
-
: 'General only — PRO unlocks
|
|
1042
|
+
? 'All 25 profiles active (General, Dev, Medical, Legal, Finance, HR…)'
|
|
1043
|
+
: 'General only — PRO unlocks 25 industry profiles';
|
|
1164
1044
|
const sizeLimit = license.isPro ? 'Unlimited' : '15,000 characters per request';
|
|
1165
1045
|
|
|
1166
1046
|
const configPath = resolveConfigPath();
|
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() {
|