cto-ai-cli 3.2.0 → 4.0.0

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/govern/audit.ts","../../src/govern/secrets.ts","../../src/engine/graph-utils.ts","../../src/govern/policy.ts","../../src/govern/snapshot.ts","../../src/govern/integrity.ts"],"sourcesContent":["import { randomUUID, createHash } from 'node:crypto';\nimport { readdir, chmod } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { userInfo } from 'node:os';\nimport { homedir } from 'node:os';\nimport type { AuditEntry, AuditAction } from '../types/govern.js';\n\n// ===== AUDIT TRAIL =====\n//\n// Immutable, tamper-evident audit log for all CTO interactions.\n// Each entry is SHA-256 hashed for integrity verification.\n\nconst CTO_DIR = '.cto-ai';\nconst AUDIT_DIR = 'audit';\nconst MAX_ENTRIES_PER_FILE = 500;\n\nfunction getAuditDir(): string {\n return join(homedir(), CTO_DIR, AUDIT_DIR);\n}\n\nfunction getCurrentAuditFile(): string {\n const date = new Date().toISOString().split('T')[0].replace(/-/g, '');\n return join(getAuditDir(), `audit_${date}.json`);\n}\n\nfunction computeIntegrityHash(entry: Omit<AuditEntry, 'integrityHash'>): string {\n const payload = JSON.stringify({\n id: entry.id,\n timestamp: entry.timestamp,\n action: entry.action,\n user: entry.user,\n projectPath: entry.projectPath,\n details: entry.details,\n });\n return createHash('sha256').update(payload).digest('hex');\n}\n\n// ===== HELPERS (inline to avoid cross-layer deps) =====\n\nasync function ensureDir(dirPath: string): Promise<void> {\n const { mkdir } = await import('node:fs/promises');\n await mkdir(dirPath, { recursive: true });\n}\n\nasync function readJSON<T>(filePath: string): Promise<T | null> {\n const { readFile } = await import('node:fs/promises');\n try {\n const content = await readFile(filePath, 'utf-8');\n return JSON.parse(content) as T;\n } catch {\n return null;\n }\n}\n\nasync function writeJSON(filePath: string, data: unknown): Promise<void> {\n const { writeFile } = await import('node:fs/promises');\n await ensureDir(join(filePath, '..'));\n await writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8');\n}\n\n// ===== PUBLIC API =====\n\nexport async function logAudit(\n action: AuditAction,\n projectPath: string,\n details: Record<string, unknown> = {},\n): Promise<AuditEntry> {\n const auditDir = getAuditDir();\n await ensureDir(auditDir);\n\n let currentUser: string;\n try {\n currentUser = userInfo().username;\n } catch {\n currentUser = process.env.USER ?? process.env.USERNAME ?? 'unknown';\n }\n\n const partialEntry = {\n id: randomUUID().substring(0, 12),\n timestamp: new Date(),\n action,\n user: currentUser,\n projectPath,\n details,\n };\n\n const entry: AuditEntry = {\n ...partialEntry,\n integrityHash: computeIntegrityHash(partialEntry),\n };\n\n const auditFile = getCurrentAuditFile();\n let entries = await readJSON<AuditEntry[]>(auditFile) ?? [];\n entries.push(entry);\n\n if (entries.length > MAX_ENTRIES_PER_FILE) {\n entries = entries.slice(-MAX_ENTRIES_PER_FILE);\n }\n\n await writeJSON(auditFile, entries);\n try { await chmod(auditFile, 0o600); } catch { /* ignore on Windows */ }\n\n return entry;\n}\n\nexport async function getAuditEntries(\n options: {\n projectPath?: string;\n action?: AuditAction;\n since?: Date;\n limit?: number;\n } = {},\n): Promise<AuditEntry[]> {\n const auditDir = getAuditDir();\n let files: string[];\n try {\n files = await readdir(auditDir);\n } catch {\n return [];\n }\n\n const auditFiles = files\n .filter((f) => f.startsWith('audit_') && f.endsWith('.json'))\n .sort()\n .reverse();\n\n const allEntries: AuditEntry[] = [];\n const limit = options.limit ?? 100;\n\n for (const file of auditFiles) {\n if (allEntries.length >= limit) break;\n\n const entries = await readJSON<AuditEntry[]>(join(auditDir, file));\n if (!entries) continue;\n\n for (const entry of entries.reverse()) {\n if (allEntries.length >= limit) break;\n if (options.projectPath && entry.projectPath !== options.projectPath) continue;\n if (options.action && entry.action !== options.action) continue;\n if (options.since && new Date(entry.timestamp) < options.since) continue;\n allEntries.push(entry);\n }\n }\n\n return allEntries;\n}\n\nexport function verifyAuditEntry(entry: AuditEntry): boolean {\n const { integrityHash, ...rest } = entry;\n const expected = computeIntegrityHash(rest as Omit<AuditEntry, 'integrityHash'>);\n return expected === integrityHash;\n}\n\nexport async function verifyAuditIntegrity(): Promise<{\n totalEntries: number;\n validEntries: number;\n invalidEntries: AuditEntry[];\n}> {\n const entries = await getAuditEntries({ limit: 10000 });\n const invalidEntries: AuditEntry[] = [];\n\n for (const entry of entries) {\n if (!verifyAuditEntry(entry)) {\n invalidEntries.push(entry);\n }\n }\n\n return {\n totalEntries: entries.length,\n validEntries: entries.length - invalidEntries.length,\n invalidEntries,\n };\n}\n\nexport async function purgeOldAuditEntries(retentionDays: number): Promise<number> {\n const auditDir = getAuditDir();\n let files: string[];\n try {\n files = await readdir(auditDir);\n } catch {\n return 0;\n }\n\n const cutoff = new Date();\n cutoff.setDate(cutoff.getDate() - retentionDays);\n const cutoffStr = cutoff.toISOString().split('T')[0].replace(/-/g, '');\n\n let purged = 0;\n const { unlink } = await import('node:fs/promises');\n\n for (const file of files) {\n if (!file.startsWith('audit_') || !file.endsWith('.json')) continue;\n const dateStr = file.replace('audit_', '').replace('.json', '');\n if (dateStr < cutoffStr) {\n try {\n await unlink(join(auditDir, file));\n purged++;\n } catch { /* ignore */ }\n }\n }\n\n return purged;\n}\n","import { readFile } from 'node:fs/promises';\nimport { resolve, relative } from 'node:path';\nimport type { SecretFinding, SecretType } from '../types/govern.js';\n\n// ===== SECRET DETECTION ENGINE =====\n\ninterface SecretPattern {\n type: SecretType;\n pattern: RegExp;\n severity: SecretFinding['severity'];\n description: string;\n}\n\nconst BUILTIN_PATTERNS: { type: SecretType; source: string; flags: string; severity: SecretFinding['severity']; description: string }[] = [\n // API Keys\n { type: 'api-key', source: '(?:api[_-]?key|apikey)\\\\s*[:=]\\\\s*[\\'\"]?([a-zA-Z0-9_\\\\-]{20,})[\\'\"]?', flags: 'gi', severity: 'critical', description: 'API Key' },\n { type: 'api-key', source: 'sk-[a-zA-Z0-9]{20,}', flags: 'g', severity: 'critical', description: 'OpenAI/Anthropic API Key' },\n { type: 'api-key', source: 'sk-ant-[a-zA-Z0-9\\\\-]{20,}', flags: 'g', severity: 'critical', description: 'Anthropic API Key' },\n\n // AWS\n { type: 'aws-key', source: 'AKIA[0-9A-Z]{16}', flags: 'g', severity: 'critical', description: 'AWS Access Key ID' },\n { type: 'aws-key', source: '(?:aws_secret_access_key|aws_secret)\\\\s*[:=]\\\\s*[\\'\"]?([a-zA-Z0-9/+=]{40})[\\'\"]?', flags: 'gi', severity: 'critical', description: 'AWS Secret Key' },\n\n // Private Keys\n { type: 'private-key', source: '-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----', flags: 'g', severity: 'critical', description: 'Private Key' },\n { type: 'private-key', source: '-----BEGIN OPENSSH PRIVATE KEY-----', flags: 'g', severity: 'critical', description: 'SSH Private Key' },\n\n // Passwords\n { type: 'password', source: '(?:password|passwd|pwd)\\\\s*[:=]\\\\s*[\\'\"]([^\\'\"]{8,})[\\'\"](?!\\\\s*\\\\{)', flags: 'gi', severity: 'high', description: 'Hardcoded Password' },\n { type: 'password', source: '(?:DB_PASSWORD|DATABASE_PASSWORD|MYSQL_PASSWORD|POSTGRES_PASSWORD)\\\\s*[:=]\\\\s*[\\'\"]?([^\\'\"{}\\\\s]{4,})[\\'\"]?', flags: 'gi', severity: 'high', description: 'Database Password' },\n\n // Tokens\n { type: 'token', source: '(?:bearer|token|auth_token|access_token|refresh_token)\\\\s*[:=]\\\\s*[\\'\"]([a-zA-Z0-9_\\\\-.]{20,})[\\'\"](?!\\\\s*\\\\{)', flags: 'gi', severity: 'high', description: 'Auth Token' },\n { type: 'token', source: 'ghp_[a-zA-Z0-9]{36}', flags: 'g', severity: 'critical', description: 'GitHub Personal Access Token' },\n { type: 'token', source: 'gho_[a-zA-Z0-9]{36}', flags: 'g', severity: 'critical', description: 'GitHub OAuth Token' },\n { type: 'token', source: 'glpat-[a-zA-Z0-9\\\\-]{20,}', flags: 'g', severity: 'critical', description: 'GitLab Personal Access Token' },\n { type: 'token', source: 'npm_[a-zA-Z0-9]{36}', flags: 'g', severity: 'high', description: 'npm Token' },\n\n // Connection strings\n { type: 'connection-string', source: '(?:mongodb(?:\\\\+srv)?|postgres(?:ql)?|mysql|redis|amqp):\\\\/\\\\/[^\\\\s\\'\"]+:[^\\\\s\\'\"]+@[^\\\\s\\'\"]+', flags: 'gi', severity: 'critical', description: 'Database Connection String' },\n { type: 'connection-string', source: '(?:DATABASE_URL|REDIS_URL|MONGODB_URI)\\\\s*[:=]\\\\s*[\\'\"]?([^\\\\s\\'\"]{10,})[\\'\"]?', flags: 'gi', severity: 'high', description: 'Database URL' },\n\n // Environment variables with secrets\n { type: 'env-variable', source: '(?:SECRET|PRIVATE|ENCRYPTION)[_-]?(?:KEY|TOKEN|PASS)\\\\s*[:=]\\\\s*[\\'\"]?([^\\\\s\\'\"]{8,})[\\'\"]?', flags: 'gi', severity: 'high', description: 'Secret Environment Variable' },\n\n // Stripe\n { type: 'api-key', source: 'sk_live_[a-zA-Z0-9]{24,}', flags: 'g', severity: 'critical', description: 'Stripe Live Secret Key' },\n { type: 'api-key', source: 'pk_live_[a-zA-Z0-9]{24,}', flags: 'g', severity: 'high', description: 'Stripe Live Publishable Key' },\n { type: 'api-key', source: 'rk_live_[a-zA-Z0-9]{24,}', flags: 'g', severity: 'critical', description: 'Stripe Restricted Key' },\n\n // Slack\n { type: 'token', source: 'xoxb-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24,}', flags: 'g', severity: 'critical', description: 'Slack Bot Token' },\n { type: 'token', source: 'xoxp-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24,}', flags: 'g', severity: 'critical', description: 'Slack User Token' },\n { type: 'api-key', source: 'https://hooks\\\\.slack\\\\.com/services/T[a-zA-Z0-9_]+/B[a-zA-Z0-9_]+/[a-zA-Z0-9_]+', flags: 'g', severity: 'high', description: 'Slack Webhook URL' },\n\n // Google\n { type: 'api-key', source: 'AIza[0-9A-Za-z_-]{35}', flags: 'g', severity: 'high', description: 'Google API Key' },\n { type: 'token', source: 'ya29\\\\.[0-9A-Za-z_-]+', flags: 'g', severity: 'high', description: 'Google OAuth Token' },\n\n // Azure\n { type: 'api-key', source: '(?:AccountKey|SharedAccessKey)\\\\s*=\\\\s*[a-zA-Z0-9+/=]{40,}', flags: 'g', severity: 'critical', description: 'Azure Storage Key' },\n\n // Twilio\n { type: 'api-key', source: 'AC[a-f0-9]{32}', flags: 'g', severity: 'high', description: 'Twilio Account SID' },\n\n // SendGrid\n { type: 'api-key', source: 'SG\\\\.[a-zA-Z0-9_-]{22}\\\\.[a-zA-Z0-9_-]{43}', flags: 'g', severity: 'critical', description: 'SendGrid API Key' },\n\n // JWT\n { type: 'token', source: 'eyJ[a-zA-Z0-9_-]{10,}\\\\.eyJ[a-zA-Z0-9_-]{10,}\\\\.[a-zA-Z0-9_-]{10,}', flags: 'g', severity: 'high', description: 'JSON Web Token' },\n\n // PII\n { type: 'pii', source: '\\\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\\\.[A-Z|a-z]{2,}\\\\b', flags: 'g', severity: 'medium', description: 'Email Address (PII)' },\n { type: 'pii', source: '\\\\b\\\\d{3}[-.]?\\\\d{2}[-.]?\\\\d{4}\\\\b', flags: 'g', severity: 'high', description: 'Possible SSN (PII)' },\n];\n\nfunction buildPatterns(customPatterns: string[] = []): SecretPattern[] {\n const patterns: SecretPattern[] = BUILTIN_PATTERNS.map((def) => ({\n type: def.type,\n pattern: new RegExp(def.source, def.flags),\n severity: def.severity,\n description: def.description,\n }));\n\n for (const custom of customPatterns) {\n try {\n patterns.push({\n type: 'custom',\n pattern: new RegExp(custom, 'gi'),\n severity: 'medium',\n description: `Custom pattern: ${custom}`,\n });\n } catch { /* skip invalid regex */ }\n }\n\n return patterns;\n}\n\nexport function scanContentForSecrets(\n content: string,\n filePath: string,\n customPatterns: string[] = [],\n): SecretFinding[] {\n const findings: SecretFinding[] = [];\n const lines = content.split('\\n');\n const allPatterns = buildPatterns(customPatterns);\n\n for (const secretPattern of allPatterns) {\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n secretPattern.pattern.lastIndex = 0;\n let match: RegExpExecArray | null;\n\n while ((match = secretPattern.pattern.exec(line)) !== null) {\n const matchText = match[0];\n if (isTemplateOrPlaceholder(matchText)) continue;\n\n findings.push({\n type: secretPattern.type,\n file: filePath,\n line: i + 1,\n match: matchText,\n redacted: redactSecret(matchText),\n severity: secretPattern.severity,\n });\n }\n }\n }\n\n return deduplicateFindings(findings);\n}\n\nexport async function scanFileForSecrets(\n filePath: string,\n projectPath: string,\n customPatterns: string[] = [],\n): Promise<SecretFinding[]> {\n try {\n const content = await readFile(filePath, 'utf-8');\n const relPath = relative(resolve(projectPath), resolve(filePath));\n return scanContentForSecrets(content, relPath, customPatterns);\n } catch {\n return [];\n }\n}\n\nexport async function scanProjectForSecrets(\n projectPath: string,\n filePaths: string[],\n customPatterns: string[] = [],\n): Promise<SecretFinding[]> {\n const allFindings: SecretFinding[] = [];\n\n for (const fp of filePaths) {\n const findings = await scanFileForSecrets(fp, projectPath, customPatterns);\n allFindings.push(...findings);\n }\n\n return allFindings.sort((a, b) => {\n const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };\n return severityOrder[a.severity] - severityOrder[b.severity];\n });\n}\n\nexport function sanitizeContent(content: string, customPatterns: string[] = []): string {\n let sanitized = content;\n const allPatterns = buildPatterns(customPatterns);\n\n for (const secretPattern of allPatterns) {\n sanitized = sanitized.replace(secretPattern.pattern, (match) => {\n if (isTemplateOrPlaceholder(match)) return match;\n return redactSecret(match);\n });\n }\n\n return sanitized;\n}\n\nfunction redactSecret(value: string): string {\n if (value.length <= 8) return '***REDACTED***';\n const prefix = value.substring(0, 4);\n const suffix = value.substring(value.length - 2);\n return `${prefix}${'*'.repeat(Math.min(value.length - 6, 20))}${suffix}`;\n}\n\nfunction isTemplateOrPlaceholder(value: string): boolean {\n const placeholders = [\n /\\$\\{.*\\}/, /\\{\\{.*\\}\\}/, /%[sd]/, /<[A-Z_]+>/, /YOUR_.*_HERE/i,\n /\\bCHANGE_ME\\b/i, /\\bPLACEHOLDER\\b/i, /\\bexample\\b/i, /\\bTODO\\b/i, /xxx+/i,\n /\\breplace.?me\\b/i, /\\bdummy\\b/i, /\\btest_?key\\b/i, /\\bsample\\b/i,\n ];\n return placeholders.some((p) => p.test(value));\n}\n\nfunction deduplicateFindings(findings: SecretFinding[]): SecretFinding[] {\n const seen = new Set<string>();\n return findings.filter((f) => {\n const key = `${f.file}:${f.line}:${f.type}:${f.match}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n\n// ===== ENTROPY ANALYSIS =====\n\nfunction shannonEntropy(str: string): number {\n const freq = new Map<string, number>();\n for (const ch of str) {\n freq.set(ch, (freq.get(ch) || 0) + 1);\n }\n let entropy = 0;\n for (const count of freq.values()) {\n const p = count / str.length;\n if (p > 0) entropy -= p * Math.log2(p);\n }\n return entropy;\n}\n\nconst HIGH_ENTROPY_RE = /['\"]([a-zA-Z0-9+/=_\\-]{30,})['\"]|=\\s*['\"]?([a-zA-Z0-9+/=_\\-]{30,})['\"]?/g;\n\nconst ENTROPY_SKIP = [\n /^[a-f0-9]{32,}$/i, // hex hashes\n /^[A-Z_]{30,}$/, // all-caps constants\n /^[a-z_]{30,}$/, // all-lowercase identifiers\n /^[a-zA-Z0-9+/]+=+$/, // base64 padding\n /^[a-z]+[A-Z][a-zA-Z]+$/, // camelCase identifiers\n /sha\\d+-/i, // integrity hashes (sha256-, sha512-)\n];\n\nexport function scanContentForHighEntropy(\n content: string,\n filePath: string,\n threshold: number = 5.0,\n): SecretFinding[] {\n const findings: SecretFinding[] = [];\n const lines = content.split('\\n');\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (line.trim().startsWith('//') || line.trim().startsWith('#') || line.trim().startsWith('*')) continue;\n\n HIGH_ENTROPY_RE.lastIndex = 0;\n let match: RegExpExecArray | null;\n\n while ((match = HIGH_ENTROPY_RE.exec(line)) !== null) {\n const value = match[1] || match[2];\n if (!value || value.length < 40) continue;\n if (isTemplateOrPlaceholder(value)) continue;\n if (ENTROPY_SKIP.some((p) => p.test(value))) continue;\n\n const entropy = shannonEntropy(value);\n if (entropy >= threshold) {\n findings.push({\n type: 'high-entropy',\n file: filePath,\n line: i + 1,\n match: value,\n redacted: redactSecret(value),\n severity: entropy >= 5.0 ? 'high' : 'medium',\n });\n }\n }\n }\n\n return deduplicateFindings(findings);\n}\n\n// ===== FULL PROJECT AUDIT =====\n\nexport interface AuditResult {\n findings: SecretFinding[];\n summary: {\n totalFiles: number;\n filesScanned: number;\n filesWithSecrets: number;\n totalFindings: number;\n bySeverity: { critical: number; high: number; medium: number; low: number };\n byType: Record<string, number>;\n };\n recommendations: string[];\n}\n\nexport async function auditProject(\n projectPath: string,\n filePaths: string[],\n options: { customPatterns?: string[]; entropyThreshold?: number; includePII?: boolean } = {},\n): Promise<AuditResult> {\n const { customPatterns = [], entropyThreshold = 4.5, includePII = true } = options;\n const allFindings: SecretFinding[] = [];\n const filesWithSecrets = new Set<string>();\n\n for (const fp of filePaths) {\n try {\n const content = await readFile(fp, 'utf-8');\n const relPath = relative(resolve(projectPath), resolve(fp));\n\n // Skip test files and declaration files for entropy (too many false positives)\n const isTestFile = /\\.(test|spec|mock)\\.[jt]sx?$/.test(relPath) || relPath.includes('__tests__');\n const isDtsFile = relPath.endsWith('.d.ts');\n\n // Pattern-based detection\n let findings = scanContentForSecrets(content, relPath, customPatterns);\n\n // Filter PII if not wanted\n if (!includePII) {\n findings = findings.filter((f) => f.type !== 'pii');\n }\n\n // Entropy-based detection (skip test/declaration files — too noisy)\n const entropyFindings = (isTestFile || isDtsFile)\n ? []\n : scanContentForHighEntropy(content, relPath, entropyThreshold);\n\n const combined = [...findings, ...entropyFindings];\n if (combined.length > 0) {\n filesWithSecrets.add(relPath);\n allFindings.push(...combined);\n }\n } catch {\n // skip unreadable files\n }\n }\n\n // Sort by severity\n allFindings.sort((a, b) => {\n const order = { critical: 0, high: 1, medium: 2, low: 3 };\n return order[a.severity] - order[b.severity];\n });\n\n // Build summary\n const bySeverity = { critical: 0, high: 0, medium: 0, low: 0 };\n const byType: Record<string, number> = {};\n for (const f of allFindings) {\n bySeverity[f.severity]++;\n byType[f.type] = (byType[f.type] || 0) + 1;\n }\n\n // Generate recommendations\n const recommendations: string[] = [];\n if (bySeverity.critical > 0) {\n recommendations.push('CRITICAL: Rotate all detected credentials immediately. They may already be compromised.');\n }\n if (byType['password'] > 0) {\n recommendations.push('Move passwords to environment variables or a secrets manager (AWS Secrets Manager, Vault, etc.).');\n }\n if (byType['api-key'] > 0 || byType['aws-key'] > 0) {\n recommendations.push('Use environment variables for API keys. Never commit them to source control.');\n }\n if (byType['connection-string'] > 0) {\n recommendations.push('Database connection strings should use environment variables, not hardcoded values.');\n }\n if (byType['private-key'] > 0) {\n recommendations.push('Private keys should NEVER be in source code. Use a key management service.');\n }\n if (byType['pii'] > 0) {\n recommendations.push('PII detected. Review for GDPR/CCPA compliance. Consider data anonymization.');\n }\n if (byType['high-entropy'] > 0) {\n recommendations.push('High-entropy strings detected that may be secrets. Review manually.');\n }\n if (allFindings.length > 0) {\n recommendations.push('Add a .gitignore entry for .env files if not already present.');\n recommendations.push('Run `npx cto-ai-cli --audit` regularly or add to CI pipeline.');\n }\n if (allFindings.length === 0) {\n recommendations.push('No secrets detected. Great job keeping your codebase clean!');\n }\n\n return {\n findings: allFindings,\n summary: {\n totalFiles: filePaths.length,\n filesScanned: filePaths.length,\n filesWithSecrets: filesWithSecrets.size,\n totalFindings: allFindings.length,\n bySeverity,\n byType,\n },\n recommendations,\n };\n}\n","import type { GraphEdge } from '../types/engine.js';\n\n// ===== SHARED GRAPH UTILITIES =====\n\nexport interface AdjacencyList {\n forward: Map<string, string[]>; // file → files it imports\n reverse: Map<string, string[]>; // file → files that import it\n}\n\nexport function buildAdjacencyList(edges: GraphEdge[]): AdjacencyList {\n const forward = new Map<string, string[]>();\n const reverse = new Map<string, string[]>();\n\n for (const edge of edges) {\n if (!forward.has(edge.from)) forward.set(edge.from, []);\n forward.get(edge.from)!.push(edge.to);\n\n if (!reverse.has(edge.to)) reverse.set(edge.to, []);\n reverse.get(edge.to)!.push(edge.from);\n }\n\n return { forward, reverse };\n}\n\nexport function bfsBidirectional(\n seeds: string[],\n adj: AdjacencyList,\n depth: number,\n): Set<string> {\n const result = new Set(seeds);\n let frontier = [...seeds];\n const visited = new Set<string>();\n\n for (let d = 0; d < depth; d++) {\n const nextFrontier: string[] = [];\n\n for (const node of frontier) {\n if (visited.has(node)) continue;\n visited.add(node);\n\n // Forward neighbors (imports)\n const fwd = adj.forward.get(node);\n if (fwd) {\n for (const neighbor of fwd) {\n if (!visited.has(neighbor)) {\n result.add(neighbor);\n nextFrontier.push(neighbor);\n }\n }\n }\n\n // Reverse neighbors (imported by)\n const rev = adj.reverse.get(node);\n if (rev) {\n for (const neighbor of rev) {\n if (!visited.has(neighbor)) {\n result.add(neighbor);\n nextFrontier.push(neighbor);\n }\n }\n }\n }\n\n frontier = nextFrontier;\n }\n\n return result;\n}\n\nexport function matchGlob(path: string, pattern: string): boolean {\n const regexStr = pattern\n .replace(/\\./g, '\\\\.')\n .replace(/\\*\\*/g, '§§')\n .replace(/\\*/g, '[^/]*')\n .replace(/§§/g, '.*')\n .replace(/\\?/g, '.');\n\n try {\n return new RegExp(`^${regexStr}$`).test(path);\n } catch {\n return false;\n }\n}\n","import type {\n PolicySet,\n PolicyRule,\n PolicyRuleType,\n PolicyValidation,\n PolicyViolation,\n PolicyWarning,\n} from '../types/govern.js';\nimport type { ContextSelection, AnalyzedFile } from '../types/engine.js';\nimport { matchGlob } from '../engine/graph-utils.js';\n\n// ===== POLICY ENGINE =====\n//\n// YAML-backed rule engine for controlling what context gets included/excluded.\n// Policies are evaluated after selection to validate compliance.\n\nexport const DEFAULT_POLICY: PolicySet = {\n version: '1.0',\n name: 'default',\n rules: [\n {\n id: 'no-env',\n type: 'exclude-always',\n pattern: '**/*.env*',\n reason: 'Environment files must never be sent to AI',\n enabled: true,\n },\n {\n id: 'no-secrets',\n type: 'secret-block',\n reason: 'Files with detected secrets are blocked',\n enabled: true,\n },\n {\n id: 'min-coverage',\n type: 'coverage-minimum',\n threshold: 70,\n reason: 'Warn if context coverage drops below 70%',\n enabled: true,\n },\n ],\n};\n\n// ===== VALIDATION =====\n\nexport function validateSelection(\n selection: ContextSelection,\n policies: PolicySet,\n allFiles?: AnalyzedFile[],\n): PolicyValidation {\n const violations: PolicyViolation[] = [];\n const warnings: PolicyWarning[] = [];\n\n const includedPaths = new Set(selection.files.map((f) => f.relativePath));\n\n for (const rule of policies.rules) {\n if (!rule.enabled) continue;\n\n switch (rule.type) {\n case 'exclude-always': {\n if (!rule.pattern) break;\n const violatingFiles = selection.files.filter((f) =>\n matchGlob(f.relativePath, rule.pattern!),\n );\n for (const f of violatingFiles) {\n violations.push({\n rule,\n message: `File \"${f.relativePath}\" is included but matches exclude-always pattern \"${rule.pattern}\"`,\n severity: 'error',\n });\n }\n break;\n }\n\n case 'include-always': {\n if (!rule.pattern || !allFiles) break;\n const requiredFiles = allFiles.filter((f) =>\n matchGlob(f.relativePath, rule.pattern!),\n );\n for (const f of requiredFiles) {\n if (!includedPaths.has(f.relativePath)) {\n violations.push({\n rule,\n message: `File \"${f.relativePath}\" matches include-always pattern \"${rule.pattern}\" but is not included`,\n severity: 'warning',\n });\n }\n }\n break;\n }\n\n case 'coverage-minimum': {\n const threshold = rule.threshold ?? 70;\n if (selection.coverage.score < threshold) {\n warnings.push({\n rule,\n message: `Coverage ${selection.coverage.score}% is below minimum ${threshold}%`,\n currentValue: selection.coverage.score,\n threshold,\n });\n }\n break;\n }\n\n case 'risk-maximum': {\n const threshold = rule.threshold ?? 50;\n if (selection.riskScore > threshold) {\n warnings.push({\n rule,\n message: `Exclusion risk ${selection.riskScore}/100 exceeds maximum ${threshold}`,\n currentValue: selection.riskScore,\n threshold,\n });\n }\n break;\n }\n\n case 'budget-limit': {\n if (!rule.category || !rule.threshold) break;\n const categoryFiles = selection.files.filter((f) =>\n fileMatchesCategory(f.relativePath, rule.category!),\n );\n const categoryTokens = categoryFiles.reduce((s, f) => s + f.tokens, 0);\n const categoryPercent = selection.totalTokens > 0\n ? (categoryTokens / selection.totalTokens) * 100\n : 0;\n\n if (categoryPercent > rule.threshold) {\n warnings.push({\n rule,\n message: `Category \"${rule.category}\" uses ${Math.round(categoryPercent)}% of budget (max: ${rule.threshold}%)`,\n currentValue: Math.round(categoryPercent),\n threshold: rule.threshold,\n });\n }\n break;\n }\n }\n }\n\n return {\n passed: violations.filter((v) => v.severity === 'error').length === 0,\n violations,\n warnings,\n };\n}\n\n// ===== POLICY CRUD =====\n\nexport function addRule(policies: PolicySet, rule: PolicyRule): PolicySet {\n return {\n ...policies,\n rules: [...policies.rules, rule],\n };\n}\n\nexport function removeRule(policies: PolicySet, ruleId: string): PolicySet {\n return {\n ...policies,\n rules: policies.rules.filter((r) => r.id !== ruleId),\n };\n}\n\nexport function toggleRule(policies: PolicySet, ruleId: string, enabled: boolean): PolicySet {\n return {\n ...policies,\n rules: policies.rules.map((r) =>\n r.id === ruleId ? { ...r, enabled } : r,\n ),\n };\n}\n\n// ===== HELPERS =====\n\nfunction fileMatchesCategory(path: string, category: string): boolean {\n switch (category) {\n case 'test':\n return /\\.(test|spec)\\.[jt]sx?$/.test(path) || /\\/__tests__\\//.test(path) || /\\/tests?\\//.test(path);\n case 'config':\n return /\\.(config|rc)\\.[jt]s$/.test(path) || /\\.json$/.test(path) || /\\.ya?ml$/.test(path);\n case 'docs':\n return /\\.(md|txt|rst)$/.test(path);\n case 'types':\n return /types?\\//i.test(path) || /\\.d\\.ts$/.test(path);\n default:\n return path.includes(category);\n }\n}\n","import { randomUUID, createHash } from 'node:crypto';\nimport { readFile } from 'node:fs/promises';\nimport type {\n ContextSnapshot,\n SnapshotFile,\n SnapshotVerification,\n} from '../types/govern.js';\nimport type { ContextSelection, ProjectAnalysis } from '../types/engine.js';\n\n// ===== CONTEXT SNAPSHOTS =====\n//\n// Reproducible, verifiable snapshots of context selections.\n// Used for: deterministic mode, audit evidence, before/after comparisons.\n\nexport function createSnapshot(\n name: string,\n analysis: ProjectAnalysis,\n selection: ContextSelection,\n metadata: Record<string, unknown> = {},\n): ContextSnapshot {\n const files: SnapshotFile[] = selection.files.map((f) => ({\n relativePath: f.relativePath,\n hash: hashString(`${f.relativePath}:${f.tokens}:${f.pruneLevel}`),\n tokens: f.tokens,\n pruneLevel: f.pruneLevel,\n }));\n\n const snapshotData = files\n .map((f) => `${f.relativePath}:${f.hash}:${f.pruneLevel}`)\n .sort()\n .join('|');\n\n return {\n id: randomUUID().substring(0, 8),\n name,\n createdAt: new Date(),\n hash: hashString(snapshotData),\n projectHash: analysis.hash,\n analysisHash: analysis.hash,\n selectionHash: selection.hash,\n files,\n totalTokens: selection.totalTokens,\n coverageScore: selection.coverage.score,\n riskScore: selection.riskScore,\n metadata,\n };\n}\n\nexport async function verifySnapshot(\n snapshot: ContextSnapshot,\n currentAnalysis: ProjectAnalysis,\n currentSelection: ContextSelection,\n): Promise<SnapshotVerification> {\n const currentFiles = new Map(\n currentSelection.files.map((f) => [f.relativePath, f]),\n );\n\n let filesMatched = 0;\n const filesMissing: string[] = [];\n const filesChanged: string[] = [];\n\n for (const snapFile of snapshot.files) {\n const current = currentFiles.get(snapFile.relativePath);\n\n if (!current) {\n filesMissing.push(snapFile.relativePath);\n continue;\n }\n\n const currentHash = hashString(\n `${current.relativePath}:${current.tokens}:${current.pruneLevel}`,\n );\n\n if (currentHash === snapFile.hash) {\n filesMatched++;\n } else {\n filesChanged.push(snapFile.relativePath);\n }\n }\n\n // Verify overall hash\n const currentSnapshotData = snapshot.files\n .map((f) => {\n const current = currentFiles.get(f.relativePath);\n if (!current) return `${f.relativePath}:MISSING:MISSING`;\n return `${current.relativePath}:${hashString(`${current.relativePath}:${current.tokens}:${current.pruneLevel}`)}:${current.pruneLevel}`;\n })\n .sort()\n .join('|');\n\n const currentHash = hashString(currentSnapshotData);\n const integrityOk = currentHash === snapshot.hash && filesMissing.length === 0 && filesChanged.length === 0;\n\n return {\n valid: integrityOk,\n snapshotId: snapshot.id,\n filesChecked: snapshot.files.length,\n filesMatched,\n filesMissing,\n filesChanged,\n integrityOk,\n };\n}\n\nexport function compareSnapshots(\n older: ContextSnapshot,\n newer: ContextSnapshot,\n): {\n added: string[];\n removed: string[];\n changed: string[];\n tokenDelta: number;\n coverageDelta: number;\n riskDelta: number;\n} {\n const olderFiles = new Map(older.files.map((f) => [f.relativePath, f]));\n const newerFiles = new Map(newer.files.map((f) => [f.relativePath, f]));\n\n const added: string[] = [];\n const removed: string[] = [];\n const changed: string[] = [];\n\n for (const [path, file] of newerFiles) {\n const old = olderFiles.get(path);\n if (!old) {\n added.push(path);\n } else if (old.hash !== file.hash) {\n changed.push(path);\n }\n }\n\n for (const path of olderFiles.keys()) {\n if (!newerFiles.has(path)) {\n removed.push(path);\n }\n }\n\n return {\n added,\n removed,\n changed,\n tokenDelta: newer.totalTokens - older.totalTokens,\n coverageDelta: newer.coverageScore - older.coverageScore,\n riskDelta: newer.riskScore - older.riskScore,\n };\n}\n\n// ===== HELPERS =====\n\nfunction hashString(input: string): string {\n return createHash('sha256').update(input).digest('hex').substring(0, 16);\n}\n","import { createHash } from 'node:crypto';\nimport { readFile, chmod, readdir, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { IntegrityManifest, IntegrityEntry } from '../types/govern.js';\n\n// ===== INTEGRITY VERIFICATION =====\n//\n// SHA-256 hash-based integrity checking for CTO artifacts.\n// Detects tampering of snapshots, audit logs, configs, and policies.\n\nexport function hashContent(content: Buffer | string): string {\n return createHash('sha256').update(content).digest('hex');\n}\n\nexport async function hashFile(filePath: string): Promise<string | null> {\n try {\n const content = await readFile(filePath);\n return hashContent(content);\n } catch {\n return null;\n }\n}\n\nexport async function buildManifest(\n projectDir: string,\n): Promise<IntegrityManifest> {\n const entries: IntegrityEntry[] = [];\n\n async function scanDir(dir: string, type: IntegrityEntry['type']): Promise<void> {\n let files: string[];\n try {\n files = await readdir(dir);\n } catch {\n return;\n }\n\n for (const file of files) {\n const fullPath = join(dir, file);\n try {\n const fileStat = await stat(fullPath);\n if (fileStat.isFile()) {\n const hash = await hashFile(fullPath);\n if (hash) {\n entries.push({\n filePath: fullPath,\n hash,\n size: fileStat.size,\n createdAt: fileStat.mtime,\n type,\n });\n }\n }\n } catch { /* skip */ }\n }\n }\n\n // Scan known CTO directories\n await scanDir(join(projectDir, 'snapshots'), 'snapshot');\n await scanDir(join(projectDir, 'audit'), 'audit');\n await scanDir(projectDir, 'config');\n\n return {\n version: '2.0',\n createdAt: new Date(),\n entries,\n };\n}\n\nexport async function verifyManifest(\n manifest: IntegrityManifest,\n): Promise<{\n totalFiles: number;\n validFiles: number;\n invalidFiles: string[];\n missingFiles: string[];\n}> {\n const invalidFiles: string[] = [];\n const missingFiles: string[] = [];\n\n for (const entry of manifest.entries) {\n const currentHash = await hashFile(entry.filePath);\n\n if (currentHash === null) {\n missingFiles.push(entry.filePath);\n } else if (currentHash !== entry.hash) {\n invalidFiles.push(entry.filePath);\n }\n }\n\n return {\n totalFiles: manifest.entries.length,\n validFiles: manifest.entries.length - invalidFiles.length - missingFiles.length,\n invalidFiles,\n missingFiles,\n };\n}\n\nexport async function securePermissions(dirPath: string): Promise<number> {\n let count = 0;\n\n try {\n await chmod(dirPath, 0o700);\n count++;\n\n const files = await readdir(dirPath);\n for (const file of files) {\n try {\n const fullPath = join(dirPath, file);\n const fileStat = await stat(fullPath);\n if (fileStat.isFile()) {\n await chmod(fullPath, 0o600);\n count++;\n }\n } catch { /* skip */ }\n }\n } catch { /* dir might not exist */ }\n\n return count;\n}\n"],"mappings":"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","names":["readFile","randomUUID","createHash","currentHash","createHash","readFile","chmod","readdir","join"]}
1
+ {"version":3,"sources":["../../src/govern/audit.ts","../../src/govern/secrets.ts","../../src/engine/graph-utils.ts","../../src/govern/policy.ts","../../src/govern/snapshot.ts","../../src/govern/integrity.ts"],"sourcesContent":["import { randomUUID, createHash } from 'node:crypto';\nimport { readdir, chmod } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { userInfo } from 'node:os';\nimport { homedir } from 'node:os';\nimport type { AuditEntry, AuditAction } from '../types/govern.js';\n\n// ===== AUDIT TRAIL =====\n//\n// Immutable, tamper-evident audit log for all CTO interactions.\n// Each entry is SHA-256 hashed for integrity verification.\n\nconst CTO_DIR = '.cto-ai';\nconst AUDIT_DIR = 'audit';\nconst MAX_ENTRIES_PER_FILE = 500;\n\nfunction getAuditDir(): string {\n return join(homedir(), CTO_DIR, AUDIT_DIR);\n}\n\nfunction getCurrentAuditFile(): string {\n const date = new Date().toISOString().split('T')[0].replace(/-/g, '');\n return join(getAuditDir(), `audit_${date}.json`);\n}\n\nfunction computeIntegrityHash(entry: Omit<AuditEntry, 'integrityHash'>): string {\n const payload = JSON.stringify({\n id: entry.id,\n timestamp: entry.timestamp,\n action: entry.action,\n user: entry.user,\n projectPath: entry.projectPath,\n details: entry.details,\n });\n return createHash('sha256').update(payload).digest('hex');\n}\n\n// ===== HELPERS (inline to avoid cross-layer deps) =====\n\nasync function ensureDir(dirPath: string): Promise<void> {\n const { mkdir } = await import('node:fs/promises');\n await mkdir(dirPath, { recursive: true });\n}\n\nasync function readJSON<T>(filePath: string): Promise<T | null> {\n const { readFile } = await import('node:fs/promises');\n try {\n const content = await readFile(filePath, 'utf-8');\n return JSON.parse(content) as T;\n } catch {\n return null;\n }\n}\n\nasync function writeJSON(filePath: string, data: unknown): Promise<void> {\n const { writeFile } = await import('node:fs/promises');\n await ensureDir(join(filePath, '..'));\n await writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8');\n}\n\n// ===== PUBLIC API =====\n\nexport async function logAudit(\n action: AuditAction,\n projectPath: string,\n details: Record<string, unknown> = {},\n): Promise<AuditEntry> {\n const auditDir = getAuditDir();\n await ensureDir(auditDir);\n\n let currentUser: string;\n try {\n currentUser = userInfo().username;\n } catch {\n currentUser = process.env.USER ?? process.env.USERNAME ?? 'unknown';\n }\n\n const partialEntry = {\n id: randomUUID().substring(0, 12),\n timestamp: new Date(),\n action,\n user: currentUser,\n projectPath,\n details,\n };\n\n const entry: AuditEntry = {\n ...partialEntry,\n integrityHash: computeIntegrityHash(partialEntry),\n };\n\n const auditFile = getCurrentAuditFile();\n let entries = await readJSON<AuditEntry[]>(auditFile) ?? [];\n entries.push(entry);\n\n if (entries.length > MAX_ENTRIES_PER_FILE) {\n entries = entries.slice(-MAX_ENTRIES_PER_FILE);\n }\n\n await writeJSON(auditFile, entries);\n try { await chmod(auditFile, 0o600); } catch { /* ignore on Windows */ }\n\n return entry;\n}\n\nexport async function getAuditEntries(\n options: {\n projectPath?: string;\n action?: AuditAction;\n since?: Date;\n limit?: number;\n } = {},\n): Promise<AuditEntry[]> {\n const auditDir = getAuditDir();\n let files: string[];\n try {\n files = await readdir(auditDir);\n } catch {\n return [];\n }\n\n const auditFiles = files\n .filter((f) => f.startsWith('audit_') && f.endsWith('.json'))\n .sort()\n .reverse();\n\n const allEntries: AuditEntry[] = [];\n const limit = options.limit ?? 100;\n\n for (const file of auditFiles) {\n if (allEntries.length >= limit) break;\n\n const entries = await readJSON<AuditEntry[]>(join(auditDir, file));\n if (!entries) continue;\n\n for (const entry of entries.reverse()) {\n if (allEntries.length >= limit) break;\n if (options.projectPath && entry.projectPath !== options.projectPath) continue;\n if (options.action && entry.action !== options.action) continue;\n if (options.since && new Date(entry.timestamp) < options.since) continue;\n allEntries.push(entry);\n }\n }\n\n return allEntries;\n}\n\nexport function verifyAuditEntry(entry: AuditEntry): boolean {\n const { integrityHash, ...rest } = entry;\n const expected = computeIntegrityHash(rest as Omit<AuditEntry, 'integrityHash'>);\n return expected === integrityHash;\n}\n\nexport async function verifyAuditIntegrity(): Promise<{\n totalEntries: number;\n validEntries: number;\n invalidEntries: AuditEntry[];\n}> {\n const entries = await getAuditEntries({ limit: 10000 });\n const invalidEntries: AuditEntry[] = [];\n\n for (const entry of entries) {\n if (!verifyAuditEntry(entry)) {\n invalidEntries.push(entry);\n }\n }\n\n return {\n totalEntries: entries.length,\n validEntries: entries.length - invalidEntries.length,\n invalidEntries,\n };\n}\n\nexport async function purgeOldAuditEntries(retentionDays: number): Promise<number> {\n const auditDir = getAuditDir();\n let files: string[];\n try {\n files = await readdir(auditDir);\n } catch {\n return 0;\n }\n\n const cutoff = new Date();\n cutoff.setDate(cutoff.getDate() - retentionDays);\n const cutoffStr = cutoff.toISOString().split('T')[0].replace(/-/g, '');\n\n let purged = 0;\n const { unlink } = await import('node:fs/promises');\n\n for (const file of files) {\n if (!file.startsWith('audit_') || !file.endsWith('.json')) continue;\n const dateStr = file.replace('audit_', '').replace('.json', '');\n if (dateStr < cutoffStr) {\n try {\n await unlink(join(auditDir, file));\n purged++;\n } catch { /* ignore */ }\n }\n }\n\n return purged;\n}\n","import { readFile, writeFile } from 'node:fs/promises';\nimport { readFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { resolve, relative, join, dirname } from 'node:path';\nimport { createHash } from 'node:crypto';\nimport type { SecretFinding, SecretType } from '../types/govern.js';\n\n// ===== SECRET DETECTION ENGINE =====\n\ninterface SecretPattern {\n type: SecretType;\n pattern: RegExp;\n severity: SecretFinding['severity'];\n description: string;\n}\n\nconst BUILTIN_PATTERNS: { type: SecretType; source: string; flags: string; severity: SecretFinding['severity']; description: string }[] = [\n // API Keys\n { type: 'api-key', source: '(?:api[_-]?key|apikey)\\\\s*[:=]\\\\s*[\\'\"]?([a-zA-Z0-9_\\\\-]{20,})[\\'\"]?', flags: 'gi', severity: 'critical', description: 'API Key' },\n { type: 'api-key', source: 'sk-[a-zA-Z0-9]{20,}', flags: 'g', severity: 'critical', description: 'OpenAI/Anthropic API Key' },\n { type: 'api-key', source: 'sk-ant-[a-zA-Z0-9\\\\-]{20,}', flags: 'g', severity: 'critical', description: 'Anthropic API Key' },\n\n // AWS\n { type: 'aws-key', source: 'AKIA[0-9A-Z]{16}', flags: 'g', severity: 'critical', description: 'AWS Access Key ID' },\n { type: 'aws-key', source: '(?:aws_secret_access_key|aws_secret)\\\\s*[:=]\\\\s*[\\'\"]?([a-zA-Z0-9/+=]{40})[\\'\"]?', flags: 'gi', severity: 'critical', description: 'AWS Secret Key' },\n\n // Private Keys\n { type: 'private-key', source: '-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----', flags: 'g', severity: 'critical', description: 'Private Key' },\n { type: 'private-key', source: '-----BEGIN OPENSSH PRIVATE KEY-----', flags: 'g', severity: 'critical', description: 'SSH Private Key' },\n\n // Passwords\n { type: 'password', source: '(?:password|passwd|pwd)\\\\s*[:=]\\\\s*[\\'\"]([^\\'\"]{8,})[\\'\"](?!\\\\s*\\\\{)', flags: 'gi', severity: 'high', description: 'Hardcoded Password' },\n { type: 'password', source: '(?:DB_PASSWORD|DATABASE_PASSWORD|MYSQL_PASSWORD|POSTGRES_PASSWORD)\\\\s*[:=]\\\\s*[\\'\"]?([^\\'\"{}\\\\s]{4,})[\\'\"]?', flags: 'gi', severity: 'high', description: 'Database Password' },\n\n // Tokens\n { type: 'token', source: '(?:bearer|token|auth_token|access_token|refresh_token)\\\\s*[:=]\\\\s*[\\'\"]([a-zA-Z0-9_\\\\-.]{20,})[\\'\"](?!\\\\s*\\\\{)', flags: 'gi', severity: 'high', description: 'Auth Token' },\n { type: 'token', source: 'ghp_[a-zA-Z0-9]{36}', flags: 'g', severity: 'critical', description: 'GitHub Personal Access Token' },\n { type: 'token', source: 'gho_[a-zA-Z0-9]{36}', flags: 'g', severity: 'critical', description: 'GitHub OAuth Token' },\n { type: 'token', source: 'glpat-[a-zA-Z0-9\\\\-]{20,}', flags: 'g', severity: 'critical', description: 'GitLab Personal Access Token' },\n { type: 'token', source: 'npm_[a-zA-Z0-9]{36}', flags: 'g', severity: 'high', description: 'npm Token' },\n\n // Connection strings\n { type: 'connection-string', source: '(?:mongodb(?:\\\\+srv)?|postgres(?:ql)?|mysql|redis|amqp):\\\\/\\\\/[^\\\\s\\'\"]+:[^\\\\s\\'\"]+@[^\\\\s\\'\"]+', flags: 'gi', severity: 'critical', description: 'Database Connection String' },\n { type: 'connection-string', source: '(?:DATABASE_URL|REDIS_URL|MONGODB_URI)\\\\s*[:=]\\\\s*[\\'\"]?([^\\\\s\\'\"]{10,})[\\'\"]?', flags: 'gi', severity: 'high', description: 'Database URL' },\n\n // Environment variables with secrets\n { type: 'env-variable', source: '(?:SECRET|PRIVATE|ENCRYPTION)[_-]?(?:KEY|TOKEN|PASS)\\\\s*[:=]\\\\s*[\\'\"]?([^\\\\s\\'\"]{8,})[\\'\"]?', flags: 'gi', severity: 'high', description: 'Secret Environment Variable' },\n\n // Stripe\n { type: 'api-key', source: 'sk_live_[a-zA-Z0-9]{24,}', flags: 'g', severity: 'critical', description: 'Stripe Live Secret Key' },\n { type: 'api-key', source: 'pk_live_[a-zA-Z0-9]{24,}', flags: 'g', severity: 'high', description: 'Stripe Live Publishable Key' },\n { type: 'api-key', source: 'rk_live_[a-zA-Z0-9]{24,}', flags: 'g', severity: 'critical', description: 'Stripe Restricted Key' },\n\n // Slack\n { type: 'token', source: 'xoxb-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24,}', flags: 'g', severity: 'critical', description: 'Slack Bot Token' },\n { type: 'token', source: 'xoxp-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{24,}', flags: 'g', severity: 'critical', description: 'Slack User Token' },\n { type: 'api-key', source: 'https://hooks\\\\.slack\\\\.com/services/T[a-zA-Z0-9_]+/B[a-zA-Z0-9_]+/[a-zA-Z0-9_]+', flags: 'g', severity: 'high', description: 'Slack Webhook URL' },\n\n // Google\n { type: 'api-key', source: 'AIza[0-9A-Za-z_-]{35}', flags: 'g', severity: 'high', description: 'Google API Key' },\n { type: 'token', source: 'ya29\\\\.[0-9A-Za-z_-]+', flags: 'g', severity: 'high', description: 'Google OAuth Token' },\n\n // Azure\n { type: 'api-key', source: '(?:AccountKey|SharedAccessKey)\\\\s*=\\\\s*[a-zA-Z0-9+/=]{40,}', flags: 'g', severity: 'critical', description: 'Azure Storage Key' },\n\n // Twilio\n { type: 'api-key', source: 'AC[a-f0-9]{32}', flags: 'g', severity: 'high', description: 'Twilio Account SID' },\n\n // SendGrid\n { type: 'api-key', source: 'SG\\\\.[a-zA-Z0-9_-]{22}\\\\.[a-zA-Z0-9_-]{43}', flags: 'g', severity: 'critical', description: 'SendGrid API Key' },\n\n // JWT\n { type: 'token', source: 'eyJ[a-zA-Z0-9_-]{10,}\\\\.eyJ[a-zA-Z0-9_-]{10,}\\\\.[a-zA-Z0-9_-]{10,}', flags: 'g', severity: 'high', description: 'JSON Web Token' },\n\n // Datadog\n { type: 'api-key', source: '(?:DD_API_KEY|DATADOG_API_KEY)\\\\s*[:=]\\\\s*[\\'\"]?([a-f0-9]{32})[\\'\"]?', flags: 'gi', severity: 'critical', description: 'Datadog API Key' },\n { type: 'api-key', source: '(?:DD_APP_KEY|DATADOG_APP_KEY)\\\\s*[:=]\\\\s*[\\'\"]?([a-f0-9]{40})[\\'\"]?', flags: 'gi', severity: 'critical', description: 'Datadog App Key' },\n\n // Sentry\n { type: 'connection-string', source: 'https://[a-f0-9]{32}@[a-z0-9]+\\\\.ingest\\\\.sentry\\\\.io/[0-9]+', flags: 'g', severity: 'high', description: 'Sentry DSN' },\n\n // Firebase\n { type: 'api-key', source: '(?:FIREBASE_API_KEY|FIREBASE_KEY)\\\\s*[:=]\\\\s*[\\'\"]?([a-zA-Z0-9_\\\\-]{30,})[\\'\"]?', flags: 'gi', severity: 'high', description: 'Firebase API Key' },\n { type: 'connection-string', source: 'firebase[a-z]*:\\\\/\\\\/[^\\\\s\\'\"]+', flags: 'gi', severity: 'high', description: 'Firebase URL' },\n\n // Supabase\n { type: 'api-key', source: 'sbp_[a-f0-9]{40}', flags: 'g', severity: 'critical', description: 'Supabase Service Key' },\n { type: 'token', source: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\\\\.[a-zA-Z0-9_-]{20,}\\\\.[a-zA-Z0-9_-]{20,}', flags: 'g', severity: 'high', description: 'Supabase Anon/Service JWT' },\n\n // Vercel\n { type: 'token', source: '(?:VERCEL_TOKEN|VERCEL_API_TOKEN)\\\\s*[:=]\\\\s*[\\'\"]?([a-zA-Z0-9]{24,})[\\'\"]?', flags: 'gi', severity: 'critical', description: 'Vercel Token' },\n\n // Heroku\n { type: 'api-key', source: '(?:HEROKU_API_KEY|HEROKU_TOKEN)\\\\s*[:=]\\\\s*[\\'\"]?([a-f0-9\\\\-]{36,})[\\'\"]?', flags: 'gi', severity: 'critical', description: 'Heroku API Key' },\n\n // DigitalOcean\n { type: 'token', source: 'dop_v1_[a-f0-9]{64}', flags: 'g', severity: 'critical', description: 'DigitalOcean Personal Access Token' },\n { type: 'token', source: 'doo_v1_[a-f0-9]{64}', flags: 'g', severity: 'critical', description: 'DigitalOcean OAuth Token' },\n\n // Mailgun\n { type: 'api-key', source: 'key-[a-zA-Z0-9]{32}', flags: 'g', severity: 'high', description: 'Mailgun API Key' },\n\n // PII\n { type: 'pii', source: '\\\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\\\.[A-Z|a-z]{2,}\\\\b', flags: 'g', severity: 'medium', description: 'Email Address (PII)' },\n { type: 'pii', source: '\\\\b(?!000|666|9\\\\d{2})(\\\\d{3})[-.]?(?!00)(\\\\d{2})[-.]?(?!0000)(\\\\d{4})\\\\b', flags: 'g', severity: 'high', description: 'Possible SSN (PII)' },\n];\n\n// Pre-compiled builtin patterns (compiled once, reused across all calls)\nlet _cachedBuiltinPatterns: SecretPattern[] | null = null;\n\nfunction getBuiltinPatterns(): SecretPattern[] {\n if (!_cachedBuiltinPatterns) {\n _cachedBuiltinPatterns = BUILTIN_PATTERNS.map((def) => ({\n type: def.type,\n pattern: new RegExp(def.source, def.flags),\n severity: def.severity,\n description: def.description,\n }));\n }\n return _cachedBuiltinPatterns;\n}\n\nfunction buildPatterns(customPatterns: string[] = []): SecretPattern[] {\n const builtins = getBuiltinPatterns();\n if (customPatterns.length === 0) return builtins;\n\n // Only create new array when custom patterns exist\n const patterns = [...builtins];\n for (const custom of customPatterns) {\n try {\n patterns.push({\n type: 'custom',\n pattern: new RegExp(custom, 'gi'),\n severity: 'medium',\n description: `Custom pattern: ${custom}`,\n });\n } catch { /* skip invalid regex */ }\n }\n\n return patterns;\n}\n\nexport function scanContentForSecrets(\n content: string,\n filePath: string,\n customPatterns: string[] = [],\n extraPiiSafeDomains?: Set<string>,\n): SecretFinding[] {\n const findings: SecretFinding[] = [];\n const lines = content.split('\\n');\n const allPatterns = buildPatterns(customPatterns);\n\n for (const secretPattern of allPatterns) {\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n secretPattern.pattern.lastIndex = 0;\n let match: RegExpExecArray | null;\n\n while ((match = secretPattern.pattern.exec(line)) !== null) {\n const matchText = match[0];\n if (isTemplateOrPlaceholder(matchText)) continue;\n if (secretPattern.type === 'pii' && isSafeEmail(matchText, extraPiiSafeDomains)) continue;\n\n findings.push({\n type: secretPattern.type,\n file: filePath,\n line: i + 1,\n match: matchText,\n redacted: redactSecret(matchText),\n severity: secretPattern.severity,\n });\n }\n }\n }\n\n return deduplicateFindings(findings);\n}\n\nexport async function scanFileForSecrets(\n filePath: string,\n projectPath: string,\n customPatterns: string[] = [],\n): Promise<SecretFinding[]> {\n try {\n const content = await readFile(filePath, 'utf-8');\n const relPath = relative(resolve(projectPath), resolve(filePath));\n return scanContentForSecrets(content, relPath, customPatterns);\n } catch {\n return [];\n }\n}\n\nexport async function scanProjectForSecrets(\n projectPath: string,\n filePaths: string[],\n customPatterns: string[] = [],\n): Promise<SecretFinding[]> {\n const allFindings: SecretFinding[] = [];\n\n for (const fp of filePaths) {\n const findings = await scanFileForSecrets(fp, projectPath, customPatterns);\n allFindings.push(...findings);\n }\n\n return allFindings.sort((a, b) => {\n const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };\n return severityOrder[a.severity] - severityOrder[b.severity];\n });\n}\n\nexport function sanitizeContent(content: string, customPatterns: string[] = []): string {\n let sanitized = content;\n const allPatterns = buildPatterns(customPatterns);\n\n for (const secretPattern of allPatterns) {\n sanitized = sanitized.replace(secretPattern.pattern, (match) => {\n if (isTemplateOrPlaceholder(match)) return match;\n return redactSecret(match);\n });\n }\n\n return sanitized;\n}\n\nfunction redactSecret(value: string): string {\n if (value.length <= 8) return '***REDACTED***';\n const prefix = value.substring(0, 4);\n const suffix = value.substring(value.length - 2);\n return `${prefix}${'*'.repeat(Math.min(value.length - 6, 20))}${suffix}`;\n}\n\nfunction isTemplateOrPlaceholder(value: string): boolean {\n const placeholders = [\n /\\$\\{.*\\}/, /\\{\\{.*\\}\\}/, /%[sd]/, /<[A-Z_]+>/, /YOUR_.*_HERE/i,\n /\\bCHANGE_ME\\b/i, /\\bPLACEHOLDER\\b/i, /\\bexample\\b/i, /\\bTODO\\b/i, /xxx+/i,\n /\\breplace.?me\\b/i, /\\bdummy\\b/i, /\\btest_?key\\b/i, /\\bsample\\b/i,\n ];\n return placeholders.some((p) => p.test(value));\n}\n\n// ===== PII SAFE DOMAIN LIST =====\n// Emails at these domains are not flagged as PII (too many false positives)\n\nconst PII_SAFE_EMAIL_DOMAINS = new Set([\n 'example.com', 'example.org', 'example.net',\n 'test.com', 'test.org', 'test.net',\n 'localhost', 'localhost.localdomain',\n 'email.com', 'mail.com',\n 'foo.com', 'bar.com', 'baz.com',\n 'acme.com', 'company.com', 'corp.com',\n 'noreply.com', 'no-reply.com',\n 'users.noreply.github.com',\n 'placeholder.com',\n]);\n\nfunction isSafeEmail(value: string, extraDomains?: Set<string>): boolean {\n const match = value.match(/@([a-zA-Z0-9.-]+)$/);\n if (!match) return false;\n const domain = match[1].toLowerCase();\n if (PII_SAFE_EMAIL_DOMAINS.has(domain)) return true;\n if (extraDomains && extraDomains.has(domain)) return true;\n return false;\n}\n\nfunction deduplicateFindings(findings: SecretFinding[]): SecretFinding[] {\n const seen = new Set<string>();\n return findings.filter((f) => {\n const key = `${f.file}:${f.line}:${f.type}:${f.match}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n\n// ===== ALLOWLIST — Mark findings as reviewed/safe =====\n\nexport interface AllowlistEntry {\n fingerprint: string; // sha256 of file:type:match\n file: string;\n type: string;\n redacted: string;\n reason: string;\n reviewedBy: string;\n reviewedAt: string;\n}\n\nfunction fingerprintFinding(f: SecretFinding): string {\n return createHash('sha256').update(`${f.file}:${f.type}:${f.match}`).digest('hex').slice(0, 32);\n}\n\nfunction getAllowlistPath(projectPath: string): string {\n return join(projectPath, '.cto', 'audit', 'allowlist.json');\n}\n\nexport function loadAllowlist(projectPath: string): AllowlistEntry[] {\n const filePath = getAllowlistPath(projectPath);\n if (!existsSync(filePath)) return [];\n try {\n return JSON.parse(readFileSync(filePath, 'utf-8'));\n } catch {\n return [];\n }\n}\n\nexport function saveAllowlist(projectPath: string, entries: AllowlistEntry[]): void {\n const filePath = getAllowlistPath(projectPath);\n mkdirSync(dirname(filePath), { recursive: true });\n writeFileSync(filePath, JSON.stringify(entries, null, 2) + '\\n');\n}\n\nexport function addToAllowlist(\n projectPath: string,\n finding: SecretFinding,\n reason: string,\n reviewedBy: string = 'manual',\n): AllowlistEntry {\n const entries = loadAllowlist(projectPath);\n const entry: AllowlistEntry = {\n fingerprint: fingerprintFinding(finding),\n file: finding.file,\n type: finding.type,\n redacted: finding.redacted,\n reason,\n reviewedBy,\n reviewedAt: new Date().toISOString(),\n };\n\n // Deduplicate by fingerprint\n const existing = entries.findIndex((e) => e.fingerprint === entry.fingerprint);\n if (existing >= 0) {\n entries[existing] = entry;\n } else {\n entries.push(entry);\n }\n\n saveAllowlist(projectPath, entries);\n return entry;\n}\n\nexport function filterByAllowlist(findings: SecretFinding[], projectPath: string): {\n filtered: SecretFinding[];\n allowed: SecretFinding[];\n} {\n const allowlist = loadAllowlist(projectPath);\n if (allowlist.length === 0) return { filtered: findings, allowed: [] };\n\n const allowedFingerprints = new Set(allowlist.map((e) => e.fingerprint));\n const filtered: SecretFinding[] = [];\n const allowed: SecretFinding[] = [];\n\n for (const f of findings) {\n if (allowedFingerprints.has(fingerprintFinding(f))) {\n allowed.push(f);\n } else {\n filtered.push(f);\n }\n }\n\n return { filtered, allowed };\n}\n\n// ===== INCREMENTAL SCANNING — Only scan changed files =====\n\ninterface FileHashMap {\n [relativePath: string]: string; // sha256 of file content\n}\n\nfunction getHashCachePath(projectPath: string): string {\n return join(projectPath, '.cto', 'audit', '.hashcache.json');\n}\n\nfunction loadHashCache(projectPath: string): FileHashMap {\n const filePath = getHashCachePath(projectPath);\n if (!existsSync(filePath)) return {};\n try {\n return JSON.parse(readFileSync(filePath, 'utf-8'));\n } catch {\n return {};\n }\n}\n\nfunction saveHashCache(projectPath: string, cache: FileHashMap): void {\n const filePath = getHashCachePath(projectPath);\n mkdirSync(dirname(filePath), { recursive: true });\n writeFileSync(filePath, JSON.stringify(cache));\n}\n\nfunction hashContent(content: string): string {\n return createHash('sha256').update(content).digest('hex').slice(0, 16);\n}\n\nexport function getChangedFiles(\n projectPath: string,\n filePaths: string[],\n): { changed: string[]; unchanged: string[]; cache: FileHashMap } {\n const oldCache = loadHashCache(projectPath);\n const newCache: FileHashMap = {};\n const changed: string[] = [];\n const unchanged: string[] = [];\n\n for (const fp of filePaths) {\n try {\n const content = readFileSync(fp, 'utf-8');\n const relPath = relative(resolve(projectPath), resolve(fp));\n const hash = hashContent(content);\n newCache[relPath] = hash;\n\n if (oldCache[relPath] === hash) {\n unchanged.push(fp);\n } else {\n changed.push(fp);\n }\n } catch {\n changed.push(fp); // Can't read → scan it\n }\n }\n\n return { changed, unchanged, cache: newCache };\n}\n\n// ===== SEVERITY CONFIGURATION =====\n\nexport interface AuditConfig {\n severityOverrides: Partial<Record<SecretType, SecretFinding['severity']>>;\n piiSafeDomains: string[];\n customPatterns: string[];\n entropyThreshold: number;\n includePII: boolean;\n incrementalScan: boolean;\n}\n\nexport const DEFAULT_AUDIT_CONFIG: AuditConfig = {\n severityOverrides: {},\n piiSafeDomains: [],\n customPatterns: [],\n entropyThreshold: 5.0,\n includePII: true,\n incrementalScan: true,\n};\n\nfunction getAuditConfigPath(projectPath: string): string {\n return join(projectPath, '.cto', 'audit', 'config.json');\n}\n\nexport function loadAuditConfig(projectPath: string): AuditConfig {\n const filePath = getAuditConfigPath(projectPath);\n if (!existsSync(filePath)) return { ...DEFAULT_AUDIT_CONFIG };\n try {\n const loaded = JSON.parse(readFileSync(filePath, 'utf-8'));\n return { ...DEFAULT_AUDIT_CONFIG, ...loaded };\n } catch {\n return { ...DEFAULT_AUDIT_CONFIG };\n }\n}\n\nexport function saveAuditConfig(projectPath: string, config: AuditConfig): void {\n const filePath = getAuditConfigPath(projectPath);\n mkdirSync(dirname(filePath), { recursive: true });\n writeFileSync(filePath, JSON.stringify(config, null, 2) + '\\n');\n}\n\nfunction applySeverityOverrides(\n findings: SecretFinding[],\n overrides: Partial<Record<SecretType, SecretFinding['severity']>>,\n): SecretFinding[] {\n if (Object.keys(overrides).length === 0) return findings;\n return findings.map((f) => {\n const override = overrides[f.type];\n if (override) return { ...f, severity: override };\n return f;\n });\n}\n\n// ===== PRE-COMMIT HOOK GENERATOR =====\n\nexport function generatePreCommitHook(projectPath: string, hookType: 'husky' | 'githooks' = 'husky'): string {\n const hookContent = `#!/bin/sh\n# CTO Secret Detection — Pre-commit hook\n# Auto-generated by: npx cto-ai-cli --audit --init-hook\n# Scans ONLY staged files for secrets before allowing commit.\n\nSTAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)\n\nif [ -z \"$STAGED_FILES\" ]; then\n exit 0\nfi\n\necho \"🔍 CTO: Scanning $(echo \"$STAGED_FILES\" | wc -l | tr -d ' ') staged files for secrets...\"\n\n# Write staged files to temp list\nTMPFILE=$(mktemp)\necho \"$STAGED_FILES\" > \"$TMPFILE\"\n\n# Run audit in CI mode on staged files only\nCI=true npx cto-ai-cli --audit --files \"$TMPFILE\"\nRESULT=$?\n\nrm -f \"$TMPFILE\"\n\nif [ $RESULT -ne 0 ]; then\n echo \"\"\n echo \"❌ Commit blocked: secrets detected in staged files.\"\n echo \" Run 'npx cto-ai-cli --audit' to see details.\"\n echo \" Use allowlist to mark reviewed findings as safe.\"\n echo \"\"\n exit 1\nfi\n\necho \"✅ No secrets detected. Proceeding with commit.\"\n`;\n\n let hookPath: string;\n if (hookType === 'husky') {\n hookPath = join(projectPath, '.husky', 'pre-commit');\n } else {\n hookPath = join(projectPath, '.git', 'hooks', 'pre-commit');\n }\n\n mkdirSync(dirname(hookPath), { recursive: true });\n writeFileSync(hookPath, hookContent, { mode: 0o755 });\n\n return hookPath;\n}\n\n// ===== ENTROPY ANALYSIS =====\n\nfunction shannonEntropy(str: string): number {\n const freq = new Map<string, number>();\n for (const ch of str) {\n freq.set(ch, (freq.get(ch) || 0) + 1);\n }\n let entropy = 0;\n for (const count of freq.values()) {\n const p = count / str.length;\n if (p > 0) entropy -= p * Math.log2(p);\n }\n return entropy;\n}\n\nconst HIGH_ENTROPY_RE = /['\"]([a-zA-Z0-9+/=_\\-]{30,})['\"]|=\\s*['\"]?([a-zA-Z0-9+/=_\\-]{30,})['\"]?/g;\n\nconst ENTROPY_SKIP = [\n /^[a-f0-9]{32,}$/i, // hex hashes\n /^[A-Z_]{30,}$/, // all-caps constants\n /^[a-z_]{30,}$/, // all-lowercase identifiers\n /^[a-zA-Z0-9+/]+=+$/, // base64 padding\n /^[a-z]+[A-Z][a-zA-Z]+$/, // camelCase identifiers\n /sha\\d+-/i, // integrity hashes (sha256-, sha512-)\n];\n\nexport function scanContentForHighEntropy(\n content: string,\n filePath: string,\n threshold: number = 5.0,\n): SecretFinding[] {\n const findings: SecretFinding[] = [];\n const lines = content.split('\\n');\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (line.trim().startsWith('//') || line.trim().startsWith('#') || line.trim().startsWith('*')) continue;\n\n HIGH_ENTROPY_RE.lastIndex = 0;\n let match: RegExpExecArray | null;\n\n while ((match = HIGH_ENTROPY_RE.exec(line)) !== null) {\n const value = match[1] || match[2];\n if (!value || value.length < 40) continue;\n if (isTemplateOrPlaceholder(value)) continue;\n if (ENTROPY_SKIP.some((p) => p.test(value))) continue;\n\n const entropy = shannonEntropy(value);\n if (entropy >= threshold) {\n findings.push({\n type: 'high-entropy',\n file: filePath,\n line: i + 1,\n match: value,\n redacted: redactSecret(value),\n severity: entropy >= 5.0 ? 'high' : 'medium',\n });\n }\n }\n }\n\n return deduplicateFindings(findings);\n}\n\n// ===== FULL PROJECT AUDIT =====\n\nexport interface AuditResult {\n findings: SecretFinding[];\n summary: {\n totalFiles: number;\n filesScanned: number;\n filesWithSecrets: number;\n totalFindings: number;\n bySeverity: { critical: number; high: number; medium: number; low: number };\n byType: Record<string, number>;\n };\n recommendations: string[];\n}\n\nexport interface AuditOptions {\n customPatterns?: string[];\n entropyThreshold?: number;\n includePII?: boolean;\n useAllowlist?: boolean;\n incrementalScan?: boolean;\n severityOverrides?: Partial<Record<SecretType, SecretFinding['severity']>>;\n piiSafeDomains?: string[];\n}\n\nexport async function auditProject(\n projectPath: string,\n filePaths: string[],\n options: AuditOptions = {},\n): Promise<AuditResult> {\n // Load config from .cto/audit/config.json (user overrides take precedence)\n const savedConfig = loadAuditConfig(projectPath);\n const customPatterns = options.customPatterns ?? savedConfig.customPatterns;\n const entropyThreshold = options.entropyThreshold ?? savedConfig.entropyThreshold;\n const includePII = options.includePII ?? savedConfig.includePII;\n const useAllowlist = options.useAllowlist ?? true;\n const incrementalScan = options.incrementalScan ?? savedConfig.incrementalScan;\n const severityOverrides = options.severityOverrides ?? savedConfig.severityOverrides;\n\n // Build per-call PII safe domains set (no global mutation)\n let extraPiiDomains: Set<string> | undefined;\n const allExtraDomains = [...(options.piiSafeDomains || []), ...savedConfig.piiSafeDomains];\n if (allExtraDomains.length > 0) {\n extraPiiDomains = new Set(allExtraDomains.map((d) => d.toLowerCase()));\n }\n\n // Incremental scanning: only scan changed files\n let filesToScan = filePaths;\n let unchangedCount = 0;\n let newCache: Record<string, string> | null = null;\n\n if (incrementalScan) {\n const { changed, unchanged, cache } = getChangedFiles(projectPath, filePaths);\n newCache = cache; // Always save cache for next run\n if (changed.length < filePaths.length) {\n filesToScan = changed;\n unchangedCount = unchanged.length;\n }\n }\n\n const allFindings: SecretFinding[] = [];\n const filesWithSecrets = new Set<string>();\n\n for (const fp of filesToScan) {\n try {\n const content = await readFile(fp, 'utf-8');\n const relPath = relative(resolve(projectPath), resolve(fp));\n\n // Skip test files and declaration files for entropy (too many false positives)\n const isTestFile = /\\.(test|spec|mock)\\.[jt]sx?$/.test(relPath) || relPath.includes('__tests__');\n const isDtsFile = relPath.endsWith('.d.ts');\n\n // Pattern-based detection (pass extra PII domains per-call, no global leak)\n let findings = scanContentForSecrets(content, relPath, customPatterns, extraPiiDomains);\n\n // Filter PII if not wanted\n if (!includePII) {\n findings = findings.filter((f) => f.type !== 'pii');\n }\n\n // Entropy-based detection (skip test/declaration files — too noisy)\n const entropyFindings = (isTestFile || isDtsFile)\n ? []\n : scanContentForHighEntropy(content, relPath, entropyThreshold);\n\n const combined = [...findings, ...entropyFindings];\n if (combined.length > 0) {\n filesWithSecrets.add(relPath);\n allFindings.push(...combined);\n }\n } catch {\n // skip unreadable files\n }\n }\n\n // Apply severity overrides from config\n let finalFindings = applySeverityOverrides(allFindings, severityOverrides);\n\n // Apply allowlist — filter out reviewed findings\n let allowedCount = 0;\n if (useAllowlist) {\n const { filtered, allowed } = filterByAllowlist(finalFindings, projectPath);\n finalFindings = filtered;\n allowedCount = allowed.length;\n }\n\n // Sort by severity\n finalFindings.sort((a, b) => {\n const order = { critical: 0, high: 1, medium: 2, low: 3 };\n return order[a.severity] - order[b.severity];\n });\n\n // Save hash cache for incremental scanning\n if (newCache) {\n saveHashCache(projectPath, newCache);\n }\n\n // Build summary\n const bySeverity = { critical: 0, high: 0, medium: 0, low: 0 };\n const byType: Record<string, number> = {};\n for (const f of finalFindings) {\n bySeverity[f.severity]++;\n byType[f.type] = (byType[f.type] || 0) + 1;\n }\n\n // Generate recommendations\n const recommendations: string[] = [];\n if (bySeverity.critical > 0) {\n recommendations.push('CRITICAL: Rotate all detected credentials immediately. They may already be compromised.');\n }\n if (byType['password'] > 0) {\n recommendations.push('Move passwords to environment variables or a secrets manager (AWS Secrets Manager, Vault, etc.).');\n }\n if (byType['api-key'] > 0 || byType['aws-key'] > 0) {\n recommendations.push('Use environment variables for API keys. Never commit them to source control.');\n }\n if (byType['connection-string'] > 0) {\n recommendations.push('Database connection strings should use environment variables, not hardcoded values.');\n }\n if (byType['private-key'] > 0) {\n recommendations.push('Private keys should NEVER be in source code. Use a key management service.');\n }\n if (byType['pii'] > 0) {\n recommendations.push('PII detected. Review for GDPR/CCPA compliance. Consider data anonymization.');\n }\n if (byType['high-entropy'] > 0) {\n recommendations.push('High-entropy strings detected that may be secrets. Review manually.');\n }\n if (finalFindings.length > 0) {\n recommendations.push('Add a .gitignore entry for .env files if not already present.');\n recommendations.push('Run `npx cto-ai-cli --audit` regularly or add to CI pipeline.');\n }\n if (finalFindings.length === 0) {\n recommendations.push('No secrets detected. Great job keeping your codebase clean!');\n }\n if (allowedCount > 0) {\n recommendations.push(`${allowedCount} finding(s) skipped via allowlist (.cto/audit/allowlist.json).`);\n }\n if (unchangedCount > 0) {\n recommendations.push(`${unchangedCount} unchanged file(s) skipped (incremental scan).`);\n }\n\n return {\n findings: finalFindings,\n summary: {\n totalFiles: filePaths.length,\n filesScanned: filesToScan.length,\n filesWithSecrets: filesWithSecrets.size,\n totalFindings: finalFindings.length,\n bySeverity,\n byType,\n },\n recommendations,\n };\n}\n","import type { GraphEdge } from '../types/engine.js';\n\n// ===== SHARED GRAPH UTILITIES =====\n\nexport interface AdjacencyList {\n forward: Map<string, string[]>; // file → files it imports\n reverse: Map<string, string[]>; // file → files that import it\n}\n\nexport function buildAdjacencyList(edges: GraphEdge[]): AdjacencyList {\n const forward = new Map<string, string[]>();\n const reverse = new Map<string, string[]>();\n\n for (const edge of edges) {\n if (!forward.has(edge.from)) forward.set(edge.from, []);\n forward.get(edge.from)!.push(edge.to);\n\n if (!reverse.has(edge.to)) reverse.set(edge.to, []);\n reverse.get(edge.to)!.push(edge.from);\n }\n\n return { forward, reverse };\n}\n\nexport function bfsBidirectional(\n seeds: string[],\n adj: AdjacencyList,\n depth: number,\n): Set<string> {\n const result = new Set(seeds);\n let frontier = [...seeds];\n const visited = new Set<string>();\n\n for (let d = 0; d < depth; d++) {\n const nextFrontier: string[] = [];\n\n for (const node of frontier) {\n if (visited.has(node)) continue;\n visited.add(node);\n\n // Forward neighbors (imports)\n const fwd = adj.forward.get(node);\n if (fwd) {\n for (const neighbor of fwd) {\n if (!visited.has(neighbor)) {\n result.add(neighbor);\n nextFrontier.push(neighbor);\n }\n }\n }\n\n // Reverse neighbors (imported by)\n const rev = adj.reverse.get(node);\n if (rev) {\n for (const neighbor of rev) {\n if (!visited.has(neighbor)) {\n result.add(neighbor);\n nextFrontier.push(neighbor);\n }\n }\n }\n }\n\n frontier = nextFrontier;\n }\n\n return result;\n}\n\nexport function matchGlob(path: string, pattern: string): boolean {\n const regexStr = pattern\n .replace(/\\./g, '\\\\.')\n .replace(/\\*\\*/g, '§§')\n .replace(/\\*/g, '[^/]*')\n .replace(/§§/g, '.*')\n .replace(/\\?/g, '.');\n\n try {\n return new RegExp(`^${regexStr}$`).test(path);\n } catch {\n return false;\n }\n}\n","import type {\n PolicySet,\n PolicyRule,\n PolicyRuleType,\n PolicyValidation,\n PolicyViolation,\n PolicyWarning,\n} from '../types/govern.js';\nimport type { ContextSelection, AnalyzedFile } from '../types/engine.js';\nimport { matchGlob } from '../engine/graph-utils.js';\n\n// ===== POLICY ENGINE =====\n//\n// YAML-backed rule engine for controlling what context gets included/excluded.\n// Policies are evaluated after selection to validate compliance.\n\nexport const DEFAULT_POLICY: PolicySet = {\n version: '1.0',\n name: 'default',\n rules: [\n {\n id: 'no-env',\n type: 'exclude-always',\n pattern: '**/*.env*',\n reason: 'Environment files must never be sent to AI',\n enabled: true,\n },\n {\n id: 'no-secrets',\n type: 'secret-block',\n reason: 'Files with detected secrets are blocked',\n enabled: true,\n },\n {\n id: 'min-coverage',\n type: 'coverage-minimum',\n threshold: 70,\n reason: 'Warn if context coverage drops below 70%',\n enabled: true,\n },\n ],\n};\n\n// ===== VALIDATION =====\n\nexport function validateSelection(\n selection: ContextSelection,\n policies: PolicySet,\n allFiles?: AnalyzedFile[],\n): PolicyValidation {\n const violations: PolicyViolation[] = [];\n const warnings: PolicyWarning[] = [];\n\n const includedPaths = new Set(selection.files.map((f) => f.relativePath));\n\n for (const rule of policies.rules) {\n if (!rule.enabled) continue;\n\n switch (rule.type) {\n case 'exclude-always': {\n if (!rule.pattern) break;\n const violatingFiles = selection.files.filter((f) =>\n matchGlob(f.relativePath, rule.pattern!),\n );\n for (const f of violatingFiles) {\n violations.push({\n rule,\n message: `File \"${f.relativePath}\" is included but matches exclude-always pattern \"${rule.pattern}\"`,\n severity: 'error',\n });\n }\n break;\n }\n\n case 'include-always': {\n if (!rule.pattern || !allFiles) break;\n const requiredFiles = allFiles.filter((f) =>\n matchGlob(f.relativePath, rule.pattern!),\n );\n for (const f of requiredFiles) {\n if (!includedPaths.has(f.relativePath)) {\n violations.push({\n rule,\n message: `File \"${f.relativePath}\" matches include-always pattern \"${rule.pattern}\" but is not included`,\n severity: 'warning',\n });\n }\n }\n break;\n }\n\n case 'coverage-minimum': {\n const threshold = rule.threshold ?? 70;\n if (selection.coverage.score < threshold) {\n warnings.push({\n rule,\n message: `Coverage ${selection.coverage.score}% is below minimum ${threshold}%`,\n currentValue: selection.coverage.score,\n threshold,\n });\n }\n break;\n }\n\n case 'risk-maximum': {\n const threshold = rule.threshold ?? 50;\n if (selection.riskScore > threshold) {\n warnings.push({\n rule,\n message: `Exclusion risk ${selection.riskScore}/100 exceeds maximum ${threshold}`,\n currentValue: selection.riskScore,\n threshold,\n });\n }\n break;\n }\n\n case 'budget-limit': {\n if (!rule.category || !rule.threshold) break;\n const categoryFiles = selection.files.filter((f) =>\n fileMatchesCategory(f.relativePath, rule.category!),\n );\n const categoryTokens = categoryFiles.reduce((s, f) => s + f.tokens, 0);\n const categoryPercent = selection.totalTokens > 0\n ? (categoryTokens / selection.totalTokens) * 100\n : 0;\n\n if (categoryPercent > rule.threshold) {\n warnings.push({\n rule,\n message: `Category \"${rule.category}\" uses ${Math.round(categoryPercent)}% of budget (max: ${rule.threshold}%)`,\n currentValue: Math.round(categoryPercent),\n threshold: rule.threshold,\n });\n }\n break;\n }\n }\n }\n\n return {\n passed: violations.filter((v) => v.severity === 'error').length === 0,\n violations,\n warnings,\n };\n}\n\n// ===== POLICY CRUD =====\n\nexport function addRule(policies: PolicySet, rule: PolicyRule): PolicySet {\n return {\n ...policies,\n rules: [...policies.rules, rule],\n };\n}\n\nexport function removeRule(policies: PolicySet, ruleId: string): PolicySet {\n return {\n ...policies,\n rules: policies.rules.filter((r) => r.id !== ruleId),\n };\n}\n\nexport function toggleRule(policies: PolicySet, ruleId: string, enabled: boolean): PolicySet {\n return {\n ...policies,\n rules: policies.rules.map((r) =>\n r.id === ruleId ? { ...r, enabled } : r,\n ),\n };\n}\n\n// ===== HELPERS =====\n\nfunction fileMatchesCategory(path: string, category: string): boolean {\n switch (category) {\n case 'test':\n return /\\.(test|spec)\\.[jt]sx?$/.test(path) || /\\/__tests__\\//.test(path) || /\\/tests?\\//.test(path);\n case 'config':\n return /\\.(config|rc)\\.[jt]s$/.test(path) || /\\.json$/.test(path) || /\\.ya?ml$/.test(path);\n case 'docs':\n return /\\.(md|txt|rst)$/.test(path);\n case 'types':\n return /types?\\//i.test(path) || /\\.d\\.ts$/.test(path);\n default:\n return path.includes(category);\n }\n}\n","import { randomUUID, createHash } from 'node:crypto';\nimport { readFile } from 'node:fs/promises';\nimport type {\n ContextSnapshot,\n SnapshotFile,\n SnapshotVerification,\n} from '../types/govern.js';\nimport type { ContextSelection, ProjectAnalysis } from '../types/engine.js';\n\n// ===== CONTEXT SNAPSHOTS =====\n//\n// Reproducible, verifiable snapshots of context selections.\n// Used for: deterministic mode, audit evidence, before/after comparisons.\n\nexport function createSnapshot(\n name: string,\n analysis: ProjectAnalysis,\n selection: ContextSelection,\n metadata: Record<string, unknown> = {},\n): ContextSnapshot {\n const files: SnapshotFile[] = selection.files.map((f) => ({\n relativePath: f.relativePath,\n hash: hashString(`${f.relativePath}:${f.tokens}:${f.pruneLevel}`),\n tokens: f.tokens,\n pruneLevel: f.pruneLevel,\n }));\n\n const snapshotData = files\n .map((f) => `${f.relativePath}:${f.hash}:${f.pruneLevel}`)\n .sort()\n .join('|');\n\n return {\n id: randomUUID().substring(0, 8),\n name,\n createdAt: new Date(),\n hash: hashString(snapshotData),\n projectHash: analysis.hash,\n analysisHash: analysis.hash,\n selectionHash: selection.hash,\n files,\n totalTokens: selection.totalTokens,\n coverageScore: selection.coverage.score,\n riskScore: selection.riskScore,\n metadata,\n };\n}\n\nexport async function verifySnapshot(\n snapshot: ContextSnapshot,\n currentAnalysis: ProjectAnalysis,\n currentSelection: ContextSelection,\n): Promise<SnapshotVerification> {\n const currentFiles = new Map(\n currentSelection.files.map((f) => [f.relativePath, f]),\n );\n\n let filesMatched = 0;\n const filesMissing: string[] = [];\n const filesChanged: string[] = [];\n\n for (const snapFile of snapshot.files) {\n const current = currentFiles.get(snapFile.relativePath);\n\n if (!current) {\n filesMissing.push(snapFile.relativePath);\n continue;\n }\n\n const currentHash = hashString(\n `${current.relativePath}:${current.tokens}:${current.pruneLevel}`,\n );\n\n if (currentHash === snapFile.hash) {\n filesMatched++;\n } else {\n filesChanged.push(snapFile.relativePath);\n }\n }\n\n // Verify overall hash\n const currentSnapshotData = snapshot.files\n .map((f) => {\n const current = currentFiles.get(f.relativePath);\n if (!current) return `${f.relativePath}:MISSING:MISSING`;\n return `${current.relativePath}:${hashString(`${current.relativePath}:${current.tokens}:${current.pruneLevel}`)}:${current.pruneLevel}`;\n })\n .sort()\n .join('|');\n\n const currentHash = hashString(currentSnapshotData);\n const integrityOk = currentHash === snapshot.hash && filesMissing.length === 0 && filesChanged.length === 0;\n\n return {\n valid: integrityOk,\n snapshotId: snapshot.id,\n filesChecked: snapshot.files.length,\n filesMatched,\n filesMissing,\n filesChanged,\n integrityOk,\n };\n}\n\nexport function compareSnapshots(\n older: ContextSnapshot,\n newer: ContextSnapshot,\n): {\n added: string[];\n removed: string[];\n changed: string[];\n tokenDelta: number;\n coverageDelta: number;\n riskDelta: number;\n} {\n const olderFiles = new Map(older.files.map((f) => [f.relativePath, f]));\n const newerFiles = new Map(newer.files.map((f) => [f.relativePath, f]));\n\n const added: string[] = [];\n const removed: string[] = [];\n const changed: string[] = [];\n\n for (const [path, file] of newerFiles) {\n const old = olderFiles.get(path);\n if (!old) {\n added.push(path);\n } else if (old.hash !== file.hash) {\n changed.push(path);\n }\n }\n\n for (const path of olderFiles.keys()) {\n if (!newerFiles.has(path)) {\n removed.push(path);\n }\n }\n\n return {\n added,\n removed,\n changed,\n tokenDelta: newer.totalTokens - older.totalTokens,\n coverageDelta: newer.coverageScore - older.coverageScore,\n riskDelta: newer.riskScore - older.riskScore,\n };\n}\n\n// ===== HELPERS =====\n\nfunction hashString(input: string): string {\n return createHash('sha256').update(input).digest('hex').substring(0, 16);\n}\n","import { createHash } from 'node:crypto';\nimport { readFile, chmod, readdir, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { IntegrityManifest, IntegrityEntry } from '../types/govern.js';\n\n// ===== INTEGRITY VERIFICATION =====\n//\n// SHA-256 hash-based integrity checking for CTO artifacts.\n// Detects tampering of snapshots, audit logs, configs, and policies.\n\nexport function hashContent(content: Buffer | string): string {\n return createHash('sha256').update(content).digest('hex');\n}\n\nexport async function hashFile(filePath: string): Promise<string | null> {\n try {\n const content = await readFile(filePath);\n return hashContent(content);\n } catch {\n return null;\n }\n}\n\nexport async function buildManifest(\n projectDir: string,\n): Promise<IntegrityManifest> {\n const entries: IntegrityEntry[] = [];\n\n async function scanDir(dir: string, type: IntegrityEntry['type']): Promise<void> {\n let files: string[];\n try {\n files = await readdir(dir);\n } catch {\n return;\n }\n\n for (const file of files) {\n const fullPath = join(dir, file);\n try {\n const fileStat = await stat(fullPath);\n if (fileStat.isFile()) {\n const hash = await hashFile(fullPath);\n if (hash) {\n entries.push({\n filePath: fullPath,\n hash,\n size: fileStat.size,\n createdAt: fileStat.mtime,\n type,\n });\n }\n }\n } catch { /* skip */ }\n }\n }\n\n // Scan known CTO directories\n await scanDir(join(projectDir, 'snapshots'), 'snapshot');\n await scanDir(join(projectDir, 'audit'), 'audit');\n await scanDir(projectDir, 'config');\n\n return {\n version: '2.0',\n createdAt: new Date(),\n entries,\n };\n}\n\nexport async function verifyManifest(\n manifest: IntegrityManifest,\n): Promise<{\n totalFiles: number;\n validFiles: number;\n invalidFiles: string[];\n missingFiles: string[];\n}> {\n const invalidFiles: string[] = [];\n const missingFiles: string[] = [];\n\n for (const entry of manifest.entries) {\n const currentHash = await hashFile(entry.filePath);\n\n if (currentHash === null) {\n missingFiles.push(entry.filePath);\n } else if (currentHash !== entry.hash) {\n invalidFiles.push(entry.filePath);\n }\n }\n\n return {\n totalFiles: manifest.entries.length,\n validFiles: manifest.entries.length - invalidFiles.length - missingFiles.length,\n invalidFiles,\n missingFiles,\n };\n}\n\nexport async function securePermissions(dirPath: string): Promise<number> {\n let count = 0;\n\n try {\n await chmod(dirPath, 0o700);\n count++;\n\n const files = await readdir(dirPath);\n for (const file of files) {\n try {\n const fullPath = join(dirPath, file);\n const fileStat = await stat(fullPath);\n if (fileStat.isFile()) {\n await chmod(fullPath, 0o600);\n count++;\n }\n } catch { /* skip */ }\n }\n } catch { /* dir might not exist */ }\n\n return count;\n}\n"],"mappings":"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","names":["readFile","writeFile","join","createHash","randomUUID","createHash","currentHash","createHash","readFile","chmod","readdir","join","hashContent"]}
@@ -2,11 +2,13 @@
2
2
  import { randomUUID as randomUUID2 } from "crypto";
3
3
 
4
4
  // src/engine/selector.ts
5
- import { createHash } from "crypto";
5
+ import { createHash as createHash2 } from "crypto";
6
6
 
7
7
  // src/govern/secrets.ts
8
8
  import { readFile } from "fs/promises";
9
- import { resolve, relative } from "path";
9
+ import { readFileSync, existsSync, mkdirSync, writeFileSync } from "fs";
10
+ import { resolve, relative, join, dirname } from "path";
11
+ import { createHash } from "crypto";
10
12
  var BUILTIN_PATTERNS = [
11
13
  // API Keys
12
14
  { type: "api-key", source: `(?:api[_-]?key|apikey)\\s*[:=]\\s*['"]?([a-zA-Z0-9_\\-]{20,})['"]?`, flags: "gi", severity: "critical", description: "API Key" },
@@ -51,17 +53,46 @@ var BUILTIN_PATTERNS = [
51
53
  { type: "api-key", source: "SG\\.[a-zA-Z0-9_-]{22}\\.[a-zA-Z0-9_-]{43}", flags: "g", severity: "critical", description: "SendGrid API Key" },
52
54
  // JWT
53
55
  { type: "token", source: "eyJ[a-zA-Z0-9_-]{10,}\\.eyJ[a-zA-Z0-9_-]{10,}\\.[a-zA-Z0-9_-]{10,}", flags: "g", severity: "high", description: "JSON Web Token" },
56
+ // Datadog
57
+ { type: "api-key", source: `(?:DD_API_KEY|DATADOG_API_KEY)\\s*[:=]\\s*['"]?([a-f0-9]{32})['"]?`, flags: "gi", severity: "critical", description: "Datadog API Key" },
58
+ { type: "api-key", source: `(?:DD_APP_KEY|DATADOG_APP_KEY)\\s*[:=]\\s*['"]?([a-f0-9]{40})['"]?`, flags: "gi", severity: "critical", description: "Datadog App Key" },
59
+ // Sentry
60
+ { type: "connection-string", source: "https://[a-f0-9]{32}@[a-z0-9]+\\.ingest\\.sentry\\.io/[0-9]+", flags: "g", severity: "high", description: "Sentry DSN" },
61
+ // Firebase
62
+ { type: "api-key", source: `(?:FIREBASE_API_KEY|FIREBASE_KEY)\\s*[:=]\\s*['"]?([a-zA-Z0-9_\\-]{30,})['"]?`, flags: "gi", severity: "high", description: "Firebase API Key" },
63
+ { type: "connection-string", source: `firebase[a-z]*:\\/\\/[^\\s'"]+`, flags: "gi", severity: "high", description: "Firebase URL" },
64
+ // Supabase
65
+ { type: "api-key", source: "sbp_[a-f0-9]{40}", flags: "g", severity: "critical", description: "Supabase Service Key" },
66
+ { type: "token", source: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\\.[a-zA-Z0-9_-]{20,}\\.[a-zA-Z0-9_-]{20,}", flags: "g", severity: "high", description: "Supabase Anon/Service JWT" },
67
+ // Vercel
68
+ { type: "token", source: `(?:VERCEL_TOKEN|VERCEL_API_TOKEN)\\s*[:=]\\s*['"]?([a-zA-Z0-9]{24,})['"]?`, flags: "gi", severity: "critical", description: "Vercel Token" },
69
+ // Heroku
70
+ { type: "api-key", source: `(?:HEROKU_API_KEY|HEROKU_TOKEN)\\s*[:=]\\s*['"]?([a-f0-9\\-]{36,})['"]?`, flags: "gi", severity: "critical", description: "Heroku API Key" },
71
+ // DigitalOcean
72
+ { type: "token", source: "dop_v1_[a-f0-9]{64}", flags: "g", severity: "critical", description: "DigitalOcean Personal Access Token" },
73
+ { type: "token", source: "doo_v1_[a-f0-9]{64}", flags: "g", severity: "critical", description: "DigitalOcean OAuth Token" },
74
+ // Mailgun
75
+ { type: "api-key", source: "key-[a-zA-Z0-9]{32}", flags: "g", severity: "high", description: "Mailgun API Key" },
54
76
  // PII
55
77
  { type: "pii", source: "\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b", flags: "g", severity: "medium", description: "Email Address (PII)" },
56
- { type: "pii", source: "\\b\\d{3}[-.]?\\d{2}[-.]?\\d{4}\\b", flags: "g", severity: "high", description: "Possible SSN (PII)" }
78
+ { type: "pii", source: "\\b(?!000|666|9\\d{2})(\\d{3})[-.]?(?!00)(\\d{2})[-.]?(?!0000)(\\d{4})\\b", flags: "g", severity: "high", description: "Possible SSN (PII)" }
57
79
  ];
80
+ var _cachedBuiltinPatterns = null;
81
+ function getBuiltinPatterns() {
82
+ if (!_cachedBuiltinPatterns) {
83
+ _cachedBuiltinPatterns = BUILTIN_PATTERNS.map((def) => ({
84
+ type: def.type,
85
+ pattern: new RegExp(def.source, def.flags),
86
+ severity: def.severity,
87
+ description: def.description
88
+ }));
89
+ }
90
+ return _cachedBuiltinPatterns;
91
+ }
58
92
  function buildPatterns(customPatterns = []) {
59
- const patterns = BUILTIN_PATTERNS.map((def) => ({
60
- type: def.type,
61
- pattern: new RegExp(def.source, def.flags),
62
- severity: def.severity,
63
- description: def.description
64
- }));
93
+ const builtins = getBuiltinPatterns();
94
+ if (customPatterns.length === 0) return builtins;
95
+ const patterns = [...builtins];
65
96
  for (const custom of customPatterns) {
66
97
  try {
67
98
  patterns.push({
@@ -75,7 +106,7 @@ function buildPatterns(customPatterns = []) {
75
106
  }
76
107
  return patterns;
77
108
  }
78
- function scanContentForSecrets(content, filePath, customPatterns = []) {
109
+ function scanContentForSecrets(content, filePath, customPatterns = [], extraPiiSafeDomains) {
79
110
  const findings = [];
80
111
  const lines = content.split("\n");
81
112
  const allPatterns = buildPatterns(customPatterns);
@@ -87,6 +118,7 @@ function scanContentForSecrets(content, filePath, customPatterns = []) {
87
118
  while ((match = secretPattern.pattern.exec(line)) !== null) {
88
119
  const matchText = match[0];
89
120
  if (isTemplateOrPlaceholder(matchText)) continue;
121
+ if (secretPattern.type === "pii" && isSafeEmail(matchText, extraPiiSafeDomains)) continue;
90
122
  findings.push({
91
123
  type: secretPattern.type,
92
124
  file: filePath,
@@ -134,6 +166,36 @@ function isTemplateOrPlaceholder(value) {
134
166
  ];
135
167
  return placeholders.some((p) => p.test(value));
136
168
  }
169
+ var PII_SAFE_EMAIL_DOMAINS = /* @__PURE__ */ new Set([
170
+ "example.com",
171
+ "example.org",
172
+ "example.net",
173
+ "test.com",
174
+ "test.org",
175
+ "test.net",
176
+ "localhost",
177
+ "localhost.localdomain",
178
+ "email.com",
179
+ "mail.com",
180
+ "foo.com",
181
+ "bar.com",
182
+ "baz.com",
183
+ "acme.com",
184
+ "company.com",
185
+ "corp.com",
186
+ "noreply.com",
187
+ "no-reply.com",
188
+ "users.noreply.github.com",
189
+ "placeholder.com"
190
+ ]);
191
+ function isSafeEmail(value, extraDomains) {
192
+ const match = value.match(/@([a-zA-Z0-9.-]+)$/);
193
+ if (!match) return false;
194
+ const domain = match[1].toLowerCase();
195
+ if (PII_SAFE_EMAIL_DOMAINS.has(domain)) return true;
196
+ if (extraDomains && extraDomains.has(domain)) return true;
197
+ return false;
198
+ }
137
199
  function deduplicateFindings(findings) {
138
200
  const seen = /* @__PURE__ */ new Set();
139
201
  return findings.filter((f) => {
@@ -147,8 +209,8 @@ function deduplicateFindings(findings) {
147
209
  // src/engine/pruner.ts
148
210
  import { Project, SyntaxKind } from "ts-morph";
149
211
  import { readFile as readFile3 } from "fs/promises";
150
- import { existsSync } from "fs";
151
- import { join } from "path";
212
+ import { existsSync as existsSync2 } from "fs";
213
+ import { join as join2 } from "path";
152
214
 
153
215
  // src/engine/tokenizer.ts
154
216
  import { encodingForModel } from "js-tiktoken";
@@ -404,9 +466,9 @@ function addJSDoc(node, parts) {
404
466
  function findTsConfig(filePath) {
405
467
  let dir = filePath;
406
468
  for (let i = 0; i < 10; i++) {
407
- dir = join(dir, "..");
408
- const candidate = join(dir, "tsconfig.json");
409
- if (existsSync(candidate)) return candidate;
469
+ dir = join2(dir, "..");
470
+ const candidate = join2(dir, "tsconfig.json");
471
+ if (existsSync2(candidate)) return candidate;
410
472
  }
411
473
  return void 0;
412
474
  }
@@ -673,7 +735,7 @@ async function selectContext(input) {
673
735
  );
674
736
  const excludedRisk = excludedFiles.length > 0 ? Math.round(excludedFiles.reduce((s, f) => s + f.riskScore, 0) / excludedFiles.length) : 0;
675
737
  const hashInput = selectedFiles.map((f) => `${f.relativePath}:${f.pruneLevel}`).sort().join("|") + `|budget:${budget}`;
676
- const hash = createHash("sha256").update(hashInput).digest("hex").substring(0, 16);
738
+ const hash = createHash2("sha256").update(hashInput).digest("hex").substring(0, 16);
677
739
  return {
678
740
  files: selectedFiles,
679
741
  totalTokens: usedTokens,
@@ -1166,20 +1228,20 @@ function makeSection(id, role, content) {
1166
1228
  }
1167
1229
 
1168
1230
  // src/govern/audit.ts
1169
- import { randomUUID, createHash as createHash2 } from "crypto";
1231
+ import { randomUUID, createHash as createHash3 } from "crypto";
1170
1232
  import { readdir, chmod } from "fs/promises";
1171
- import { join as join2 } from "path";
1233
+ import { join as join3 } from "path";
1172
1234
  import { userInfo } from "os";
1173
1235
  import { homedir } from "os";
1174
1236
  var CTO_DIR = ".cto-ai";
1175
1237
  var AUDIT_DIR = "audit";
1176
1238
  var MAX_ENTRIES_PER_FILE = 500;
1177
1239
  function getAuditDir() {
1178
- return join2(homedir(), CTO_DIR, AUDIT_DIR);
1240
+ return join3(homedir(), CTO_DIR, AUDIT_DIR);
1179
1241
  }
1180
1242
  function getCurrentAuditFile() {
1181
1243
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0].replace(/-/g, "");
1182
- return join2(getAuditDir(), `audit_${date}.json`);
1244
+ return join3(getAuditDir(), `audit_${date}.json`);
1183
1245
  }
1184
1246
  function computeIntegrityHash(entry) {
1185
1247
  const payload = JSON.stringify({
@@ -1190,7 +1252,7 @@ function computeIntegrityHash(entry) {
1190
1252
  projectPath: entry.projectPath,
1191
1253
  details: entry.details
1192
1254
  });
1193
- return createHash2("sha256").update(payload).digest("hex");
1255
+ return createHash3("sha256").update(payload).digest("hex");
1194
1256
  }
1195
1257
  async function ensureDir(dirPath) {
1196
1258
  const { mkdir } = await import("fs/promises");
@@ -1206,9 +1268,9 @@ async function readJSON(filePath) {
1206
1268
  }
1207
1269
  }
1208
1270
  async function writeJSON(filePath, data) {
1209
- const { writeFile } = await import("fs/promises");
1210
- await ensureDir(join2(filePath, ".."));
1211
- await writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
1271
+ const { writeFile: writeFile2 } = await import("fs/promises");
1272
+ await ensureDir(join3(filePath, ".."));
1273
+ await writeFile2(filePath, JSON.stringify(data, null, 2), "utf-8");
1212
1274
  }
1213
1275
  async function logAudit(action, projectPath, details = {}) {
1214
1276
  const auditDir = getAuditDir();