@privacyscrubber/mcp-server 1.7.1 → 1.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/config-schema.json +10 -0
- package/index.js +293 -176
- package/install.js +63 -0
- package/package.json +9 -3
- package/pii-masking-run.js +2 -2
- package/ps-license-manager.js +263 -0
- package/ps-pii-engine.cjs +823 -0
- package/ps-pii-engine.js +823 -0
- package/scrubber-core.cjs +1419 -11
package/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// LicenseManager will be required below
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* PrivacyScrubber MCP Server
|
|
@@ -36,6 +37,10 @@ const MCP_VERSION = require('./package.json').version;
|
|
|
36
37
|
// Import the production core engine with 100% parity
|
|
37
38
|
const scrubberCorePath = path.resolve(__dirname, './scrubber-core.cjs');
|
|
38
39
|
const PrivacyScrubberCore = require(scrubberCorePath);
|
|
40
|
+
let LicenseManager = require('./ps-license-manager.js');
|
|
41
|
+
if (!LicenseManager || typeof LicenseManager.validate !== 'function') {
|
|
42
|
+
LicenseManager = global.LicenseManager;
|
|
43
|
+
}
|
|
39
44
|
|
|
40
45
|
// Initialize core engine
|
|
41
46
|
PrivacyScrubberCore.init();
|
|
@@ -74,83 +79,160 @@ const DEVOPS_SECRETS_DETECTOR = [
|
|
|
74
79
|
{ 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 }
|
|
75
80
|
];
|
|
76
81
|
|
|
77
|
-
// Hardcoded public key for offline cryptographic verification
|
|
78
|
-
const PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
|
79
|
-
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqN700s8jMq9Rf09prTPB
|
|
80
|
-
ekrFTXi+mnzSgMzwy+rMrJ4kyZQRDTxSFO1d9r5JQIhHfK5ToPiiS/Z/AS+Ga5bb
|
|
81
|
-
BCy50SORDRn1HzrvFjNP+kogYyOeFk2lWw0Lt4/mX3M98zRE2D1HBDQJF8Y5Y/6l
|
|
82
|
-
735iwlF6T1oO0mEbb1FM5tKvO44OkTih7krc3w56R07S1WuYIhGLR6IfDbNU/drL
|
|
83
|
-
nbhUR4HqBRepIsn9mcSODw/FP5t3QlFLfDvZo/1SIUSBa9NowhPtPk0Hq3hndSvR
|
|
84
|
-
pXkyvjeA7HLyXU8hhvausn85QRdEYTMXP9BehDjhli4VELSAY3ARGzemcUgggejg
|
|
85
|
-
CwIDAQAB
|
|
86
|
-
-----END PUBLIC KEY-----`;
|
|
87
|
-
|
|
88
82
|
function checkLicenseStatus() {
|
|
89
83
|
const key = (process.env.PRIVACYSCRUBBER_KEY || "").trim();
|
|
90
84
|
if (!key) return { isPro: false, type: null, error: "No license key provided." };
|
|
91
85
|
|
|
86
|
+
const result = LicenseManager.validate(key);
|
|
87
|
+
|
|
88
|
+
if (!result.valid) {
|
|
89
|
+
mcpLog(`DEBUG: License invalid because: ${result.reason || "Unknown"}`);
|
|
90
|
+
return { isPro: false, type: null, error: result.reason || "Invalid license format or signature." };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return { isPro: true, type: result.tier, error: null };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── Free Tier Usage Tracking (Persistent) ─────────────────────────────
|
|
97
|
+
const FREE_TIER_DAILY_LIMIT = 10;
|
|
98
|
+
const STAR_PROMPT_THRESHOLD = 5;
|
|
99
|
+
|
|
100
|
+
function getUsageFilePath() {
|
|
101
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
102
|
+
return path.resolve(homeDir, '.privacyscrubber-usage.json');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getDailyUsage() {
|
|
106
|
+
const file = getUsageFilePath();
|
|
107
|
+
const today = new Date().toISOString().split('T')[0];
|
|
108
|
+
if (!fs.existsSync(file)) return 0;
|
|
92
109
|
try {
|
|
93
|
-
const
|
|
94
|
-
if (
|
|
95
|
-
return
|
|
110
|
+
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
111
|
+
if (data.date === today) {
|
|
112
|
+
return data.count || 0;
|
|
96
113
|
}
|
|
114
|
+
} catch (e) {}
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
97
117
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
118
|
+
function incrementDailyUsage() {
|
|
119
|
+
const file = getUsageFilePath();
|
|
120
|
+
const today = new Date().toISOString().split('T')[0];
|
|
121
|
+
let count = 0;
|
|
122
|
+
if (fs.existsSync(file)) {
|
|
123
|
+
try {
|
|
124
|
+
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
125
|
+
if (data.date === today) count = data.count || 0;
|
|
126
|
+
} catch (e) {}
|
|
127
|
+
}
|
|
128
|
+
count++;
|
|
129
|
+
try {
|
|
130
|
+
fs.writeFileSync(file, JSON.stringify({ date: today, count }), 'utf8');
|
|
131
|
+
} catch (e) {}
|
|
132
|
+
return count;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function checkFreeTierLimit(isPro) {
|
|
136
|
+
if (isPro) return { blocked: false, count: 0 };
|
|
137
|
+
|
|
138
|
+
const count = incrementDailyUsage();
|
|
139
|
+
// Feedback / Growth loop triggers
|
|
140
|
+
if (count === 3) {
|
|
141
|
+
mcpLog(`${colors.cyan}💬 We are building the ultimate privacy tool for developers. What feature should we add next? Let us know: https://privacyscrubber.com/feedback${colors.reset}\n`);
|
|
142
|
+
}
|
|
143
|
+
if (count === STAR_PROMPT_THRESHOLD) {
|
|
144
|
+
mcpLog(`${colors.yellowBold}🔒 PrivacyScrubber is running securely. If this tool saved your PII today, please drop a star on GitHub: https://github.com/moxno/privacyscrubber-mcp${colors.reset}\n`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (count >= FREE_TIER_DAILY_LIMIT) {
|
|
148
|
+
mcpLog(`${colors.redBold}🚫 [PrivacyScrubber] Free tier daily limit exhausted (${FREE_TIER_DAILY_LIMIT} requests). Request BLOCKED.${colors.reset}\n${colors.cyan}👉 Get a PRO key for unlimited use: https://privacyscrubber.com/pricing${colors.reset}\n`);
|
|
149
|
+
return { blocked: true, count };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { blocked: false, count };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function buildCisoAuditTelemetry(currentTokenMap = {}) {
|
|
156
|
+
const entities = {};
|
|
157
|
+
let totalCount = 0;
|
|
158
|
+
|
|
159
|
+
if (currentTokenMap && typeof currentTokenMap === 'object') {
|
|
160
|
+
const keys = Array.isArray(currentTokenMap) ? currentTokenMap : Object.keys(currentTokenMap);
|
|
161
|
+
for (const t of keys) {
|
|
162
|
+
const tokenStr = typeof t === 'string' ? t : (t.token || t.mask || '');
|
|
163
|
+
const match = tokenStr.match(/\[([A-Z_]+)_\d+\]/);
|
|
164
|
+
const baseType = match ? match[1] : (tokenStr.replace(/\[|\]/g, '').replace(/_[0-9]+$/, '') || 'CUSTOM');
|
|
165
|
+
entities[baseType] = (entities[baseType] || 0) + 1;
|
|
166
|
+
totalCount++;
|
|
167
|
+
}
|
|
104
168
|
}
|
|
105
169
|
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
170
|
+
const types = Object.keys(entities);
|
|
171
|
+
let riskLevel = 'LOW EXPOSURE';
|
|
172
|
+
|
|
173
|
+
if (totalCount > 0) {
|
|
174
|
+
const hasHighRiskEntities = types.some(t => ['ID', 'SSN', 'CREDIT_CARD', 'PASSPORT', 'BANK', 'API_KEY', 'SECRET', 'PASSWORD', 'MRN', 'KEY'].includes(t));
|
|
175
|
+
if (hasHighRiskEntities || types.length >= 3 || totalCount >= 10) {
|
|
176
|
+
riskLevel = 'CRITICAL (HIGH EXPOSURE)';
|
|
177
|
+
} else if (types.length >= 2 || totalCount >= 3) {
|
|
178
|
+
riskLevel = 'MODERATE EXPOSURE';
|
|
179
|
+
} else {
|
|
180
|
+
riskLevel = 'LOW EXPOSURE';
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
riskLevel = 'CLEAN (ZERO PII)';
|
|
115
184
|
}
|
|
116
185
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
186
|
+
const frameworksSet = new Set(['ZTDS Standard']);
|
|
187
|
+
if (types.includes('NAME') || types.includes('EMAIL') || types.includes('PHONE')) {
|
|
188
|
+
frameworksSet.add('GDPR (Art. 4)');
|
|
189
|
+
frameworksSet.add('CCPA/CPRA');
|
|
190
|
+
}
|
|
191
|
+
if (types.includes('ID') || types.includes('SSN') || types.includes('PASSPORT')) {
|
|
192
|
+
frameworksSet.add('SOC 2 Type II');
|
|
193
|
+
frameworksSet.add('ISO 27001 (A.8.11)');
|
|
194
|
+
}
|
|
195
|
+
if (types.some(t => ['CREDIT_CARD', 'BANK', 'IBAN', 'FINANCIAL', 'CARD'].includes(t))) {
|
|
196
|
+
frameworksSet.add('PCI DSS v4.0');
|
|
197
|
+
}
|
|
198
|
+
if (types.some(t => ['MRN', 'HEALTH', 'MEDICAL', 'PATIENT'].includes(t))) {
|
|
199
|
+
frameworksSet.add('HIPAA §164.514');
|
|
200
|
+
}
|
|
201
|
+
if (types.some(t => ['API_KEY', 'SECRET', 'PASSWORD', 'TOKEN', 'KEY'].includes(t))) {
|
|
202
|
+
frameworksSet.add('NIST SP 800-53');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
totalCount,
|
|
207
|
+
entities,
|
|
208
|
+
types,
|
|
209
|
+
riskLevel,
|
|
210
|
+
frameworksList: Array.from(frameworksSet)
|
|
211
|
+
};
|
|
121
212
|
}
|
|
122
213
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
214
|
+
function formatAuditReceipt(telemetry) {
|
|
215
|
+
if (telemetry.totalCount === 0) {
|
|
216
|
+
return "\\n\\n> 🛡️ **PrivacyScrubber Audit Receipt**: CLEAN (ZERO PII DETECTED)\\n";
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const entitiesList = Object.entries(telemetry.entities)
|
|
220
|
+
.map(([type, count]) => `${count} ${type}`)
|
|
221
|
+
.join(', ');
|
|
222
|
+
|
|
223
|
+
const icon = telemetry.riskLevel.includes('CRITICAL') ? '🔴' : (telemetry.riskLevel.includes('MODERATE') ? '🟠' : '🟢');
|
|
126
224
|
|
|
127
|
-
|
|
225
|
+
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`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Build a clean upsell content block (Disabled to prevent agent-visible marketing content)
|
|
128
229
|
function buildUpsellBlock(reason) {
|
|
129
|
-
|
|
130
|
-
let lines = [
|
|
131
|
-
'─────────────────────────────────────────────',
|
|
132
|
-
'⚡ PrivacyScrubber PRO — Upgrade your AI setup',
|
|
133
|
-
'─────────────────────────────────────────────',
|
|
134
|
-
];
|
|
135
|
-
if (reason) lines.push(`ℹ️ ${reason}`);
|
|
136
|
-
lines.push(
|
|
137
|
-
`📊 Free session requests: ${count}`,
|
|
138
|
-
'🔒 PRO unlocks: 22 industry profiles (Dev, Medical, Legal…)',
|
|
139
|
-
' + Custom regex rules (privacyscrubber.json)',
|
|
140
|
-
' + Unlimited input size',
|
|
141
|
-
'',
|
|
142
|
-
'💳 PRO Lifetime: $110 → https://privacyscrubber.com/pricing',
|
|
143
|
-
'',
|
|
144
|
-
'After purchase, add your key to your MCP config:',
|
|
145
|
-
' "PRIVACYSCRUBBER_KEY": "<your-key-here>"',
|
|
146
|
-
'─────────────────────────────────────────────'
|
|
147
|
-
);
|
|
148
|
-
return { type: 'text', text: lines.join('\n') };
|
|
230
|
+
return null;
|
|
149
231
|
}
|
|
150
232
|
|
|
151
|
-
// Decide if we should attach a soft periodic nudge (
|
|
233
|
+
// Decide if we should attach a soft periodic nudge (Disabled to maintain clean MCP output)
|
|
152
234
|
function shouldNudge(isPro) {
|
|
153
|
-
return
|
|
235
|
+
return false;
|
|
154
236
|
}
|
|
155
237
|
|
|
156
238
|
// Create the MCP server
|
|
@@ -435,43 +517,45 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
435
517
|
let finalProfile = targetProfile;
|
|
436
518
|
const extraBlocks = [];
|
|
437
519
|
|
|
438
|
-
|
|
520
|
+
const limitStatus = checkFreeTierLimit(license.isPro);
|
|
521
|
+
if (limitStatus.blocked) {
|
|
522
|
+
return {
|
|
523
|
+
isError: true,
|
|
524
|
+
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` }]
|
|
525
|
+
};
|
|
526
|
+
}
|
|
439
527
|
|
|
528
|
+
const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
|
|
440
529
|
if (isAdvanced && !license.isPro) {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
mcpLog(`${colors.yellowBold}⚠️ [PrivacyScrubber] Profile '${targetProfile}' on Free tier is limited to 5,000 chars. Falling back to 'General'.${reason}${colors.reset}\n${colors.cyan}👉 Get a PRO key at: https://privacyscrubber.com/pricing${colors.reset}\n`);
|
|
444
|
-
finalProfile = "General";
|
|
445
|
-
extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' on Free tier is limited to 5,000 chars. Upgrade to PRO for unlimited scanning.`));
|
|
446
|
-
} else {
|
|
447
|
-
mcpLog(`${colors.cyan}ℹ️ [PrivacyScrubber] Profile '${targetProfile}' active (Free tier: up to 5,000 chars).${colors.reset}\n`);
|
|
448
|
-
}
|
|
530
|
+
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`);
|
|
531
|
+
extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' active (Free Tier limit: 5,000 chars). Upgrade to PRO for unlimited text.`));
|
|
449
532
|
}
|
|
450
533
|
|
|
451
|
-
const { processedText, wasTruncated } = truncateIfFree(text, license.isPro);
|
|
534
|
+
const { processedText, wasTruncated } = truncateIfFree(text, license.isPro, charLimit);
|
|
452
535
|
if (wasTruncated) {
|
|
453
|
-
extraBlocks.push(buildUpsellBlock(`Input was truncated to
|
|
536
|
+
extraBlocks.push(buildUpsellBlock(`Input was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
|
|
454
537
|
}
|
|
455
538
|
|
|
456
539
|
if (!license.isPro) {
|
|
457
540
|
const detected = detectSecrets(processedText);
|
|
458
541
|
if (detected.length > 0) {
|
|
459
|
-
mcpLog(`${colors.
|
|
460
|
-
|
|
542
|
+
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`);
|
|
543
|
+
return {
|
|
544
|
+
isError: true,
|
|
545
|
+
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` }]
|
|
546
|
+
};
|
|
461
547
|
}
|
|
462
548
|
}
|
|
463
549
|
|
|
464
|
-
const
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
extraBlocks.push(buildUpsellBlock(null));
|
|
469
|
-
}
|
|
550
|
+
const { scrubbedText, newTokens } = performSanitization(processedText, finalProfile);
|
|
551
|
+
|
|
552
|
+
const telemetry = buildCisoAuditTelemetry(newTokens);
|
|
553
|
+
const receiptMd = formatAuditReceipt(telemetry);
|
|
470
554
|
|
|
471
555
|
return {
|
|
472
556
|
content: [
|
|
473
|
-
{ type: "text", text:
|
|
474
|
-
...extraBlocks
|
|
557
|
+
{ type: "text", text: scrubbedText + receiptMd },
|
|
558
|
+
...extraBlocks.filter(Boolean)
|
|
475
559
|
]
|
|
476
560
|
};
|
|
477
561
|
}
|
|
@@ -584,36 +668,44 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
584
668
|
let finalProfile = targetProfile;
|
|
585
669
|
const extraBlocks = [];
|
|
586
670
|
|
|
587
|
-
|
|
671
|
+
const limitStatus = checkFreeTierLimit(license.isPro);
|
|
672
|
+
if (limitStatus.blocked) {
|
|
673
|
+
return {
|
|
674
|
+
isError: true,
|
|
675
|
+
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` }]
|
|
676
|
+
};
|
|
677
|
+
}
|
|
588
678
|
|
|
679
|
+
const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
|
|
589
680
|
if (isAdvanced && !license.isPro) {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
finalProfile = "General";
|
|
593
|
-
extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' requires PRO. Using 'General' as fallback.`));
|
|
681
|
+
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`);
|
|
682
|
+
extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' active (Free Tier limit: 5,000 chars). Upgrade to PRO for unlimited text.`));
|
|
594
683
|
}
|
|
595
684
|
|
|
596
|
-
const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro);
|
|
597
|
-
if (wasTruncated) extraBlocks.push(buildUpsellBlock(`File content was truncated to
|
|
685
|
+
const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro, charLimit);
|
|
686
|
+
if (wasTruncated) extraBlocks.push(buildUpsellBlock(`File content was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
|
|
598
687
|
|
|
599
688
|
if (!license.isPro) {
|
|
600
689
|
const detected = detectSecrets(processedContent);
|
|
601
690
|
if (detected.length > 0) {
|
|
602
|
-
mcpLog(`${colors.
|
|
603
|
-
|
|
691
|
+
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`);
|
|
692
|
+
return {
|
|
693
|
+
isError: true,
|
|
694
|
+
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` }]
|
|
695
|
+
};
|
|
604
696
|
}
|
|
605
697
|
}
|
|
606
698
|
|
|
607
699
|
const sanitized = performSanitization(processedContent, finalProfile);
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
}
|
|
700
|
+
const { scrubbedText, newTokens } = sanitized;
|
|
701
|
+
const telemetry = buildCisoAuditTelemetry(newTokens);
|
|
702
|
+
const receiptMarkdown = formatAuditReceipt(telemetry);
|
|
703
|
+
const combinedOutput = `${scrubbedText}\n\n${receiptMarkdown}`;
|
|
612
704
|
|
|
613
705
|
return {
|
|
614
706
|
content: [
|
|
615
|
-
{ type: "text", text:
|
|
616
|
-
...extraBlocks
|
|
707
|
+
{ type: "text", text: combinedOutput },
|
|
708
|
+
...extraBlocks.filter(Boolean)
|
|
617
709
|
]
|
|
618
710
|
};
|
|
619
711
|
} catch (docxError) {
|
|
@@ -637,7 +729,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
637
729
|
content: [
|
|
638
730
|
{
|
|
639
731
|
type: "text",
|
|
640
|
-
text: `Error: PDF and Excel file sanitization is a PRO feature. Set PRIVACYSCRUBBER_KEY to your PRO license key to unlock local document parsing. Get a key at: https://privacyscrubber.com/pricing`
|
|
732
|
+
text: `Error: PDF and Excel file sanitization is a PRO feature. Set PRIVACYSCRUBBER_KEY to your PRO license key to unlock local document parsing. Get a key at: https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal`
|
|
641
733
|
}
|
|
642
734
|
]
|
|
643
735
|
};
|
|
@@ -654,36 +746,44 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
654
746
|
let finalProfile = targetProfile;
|
|
655
747
|
const extraBlocks = [];
|
|
656
748
|
|
|
657
|
-
|
|
749
|
+
const limitStatus = checkFreeTierLimit(license.isPro);
|
|
750
|
+
if (limitStatus.blocked) {
|
|
751
|
+
return {
|
|
752
|
+
isError: true,
|
|
753
|
+
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` }]
|
|
754
|
+
};
|
|
755
|
+
}
|
|
658
756
|
|
|
757
|
+
const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
|
|
659
758
|
if (isAdvanced && !license.isPro) {
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
finalProfile = "General";
|
|
663
|
-
extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' requires PRO. Using 'General' as fallback.`));
|
|
759
|
+
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`);
|
|
760
|
+
extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' active (Free Tier limit: 5,000 chars). Upgrade to PRO for unlimited text.`));
|
|
664
761
|
}
|
|
665
762
|
|
|
666
|
-
const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro);
|
|
667
|
-
if (wasTruncated) extraBlocks.push(buildUpsellBlock(`File content was truncated to
|
|
763
|
+
const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro, charLimit);
|
|
764
|
+
if (wasTruncated) extraBlocks.push(buildUpsellBlock(`File content was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
|
|
668
765
|
|
|
669
766
|
if (!license.isPro) {
|
|
670
767
|
const detected = detectSecrets(processedContent);
|
|
671
768
|
if (detected.length > 0) {
|
|
672
|
-
mcpLog(`${colors.
|
|
673
|
-
|
|
769
|
+
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`);
|
|
770
|
+
return {
|
|
771
|
+
isError: true,
|
|
772
|
+
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` }]
|
|
773
|
+
};
|
|
674
774
|
}
|
|
675
775
|
}
|
|
676
776
|
|
|
677
777
|
const sanitized = performSanitization(processedContent, finalProfile);
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
}
|
|
778
|
+
const { scrubbedText, newTokens } = sanitized;
|
|
779
|
+
const telemetry = buildCisoAuditTelemetry(newTokens);
|
|
780
|
+
const receiptMarkdown = formatAuditReceipt(telemetry);
|
|
781
|
+
const combinedOutput = `${scrubbedText}\n\n${receiptMarkdown}`;
|
|
682
782
|
|
|
683
783
|
return {
|
|
684
784
|
content: [
|
|
685
|
-
{ type: "text", text:
|
|
686
|
-
...extraBlocks
|
|
785
|
+
{ type: "text", text: combinedOutput },
|
|
786
|
+
...extraBlocks.filter(Boolean)
|
|
687
787
|
]
|
|
688
788
|
};
|
|
689
789
|
} catch (pdfError) {
|
|
@@ -711,36 +811,44 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
711
811
|
let finalProfile = targetProfile;
|
|
712
812
|
const extraBlocks = [];
|
|
713
813
|
|
|
714
|
-
|
|
814
|
+
const limitStatus = checkFreeTierLimit(license.isPro);
|
|
815
|
+
if (limitStatus.blocked) {
|
|
816
|
+
return {
|
|
817
|
+
isError: true,
|
|
818
|
+
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` }]
|
|
819
|
+
};
|
|
820
|
+
}
|
|
715
821
|
|
|
822
|
+
const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
|
|
716
823
|
if (isAdvanced && !license.isPro) {
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
finalProfile = "General";
|
|
720
|
-
extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' requires PRO. Using 'General' as fallback.`));
|
|
824
|
+
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`);
|
|
825
|
+
extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' active (Free Tier limit: 5,000 chars). Upgrade to PRO for unlimited text.`));
|
|
721
826
|
}
|
|
722
827
|
|
|
723
|
-
const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro);
|
|
724
|
-
if (wasTruncated) extraBlocks.push(buildUpsellBlock(`File content was truncated to
|
|
828
|
+
const { processedText: processedContent, wasTruncated } = truncateIfFree(content, license.isPro, charLimit);
|
|
829
|
+
if (wasTruncated) extraBlocks.push(buildUpsellBlock(`File content was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
|
|
725
830
|
|
|
726
831
|
if (!license.isPro) {
|
|
727
832
|
const detected = detectSecrets(processedContent);
|
|
728
833
|
if (detected.length > 0) {
|
|
729
|
-
mcpLog(`${colors.
|
|
730
|
-
|
|
834
|
+
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`);
|
|
835
|
+
return {
|
|
836
|
+
isError: true,
|
|
837
|
+
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` }]
|
|
838
|
+
};
|
|
731
839
|
}
|
|
732
840
|
}
|
|
733
841
|
|
|
734
842
|
const sanitized = performSanitization(processedContent, finalProfile);
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
}
|
|
843
|
+
const { scrubbedText, newTokens } = sanitized;
|
|
844
|
+
const telemetry = buildCisoAuditTelemetry(newTokens);
|
|
845
|
+
const receiptMarkdown = formatAuditReceipt(telemetry);
|
|
846
|
+
const combinedOutput = `${scrubbedText}\n\n${receiptMarkdown}`;
|
|
739
847
|
|
|
740
848
|
return {
|
|
741
849
|
content: [
|
|
742
|
-
{ type: "text", text:
|
|
743
|
-
...extraBlocks
|
|
850
|
+
{ type: "text", text: combinedOutput },
|
|
851
|
+
...extraBlocks.filter(Boolean)
|
|
744
852
|
]
|
|
745
853
|
};
|
|
746
854
|
} catch (xlsxError) {
|
|
@@ -782,36 +890,42 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
782
890
|
let finalProfile = targetProfile;
|
|
783
891
|
const extraBlocks = [];
|
|
784
892
|
|
|
785
|
-
|
|
893
|
+
const limitStatus = checkFreeTierLimit(license.isPro);
|
|
894
|
+
if (limitStatus.blocked) {
|
|
895
|
+
return {
|
|
896
|
+
isError: true,
|
|
897
|
+
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` }]
|
|
898
|
+
};
|
|
899
|
+
}
|
|
786
900
|
|
|
901
|
+
const charLimit = (isAdvanced && !license.isPro) ? 5000 : 15000;
|
|
787
902
|
if (isAdvanced && !license.isPro) {
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
finalProfile = "General";
|
|
791
|
-
extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' requires PRO. Using 'General' as fallback.`));
|
|
903
|
+
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`);
|
|
904
|
+
extraBlocks.push(buildUpsellBlock(`Profile '${targetProfile}' active (Free Tier limit: 5,000 chars). Upgrade to PRO for unlimited text.`));
|
|
792
905
|
}
|
|
793
906
|
|
|
794
|
-
const { processedText: processedContent2, wasTruncated: wasTruncated2 } = truncateIfFree(content, license.isPro);
|
|
795
|
-
if (wasTruncated2) extraBlocks.push(buildUpsellBlock(`File content was truncated to
|
|
907
|
+
const { processedText: processedContent2, wasTruncated: wasTruncated2 } = truncateIfFree(content, license.isPro, charLimit);
|
|
908
|
+
if (wasTruncated2) extraBlocks.push(buildUpsellBlock(`File content was truncated to ${charLimit.toLocaleString()} characters (Free Tier limit).`));
|
|
796
909
|
|
|
797
910
|
if (!license.isPro) {
|
|
798
911
|
const detected = detectSecrets(processedContent2);
|
|
799
912
|
if (detected.length > 0) {
|
|
800
|
-
mcpLog(`${colors.
|
|
801
|
-
|
|
913
|
+
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`);
|
|
914
|
+
return {
|
|
915
|
+
isError: true,
|
|
916
|
+
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` }]
|
|
917
|
+
};
|
|
802
918
|
}
|
|
803
919
|
}
|
|
804
920
|
|
|
805
|
-
const
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
extraBlocks.push(buildUpsellBlock(null));
|
|
809
|
-
}
|
|
921
|
+
const { scrubbedText, newTokens } = performSanitization(processedContent2, finalProfile);
|
|
922
|
+
const telemetry = buildCisoAuditTelemetry(newTokens);
|
|
923
|
+
const receiptMd = formatAuditReceipt(telemetry);
|
|
810
924
|
|
|
811
925
|
return {
|
|
812
926
|
content: [
|
|
813
|
-
{ type: "text", text:
|
|
814
|
-
...extraBlocks
|
|
927
|
+
{ type: "text", text: scrubbedText + receiptMd },
|
|
928
|
+
...extraBlocks.filter(Boolean)
|
|
815
929
|
]
|
|
816
930
|
};
|
|
817
931
|
}
|
|
@@ -837,6 +951,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
837
951
|
const customRules = loadCustomRules();
|
|
838
952
|
const normalizedProfile = (profile || "general").trim().toLowerCase();
|
|
839
953
|
const license = checkLicenseStatus();
|
|
954
|
+
|
|
955
|
+
const limitStatus = checkFreeTierLimit(license.isPro);
|
|
956
|
+
if (limitStatus.blocked) {
|
|
957
|
+
return {
|
|
958
|
+
isError: true,
|
|
959
|
+
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` }]
|
|
960
|
+
};
|
|
961
|
+
}
|
|
840
962
|
const localSessionMap = {};
|
|
841
963
|
|
|
842
964
|
const result = PrivacyScrubberCore.scrubText(content, customRules, {}, normalizedProfile, localSessionMap, license.isPro);
|
|
@@ -873,7 +995,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
873
995
|
}
|
|
874
996
|
|
|
875
997
|
if (isSecretLeak) {
|
|
876
|
-
return { isError: true, content: [{ type: "text", text: `Error: API Key or Secret detected. Sanitization of raw secrets requires PRO Profile. Upgrade at https://privacyscrubber.com/pricing` }] };
|
|
998
|
+
return { isError: true, content: [{ type: "text", text: `Error: API Key or Secret detected. Sanitization of raw secrets requires PRO Profile. Upgrade at https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal` }] };
|
|
877
999
|
}
|
|
878
1000
|
|
|
879
1001
|
if (!no_backup) {
|
|
@@ -906,7 +1028,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
906
1028
|
}
|
|
907
1029
|
|
|
908
1030
|
const defaultTemplate = {
|
|
909
|
-
"_comment": "PrivacyScrubber local configuration. Add custom rules and restart your MCP client (Cursor/Windsurf). Details: https://privacyscrubber.com/docs/config",
|
|
1031
|
+
"_comment": "PrivacyScrubber local configuration. Add custom rules and restart your MCP client (Cursor/Windsurf). Details: https://privacyscrubber.com/docs/config?utm_source=mcp_cli&utm_medium=terminal",
|
|
910
1032
|
"customRules": [
|
|
911
1033
|
{
|
|
912
1034
|
"pattern": "my-secret-pattern-\\d+",
|
|
@@ -955,7 +1077,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
955
1077
|
const profileList = license.isPro
|
|
956
1078
|
? 'All 23 profiles active (General, Dev, Medical, Legal, Finance, HR…)'
|
|
957
1079
|
: 'General only — PRO unlocks 22 industry profiles';
|
|
958
|
-
const sizeLimit = license.isPro ? 'Unlimited' : '
|
|
1080
|
+
const sizeLimit = license.isPro ? 'Unlimited' : '15,000 characters per request';
|
|
959
1081
|
|
|
960
1082
|
const configPath = resolveConfigPath();
|
|
961
1083
|
let rulesStatus = '';
|
|
@@ -987,7 +1109,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
987
1109
|
`║ PrivacyScrubber MCP Server v${MCP_VERSION.padEnd(10)} ║`,
|
|
988
1110
|
'╠══════════════════════════════════════════════════╣',
|
|
989
1111
|
`║ ${tierIcon} Tier: ${tier.padEnd(43)}║`,
|
|
990
|
-
`║ 📊 Session requests: ${String(
|
|
1112
|
+
`║ 📊 Session requests: ${String(getDailyUsage()).padEnd(27)}║`,
|
|
991
1113
|
`║ 📁 Input size limit: ${sizeLimit.padEnd(27)}║`,
|
|
992
1114
|
'╠══════════════════════════════════════════════════╣',
|
|
993
1115
|
`║ 🏷️ Profiles: ${profileList.substring(0,35).padEnd(35)}║`,
|
|
@@ -1001,12 +1123,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1001
1123
|
lines.push(
|
|
1002
1124
|
'║ ✅ PRO is active. All features unlocked. ║',
|
|
1003
1125
|
'║ To regenerate your key or manage billing: ║',
|
|
1004
|
-
'║ https://privacyscrubber.com/pricing ║'
|
|
1126
|
+
'║ https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal ║'
|
|
1005
1127
|
);
|
|
1006
1128
|
} else {
|
|
1007
1129
|
lines.push(
|
|
1008
1130
|
'║ 💳 Upgrade to PRO — $110 Lifetime ║',
|
|
1009
|
-
'║ https://privacyscrubber.com/pricing ║',
|
|
1131
|
+
'║ https://privacyscrubber.com/pricing?utm_source=mcp_cli&utm_medium=terminal ║',
|
|
1010
1132
|
'╠══════════════════════════════════════════════════╣',
|
|
1011
1133
|
'║ After purchase, add your key to MCP config: ║',
|
|
1012
1134
|
'║ ║',
|
|
@@ -1014,7 +1136,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1014
1136
|
'║ "PRIVACYSCRUBBER_KEY": "<your-key-here>" ║',
|
|
1015
1137
|
'║ ║',
|
|
1016
1138
|
'║ Full setup guide: ║',
|
|
1017
|
-
'║ https://privacyscrubber.com/features/mcp
|
|
1139
|
+
'║ https://privacyscrubber.com/features/mcp/?utm_source=mcp_cli&utm_medium=terminal ║'
|
|
1018
1140
|
);
|
|
1019
1141
|
}
|
|
1020
1142
|
|
|
@@ -1057,20 +1179,30 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1057
1179
|
const customRules = loadCustomRules();
|
|
1058
1180
|
const normalizedProfile = (profile || "general").trim().toLowerCase();
|
|
1059
1181
|
const license = checkLicenseStatus();
|
|
1182
|
+
|
|
1183
|
+
const limitStatus = checkFreeTierLimit(license.isPro);
|
|
1184
|
+
if (limitStatus.blocked) {
|
|
1185
|
+
return {
|
|
1186
|
+
isError: true,
|
|
1187
|
+
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` }]
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1060
1190
|
|
|
1191
|
+
let totalTokensForDirectory = {};
|
|
1061
1192
|
for (const file of filesToScan) {
|
|
1062
1193
|
try {
|
|
1063
1194
|
const stat = await fs.promises.stat(file);
|
|
1064
1195
|
if (stat.size > 10 * 1024 * 1024) continue; // skip >10MB
|
|
1065
1196
|
|
|
1066
1197
|
const buffer = fs.readFileSync(file);
|
|
1067
|
-
// basic binary check
|
|
1068
1198
|
if (buffer.indexOf(0) !== -1) continue;
|
|
1069
1199
|
const content = buffer.toString('utf8');
|
|
1070
1200
|
|
|
1071
1201
|
const localSessionMap = {};
|
|
1072
1202
|
const result = PrivacyScrubberCore.scrubText(content, customRules, {}, normalizedProfile, localSessionMap, license.isPro);
|
|
1073
1203
|
|
|
1204
|
+
Object.assign(totalTokensForDirectory, localSessionMap);
|
|
1205
|
+
|
|
1074
1206
|
let isSecretLeak = false;
|
|
1075
1207
|
if (!license.isPro && result.tokenMap) {
|
|
1076
1208
|
Object.entries(result.tokenMap).forEach(([token, original]) => {
|
|
@@ -1106,8 +1238,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1106
1238
|
reportLines.push("✅ Clean. No PII or secrets detected in the scanned files.");
|
|
1107
1239
|
}
|
|
1108
1240
|
|
|
1241
|
+
const telemetry = buildCisoAuditTelemetry(totalTokensForDirectory);
|
|
1242
|
+
const receiptMd = formatAuditReceipt(telemetry);
|
|
1243
|
+
|
|
1109
1244
|
return {
|
|
1110
|
-
content: [{ type: "text", text: reportLines.join('\n') }]
|
|
1245
|
+
content: [{ type: "text", text: reportLines.join('\n') + receiptMd }]
|
|
1111
1246
|
};
|
|
1112
1247
|
}
|
|
1113
1248
|
|
|
@@ -1209,41 +1344,23 @@ function performSanitization(text, profile) {
|
|
|
1209
1344
|
const normalizedProfile = (profile || "general").trim().toLowerCase();
|
|
1210
1345
|
const license = checkLicenseStatus();
|
|
1211
1346
|
const result = PrivacyScrubberCore.scrubText(text, customRules, {}, normalizedProfile, sessionMap, license.isPro);
|
|
1212
|
-
if (!license.isPro && result.tokenMap) {
|
|
1213
|
-
// Re-scan each masked token's original value against the shared detector.
|
|
1214
|
-
// If it was a raw secret, restore it in the output and remove from sessionMap
|
|
1215
|
-
// so it is never accidentally revealed by reveal_text.
|
|
1216
|
-
Object.entries(result.tokenMap).forEach(([token, original]) => {
|
|
1217
|
-
let isSecret = false;
|
|
1218
|
-
for (const detector of DEVOPS_SECRETS_DETECTOR) {
|
|
1219
|
-
detector.regex.lastIndex = 0;
|
|
1220
|
-
if (detector.regex.test(original)) {
|
|
1221
|
-
isSecret = true;
|
|
1222
|
-
break;
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
if (isSecret) {
|
|
1226
|
-
result.scrubbedText = result.scrubbedText.replace(token, original);
|
|
1227
|
-
delete result.tokenMap[token];
|
|
1228
|
-
}
|
|
1229
|
-
});
|
|
1230
|
-
}
|
|
1231
1347
|
|
|
1348
|
+
const newTokens = {};
|
|
1232
1349
|
// Update our volatile map with new matches
|
|
1233
1350
|
if (result.tokenMap) {
|
|
1234
1351
|
Object.entries(result.tokenMap).forEach(([token, original]) => {
|
|
1235
1352
|
sessionMap[token] = original;
|
|
1353
|
+
newTokens[token] = original;
|
|
1236
1354
|
});
|
|
1237
1355
|
}
|
|
1238
1356
|
|
|
1239
|
-
return result.scrubbedText;
|
|
1357
|
+
return { scrubbedText: result.scrubbedText, newTokens };
|
|
1240
1358
|
}
|
|
1241
1359
|
|
|
1242
|
-
function truncateIfFree(text, isPro) {
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
return { processedText: text.substring(0, MAX_FREE_CHARS), wasTruncated: true };
|
|
1360
|
+
function truncateIfFree(text, isPro, charLimit = 15000) {
|
|
1361
|
+
if (!isPro && text.length > charLimit) {
|
|
1362
|
+
mcpLog(`${colors.yellowBold}⚠️ [PrivacyScrubber] Input truncated to ${charLimit.toLocaleString()} characters (Free Tier Limit).${colors.reset}\n${colors.cyan}👉 Set PRIVACYSCRUBBER_KEY to your PRO license key for unlimited size.${colors.reset}\n`);
|
|
1363
|
+
return { processedText: text.substring(0, charLimit), wasTruncated: true };
|
|
1247
1364
|
}
|
|
1248
1365
|
return { processedText: text, wasTruncated: false };
|
|
1249
1366
|
}
|