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>
143 lines
4 KiB
JavaScript
Executable file
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
|
|
}
|