tractatus/scripts/check-csp-violations.js
TheFlow 2af47035ac refactor: remove website code and fix critical startup crashes (Phase 8)
CRITICAL FIX: Server would CRASH ON STARTUP (multiple import errors)

REMOVED (2 scripts):
1. scripts/framework-watchdog.js
   - Monitored .claude/session-state.json (OUR Claude Code setup)
   - Monitored .claude/token-checkpoints.json (OUR file structure)
   - Implementers won't have our .claude/ directory

2. scripts/init-db.js
   - Created website collections: blog_posts, media_inquiries, case_submissions
   - Created website collections: resources, moderation_queue, users, citations
   - Created website collections: translations, koha_donations
   - Next steps referenced deleted scripts (npm run seed:admin)

REWRITTEN (2 files):

src/models/index.js (29 lines → 27 lines)
- REMOVED imports: Document, BlogPost, MediaInquiry, CaseSubmission, Resource
- REMOVED imports: ModerationQueue, User (all deleted in Phase 2)
- KEPT imports: AuditLog, DeliberationSession, GovernanceLog, GovernanceRule
- KEPT imports: Precedent, Project, SessionState, VariableValue, VerificationLog
- Result: Only framework models exported

src/server.js (284 lines → 163 lines, 43% reduction)
- REMOVED: Imports to deleted middleware (csrf-protection, response-sanitization)
- REMOVED: Stripe webhook handling (/api/koha/webhook)
- REMOVED: Static file caching (for deleted public/ directory)
- REMOVED: Static file serving (public/ deleted in Phase 6)
- REMOVED: CSRF token endpoint
- REMOVED: Website homepage with "auth, documents, blog, admin" references
- REMOVED: Instruction sync (scripts/sync-instructions-to-db.js reference)
- REMOVED: Hardcoded log path (${process.env.HOME}/var/log/tractatus/...)
- REMOVED: Website-specific security middleware
- KEPT: Security headers, rate limiting, CORS, body parsers
- KEPT: API routes, governance services, MongoDB connections
- RESULT: Clean framework-only server

RESULT: Repository can now start without crashes, all imports resolve

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 22:17:02 +13:00

212 lines
5.2 KiB
JavaScript

#!/usr/bin/env node
/**
* CSP Violation Scanner
* Scans HTML and JS files for Content Security Policy violations
*
* Violations checked (inst_008):
* - Inline event handlers (onclick, onload, etc.)
* - Inline styles (style="...")
* - Inline scripts (<script>...code...</script>)
* - javascript: URLs
*
* Usage:
* node scripts/check-csp-violations.js [pattern]
*
* Examples:
* node scripts/check-csp-violations.js
* node scripts/check-csp-violations.js public
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
// Default patterns to scan
const DEFAULT_PATTERNS = [
'public/**/*.html',
'public/**/*.js'
];
// CSP violation patterns
const VIOLATION_PATTERNS = {
inline_event_handlers: {
regex: /\s(on[a-z]+)=["'][^"']*["']/gi,
description: 'Inline event handler',
severity: 'HIGH'
},
inline_styles: {
regex: /\sstyle=["'][^"']*["']/gi,
description: 'Inline style attribute',
severity: 'HIGH'
},
inline_scripts: {
regex: /<script(?![^>]*\ssrc=)[^>]*>[\s\S]*?<\/script>/gi,
description: 'Inline script block',
severity: 'CRITICAL'
},
javascript_urls: {
regex: /href=["']javascript:/gi,
description: 'javascript: URL',
severity: 'CRITICAL'
}
};
// Files to exclude from scanning
const EXCLUDED_PATTERNS = [
'node_modules',
'.git',
'dist',
'build'
];
/**
* Find files matching glob patterns
*/
function findFiles(patterns) {
const allPatterns = Array.isArray(patterns) ? patterns : [patterns];
const files = new Set();
allPatterns.forEach(pattern => {
try {
const result = execSync(`find public -type f \\( -name "*.html" -o -name "*.js" \\) 2>/dev/null`, {
encoding: 'utf8',
maxBuffer: 10 * 1024 * 1024
});
result.split('\n').filter(f => f.trim()).forEach(file => {
// Exclude patterns
if (!EXCLUDED_PATTERNS.some(excluded => file.includes(excluded))) {
files.add(file);
}
});
} catch (error) {
// Ignore errors from find command
}
});
return Array.from(files).sort();
}
/**
* Scan a single file for CSP violations
*/
function scanFile(filePath) {
const violations = [];
try {
const content = fs.readFileSync(filePath, 'utf8');
const lines = content.split('\n');
// Check each violation pattern
Object.entries(VIOLATION_PATTERNS).forEach(([type, pattern]) => {
const matches = content.matchAll(pattern.regex);
for (const match of matches) {
// Find line number
const beforeMatch = content.substring(0, match.index);
const lineNumber = beforeMatch.split('\n').length;
// Get the matched text (truncate if too long)
let matchedText = match[0];
if (matchedText.length > 80) {
matchedText = matchedText.substring(0, 77) + '...';
}
violations.push({
file: filePath,
line: lineNumber,
type,
description: pattern.description,
severity: pattern.severity,
matched: matchedText
});
}
});
} catch (error) {
console.error(`Error scanning ${filePath}:`, error.message);
}
return violations;
}
/**
* Main scanner function
*/
function scanForViolations(patterns = DEFAULT_PATTERNS) {
console.log('🔍 Scanning for CSP violations...\n');
const files = findFiles(patterns);
console.log(`Found ${files.length} files to scan\n`);
const allViolations = [];
files.forEach(file => {
const violations = scanFile(file);
if (violations.length > 0) {
allViolations.push(...violations);
}
});
return allViolations;
}
/**
* Format and display violations
*/
function displayViolations(violations) {
if (violations.length === 0) {
console.log('✅ No CSP violations found!\n');
return 0;
}
console.log(`❌ Found ${violations.length} CSP violation(s):\n`);
// Group by file
const byFile = {};
violations.forEach(v => {
if (!byFile[v.file]) byFile[v.file] = [];
byFile[v.file].push(v);
});
// Display by file
Object.entries(byFile).forEach(([file, fileViolations]) => {
console.log(`📄 ${file} (${fileViolations.length} violation(s)):`);
fileViolations.forEach(v => {
const severity = v.severity === 'CRITICAL' ? '🔴' : '🟡';
console.log(` ${severity} Line ${v.line}: ${v.description}`);
console.log(` ${v.matched}`);
});
console.log('');
});
// Summary by type
console.log('📊 Violations by type:');
const byType = {};
violations.forEach(v => {
byType[v.description] = (byType[v.description] || 0) + 1;
});
Object.entries(byType).forEach(([type, count]) => {
console.log(` - ${type}: ${count}`);
});
console.log(`\n💡 To fix violations, run: node scripts/fix-csp-violations.js\n`);
return violations.length;
}
// Run scanner if called directly
if (require.main === module) {
const pattern = process.argv[2] || DEFAULT_PATTERNS;
const violations = scanForViolations(pattern);
const exitCode = displayViolations(violations);
process.exit(exitCode > 0 ? 1 : 0);
}
// Export for use in other scripts
module.exports = {
scanForViolations,
displayViolations,
scanFile
};