tractatus/scripts/check-csp-violations.js
TheFlow 65784f02f8 feat(blog): integrate Tractatus framework governance into blog publishing
Implements architectural enforcement of governance rules (inst_016/017/018/079)
for all external communications. Publication blocked at API level if violations
detected.

New Features:
- Framework content checker script with pattern matching for prohibited terms
- Admin UI displays framework violations with severity indicators
- Manual "Check Framework" button for pre-publication validation
- API endpoint /api/blog/check-framework for real-time content analysis

Governance Rules Added:
- inst_078: "ff" trigger for manual framework invocation in conversations
- inst_079: Dark patterns prohibition (sovereignty principle)
- inst_080: Open source commitment enforcement (community principle)
- inst_081: Pluralism principle with indigenous framework recognition

Session Management:
- Fix session-init.js infinite loop (removed early return after tests)
- Add session-closedown.js for comprehensive session handoff
- Refactor check-csp-violations.js to prevent parent process exit

Framework Services:
- Enhanced PluralisticDeliberationOrchestrator with audit logging
- Updated all 6 services with consistent initialization patterns
- Added framework invocation scripts for blog content validation

Files: blog.controller.js:1211-1305, blog.routes.js:77-82,
blog-curation.html:61-72, blog-curation.js:320-446

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-25 08:47:31 +13:00

143 lines
4 KiB
JavaScript
Executable file

#!/usr/bin/env node
/**
* CSP Violations Checker
* Enforces Content Security Policy compliance (inst_008)
*
* Checks staged files for:
* - Inline scripts (<script> tags with code)
* - Inline event handlers (onclick, onload, etc.)
* - Inline styles in HTML
*
* Does NOT check:
* - Non-HTML files (JS, CSS, MD, etc.)
* - <script src="..."> external scripts
*/
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
/**
* Scan for CSP violations in HTML files
* @param {Array<string>} files - Optional array of files to scan (defaults to staged files)
* @returns {Array} Array of violation objects
*/
function scanForViolations(files = null) {
let htmlFiles;
if (files) {
// Use provided files
htmlFiles = files.filter(f => f.endsWith('.html'));
} else {
// Get list of staged files
try {
const stagedFiles = execSync('git diff --cached --name-only --diff-filter=ACMR', { encoding: 'utf8' })
.split('\n')
.filter(f => f.trim() !== '');
htmlFiles = stagedFiles.filter(f => f.endsWith('.html'));
} catch (error) {
console.error('Error getting staged files:', error.message);
return []; // Return empty array if can't check
}
}
if (htmlFiles.length === 0) {
return [];
}
const violations = [];
// Check each HTML file
htmlFiles.forEach(file => {
const filePath = path.join(process.cwd(), file);
if (!fs.existsSync(filePath)) {
return; // File deleted, skip
}
const content = fs.readFileSync(filePath, 'utf8');
const lines = content.split('\n');
lines.forEach((line, index) => {
const lineNum = index + 1;
// Check for inline scripts (but not <script src="...">)
if (/<script(?!.*src=)[^>]*>[\s\S]*?<\/script>/i.test(line)) {
if (line.includes('<script>') || (line.includes('<script ') && !line.includes('src='))) {
violations.push({
file,
line: lineNum,
type: 'inline-script',
content: line.trim().substring(0, 80)
});
}
}
// Check for inline event handlers
const inlineHandlers = ['onclick', 'onload', 'onmouseover', 'onsubmit', 'onerror', 'onchange'];
inlineHandlers.forEach(handler => {
if (new RegExp(`\\s${handler}=`, 'i').test(line)) {
violations.push({
file,
line: lineNum,
type: 'inline-handler',
handler,
content: line.trim().substring(0, 80)
});
}
});
// Check for inline styles (style attribute)
if (/\sstyle\s*=\s*["'][^"']*["']/i.test(line)) {
// Allow Tailwind utility classes pattern (common false positive)
if (!line.includes('class=') || line.match(/style\s*=\s*["'][^"']{20,}/)) {
violations.push({
file,
line: lineNum,
type: 'inline-style',
content: line.trim().substring(0, 80)
});
}
}
});
});
return violations;
}
/**
* Display violations to console
* @param {Array} violations - Array of violation objects
*/
function displayViolations(violations) {
if (violations.length === 0) {
return;
}
console.error('\nCSP Violations Found:\n');
violations.forEach(v => {
console.error(` ${v.file}:${v.line}`);
console.error(` Type: ${v.type}${v.handler ? ' (' + v.handler + ')' : ''}`);
console.error(` Content: ${v.content}`);
console.error('');
});
console.error(`Total violations: ${violations.length}\n`);
console.error('Fix these violations or use --no-verify to bypass (not recommended)\n');
}
// Export functions for use as a module
module.exports = { scanForViolations, displayViolations };
// Run as CLI if called directly
if (require.main === module) {
const violations = scanForViolations();
if (violations.length === 0) {
process.exit(0); // No violations, allow commit
}
displayViolations(violations);
process.exit(1); // Block commit
}