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>
93 lines
3.8 KiB
JavaScript
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
|
|
}
|
|
}
|