@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/install.js ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import os from 'os';
6
+
7
+ console.log('šŸ›”ļø PrivacyScrubber MCP Server 1-Click Installer');
8
+ console.log('================================================\n');
9
+
10
+ const mcpConfig = {
11
+ command: "npx",
12
+ args: ["-y", "@privacyscrubber/mcp-server"]
13
+ };
14
+
15
+ let claudeConfigPath = '';
16
+ if (process.platform === 'darwin') {
17
+ claudeConfigPath = path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
18
+ } else if (process.platform === 'win32') {
19
+ claudeConfigPath = path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'Claude', 'claude_desktop_config.json');
20
+ } else {
21
+ // Linux fallback, though Claude Desktop is not officially supported on Linux yet, we check common paths
22
+ claudeConfigPath = path.join(os.homedir(), '.config', 'Claude', 'claude_desktop_config.json');
23
+ }
24
+
25
+ // 1. Configure Claude Desktop
26
+ let installedClaude = false;
27
+ try {
28
+ let configDir = path.dirname(claudeConfigPath);
29
+ if (!fs.existsSync(configDir)) {
30
+ fs.mkdirSync(configDir, { recursive: true });
31
+ }
32
+
33
+ let config = {};
34
+ if (fs.existsSync(claudeConfigPath)) {
35
+ const rawData = fs.readFileSync(claudeConfigPath, 'utf8');
36
+ if (rawData.trim() !== '') {
37
+ config = JSON.parse(rawData);
38
+ }
39
+ }
40
+
41
+ if (!config.mcpServers) {
42
+ config.mcpServers = {};
43
+ }
44
+
45
+ config.mcpServers['privacyscrubber'] = mcpConfig;
46
+
47
+ fs.writeFileSync(claudeConfigPath, JSON.stringify(config, null, 2), 'utf8');
48
+ console.log(`āœ… [Claude Desktop] Successfully injected PrivacyScrubber MCP into:`);
49
+ console.log(` ${claudeConfigPath}`);
50
+ installedClaude = true;
51
+ } catch (e) {
52
+ console.log(`āš ļø [Claude Desktop] Could not configure automatically: ${e.message}`);
53
+ }
54
+
55
+ console.log('\n---');
56
+ console.log('\nšŸ”µ [Cursor / Windsurf Users]');
57
+ console.log('To install PrivacyScrubber in Cursor or Windsurf:');
58
+ console.log('1. Open Settings -> Features -> MCP Servers (or type "MCP" in settings).');
59
+ console.log('2. Click "+ Add New MCP Server".');
60
+ console.log('3. Set Name: privacyscrubber');
61
+ console.log('4. Set Type: command');
62
+ console.log('5. Set Command: npx -y @privacyscrubber/mcp-server');
63
+ console.log('\nšŸš€ Installation complete! Restart your IDE or Claude Desktop to start redacting PII locally.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@privacyscrubber/mcp-server",
3
- "version": "1.7.1",
3
+ "version": "1.7.6",
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",
@@ -10,8 +10,13 @@
10
10
  },
11
11
  "files": [
12
12
  "index.js",
13
+ "install.js",
13
14
  "pii-masking-run.js",
14
15
  "scrubber-core.cjs",
16
+ "ps-license-manager.js",
17
+ "ps-pii-engine.cjs",
18
+ "ps-pii-engine.js",
19
+ "config-schema.json",
15
20
  "smithery.yaml",
16
21
  "server.json",
17
22
  "manifest.json",
@@ -21,7 +26,8 @@
21
26
  "bin": {
22
27
  "mcp-server": "index.js",
23
28
  "privacyscrubber-mcp": "index.js",
24
- "pii-masking-run": "pii-masking-run.js"
29
+ "pii-masking-run": "pii-masking-run.js",
30
+ "privacyscrubber-mcp-install": "install.js"
25
31
  },
26
32
  "scripts": {
27
33
  "start": "node index.js"
@@ -57,7 +63,7 @@
57
63
  ],
58
64
  "author": "Ilya Sibiryakov (BrandMeWeb)",
59
65
  "license": "MIT",
60
- "homepage": "https://privacyscrubber.com/pii-mcp/",
66
+ "homepage": "https://privacyscrubber.com/pii-mcp/?utm_source=npm&utm_medium=readme&utm_campaign=mcp_server",
61
67
  "repository": {
62
68
  "type": "git",
63
69
  "url": "git+https://github.com/moxno/privacyscrubber-mcp.git"
@@ -190,12 +190,12 @@ function performSanitization(text) {
190
190
  }
191
191
  if (isSecret) {
192
192
  hasSecrets = true;
193
- result.scrubbedText = result.scrubbedText.replace(token, original);
193
+ result.scrubbedText = result.scrubbedText.replace(token, '[SECRET_BLOCKED_UPGRADE_TO_PRO]');
194
194
  }
195
195
  });
196
196
 
197
197
  if (hasSecrets) {
198
- process.stderr.write(`${colors.yellowBold}āš ļø [PrivacyScrubber] Secrets/Keys detected in output. Redaction skipped (Requires PRO tier).${colors.reset}\n`);
198
+ process.stderr.write(`\x1b[1;31m🚫 [PrivacyScrubber] Secrets/Keys detected in output. Replaced with [SECRET_BLOCKED_UPGRADE_TO_PRO] to protect your data (Requires PRO tier).\x1b[0m\n`);
199
199
  }
200
200
  }
201
201
 
@@ -0,0 +1,263 @@
1
+ (function(global) {
2
+ // ── START LICENSE CRYPTO ──
3
+ const PUBLIC_KEY_N = BigInt("21317979104050344678233452423803986868842680501941538057759536089609908024281729760556922188810565541201321990374637212776148857190553192389608364990754937991254608109727112734447704854882786946805547031142530865504430968628760311494129154950865158735816268612258108296781892583171421605668998979064782378729322077179022581884976242706169323540456286526420742434868559866211716347133681429395477256705746276962607312574959642065929462114149296066812527505837608212730874704915352119747022918247963886630350365119206627670591400728523813746184290857872958847087691236909900501703203494160206941648263514117706558791691");
4
+ const PUBLIC_KEY_E = 65537n;
5
+
6
+ function modPow(base, exp, mod) {
7
+ let res = 1n;
8
+ base = base % mod;
9
+ while (exp > 0n) {
10
+ if (exp % 2n === 1n) res = (res * base) % mod;
11
+ exp = exp / 2n;
12
+ base = (base * base) % mod;
13
+ }
14
+ return res;
15
+ }
16
+
17
+ function bigIntToString(bi) {
18
+ let hex = bi.toString(16);
19
+ if (hex.length % 2 !== 0) hex = '0' + hex;
20
+ let str = '';
21
+ for (let i = 0; i < hex.length; i += 2) {
22
+ str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
23
+ }
24
+ return str;
25
+ }
26
+
27
+ function stringToBigInt(str) {
28
+ let hex = '';
29
+ for (let i = 0; i < str.length; i++) {
30
+ hex += str.charCodeAt(i).toString(16).padStart(2, '0');
31
+ }
32
+ return BigInt('0x' + hex);
33
+ }
34
+
35
+ function sha256(ascii) {
36
+ function rightRotate(value, amount) { return (value>>>amount) | (value<<(32 - amount)); }
37
+ var mathPow = Math.pow; var maxWord = mathPow(2, 32);
38
+ var lengthProperty = 'length'; var i, j;
39
+ var result = ''; var words = []; var asciiBitLength = ascii[lengthProperty]*8;
40
+ var hash = sha256.h = sha256.h || [];
41
+ var k = sha256.k = sha256.k || [];
42
+ var primeCounter = k[lengthProperty];
43
+ var isComposite = {};
44
+ for (var candidate = 2; primeCounter < 64; candidate++) {
45
+ if (!isComposite[candidate]) {
46
+ for (i = 0; i < 313; i += candidate) { isComposite[i] = candidate; }
47
+ hash[primeCounter] = (mathPow(candidate, .5)*maxWord)|0;
48
+ k[primeCounter++] = (mathPow(candidate, 1/3)*maxWord)|0;
49
+ }
50
+ }
51
+ ascii += '\x80';
52
+ while (ascii[lengthProperty]%64 - 56) ascii += '\x00';
53
+ for (i = 0; i < ascii[lengthProperty]; i++) {
54
+ j = ascii.charCodeAt(i);
55
+ if (j>>8) return;
56
+ words[i>>2] |= j << ((3 - i)%4)*8;
57
+ }
58
+ words[words[lengthProperty]] = ((asciiBitLength/maxWord)|0);
59
+ words[words[lengthProperty]] = (asciiBitLength)
60
+ for (j = 0; j < words[lengthProperty];) {
61
+ var w = words.slice(j, j += 16);
62
+ var oldHash = hash;
63
+ hash = hash.slice(0, 8);
64
+ for (i = 0; i < 64; i++) {
65
+ var w15 = w[i - 15], w2 = w[i - 2];
66
+ var a = hash[0], e = hash[4];
67
+ var temp1 = hash[7] + (rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25)) + ((e&hash[5])^((~e)&hash[6])) + k[i] + (w[i] = (i < 16) ? w[i] : (w[i - 16] + (rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15>>>3)) + w[i - 7] + (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2>>>10)))|0);
68
+ var temp2 = (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22)) + ((a&hash[1])^(a&hash[2])^(hash[1]&hash[2]));
69
+ hash = [(temp1 + temp2)|0].concat(hash);
70
+ hash[4] = (hash[4] + temp1)|0;
71
+ }
72
+ for (i = 0; i < 8; i++) { hash[i] = (hash[i] + oldHash[i])|0; }
73
+ }
74
+ for (i = 0; i < 8; i++) {
75
+ for (j = 3; j + 1; j--) {
76
+ var b = (hash[i]>>(j*8))&255;
77
+ result += ((b < 16) ? 0 : '') + b.toString(16);
78
+ }
79
+ }
80
+ return result;
81
+ }
82
+ // ── END LICENSE CRYPTO ──
83
+
84
+ class LicenseManager {
85
+ /**
86
+ * Validates a license key universally across all environments.
87
+ * @param {string} key - The license key.
88
+ * @param {object} options - Options mapping to environment specifics.
89
+ * @returns {object} { valid: boolean, tier: string|null, reason?: string, expires?: number }
90
+ */
91
+ static validate(key, options = {}) {
92
+ if (!key) return { valid: false, tier: null, reason: "No key provided" };
93
+ const rawKey = key.trim();
94
+ const k = rawKey.toUpperCase();
95
+ const parts = k.split('-');
96
+
97
+ const now = options.mockTime !== undefined && options.mockTime !== null ? options.mockTime : Date.now();
98
+ let lastSeen = 0;
99
+
100
+ if (options.storage) {
101
+ lastSeen = parseInt(options.storage.getItem('ps_last_seen_time') || '0', 10);
102
+ if (options.mockTime === undefined || options.mockTime === null) {
103
+ options.storage.setItem('ps_last_seen_time', now.toString());
104
+ }
105
+ }
106
+
107
+ // Clock rollback guard
108
+ if ((options.mockTime === undefined || options.mockTime === null) && now < lastSeen) {
109
+ if (options.onAnomaly) options.onAnomaly();
110
+ return { valid: false, tier: null, reason: "Clock rollback anomaly detected" };
111
+ }
112
+
113
+ const _atob = typeof atob !== 'undefined' ? atob : (str) => Buffer.from(str, 'base64').toString('binary');
114
+
115
+ // 1. Process Pilot Keys (RSA-1024 Encrypted Timestamps)
116
+ if (k.startsWith('PS-PILOT-')) {
117
+ try {
118
+ const payload = rawKey.substring(9);
119
+ const binary = _atob(payload);
120
+ const bytes = new Uint8Array(binary.length);
121
+ for (let i = 0; i < binary.length; i++) {
122
+ bytes[i] = binary.charCodeAt(i);
123
+ }
124
+ let hex = '';
125
+ for (let i = 0; i < bytes.length; i++) {
126
+ hex += bytes[i].toString(16).padStart(2, '0');
127
+ }
128
+ const S = BigInt('0x' + hex);
129
+
130
+ const M = modPow(S, PUBLIC_KEY_E, PUBLIC_KEY_N);
131
+ const decryptedStr = bigIntToString(M);
132
+
133
+ if (decryptedStr.startsWith('ZTDS-PILOT-')) {
134
+ const expDate = parseInt(decryptedStr.substring(11), 10);
135
+ if (!isNaN(expDate)) {
136
+ if (now < expDate) return { valid: true, tier: 'TEAMS', expires: expDate };
137
+
138
+ if (options.onExpired) options.onExpired('Pilot');
139
+ return { valid: false, tier: null, reason: "Pilot expired" };
140
+ }
141
+ }
142
+ } catch(e) {
143
+ return { valid: false, tier: null, reason: "Pilot decryption failed" };
144
+ }
145
+ }
146
+
147
+ // 1.5. Process RSA Signed Keys (PS-RSA-...)
148
+ if (k.startsWith('PS-RSA-')) {
149
+ try {
150
+ const payload = rawKey.substring(7);
151
+ const binary = _atob(payload);
152
+ const bytes = new Uint8Array(binary.length);
153
+ for (let i = 0; i < binary.length; i++) {
154
+ bytes[i] = binary.charCodeAt(i);
155
+ }
156
+ let hex = '';
157
+ for (let i = 0; i < bytes.length; i++) {
158
+ hex += bytes[i].toString(16).padStart(2, '0');
159
+ }
160
+ const S = BigInt('0x' + hex);
161
+
162
+ const M = modPow(S, PUBLIC_KEY_E, PUBLIC_KEY_N);
163
+ const decryptedStr = bigIntToString(M);
164
+
165
+ if (decryptedStr.startsWith('ZTDS-LIC-')) {
166
+ const pieces = decryptedStr.split('-');
167
+ if (pieces.length >= 5) {
168
+ const tier = pieces[2];
169
+ const expires = parseInt(pieces[4], 10);
170
+ if (!isNaN(expires)) {
171
+ if (now < expires) return { valid: true, tier: tier, expires: expires };
172
+
173
+ if (options.onExpired) options.onExpired('RSA');
174
+ return { valid: false, tier: null, reason: "RSA key expired" };
175
+ }
176
+ }
177
+ }
178
+ } catch(e) {
179
+ return { valid: false, tier: null, reason: "RSA decryption failed" };
180
+ }
181
+ }
182
+
183
+ // 2. Process Modern Prefixed Keys (v1.4.2+ Salted Checksum)
184
+ if (parts.length === 4 && parts[0] === 'PS' && (parts[1] === 'PRO' || parts[1] === 'TEAMS')) {
185
+ const tier = parts[1];
186
+ const core = parts[2];
187
+ const checksum = parts[3];
188
+ const salt = "ZTDS_SALT_2026_!@#";
189
+ const input = core + salt + tier;
190
+ let sum = 0;
191
+ for (let i = 0; i < input.length; i++) {
192
+ sum += input.charCodeAt(i) * (i + 1);
193
+ }
194
+ const expectedChecksum = (sum % 9999).toString().padStart(4, '0');
195
+ if (checksum === expectedChecksum) {
196
+ return { valid: true, tier: tier };
197
+ }
198
+
199
+ // Algorithm B: Legacy unsalted checksum (pre-v1.4.2, migration only)
200
+ const MIGRATION_CUTOFF = 1759276800000; // 2026-10-01T00:00:00Z
201
+ if (now < MIGRATION_CUTOFF) {
202
+ let sumOld = 0;
203
+ for (let i = 0; i < core.length; i++) sumOld += core.charCodeAt(i);
204
+ if (checksum === (sumOld * 42 % 9999).toString().padStart(4, '0')) {
205
+ if (options.onLegacyUpgrade) {
206
+ options.onLegacyUpgrade(tier, k);
207
+ }
208
+ return { valid: true, tier: tier };
209
+ }
210
+ }
211
+ }
212
+
213
+ // 3. Process Standard RS256 JWTs (eyJ...)
214
+ if (rawKey.startsWith('eyJ') && rawKey.split('.').length === 3) {
215
+ try {
216
+ const [b64Header, b64Payload, b64Signature] = rawKey.split('.');
217
+
218
+ let sigStr = b64Signature.replace(/-/g, '+').replace(/_/g, '/');
219
+ while (sigStr.length % 4) sigStr += '=';
220
+ const binarySig = _atob(sigStr);
221
+ let hexSig = '';
222
+ for (let i = 0; i < binarySig.length; i++) {
223
+ hexSig += binarySig.charCodeAt(i).toString(16).padStart(2, '0');
224
+ }
225
+
226
+ const S = BigInt('0x' + hexSig);
227
+ const M = modPow(S, PUBLIC_KEY_E, PUBLIC_KEY_N);
228
+
229
+ let mHex = M.toString(16);
230
+ if (mHex.length % 2 !== 0) mHex = '0' + mHex;
231
+
232
+ const expectedHash = sha256(b64Header + '.' + b64Payload);
233
+
234
+ if (mHex.endsWith(expectedHash)) {
235
+ const payloadJson = _atob(b64Payload.replace(/-/g, '+').replace(/_/g, '/'));
236
+ const payload = JSON.parse(payloadJson);
237
+
238
+ if (payload.exp && (now / 1000) < payload.exp) {
239
+ return { valid: true, tier: payload.tier || 'PRO', expires: payload.exp * 1000 };
240
+ } else {
241
+ if (options.onExpired) options.onExpired('JWT');
242
+ return { valid: false, tier: null, reason: "JWT expired" };
243
+ }
244
+ }
245
+ } catch(e) {
246
+ return { valid: false, tier: null, reason: "JWT validation failed" };
247
+ }
248
+ }
249
+
250
+ return { valid: false, tier: null, reason: "Invalid format" };
251
+ }
252
+ }
253
+
254
+ // Export module for ES modules, CommonJS, and Browser Global
255
+ if (typeof exports !== 'undefined') {
256
+ if (typeof module !== 'undefined' && module.exports) {
257
+ module.exports = LicenseManager;
258
+ }
259
+ exports.LicenseManager = LicenseManager;
260
+ } else {
261
+ global.LicenseManager = LicenseManager;
262
+ }
263
+ })(typeof globalThis !== 'undefined' ? globalThis : (typeof window !== 'undefined' ? window : this));