@privacyscrubber/mcp-server 1.0.1 → 1.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.
@@ -0,0 +1,60 @@
1
+ {
2
+ "serverInfo": {
3
+ "name": "privacyscrubber/pii-masking-mcp",
4
+ "version": "1.0.3"
5
+ },
6
+ "tools": [
7
+ {
8
+ "name": "sanitize_text",
9
+ "description": "Locally scrubs PII, secrets, and credentials (like API keys, passwords, emails, phones, names) from code, logs, or text. Replaces them with safe placeholders (e.g., [EMAIL_1], [API_KEY_1]). Keep your data secure before passing it to any LLM.",
10
+ "inputSchema": {
11
+ "type": "object",
12
+ "properties": {
13
+ "text": {
14
+ "type": "string",
15
+ "description": "The raw text, code, or logs to sanitize."
16
+ },
17
+ "profile": {
18
+ "type": "string",
19
+ "description": "The detection profile to use. Available: 'General' (Free), or PRO profiles. Defaults to 'General'."
20
+ }
21
+ },
22
+ "required": ["text"]
23
+ }
24
+ },
25
+ {
26
+ "name": "reveal_text",
27
+ "description": "Replaces masked tokens (e.g., [EMAIL_1], [API_KEY_1]) in the LLM's response back with the original private data from the local volatile RAM-only session map.",
28
+ "inputSchema": {
29
+ "type": "object",
30
+ "properties": {
31
+ "text": {
32
+ "type": "string",
33
+ "description": "The AI generated response containing placeholders to restore."
34
+ }
35
+ },
36
+ "required": ["text"]
37
+ }
38
+ },
39
+ {
40
+ "name": "sanitize_file",
41
+ "description": "Reads a local file, sanitizes its contents using the selected profile, and outputs the safe version for AI analysis. Securely keeps original identifiers in memory.",
42
+ "inputSchema": {
43
+ "type": "object",
44
+ "properties": {
45
+ "file_path": {
46
+ "type": "string",
47
+ "description": "Absolute path to the file to sanitize."
48
+ },
49
+ "profile": {
50
+ "type": "string",
51
+ "description": "The detection profile to use. Available: 'General' (Free), or PRO profiles. Defaults to 'General'."
52
+ }
53
+ },
54
+ "required": ["file_path"]
55
+ }
56
+ }
57
+ ],
58
+ "resources": [],
59
+ "prompts": []
60
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ilya Sibiryakov (BrandMeWeb)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,35 +1,28 @@
1
- # @privacyscrubber/mcp-server
1
+ # PrivacyScrubber MCP Server v1.0.3
2
2
 
3
- **Zero-Trust Data Sanitization (ZTDS) Model Context Protocol (MCP) Server.**
4
- Locally scrubs PII, secrets, credentials, and custom patterns from files and text contexts before sending them to LLMs.
3
+ Zero-Trust Data Sanitization (ZTDS) Model Context Protocol (MCP) server for local PII and secrets masking.
5
4
 
6
- ---
5
+ ## What's New in v1.0.3
6
+ - **Robust Configuration Parsing**: Protected Smithery integration hooks from undefined configuration objects, ensuring reliable initialization across all MCP clients (Claude Desktop, Cursor, Windsurf).
7
+ - **Microsoft Word (.docx) Support**: Enhanced the `sanitize_file` tool to parse and redact sensitive information from Word documents locally.
8
+ - **Unified Branding & Metadata**: Corrected repository fields and aligned server identity across NPM, Smithery, and Glama registries.
9
+ - **Enhanced Scanner Discovery**: Implemented static `server-card.json` configurations to support automated registry scanning.
7
10
 
8
- ## šŸ”’ Security & Privacy Guarantees
11
+ ## Key Features & Security Guarantees
12
+ - **100% Local Processing**: All regex scanning, PII tokenization, and reverse-scrubbing occur directly in your machine's RAM.
13
+ - **Airplane Mode Verified**: Fully operational without an internet connection after the initial download.
14
+ - **Zero Server Logs**: No data, credentials, or prompts are sent to external APIs or remote databases.
9
15
 
10
- * **100% Offline / Local Execution:** All PII detection, regex matching, and tokenization happen entirely inside your local machine's RAM. No prompt context or file data is ever sent to any remote servers.
11
- * **Volatile In-Memory Mapping:** The token replacement map (`sessionMap`) is kept in volatile RAM only. It is never persisted to disk, cookies, or database storage.
12
- * **Support for Reverse-Scrubbing (Reveal):** Replaces tokens in LLM responses back with original values securely inside your local context.
13
-
14
- ---
15
-
16
- ## šŸš€ Installation & Usage
17
-
18
- ### 1. Instant Run with NPX
19
- You can run the MCP server directly using `npx`:
16
+ ## Quick Start
17
+ Run the server instantly without local installation:
20
18
  ```bash
21
- npx @privacyscrubber/mcp-server
19
+ npx -y @privacyscrubber/mcp-server
22
20
  ```
23
21
 
24
- ---
25
-
26
- ## āš™ļø Client Integrations
22
+ ## Client Integration Examples
27
23
 
28
24
  ### Claude Desktop
29
- Add the server configuration to your Claude Desktop config file:
30
- * **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
31
- * **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
32
-
25
+ Add to your `claude_desktop_config.json`:
33
26
  ```json
34
27
  {
35
28
  "mcpServers": {
@@ -37,7 +30,7 @@ Add the server configuration to your Claude Desktop config file:
37
30
  "command": "npx",
38
31
  "args": ["-y", "@privacyscrubber/mcp-server"],
39
32
  "env": {
40
- "PRIVACYSCRUBBER_KEY": "YOUR_OPTIONAL_PRO_OR_TEAMS_KEY"
33
+ "PRIVACYSCRUBBER_KEY": "YOUR_OPTIONAL_PRO_LICENSE_KEY"
41
34
  }
42
35
  }
43
36
  }
@@ -45,22 +38,13 @@ Add the server configuration to your Claude Desktop config file:
45
38
  ```
46
39
 
47
40
  ### Cursor / Windsurf
48
- 1. Open settings (Settings -> Features -> MCP).
49
- 2. Add new MCP server:
50
- * **Name:** `privacyscrubber`
51
- * **Type:** `command`
52
- * **Command:** `npx -y @privacyscrubber/mcp-server`
53
- 3. If you have a PRO key, add `PRIVACYSCRUBBER_KEY` as an environment variable in your system shell or configure it locally.
54
-
55
- ---
56
-
57
- ## šŸ› ļø Provided Tools
58
-
59
- 1. `sanitize_text` — Sanitizes a raw string using the selected detection profile.
60
- 2. `reveal_text` — Detokenizes a response containing PII tokens back to the original text.
61
- 3. `sanitize_file` — Reads a local file, sanitizes its contents, and outputs the safe version (supports up to 10MB).
62
-
63
- ---
64
-
65
- ## šŸ“„ License & Commercial Key
66
- Standard use includes the **Free Tier** (limits to the `General` PII profile). To unlock 22+ specialized industry profiles (DevOps, Medical, Legal, Finance) and custom regex rules, acquire a license at [privacyscrubber.com/pricing](https://privacyscrubber.com/pricing).
41
+ Add a new command-type MCP server:
42
+ - **Name**: `privacyscrubber`
43
+ - **Command**: `npx -y @privacyscrubber/mcp-server`
44
+
45
+ ## Useful Links
46
+ - **Official Website**: https://privacyscrubber.com
47
+ - **Chrome Web Store Extension**: https://chromewebstore.google.com/detail/privacyscrubber-%E2%80%94-pii-red/pimoejgefeilajmmbpghifdmhdlkgjol
48
+ - **MCP Documentation**: https://privacyscrubber.com/pii-mcp/
49
+ - **Pricing & Licensing**: https://privacyscrubber.com/pricing/
50
+ - **GitHub Repository**: https://github.com/moxno/privacyscrubber-mcp
package/bundle.mcpb ADDED
Binary file
package/index.js CHANGED
@@ -83,8 +83,8 @@ function checkLicenseStatus() {
83
83
  // Create the MCP server
84
84
  const server = new Server(
85
85
  {
86
- name: "privacyscrubber-mcp",
87
- version: "1.0.0",
86
+ name: "privacyscrubber/pii-masking-mcp",
87
+ version: "1.0.3",
88
88
  },
89
89
  {
90
90
  capabilities: {
@@ -135,7 +135,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
135
135
  inputSchema: {
136
136
  type: "object",
137
137
  properties: {
138
- filePath: {
138
+ file_path: {
139
139
  type: "string",
140
140
  description: "Absolute path to the file to sanitize."
141
141
  },
@@ -144,7 +144,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
144
144
  description: "The detection profile to use. Available: 'General' (Free), or PRO profiles: 'Dev' (Engineering/Code), 'Medical', 'Pharma', 'Legal', 'Compliance', 'CCPA', 'Finance', 'Bizops', 'Sales', 'WealthMgmt', 'Insurance', 'Accounting', 'HR', 'Security', 'Marketing', 'Support', 'RealEstate', 'Agents', 'Academic', 'Creative', 'Tech', 'Personal'. Defaults to 'General'."
145
145
  }
146
146
  },
147
- required: ["filePath"]
147
+ required: ["file_path"]
148
148
  }
149
149
  }
150
150
  ]
@@ -169,7 +169,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
169
169
  content: [
170
170
  {
171
171
  type: "text",
172
- text: `āš ļø [PrivacyScrubber] Advanced profile '${targetProfile}' is locked in the FREE tier. Falling back to 'General' profile.${reason}\nTo unlock 22+ advanced industry profiles, custom regex, and unlimited logs protection, set PRIVACYSCRUBBER_KEY to your PRO license key.\nGet a key at: https://privacyscrubber.com/pricing\n\n--- Sanitized Output (General Profile) ---\n`
172
+ text: `āš ļø [PrivacyScrubber] Advanced profile '${targetProfile}' is locked in the FREE tier. Falling back to 'General' profile.${reason}\nTo unlock 22+ advanced industry profiles, custom regex, and unlimited logs protection, set PRIVACYSCRUBBER_KEY to your PRO license key.\nGet a key at: https://privacyscrubber.com/pricing\nBrowser Extension: https://chromewebstore.google.com/detail/privacyscrubber-%E2%80%94-pii-red/pimoejgefeilajmmbpghifdmhdlkgjol\n\n--- Sanitized Output (General Profile) ---\n`
173
173
  },
174
174
  {
175
175
  type: "text",
@@ -204,7 +204,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
204
204
  }
205
205
 
206
206
  if (name === "sanitize_file") {
207
- const { filePath, profile = "General" } = args;
207
+ const rawPath = args.file_path || args.filePath;
208
+ if (!rawPath) {
209
+ return {
210
+ isError: true,
211
+ content: [
212
+ {
213
+ type: "text",
214
+ text: "Error: Missing required parameter 'file_path'."
215
+ }
216
+ ]
217
+ };
218
+ }
219
+ const { profile = "General" } = args;
220
+ const filePath = rawPath;
208
221
 
209
222
  const resolvedPath = path.resolve(filePath);
210
223
  if (!fs.existsSync(resolvedPath)) {
@@ -246,7 +259,71 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
246
259
  };
247
260
  }
248
261
 
249
- const content = fs.readFileSync(resolvedPath, "utf8");
262
+ if (resolvedPath.toLowerCase().endsWith(".docx")) {
263
+ try {
264
+ const mammoth = require('mammoth');
265
+ const result = await mammoth.extractRawText({ path: resolvedPath });
266
+ const content = result.value;
267
+ const targetProfile = profile.trim();
268
+ const isAdvanced = targetProfile.toLowerCase() !== "general";
269
+ const license = checkLicenseStatus();
270
+
271
+ let prefix = "";
272
+ let finalProfile = targetProfile;
273
+
274
+ if (isAdvanced && !license.isPro) {
275
+ const reason = license.error ? ` (${license.error})` : "";
276
+ prefix = `āš ļø [PrivacyScrubber] Advanced profile '${targetProfile}' is locked in the FREE tier. Falling back to 'General' profile.${reason}\nGet a PRO key at: https://privacyscrubber.com/pricing\nBrowser Extension: https://chromewebstore.google.com/detail/privacyscrubber-%E2%80%94-pii-red/pimoejgefeilajmmbpghifdmhdlkgjol\n\n`;
277
+ finalProfile = "General";
278
+ }
279
+
280
+ const sanitized = performSanitization(content, finalProfile);
281
+ return {
282
+ content: [
283
+ {
284
+ type: "text",
285
+ text: `${prefix}${sanitized}`
286
+ }
287
+ ]
288
+ };
289
+ } catch (docxError) {
290
+ return {
291
+ isError: true,
292
+ content: [
293
+ {
294
+ type: "text",
295
+ text: `Error: Failed to parse DOCX file: ${docxError.message}`
296
+ }
297
+ ]
298
+ };
299
+ }
300
+ }
301
+
302
+ const buffer = fs.readFileSync(resolvedPath);
303
+
304
+ // Check for null bytes in the first 8KB to detect binary files
305
+ let isBinary = false;
306
+ const checkLen = Math.min(buffer.length, 8000);
307
+ for (let i = 0; i < checkLen; i++) {
308
+ if (buffer[i] === 0) {
309
+ isBinary = true;
310
+ break;
311
+ }
312
+ }
313
+
314
+ if (isBinary) {
315
+ return {
316
+ isError: true,
317
+ content: [
318
+ {
319
+ type: "text",
320
+ text: `Error: Binary file format detected. The local MCP server only supports plain text files (e.g., source code, logs, CSV, markdown, JSON). To sanitize complex formats like PDF or DOCX, please use the web interface at: https://privacyscrubber.com/`
321
+ }
322
+ ]
323
+ };
324
+ }
325
+
326
+ const content = buffer.toString("utf8");
250
327
  const targetProfile = profile.trim();
251
328
  const isAdvanced = targetProfile.toLowerCase() !== "general";
252
329
  const license = checkLicenseStatus();
@@ -256,7 +333,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
256
333
 
257
334
  if (isAdvanced && !license.isPro) {
258
335
  const reason = license.error ? ` (${license.error})` : "";
259
- prefix = `āš ļø [PrivacyScrubber] Advanced profile '${targetProfile}' is locked in the FREE tier. Falling back to 'General' profile.${reason}\nGet a PRO key at: https://privacyscrubber.com/pricing\n\n`;
336
+ prefix = `āš ļø [PrivacyScrubber] Advanced profile '${targetProfile}' is locked in the FREE tier. Falling back to 'General' profile.${reason}\nGet a PRO key at: https://privacyscrubber.com/pricing\nBrowser Extension: https://chromewebstore.google.com/detail/privacyscrubber-%E2%80%94-pii-red/pimoejgefeilajmmbpghifdmhdlkgjol\n\n`;
260
337
  finalProfile = "General";
261
338
  }
262
339
 
package/manifest.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "manifest_version": "0.3",
3
+ "name": "privacyscrubber/pii-masking-mcp",
4
+ "version": "1.0.3",
5
+ "description": "Zero-Trust Data Sanitization (ZTDS) MCP Server for AI IDEs and Claude Desktop. Locally scrubs PII, secrets, and credentials before sending context to LLMs.",
6
+ "author": {
7
+ "name": "PrivacyScrubber",
8
+ "email": "legal@privacyscrubber.com"
9
+ },
10
+ "server": {
11
+ "type": "node",
12
+ "entry_point": "index.js",
13
+ "mcp_config": {
14
+ "command": "node",
15
+ "args": ["${__dirname}/index.js"],
16
+ "env": {
17
+ "PRIVACYSCRUBBER_KEY": "${user_config.PRIVACYSCRUBBER_KEY}"
18
+ }
19
+ }
20
+ },
21
+ "user_config": {
22
+ "PRIVACYSCRUBBER_KEY": {
23
+ "type": "string",
24
+ "title": "PrivacyScrubber License Key",
25
+ "description": "Optional PRO or TEAMS license key. Leave blank to use the Free tier."
26
+ }
27
+ }
28
+ }
package/package.json CHANGED
@@ -1,9 +1,22 @@
1
1
  {
2
2
  "name": "@privacyscrubber/mcp-server",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
+ "mcpName": "io.github.moxno/privacyscrubber-mcp",
4
5
  "description": "Zero-Trust Data Sanitization (ZTDS) MCP Server for AI IDEs and Claude Desktop. Locally scrubs PII, secrets, and credentials before sending context to LLMs.",
5
6
  "main": "index.js",
6
7
  "type": "module",
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "files": [
12
+ "index.js",
13
+ "scrubber-core.cjs",
14
+ "smithery.yaml",
15
+ "server.json",
16
+ "manifest.json",
17
+ "bundle.mcpb",
18
+ ".well-known"
19
+ ],
7
20
  "bin": {
8
21
  "mcp-server": "index.js",
9
22
  "privacyscrubber-mcp": "index.js"
@@ -12,7 +25,8 @@
12
25
  "start": "node index.js"
13
26
  },
14
27
  "dependencies": {
15
- "@modelcontextprotocol/sdk": "^0.6.0"
28
+ "@modelcontextprotocol/sdk": "^1.29.0",
29
+ "mammoth": "^1.8.0"
16
30
  },
17
31
  "keywords": [
18
32
  "mcp",
@@ -26,6 +40,14 @@
26
40
  "claude-desktop",
27
41
  "cursor"
28
42
  ],
29
- "author": "Ilya Sibiryakov (Brand Me Web)",
30
- "license": "MIT"
43
+ "author": "Ilya Sibiryakov (BrandMeWeb)",
44
+ "license": "MIT",
45
+ "homepage": "https://privacyscrubber.com/pii-mcp/",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/moxno/privacyscrubber-mcp.git"
49
+ },
50
+ "bugs": {
51
+ "url": "https://github.com/moxno/privacyscrubber-mcp/issues"
52
+ }
31
53
  }
package/server.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "privacyscrubber/pii-masking-mcp",
4
+ "description": "Zero-Trust PII & secrets sanitizer. Locally scrubs data in-memory before sending context to LLMs.",
5
+ "repository": {
6
+ "url": "https://github.com/moxno/privacyscrubber-mcp",
7
+ "source": "github"
8
+ },
9
+ "version": "1.0.3",
10
+ "packages": [
11
+ {
12
+ "registryType": "npm",
13
+ "identifier": "@privacyscrubber/mcp-server",
14
+ "version": "1.0.3",
15
+ "transport": {
16
+ "type": "stdio"
17
+ },
18
+ "environmentVariables": [
19
+ {
20
+ "description": "Optional PRO or TEAMS license key to unlock specialized detection profiles.",
21
+ "isRequired": false,
22
+ "format": "string",
23
+ "isSecret": true,
24
+ "name": "PRIVACYSCRUBBER_KEY"
25
+ }
26
+ ]
27
+ }
28
+ ]
29
+ }
package/smithery.yaml ADDED
@@ -0,0 +1,12 @@
1
+ # Smithery.ai configuration for PrivacyScrubber MCP Server
2
+ startCommand:
3
+ type: "stdio"
4
+ commandFunction: |-
5
+ (config) => ({
6
+ command: 'node',
7
+ args: ['./index.js'],
8
+ env: {
9
+ PRIVACYSCRUBBER_KEY: (config && config.PRIVACYSCRUBBER_KEY) || ''
10
+ }
11
+ })
12
+ exampleConfig: {}
package/test-mcp.js DELETED
@@ -1,114 +0,0 @@
1
- /**
2
- * Complete Cryptographic Verification Test Suite for @privacyscrubber/mcp-server
3
- */
4
-
5
- import { createRequire } from 'module';
6
- import path from 'path';
7
- import { fileURLToPath } from 'url';
8
-
9
- const require = createRequire(import.meta.url);
10
- const __filename = fileURLToPath(import.meta.url);
11
- const __dirname = path.dirname(__filename);
12
-
13
- const scrubberCorePath = path.resolve(__dirname, '../chrome-extension/scrubber-core.js');
14
- const mcpServerIndex = path.resolve(__dirname, 'index.js');
15
-
16
- console.log("Loading modules for verification test...");
17
- try {
18
- const PrivacyScrubberCore = require(scrubberCorePath);
19
- PrivacyScrubberCore.init();
20
-
21
- // Load index.js to test its internal validate functions
22
- // We can simulate its checkLicenseStatus by mocking environment variables
23
- const indexModule = await import('./index.js?update=' + Date.now());
24
-
25
- // Test Case 1: No license key
26
- console.log("\n[Test 1] Testing with no license key (Free tier)...");
27
- process.env.PRIVACYSCRUBBER_KEY = "";
28
- const t1 = global.checkLicenseStatus ? global.checkLicenseStatus() : mockCheckLicenseStatus();
29
- console.log("Status:", t1);
30
- if (t1.isPro) {
31
- console.error("āŒ Test 1 failed: keyless state should not be PRO.");
32
- process.exit(1);
33
- }
34
- console.log("āœ… Test 1 passed.");
35
-
36
- // Test Case 2: Expired trial key
37
- console.log("\n[Test 2] Testing with expired signed trial key...");
38
- // Expired key generated by generate-license.js
39
- const expiredKey = "eyJ0eXBlIjoidHJpYWwiLCJleHBpcmVzIjoxNzgzMjcwMTgzLCJpc3N1ZWRBdCI6MTc4MzcwMjE4Mywibm9uY2UiOiIzYzRkYzEwNmRkOGJiZjczIn0=.Wc1cPwvRpEQxG3BarmedBYm9sUsGdwznboh8Gx3a7qQZm2I+Dha8lk1CtzmaaBt5O8pQszGuTCHapASFwZX2Ku8uZxkGipPI+tbNI39KK7cM6wA+Txc0Ufec/lnDE/27WelBswStSIMgR5LPVScfamMO5BK6jvJ9RgOrEdJeqJPcpab0mwy4jzZwSATyZ1JOS61i8ZlJ+n99zppBrrMZQQo/FJ5t70RpqZiZoMuyNbeDJB9UWGDEAELiJLBkaGeYgBo65zp7fRcTuyjmoZjINnOhCcXmKsmeB1vkHdjPg8jXaGVWFN7bJryeV7j/OkpNqO2q74x3hXDvNwlGtg4PUg==";
40
- process.env.PRIVACYSCRUBBER_KEY = expiredKey;
41
- const t2 = mockCheckLicenseStatus();
42
- console.log("Status:", t2);
43
- if (t2.isPro || !t2.error.includes("expired")) {
44
- console.error("āŒ Test 2 failed: expired key should be blocked with expiration reason.");
45
- process.exit(1);
46
- }
47
- console.log("āœ… Test 2 passed.");
48
-
49
- // Test Case 3: Valid signed PRO key
50
- console.log("\n[Test 3] Testing with valid signed PRO key...");
51
- // Valid key generated in this session
52
- const validKey = "eyJ0eXBlIjoicHJvIiwiZXhwaXJlcyI6MjA5OTA2MjE3NCwiaXNzdWVkQXQiOjE3ODM3MDIxNzQsIm5vbmNlIjoiMmI4MTllN2ZmM2ZmOTFmYiJ9.ZmhaKDGf9vG0nTH0nyOy3EInsuUmzjBOk9IOyiqzt6Y5s3UG+2yRExuKBoeXWymbpJt3NIJNM9sVxxl+lcop5wqbi2LNtPY4MuwBRA7pO4nn3Bes5R0lxLbEYVE8Iiw3zbfK4uVYQ53BJ7El6JCeFKPJ5WbKUsPLsjb1Lr2iQzW3ODOMaM5jKdgAGcNpuWQ373D7SW7I03Jec9kvP5hL7j3u4DV8ZzuQFzy2Nh+uMH54suydg2sNIpxrRQyxpGv7rZjTO1KT+xzzqnjqX4Pein0e6GrC5E4JUEggADtXPFMKW5SEiWzeeirB5RUPMO/F927S5fFuOYHAfuar2FqErA==";
53
- process.env.PRIVACYSCRUBBER_KEY = validKey;
54
- const t3 = mockCheckLicenseStatus();
55
- console.log("Status:", t3);
56
- if (!t3.isPro) {
57
- console.error("āŒ Test 3 failed: valid key was not accepted.");
58
- process.exit(1);
59
- }
60
- console.log("āœ… Test 3 passed.");
61
-
62
- console.log("\nšŸŽ‰ All local cryptographic validation tests passed!");
63
- process.exit(0);
64
-
65
- } catch (error) {
66
- console.error("āŒ Test run crashed:", error);
67
- process.exit(1);
68
- }
69
-
70
- // Mock checker mapping index.js logic locally for direct assertion
71
- function mockCheckLicenseStatus() {
72
- const crypto = require('crypto');
73
- const PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
74
- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw3f37srO402PU4++Baf8
75
- FG8LY4l/IA3NKLlBnYmNHRTjfI/O/w5PDZn1xPcUQevojA1J+A5moKcjXsJ5b21X
76
- hJoYSkE4vLpcVYOt1FhRwEHs1APDSyss0HixboLz2eW2XQf2NbwajWtNlyxvgczO
77
- KE6ClnLomtsaKywwqB4alzdYnnnFJttFPjwmgPSO7D9AgN9sYaVkXOaOFrIZ90Ng
78
- TRhSHUeL7ReltWlCHwz9xf5m2FrKtxr2VBlEoyPjsFzalHMey1EX+yXe81zM7IIi
79
- t1Z8agLzo7WIfNBAIWmRlerTplaFFZrQgdF5g/Y0n8IIMZOtadgoY8E855psDNZV
80
- 7wIDAQAB
81
- -----END PUBLIC KEY-----`;
82
-
83
- const key = (process.env.PRIVACYSCRUBBER_KEY || "").trim();
84
- if (!key) return { isPro: false, type: null, error: "No license key provided." };
85
-
86
- try {
87
- const [payloadBase64, signatureBase64] = key.split('.');
88
- if (!payloadBase64 || !signatureBase64) {
89
- return { isPro: false, type: null, error: "Invalid license key structure." };
90
- }
91
-
92
- const verifier = crypto.createVerify('SHA256');
93
- verifier.update(payloadBase64);
94
- const isVerified = verifier.verify(PUBLIC_KEY, signatureBase64, 'base64');
95
-
96
- if (!isVerified) {
97
- return { isPro: false, type: null, error: "Signature verification failed." };
98
- }
99
-
100
- const payload = JSON.parse(Buffer.from(payloadBase64, 'base64').toString('utf8'));
101
-
102
- if (payload.expires && payload.expires < Math.floor(Date.now() / 1000)) {
103
- return {
104
- isPro: false,
105
- type: payload.type,
106
- error: `License expired on ${new Date(payload.expires * 1000).toLocaleDateString()}`
107
- };
108
- }
109
-
110
- return { isPro: true, type: payload.type, error: null };
111
- } catch (e) {
112
- return { isPro: false, type: null, error: "Error parsing license: " + e.message };
113
- }
114
- }