ktg-plugin-marketplace/plugins/ms-ai-architect/hooks/scripts/pre-edit-secrets.mjs
Kjell Tore Guttormsen 6a7632146e feat(ms-ai-architect): add plugin to open marketplace (v1.5.0 baseline)
Initial addition of ms-ai-architect plugin to the open-source marketplace.
Private content excluded: orchestrator/ (Linear tooling), docs/utredning/
(client investigation), generated test reports and PDF export script.
skill-gen tooling moved from orchestrator/ to scripts/skill-gen/.

Security scan: WARNING (risk 20/100) — no secrets, no injection found.
False positive fixed: added gitleaks:allow to Python variable reference
in output-validation-grounding-verification.md line 109.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 17:17:17 +02:00

93 lines
3.8 KiB
JavaScript

#!/usr/bin/env node
// pre-edit-secrets.mjs
// Scans file edits for potential secrets before allowing save (cross-platform, no jq dependency)
//
// Exit codes:
// 0 = Allow edit
// 2 = Block edit (secrets detected)
import { appendFileSync, mkdirSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
const input = readFileSync(0, 'utf-8');
const { tool_input } = JSON.parse(input);
const content = tool_input?.content ?? tool_input?.new_string ?? '';
const filePath = tool_input?.file_path ?? tool_input?.path ?? '';
if (!content) process.exit(0);
// Skip test files, mocks, example/template files
if (/\.(test|spec|mock)\.(ts|js|tsx|jsx)$|__tests__|__mocks__/.test(filePath)) {
process.exit(0);
}
if (/\.(example|template|sample)(\..*)?$|\.env\.example|\.env\.template|\.env\.sample/.test(filePath)) {
process.exit(0);
}
// === SECRET PATTERNS ===
const SECRETS = [
// AWS Keys
[/AKIA[0-9A-Z]{16}/, 'AWS Access Key detected in edit.', 'Use environment variables instead.'],
// Generic API Keys
[/(api[_-]?key|apikey)\s*[:=]\s*["'][a-zA-Z0-9]{20,}["']/, 'Potential API key detected.', 'Use environment variables (process.env.API_KEY) instead.'],
// Private keys
[/-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/, 'Private key detected in edit.', 'Never commit private keys. Use environment variables or secret managers.'],
// JWT secrets
[/(jwt[_-]?secret|JWT_SECRET)\s*[:=]\s*["'][^"']{10,}["']/, 'JWT secret detected.', 'Use environment variables instead.'],
// Database connection strings with passwords
[/(postgres|mysql|mongodb):\/\/[^:]+:[^@]+@/, 'Database connection string with credentials detected.', 'Use environment variables for database URLs.'],
// Azure Storage connection strings
[/DefaultEndpointsProtocol=https;AccountName=[^;]+;AccountKey=[A-Za-z0-9+/=]{40,}/, 'Azure Storage connection string with AccountKey detected.', 'Use DefaultAzureCredential or environment variables instead.'],
// Azure AD / Entra client secrets
[/(client[_-]?secret|ClientSecret)\s*[:=]\s*["'][A-Za-z0-9~._-]{34,}["']/, 'Azure AD client secret detected.', 'Use managed identity or environment variables instead.'],
// Azure Cognitive Services / AI Services keys
[/(Ocp-Apim-Subscription-Key|cognitive[_-]?key|ai[_-]?key)\s*[:=]\s*["'][0-9a-f]{32}["']/, 'Azure AI Services key detected.', 'Use DefaultAzureCredential or environment variables instead.'],
// Slack/Discord webhooks
[/https:\/\/hooks\.(slack|discord)\.com\/services\/[A-Za-z0-9/]+/, 'Webhook URL detected.', 'Webhook URLs are secrets. Use environment variables.'],
// GitHub tokens
[/(ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,}/, 'GitHub token detected.', null],
];
for (const [pattern, reason, advice] of SECRETS) {
if (pattern.test(content)) {
process.stderr.write(`BLOCKED: ${reason}\n`);
if (filePath) process.stderr.write(`File: ${filePath}\n`);
if (advice) process.stderr.write(`${advice}\n`);
logBlock('secrets', filePath || 'unknown');
process.exit(2);
}
}
// Generic password assignment (warning only, don't block)
if (/password\s*[:=]\s*["'][^"']{8,}["']/.test(content)) {
if (!/example|placeholder|xxx|your.?password/.test(content)) {
process.stderr.write(`WARNING: Possible hardcoded password detected.\n`);
if (filePath) process.stderr.write(`File: ${filePath}\n`);
process.stderr.write(`Consider using environment variables.\n`);
}
}
process.exit(0);
// --- Audit logging ---
function logBlock(hook, target) {
try {
const home = process.env.HOME || process.env.USERPROFILE || '';
const dir = join(home, '.claude', 'audit');
mkdirSync(dir, { recursive: true });
const ts = new Date().toISOString();
appendFileSync(join(dir, 'blocked.log'), `[${ts}] [${hook}] ${target}\n`);
} catch {
// Audit logging is best-effort
}
}