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>
290 lines
8.5 KiB
JavaScript
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
|
|
};
|