@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/README.md +50 -11
- package/index.js +881 -79
- package/manifest.json +84 -8
- package/package.json +3 -2
- package/pii-masking-run.js +103 -26
- package/ps-license-manager.cjs +261 -11
- package/ps-license-manager.js +4 -0
- package/ps-pii-engine.cjs +450 -96
- package/ps-pii-engine.js +450 -96
- package/scrubber-core.cjs +31 -25
- package/server.json +27 -4
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.
|
|
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
|
|
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": "
|
|
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": "
|
|
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
|
|
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
|
|
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": "
|
|
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": "
|
|
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
|
+
"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": {
|
package/pii-masking-run.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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,
|
|
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.
|
|
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
|
|
283
|
-
frameworksSet.add('ISO 27001
|
|
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-
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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);
|