@privacyscrubber/mcp-server 2.3.9 → 2.4.1

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/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": "0.3",
3
3
  "name": "privacyscrubber/pii-masking-mcp",
4
- "version": "2.3.9",
4
+ "version": "2.4.1",
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",
@@ -23,35 +23,111 @@
23
23
  "tools": [
24
24
  {
25
25
  "name": "sanitize_text",
26
- "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."
26
+ "description": "STEP 1: Call this first. Locally scrubs PII, secrets, and credentials 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."
27
27
  },
28
28
  {
29
29
  "name": "reveal_text",
30
- "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."
30
+ "description": "STEP 3: Call this last. Restores masked tokens back with original private data from the local volatile RAM-only session map."
31
+ },
32
+ {
33
+ "name": "audit_context",
34
+ "description": "STEP 0 (Pre-Flight): Non-destructive security inspection of raw prompts or document chunks before sending to LLMs. Evaluates PII, secrets, risk severity, and triggered regulatory frameworks."
35
+ },
36
+ {
37
+ "name": "scrub_text",
38
+ "description": "Alias for sanitize_text."
31
39
  },
32
40
  {
33
41
  "name": "sanitize_file",
34
42
  "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."
35
43
  },
44
+ {
45
+ "name": "scrub_file",
46
+ "description": "Alias for sanitize_file."
47
+ },
36
48
  {
37
49
  "name": "audit_directory_for_pii",
38
- "description": "Audits a local directory or project tree for PII, secrets, and compliance vulnerabilities without sending data to any external server."
50
+ "description": "Scans a local directory for leaks of secrets, keys, and PII. Returns a structured security summary report."
51
+ },
52
+ {
53
+ "name": "redact_file",
54
+ "description": "In-place redaction of a local file. Replaces PII and secrets with tokens and saves the file with an optional safety backup."
55
+ },
56
+ {
57
+ "name": "create_default_config",
58
+ "description": "Creates a default privacyscrubber.json configuration file in current working directory."
39
59
  },
40
60
  {
41
61
  "name": "generate_compliance_report",
42
- "description": "Generates a structured compliance report (GDPR, HIPAA, SOC 2, PCI DSS) and enterprise CISO audit receipt for the current session or workspace."
62
+ "description": "Generates an official Zero-Trust Compliance Audit Certificate (GDPR, HIPAA, EU AI Act, SOC 2) for the current MCP session."
43
63
  },
44
64
  {
45
65
  "name": "export_audit_log",
46
- "description": "Exports a zero-knowledge immutable audit receipt verifying zero-egress data sanitization for compliance documentation."
66
+ "description": "Exports an immutable, tamper-evident audit log in SIEM standard formats (CEF, Syslog, JSONL) with zero plaintext PII leakage."
67
+ },
68
+ {
69
+ "name": "mark_false_positive",
70
+ "description": "Marks a previously detected token as a false positive, excluding it from future masking in the active session."
71
+ },
72
+ {
73
+ "name": "check_status",
74
+ "description": "Returns current PrivacyScrubber MCP tier, session usage, available profiles, and PRO upgrade instructions."
47
75
  },
48
76
  {
49
77
  "name": "guard_exec",
50
- "description": "Sanitizes command line execution strings and arguments for autonomous AI agents prior to tool dispatch."
78
+ "description": "Zero-Trust Agentic Guard: Safely executes a shell/terminal command locally in isolated process, sanitizing stdout/stderr in volatile RAM before sending to LLM."
79
+ },
80
+ {
81
+ "name": "guard_read_file",
82
+ "description": "Zero-Trust Agentic Guard: Reads a sensitive local file (.env, config, source code) and sanitizes credentials and PII in volatile RAM."
83
+ },
84
+ {
85
+ "name": "guard_git_diff",
86
+ "description": "Zero-Trust Agentic Guard: Inspects unstaged or staged git diffs locally, sanitizing credentials and PII in RAM for safe AI commit generation or review."
87
+ },
88
+ {
89
+ "name": "guard_apply_patch",
90
+ "description": "Zero-Trust Agentic Guard: Reverses token placeholders in AI code, restoring authentic values from RAM directly to the target file on disk."
91
+ },
92
+ {
93
+ "name": "guard_unmask_args",
94
+ "description": "Zero-Trust Agentic Guard: Detokenizes parameters, JSON objects, or arguments in local RAM before dispatching to local tools or APIs."
95
+ },
96
+ {
97
+ "name": "guard_session_info",
98
+ "description": "Zero-Trust Agentic Guard: Returns diagnostic metrics on active in-memory sessions and memory footprint with zero plaintext leakage."
99
+ },
100
+ {
101
+ "name": "guard_session_reset",
102
+ "description": "Zero-Trust Agentic Guard: Explicitly purges the in-memory token map for a specific session_id or all sessions upon task completion."
103
+ },
104
+ {
105
+ "name": "guard_rag_chunk",
106
+ "description": "Zero-Trust Agentic Guard: Sanitizes document chunks before sending to embedding models or vector databases (Chroma, Pinecone, Qdrant)."
107
+ },
108
+ {
109
+ "name": "guard_rag_restore",
110
+ "description": "Zero-Trust Agentic Guard: Re-hydrates retrieved vector search results by restoring token placeholders with authentic data from RAM."
111
+ },
112
+ {
113
+ "name": "create_agent_rules",
114
+ "description": "Zero-Trust Agentic Guard: Scaffolds CISO-grade AI Agent Rules in workspace (.cursorrules, .windsurfrules, CLAUDE.md, copilot-instructions.md)."
51
115
  },
52
116
  {
53
117
  "name": "generate_procurement_memo",
54
- "description": "Generates a comprehensive CISO/DPO enterprise procurement memo and ROI business case for PrivacyScrubber TEAMS/SDK licensing."
118
+ "description": "Zero-Trust Enterprise Procurement: Generates an executive-ready Procurement & ROI Memorandum for CISOs, DPOs, and VP of Engineering."
119
+ },
120
+ {
121
+ "name": "secure_ai_workflow",
122
+ "description": "Zero-Trust Data Sanitization workflow instruction to automatically scrub PII before processing data."
123
+ },
124
+ {
125
+ "name": "safe_redaction_workflow",
126
+ "description": "Instructions for using the redact_file tool safely with QA checks."
127
+ },
128
+ {
129
+ "name": "agentic_guard_instructions",
130
+ "description": "Zero-Trust Agentic Guard instructions for autonomous AI agents (Cursor, Windsurf, Claude Code)."
55
131
  }
56
132
  ],
57
133
  "user_config": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@privacyscrubber/mcp-server",
3
- "version": "2.3.9",
3
+ "version": "2.4.1",
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",
@@ -35,7 +35,8 @@
35
35
  "scripts": {
36
36
  "start": "node index.js",
37
37
  "build": "node -e \"const fs=require('fs'); if(fs.existsSync('../scripts/build-mcp.js')) require('child_process').execSync('node ../scripts/build-mcp.js', {stdio:'inherit'}); else console.log('MCP assets are pre-built.');\"",
38
- "test": "node test-mcp.js && node test-mcp-deep.js",
38
+ "test": "node test-mcp.js && node test-mcp-deep.js && node test-mcp-streaming.js",
39
+ "test:stream": "node test-mcp-streaming.js",
39
40
  "prepublishOnly": "npm test"
40
41
  },
41
42
  "dependencies": {
@@ -27,7 +27,7 @@ const cmdIndex = args.indexOf("--");
27
27
 
28
28
  if (args.includes("--version") || args.includes("-v")) {
29
29
  const pkgPath = path.resolve(__dirname, './package.json');
30
- let version = '2.2.10';
30
+ let version = '2.4.1';
31
31
  try { version = JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version; } catch (_) {}
32
32
  console.log(`ps-guard / pii-masking-run v${version} (PrivacyScrubber MCP)`);
33
33
  process.exit(0);
@@ -89,18 +89,35 @@ const colors = {
89
89
  };
90
90
 
91
91
  const DEVOPS_SECRETS_DETECTOR = [
92
- { name: 'AWS Credentials', regex: /\b(?:AKIA|ASIA|AGPA|AIDA|AROA|AIPA)[A-Z0-9]{16}\b/g },
93
- { name: 'JSON Web Token (JWT)', regex: /\beyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\b/g },
94
- { name: 'API Token/Key (GitHub/Slack/NPM)', regex: /\b(?:ghp|gho|ghu|ghs|ghr|glpat|npm|xox[baprs])[-_][A-Za-z0-9_]{10,}\b/g },
95
- { name: 'Stripe API Key', regex: /\b(?:[rs]k)_(?:test|live)_[a-zA-Z0-9]{24,}\b/g },
96
- { name: 'OpenAI Project API Key', regex: /\b(?:sk|pk)-(?:proj-)?[a-zA-Z0-9_-]{16,}\b/gi },
97
- { name: 'Database Connection URI', regex: /\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqp|mssql):\/\/[^\s"']+/gi },
98
- { name: 'Embedded Credentials URI', regex: /\b(?:https?|s?ftp):\/\/[a-zA-Z0-9_.%+-]+:[^\s/@:"']+@[a-zA-Z0-9_.-]+(?::\d+)?(?:\/[^\s"']*)?/gi },
99
- { name: 'URL Query Parameter Secret', regex: /(?<=[?&](?:api_key|apikey|access_token|client_secret|auth_token|token|secret|password|private_key)=)[^\s"'\x26#)>]{8,}/gi },
100
- { name: 'Bearer / Auth Header Token', regex: /(?<=\b(?:Bearer|Token)\s+)[a-zA-Z0-9_\-\.~+/=]{16,}\b/gi },
101
- { 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 },
102
- { name: 'Private Cryptographic Key (PEM Block)', regex: /-----BEGIN (?:RSA |EC |PGP |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY(?: BLOCK)?-----[\s\S]{10,8192}?-----END (?:RSA |EC |PGP |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY(?: BLOCK)?-----/g },
103
- { name: 'Private Cryptographic Key (Header)', regex: /-----BEGIN (?:RSA |EC |PGP |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY(?: BLOCK)?-----/g }
92
+ { name: 'AWS Credentials', regex: /\b(?:AKIA|ASIA|AGPA|AIDA|AROA|AIPA)[A-Z0-9]{12,28}\b/g, label: 'AWS_KEY' },
93
+ { name: 'Google Cloud / Gemini API Key', regex: /\bAIza[0-9A-Za-z_-]{35}\b/g, label: 'API_TOKEN' },
94
+ { name: 'Anthropic API Key', regex: /\bsk-ant-(?:api\d{2}|admin\d{2}|sid\d{2})-[a-zA-Z0-9_-]{16,128}\b/g, label: 'API_TOKEN' },
95
+ { name: 'HuggingFace / Perplexity API Key', regex: /\b(?:hf_[a-zA-Z0-9]{20,64}|pplx-[a-zA-Z0-9]{24,64})\b/g, label: 'API_TOKEN' },
96
+ { name: 'SendGrid API Key', regex: /\bSG\.[a-zA-Z0-9_-]{16,32}\.[a-zA-Z0-9_-]{32,64}\b/g, label: 'API_TOKEN' },
97
+ { name: 'Postman API Key', regex: /\bPMAK-[a-fA-F0-9]{24}-[a-fA-F0-9]{34}\b/g, label: 'API_TOKEN' },
98
+ { name: 'GitHub Fine-Grained PAT', regex: /\bgithub_pat_[a-zA-Z0-9]{20,30}_[a-zA-Z0-9]{40,80}\b/g, label: 'API_TOKEN' },
99
+ { name: 'Slack Enterprise / Refresh Token', regex: /\bxoxe\.xox[baprs]-\d-[a-zA-Z0-9-]{10,}\b|\bxox[eapbrs]-[a-zA-Z0-9-]{10,}\b/g, label: 'API_TOKEN' },
100
+ { name: 'Telegram Bot API Token', regex: /\b\d{8,10}:AA[A-Za-z0-9_-]{33}\b/g, label: 'API_TOKEN' },
101
+ { name: 'Age Encryption Secret Key', regex: /\bAGE-SECRET-KEY-1[A-Z0-9]{58}\b/g, label: 'SECRET' },
102
+ { name: 'Modern Cloud & AI Platform Key', regex: /\b(?:sbp_[a-fA-F0-9]{40}|dop_v1_[a-fA-F0-9]{64}|doo_v1_[a-fA-F0-9]{64}|dp\.(?:st|pt|ct|sa)\.[a-zA-Z0-9_-]{20,}|lin_api_[a-zA-Z0-9]{32,64}|ntn_[a-zA-Z0-9]{32,64}|secret_[a-zA-Z0-9]{32,64}|gsk_[a-zA-Z0-9]{32,64}|r8_[a-zA-Z0-9]{24,64}|tvly-[a-zA-Z0-9_-]{20,64}|fc-[a-fA-F0-9]{32}|dapi[a-fA-F0-9]{32}(?:-\d)?|re_[a-zA-Z0-9_]{20,64}|napi_[a-zA-Z0-9]{32,64}|[a-zA-Z0-9]{14}\.atlasv1\.[a-zA-Z0-9]{50,80}|pypi-[A-Za-z0-9_-]{16,128}|whsec_[A-Za-z0-9+/=_-]{24,128}|shpat_[a-fA-F0-9]{32}|shpss_[a-fA-F0-9]{32}|shpca_[a-fA-F0-9]{32}|shppa_[a-fA-F0-9]{32}|hvs\.[A-Za-z0-9_-]{24,128}|hvb\.[A-Za-z0-9_-]{24,128}|glsa_[A-Za-z0-9]{32}_[A-Fa-f0-9]{8}|glc_[A-Za-z0-9+/=_-]{20,128}|pscale_tkn_[A-Za-z0-9_.-]{20,64}|pscale_pw_[A-Za-z0-9_.-]{20,64}|pat[A-Za-z0-9]{14}\.[a-fA-F0-9]{64}|pat-(?:na1|eu1)-[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}|sq0atp-[A-Za-z0-9_-]{22,64}|sq0csp-[A-Za-z0-9_-]{32,64}|NRAK-[A-Z0-9]{27}|v1\.0-[a-fA-F0-9]{24}-[a-fA-F0-9]{16,160}|ATATT3xFfGF0[A-Za-z0-9+/=_-]{24,128}|tskey-(?:auth|api|client)-[A-Za-z0-9_-]{20,96}|sntry[su]_[A-Za-z0-9+/=_-]{32,160}|(?:pk|sk)\.eyJ1[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{16,}|key-[a-f0-9]{32}|[a-f0-9]{32}-us\d{1,2})\b/g, label: 'API_TOKEN' },
103
+ { name: 'Twilio Account SID / API Key', regex: /\b(?:AC|SK)[a-fA-F0-9]{32}\b/g, label: 'API_TOKEN' },
104
+ { name: 'Bcrypt / Argon2 Password Hash', regex: /(?<![A-Za-z0-9])(?:\$2[aby]\$\d{2}\$[./A-Za-z0-9=]{20,60}|\$argon2(?:id|i|d)\$[^\s"']{15,120})/g, label: 'SECRET' },
105
+ { name: 'JSON Web Token (JWT)', regex: /\beyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\b/g, label: 'JWT_TOKEN' },
106
+ { name: 'API Token/Key (GitHub/Slack/NPM)', regex: /\b(?:ghp|gho|ghu|ghs|ghr|glpat|npm|xox[baprs])[-_][A-Za-z0-9_-]{10,}\b/g, label: 'API_TOKEN' },
107
+ { name: 'Stripe API Key', regex: /\b(?:[rs]k)_(?:test|live)_[a-zA-Z0-9]{10,}\b/g, label: 'STRIPE_KEY' },
108
+ { name: 'OpenAI Project API Key', regex: /\b(?:sk|pk)-(?:proj-)?[a-zA-Z0-9_-]{16,}\b/gi, label: 'OPENAI_KEY' },
109
+ { name: 'Slack / Discord Incoming Webhook URL', regex: /\bhttps:\/\/(?:hooks\.slack\.com\/services\/T[A-Z0-9_]+\/B[A-Z0-9_]+\/[A-Za-z0-9_-]+|(?:ptb\.|canary\.)?discord(?:app)?\.com\/api\/webhooks\/\d{17,20}\/[A-Za-z0-9_-]{60,70})\b/gi, label: 'SECRET' },
110
+ { name: 'Azure Connection String / SAS Token', regex: /(?:DefaultEndpointsProtocol=https?;AccountName=[^;\s"']+;AccountKey=[A-Za-z0-9+/=]{20,}(?:;EndpointSuffix=[^;\s"']+)?|Endpoint=sb:\/\/[^;\s"']+;SharedAccessKeyName=[^;\s"']+;SharedAccessKey=[A-Za-z0-9+/=]{16,}|\bSharedAccessSignature=sr=[^\s"']+&sig=[A-Za-z0-9%/+=]{16,}|\bAccountKey=[A-Za-z0-9+/=]{40,88}==)/gi, label: 'SECRET' },
111
+ { name: 'Database Connection URI', regex: /\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqp|mssql):\/\/[^\s"']+/gi, label: 'DB_URI' },
112
+ { name: 'Embedded Credentials URI', regex: /\b(?:https?|hxxps?|s?ftp|fxp|wss?):\/\/[a-zA-Z0-9_.%+-]+:[^\s/@:"']+@[a-zA-Z0-9_.-]+(?:\[\.\][a-zA-Z0-9_.-]+|\(\.\)[a-zA-Z0-9_.-]+)*(?::\d+)?(?:\/[^\s"']*)?/gi, label: 'SECRET' },
113
+ { name: 'URL Query Parameter Secret', regex: /(?<=[?&](?:api_key|apikey|access_token|client_secret|auth_token|token|secret|password|private_key)=)[^\s"'\x26#)>]{8,}/gi, label: 'SECRET' },
114
+ { name: 'CLI Credential Flag / Curl User Auth', regex: /(?:\bcurl\b[^\r\n]*?\s(?:-u|--user)\s+["']?[A-Za-z0-9_.+-]+:[^\s"']+|\b(?:--password|--api-key|--apikey|--access-token|--auth-token|--client-secret|--secret-key|--private-key)(?:=|\s+)["']?[^\s"']{6,}["']?)/gi, label: 'SECRET' },
115
+ { name: 'HTTP API Key Header', regex: /\b(?:X-API-Key|X-Amz-Security-Token|X-Functions-Key|X-Shopify-Access-Token|X-Auth-Token|Api-Key)\s*:\s*["']?[A-Za-z0-9_.\-~+/=]{12,}["']?/gi, label: 'SECRET' },
116
+ { name: 'Docker Registry Auth / K8s Secret Base64', regex: /(?<=["']?(?:auth|identitytoken|registrytoken|\.dockerconfigjson)["']?\s*:\s*["'])[A-Za-z0-9+/=]{16,}(?=["'])/gi, label: 'SECRET' },
117
+ { name: 'Bearer / Auth Header Token', regex: /(?<=\b(?:Bearer|Token)\s+)[a-zA-Z0-9_\-\.~+/=]{16,}\b/gi, label: 'SECRET' },
118
+ { 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,}/gi, label: 'SECRET' },
119
+ { name: 'Private Cryptographic Key (PEM Block)', regex: /-----BEGIN (?:RSA |EC |PGP |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY(?: BLOCK)?-----[\s\S]{10,8192}?-----END (?:RSA |EC |PGP |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY(?: BLOCK)?-----/g, label: 'SECRET' },
120
+ { name: 'Private Cryptographic Key (Header)', regex: /-----BEGIN (?:RSA |EC |PGP |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY(?: BLOCK)?-----/g, label: 'SECRET' }
104
121
  ];
105
122
 
106
123
  let LicenseManager;
@@ -209,9 +226,19 @@ function performSanitization(text) {
209
226
  sessionTruncatedLimit = limit;
210
227
  }
211
228
 
229
+ const activeRules = [...customRules];
230
+ DEVOPS_SECRETS_DETECTOR.forEach(rule => {
231
+ activeRules.push({
232
+ name: rule.name,
233
+ pattern: rule.regex.source,
234
+ regex: new RegExp(rule.regex.source, rule.regex.flags),
235
+ label: rule.label || 'SECRET'
236
+ });
237
+ });
238
+
212
239
  let result;
213
240
  try {
214
- result = PrivacyScrubberCore.scrubText(textToScrub, customRules, {}, normalizedProfile, localSessionMap, license.isPro);
241
+ result = PrivacyScrubberCore.scrubText(textToScrub, activeRules, {}, normalizedProfile, localSessionMap, license.isPro);
215
242
  } catch (err) {
216
243
  return text;
217
244
  }
@@ -234,7 +261,7 @@ function performSanitization(text) {
234
261
  }
235
262
  if (isSecret) {
236
263
  hasSecrets = true;
237
- result.scrubbedText = result.scrubbedText.replace(token, '[SECRET_BLOCKED_UPGRADE_TO_PRO]');
264
+ result.scrubbedText = result.scrubbedText.replaceAll(token, '[SECRET_BLOCKED_UPGRADE_TO_PRO]');
238
265
  }
239
266
  });
240
267
 
@@ -279,8 +306,8 @@ function buildCisoAuditTelemetry(currentTokenMap = {}) {
279
306
  frameworksSet.add('CCPA/CPRA');
280
307
  }
281
308
  if (types.includes('ID') || types.includes('SSN') || types.includes('PASSPORT') || types.includes('NATIONAL_ID')) {
282
- frameworksSet.add('SOC 2 Type II');
283
- frameworksSet.add('ISO 27001 (A.8.11)');
309
+ frameworksSet.add('SOC 2 controls');
310
+ frameworksSet.add('ISO 27001 A.8.11 controls');
284
311
  }
285
312
  if (types.some(t => ['CREDIT_CARD', 'BANK', 'IBAN', 'FINANCIAL', 'CARD', 'STRIPE_KEY', 'TAX_ID', 'EIN', 'W2', '1099', 'SALARY', 'WAGE'].includes(t))) {
286
313
  frameworksSet.add('PCI DSS v4.0');
@@ -300,7 +327,7 @@ function buildCisoAuditTelemetry(currentTokenMap = {}) {
300
327
  }
301
328
 
302
329
  const ipHoldings = {
303
- standard: 'IETF draft-sibiryakov-ztds-protocol-00',
330
+ standard: 'IETF draft-sibiryakov-ztds-protocol-01',
304
331
  standardUrl: 'https://datatracker.ietf.org/doc/draft-sibiryakov-ztds-protocol/',
305
332
  patent: "U.S. & Int'l Patents Pending (Paris Convention Priority)",
306
333
  trademark: 'ZTDS™ Reg. #182655957 (ILPO Cl 9 & 42)'
@@ -310,27 +337,27 @@ function buildCisoAuditTelemetry(currentTokenMap = {}) {
310
337
  {
311
338
  statute: 'EU AI Act (Art. 50)',
312
339
  scope: 'AI Transparency & Data Minimization',
313
- rule: '0-byte external network transmission; prevents unauthorized ingestion into frontier models.'
340
+ rule: 'Sanitization runs locally by design; reduces the content sent to external AI models. Informational mapping, not a compliance determination.'
314
341
  },
315
342
  {
316
343
  statute: 'GDPR (Art. 4, 25, 28, 32)',
317
344
  scope: 'Pseudonymization & Processor Liability',
318
- rule: 'Volatile client-side tokenization eliminates GDPR Article 28 data processor liability.'
345
+ rule: 'Client-side pseudonymization is a technical measure supporting Art. 25 and 32. It does not by itself remove controller or processor obligations.'
319
346
  },
320
347
  {
321
348
  statute: 'HIPAA 45 CFR § 164.514(b)',
322
349
  scope: 'Safe Harbor De-Identification',
323
- rule: 'Deterministic removal of all 18 PHI identifiers prior to AI analysis.'
350
+ rule: 'Pattern-based masking of direct identifiers supports Safe Harbor de-identification workflows. Review output for residual identifiers before sharing.'
324
351
  },
325
352
  {
326
353
  statute: 'IRC § 7216 & IRS Pub. 1075',
327
354
  scope: 'Tax Return Information Confidentiality',
328
- rule: 'Strict criminal penalty safeguard against unauthorized disclosure of tax records to cloud LLMs.'
355
+ rule: 'Masking identifiers before AI use can reduce exposure of tax return information. Informational mapping, not legal advice.'
329
356
  },
330
357
  {
331
358
  statute: 'FRE 502',
332
359
  scope: 'Attorney-Client Privilege Preservation',
333
- rule: 'Automated in-memory redaction prevents inadvertent waiver of legal privilege in AI workflows.'
360
+ rule: 'In-memory redaction can reduce disclosure of privileged details in AI workflows. Privilege determinations remain a legal judgment.'
334
361
  }
335
362
  ];
336
363
 
@@ -346,6 +373,54 @@ function buildCisoAuditTelemetry(currentTokenMap = {}) {
346
373
  };
347
374
  }
348
375
 
376
+ // ── PSR-2 Receipt Integrity (real SHA-256; parity with src/modules/receipt-integrity.js) ──
377
+ const PSR2_VERSION = 'PSR-2';
378
+ const PSR2_STATEMENTS = {
379
+ measured: [
380
+ 'Token Manifest SHA-256: digest of the sorted token labels held in this session (original values are never hashed or exported).',
381
+ 'Entity counts per type.',
382
+ 'Receipt SHA-256: digest of the canonical field string, so edits to this receipt are detectable.'
383
+ ],
384
+ byDesign: [
385
+ 'The sanitization engine runs locally in process memory and does not transmit prompt content. This is an architectural property and is not measured by this receipt.',
386
+ 'Token-to-original mappings are held in volatile memory for the session. This receipt does not record their contents.'
387
+ ],
388
+ limits: [
389
+ 'This receipt is not a digital signature and does not prove who generated it or when.',
390
+ 'No final output digest is recorded by this export (Output SHA-256: not recorded).',
391
+ 'Detection is pattern-based. Free-text narrative can contain indirect identifiers that remain after sanitization; review output before sharing.',
392
+ 'Regulatory references are informational technical-safeguard mappings, not a legal determination of compliance.'
393
+ ]
394
+ };
395
+ function psr2Sha256(input) {
396
+ return crypto.createHash('sha256').update(String(input == null ? '' : input), 'utf8').digest('hex');
397
+ }
398
+ function buildPsr2Receipt(tokenMap, timestamp = new Date().toISOString()) {
399
+ const labels = (tokenMap && typeof tokenMap === 'object' ? Object.keys(tokenMap) : []).slice().sort();
400
+ const counts = {};
401
+ for (const t of labels) {
402
+ const m = t.match(/\[([A-Z_]+)_\d+\]/);
403
+ const type = m ? m[1] : 'CUSTOM';
404
+ counts[type] = (counts[type] || 0) + 1;
405
+ }
406
+ const entityCounts = Object.keys(counts).sort().map(k => `${k}=${counts[k]}`).join(',') || 'none';
407
+ const manifestSha256 = psr2Sha256(labels.join('\n'));
408
+ const canonical = [PSR2_VERSION, timestamp, 'none', manifestSha256, entityCounts, 'na'].join('|');
409
+ return { version: PSR2_VERSION, timestamp, outputSha256: null, manifestSha256, entityCounts, tokensTotal: labels.length, tokensPresentInOutput: null, canonical, receiptSha256: psr2Sha256(canonical) };
410
+ }
411
+ function psr2Lines(r) {
412
+ return [
413
+ `Receipt Version: ${r.version}`,
414
+ `Timestamp (UTC): ${r.timestamp}`,
415
+ `Output SHA-256: not recorded`,
416
+ `Token Manifest SHA-256: ${r.manifestSha256}`,
417
+ `Entity Counts: ${r.entityCounts}`,
418
+ `Tokens In Final Output: not recorded`,
419
+ `Canonical: ${r.canonical}`,
420
+ `Receipt SHA-256: ${r.receiptSha256}`
421
+ ];
422
+ }
423
+
349
424
  function emitProofFeedback() {
350
425
  const isQuiet = args.includes("--quiet") || args.includes("-q");
351
426
  if (isQuiet) return;
@@ -504,7 +579,7 @@ if (isDiffMode) {
504
579
  }
505
580
  if (!diffOutput.trim()) {
506
581
  if (isJsonMode) {
507
- console.log(JSON.stringify({ isDiff: true, empty: true, diff: "", count: 0, telemetry: buildCisoAuditTelemetry({}) }, null, 2));
582
+ console.log(JSON.stringify({ isDiff: true, empty: true, diff: "", count: 0, telemetry: buildCisoAuditTelemetry({}), receipt: buildPsr2Receipt({}) }, null, 2));
508
583
  } else {
509
584
  console.log(`[Zero-Trust Agentic Guard: Git Diff]\nNo git diff changes detected (${isStaged ? 'staged' : 'unstaged'}).`);
510
585
  }
@@ -518,7 +593,8 @@ if (isDiffMode) {
518
593
  sanitizedDiff: sanitizedDiff,
519
594
  tokenMap: localSessionMap,
520
595
  count: Object.keys(localSessionMap).length,
521
- telemetry: telemetry
596
+ telemetry: telemetry,
597
+ receipt: buildPsr2Receipt(localSessionMap)
522
598
  }, null, 2));
523
599
  } else {
524
600
  process.stdout.write(sanitizedDiff.endsWith('\n') ? sanitizedDiff : sanitizedDiff + '\n');
@@ -544,7 +620,8 @@ if (hasStdin && !hasCommand && !isRestoreMode && !isDiffMode && !isRulesMode) {
544
620
  scrubbedText: sanitized,
545
621
  tokenMap: localSessionMap,
546
622
  count: Object.keys(localSessionMap).length,
547
- telemetry: telemetry
623
+ telemetry: telemetry,
624
+ receipt: buildPsr2Receipt(localSessionMap)
548
625
  }, null, 2) + '\n');
549
626
  emitProofFeedback();
550
627
  process.exit(0);