tractatus/scripts/fix-csp-violations.js
TheFlow 0e6be3eaf1 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

290 lines
8.5 KiB
JavaScript

#!/usr/bin/env node
/**
* CSP Violation Auto-Remediation Script
*
* Analyzes CSP violations and provides fix recommendations.
* Can optionally attempt automatic fixes for simple cases.
*
* Usage:
* node scripts/fix-csp-violations.js [--auto] [file]
*
* Options:
* --auto Attempt automatic fixes (USE WITH CAUTION)
* --dry-run Show what would be fixed without making changes
* [file] Specific file to fix (default: scan all)
*
* Copyright 2025 Tractatus Project
* Licensed under Apache License 2.0
*/
const fs = require('fs');
const path = require('path');
const { scanForViolations, scanFile } = require('./check-csp-violations');
const colors = {
reset: '\x1b[0m',
green: '\x1b[32m',
yellow: '\x1b[33m',
red: '\x1b[31m',
cyan: '\x1b[36m',
bold: '\x1b[1m'
};
function log(message, color = 'reset') {
console.log(`${colors[color]}${message}${colors.reset}`);
}
/**
* Parse command-line arguments
*/
function parseArgs() {
const args = process.argv.slice(2);
return {
auto: args.includes('--auto'),
dryRun: args.includes('--dry-run'),
file: args.find(arg => !arg.startsWith('--'))
};
}
/**
* Generate fix recommendations for a violation
*/
function generateFixRecommendation(violation) {
const recommendations = {
inline_event_handlers: {
priority: 'HIGH',
approach: 'Move to external JavaScript',
steps: [
`1. Create event listener in external JS file:`,
` document.getElementById('element-id').addEventListener('click', function() {`,
` // Handler code here`,
` });`,
``,
`2. Remove ${violation.matched.split('=')[0]}= attribute from HTML`,
``,
`3. Add unique ID to element if needed for selection`
],
example: 'See public/js/components/*.js for examples'
},
inline_styles: {
priority: 'HIGH',
approach: 'Move to Tailwind CSS classes or external CSS',
steps: [
`1. For dynamic styles: Use CSS classes with JavaScript`,
` element.classList.add('custom-style');`,
``,
`2. For static styles: Add Tailwind classes to HTML`,
` Replace style="${violation.matched}" with Tailwind utilities`,
``,
`3. For complex styles: Add to public/css/custom.css`
],
example: 'Project uses Tailwind CSS - prefer utility classes'
},
inline_scripts: {
priority: 'CRITICAL',
approach: 'Extract to external JavaScript file',
steps: [
`1. Create or identify appropriate JS file in public/js/`,
``,
`2. Move script content to external file`,
``,
`3. Replace inline script with:`,
` <script src="/js/your-file.js"></script>`,
``,
`4. Ensure script loads at appropriate time (defer/async if needed)`
],
example: 'See public/js/*.js for existing patterns'
},
javascript_urls: {
priority: 'CRITICAL',
approach: 'Replace with proper event handlers',
steps: [
`1. Remove href="javascript:..." attribute`,
``,
`2. Add event listener in external JS:`,
` document.getElementById('link-id').addEventListener('click', function(e) {`,
` e.preventDefault();`,
` // Action code here`,
` });`,
``,
`3. For links that don't navigate, consider using <button> instead of <a>`
],
example: 'Use semantic HTML and event listeners'
}
};
return recommendations[violation.type] || {
priority: 'MEDIUM',
approach: 'Manual review required',
steps: ['Review violation and apply appropriate CSP-compliant fix'],
example: ''
};
}
/**
* Display fix recommendations for violations
*/
function displayFixRecommendations(violations) {
if (violations.length === 0) {
log('\n✅ No violations to fix!\n', 'green');
return;
}
log(`\n${'='.repeat(70)}`, 'cyan');
log(`CSP VIOLATION FIX RECOMMENDATIONS`, 'bold');
log(`${'='.repeat(70)}\n`, 'cyan');
// Group by file
const byFile = {};
violations.forEach(v => {
if (!byFile[v.file]) byFile[v.file] = [];
byFile[v.file].push(v);
});
Object.entries(byFile).forEach(([file, fileViolations]) => {
log(`\n📄 ${file}`, 'cyan');
log(` ${fileViolations.length} violation(s)\n`, 'yellow');
fileViolations.forEach((violation, idx) => {
const rec = generateFixRecommendation(violation);
log(` ${idx + 1}. Line ${violation.line}: ${violation.description}`, 'bold');
log(` ${violation.matched}`, 'yellow');
log(``, 'reset');
log(` Priority: ${rec.priority}`, rec.priority === 'CRITICAL' ? 'red' : 'yellow');
log(` Approach: ${rec.approach}`, 'cyan');
log(``, 'reset');
rec.steps.forEach(step => {
log(` ${step}`, 'reset');
});
if (rec.example) {
log(``, 'reset');
log(` 💡 ${rec.example}`, 'green');
}
log(``, 'reset');
});
});
log(`${'='.repeat(70)}`, 'cyan');
log(`\n📊 Summary:`, 'bold');
log(` Total violations: ${violations.length}`, 'yellow');
const byType = {};
violations.forEach(v => {
byType[v.description] = (byType[v.description] || 0) + 1;
});
Object.entries(byType).forEach(([type, count]) => {
log(` - ${type}: ${count}`, 'reset');
});
log(`\n⚠️ IMPORTANT:`, 'yellow');
log(` - Test all changes locally before deployment`, 'reset');
log(` - Verify functionality after removing inline code`, 'reset');
log(` - Use browser DevTools to debug event handlers`, 'reset');
log(` - Run npm run check:csp after fixes to verify\n`, 'reset');
}
/**
* Attempt automatic fix for simple violations (EXPERIMENTAL)
*/
function attemptAutoFix(violations, dryRun = false) {
log(`\n⚠️ AUTO-FIX MODE (EXPERIMENTAL)\n`, 'yellow');
if (dryRun) {
log(` DRY RUN - No files will be modified\n`, 'cyan');
} else {
log(` ⚠️ FILES WILL BE MODIFIED - Ensure you have backups!\n`, 'red');
}
let fixCount = 0;
const fixableTypes = ['inline_styles']; // Only auto-fix inline styles for now
violations.forEach(violation => {
if (fixableTypes.includes(violation.type)) {
log(` ${dryRun ? '[DRY RUN]' : '[FIXING]'} ${violation.file}:${violation.line}`, 'cyan');
log(` ${violation.matched}`, 'yellow');
// For now, just count fixable violations
// Actual fixing would require careful parsing and replacement
fixCount++;
log(` → Would convert to Tailwind classes or external CSS`, 'green');
log(``, 'reset');
} else {
log(` [SKIP] ${violation.file}:${violation.line} - ${violation.type}`, 'yellow');
log(` Requires manual fix`, 'reset');
log(``, 'reset');
}
});
log(`\n📊 Auto-fix summary:`, 'bold');
log(` Fixable: ${fixCount}`, 'green');
log(` Manual: ${violations.length - fixCount}\n`, 'yellow');
if (!dryRun && fixCount > 0) {
log(` ⚠️ NOTE: Auto-fix is experimental and disabled by default`, 'red');
log(` Use --dry-run to preview changes, then apply manually\n`, 'yellow');
}
}
/**
* Main function
*/
function main() {
const args = parseArgs();
log(`\n${'='.repeat(70)}`, 'cyan');
log(`CSP Violation Remediation Tool`, 'bold');
log(`${'='.repeat(70)}\n`, 'cyan');
// Scan for violations
log(`🔍 Scanning for violations...\n`, 'cyan');
let violations;
if (args.file) {
log(` Target: ${args.file}\n`, 'yellow');
violations = scanFile(args.file);
} else {
log(` Target: All public files\n`, 'yellow');
violations = scanForViolations();
}
log(` Found: ${violations.length} violation(s)\n`, violations.length > 0 ? 'yellow' : 'green');
if (violations.length === 0) {
log(`✅ No violations found!\n`, 'green');
process.exit(0);
}
// Auto-fix mode
if (args.auto || args.dryRun) {
attemptAutoFix(violations, args.dryRun);
} else {
// Display recommendations
displayFixRecommendations(violations);
}
log(`\n💡 Next steps:`, 'cyan');
log(` 1. Review recommendations above`, 'reset');
log(` 2. Apply fixes manually or use --dry-run to preview`, 'reset');
log(` 3. Test changes locally (npm start on port 9000)`, 'reset');
log(` 4. Verify fixes: npm run check:csp`, 'reset');
log(` 5. Deploy once all violations are resolved\n`, 'reset');
process.exit(violations.length > 0 ? 1 : 0);
}
// Run if called directly
if (require.main === module) {
main();
}
module.exports = {
generateFixRecommendation,
displayFixRecommendations
};