#!/usr/bin/env node // ═══════════════════════════════════════════════════════════════════════════ // EOS Policy Enforcement — Förhindra åtgärder, inte bara logga // Erik-krav: "Om en agent försöker deploya utan pipeline ska EOS förhindra åtgärden" // ═══════════════════════════════════════════════════════════════════════════ import { execSync } from 'child_process'; import { existsSync, appendFileSync } from 'fs'; const ENFORCEMENT_LOG = '/home/bernt/.openclaw/workspace/EOS/enforcement-log.jsonl'; /** * Verkställbara policyer — dessa förhindrar åtgärder */ const POLICIES = [ { id: 'block-direct-prod', name: 'Blockera direkt skrivning till produktion', check: (action) => { // Kontrollera om vi är på produktionsserver const isProd = process.env.NODE_ENV === 'production' || existsSync('/etc/ec2-release'); const isWrite = action.type === 'write' || action.type === 'deploy'; return { allowed: !(isProd && isWrite), reason: isProd && isWrite ? 'Direkt skrivning till produktion blockerad' : null }; } }, { id: 'block-no-pipeline-deploy', name: 'Blockera deployment utan pipeline', check: (action) => { if (action.type !== 'deploy') return { allowed: true }; const hasPipeline = existsSync('/home/bernt/repos/quixzoom.com/.github/workflows') || existsSync('/home/bernt/repos/quixzoom.com/.gitlab-ci.yml'); return { allowed: hasPipeline, reason: hasPipeline ? null : 'Deployment utan CI/CD pipeline blockerad' }; } }, { id: 'block-server-edit', name: 'Blockera redigering på server', check: (action) => { const isServer = existsSync('/etc/ec2-release') || existsSync('/var/lib/cloud'); const isEdit = action.type === 'edit' || action.type === 'write'; return { allowed: !(isServer && isEdit), reason: isServer && isEdit ? 'Redigering på server blockerad' : null }; } }, { id: 'block-uncommitted', name: 'Blockera ändring utan commit', check: (action) => { if (action.type !== 'commit' && action.type !== 'write') return { allowed: true }; try { const status = execSync('git status --short', { cwd: '/home/bernt/repos/quixzoom.com', encoding: 'utf8' }); const hasUncommitted = status.trim().length > 0; // Tillåt commit-action även med ocommitade filer (det är poängen) if (action.type === 'commit') return { allowed: true }; return { allowed: !hasUncommitted, reason: hasUncommitted ? 'Ocommitade ändringar finns — commita först' : null }; } catch { return { allowed: false, reason: 'Kunde inte verifiera git-status' }; } } }, { id: 'block-no-migration', name: 'Blockera databasändring utan migration', check: (action) => { if (!action.target?.match(/database|db|sql/i)) return { allowed: true }; const hasMigration = existsSync('/home/bernt/repos/quixzoom.com/migrations') || existsSync('/home/bernt/repos/quixzoom.com/prisma'); return { allowed: hasMigration, reason: hasMigration ? null : 'Databasändring utan migration blockerad' }; } }, { id: 'block-no-api-contract', name: 'Blockera API-ändring utan kontrakt', check: (action) => { if (!action.target?.match(/api|endpoint|route/i)) return { allowed: true }; const hasContract = existsSync('/home/bernt/repos/quixzoom.com/openapi.yaml') || existsSync('/home/bernt/repos/quixzoom.com/openapi.json'); return { allowed: hasContract, reason: hasContract ? null : 'API-ändring utan kontrakt blockerad' }; } }, { id: 'require-human-approval', name: 'Kräv mänskligt godkännande för kritiska åtgärder', check: (action) => { const criticalActions = ['delete', 'drop', 'destroy', 'remove']; const isCritical = criticalActions.some(a => action.type?.includes(a)); return { allowed: !isCritical, reason: isCritical ? 'KRITISK ÅTGÄRD — Kräver mänskligt godkännande' : null, requiresApproval: isCritical }; } } ]; class PolicyEnforcement { constructor() { this.policies = POLICIES; } /** * Validera en åtgärd mot alla policyer */ validateAction(action) { console.log(`🔍 Validerar åtgärd: ${action.type} ${action.target || ''}\n`); const violations = []; let requiresApproval = false; for (const policy of this.policies) { const result = policy.check(action); if (!result.allowed) { violations.push({ policy: policy.id, name: policy.name, reason: result.reason }); this.logEnforcement({ action, policy: policy.id, allowed: false, reason: result.reason, timestamp: new Date().toISOString() }); } if (result.requiresApproval) { requiresApproval = true; } } const result = { action, allowed: violations.length === 0, violations, requiresApproval, timestamp: new Date().toISOString() }; this.printResult(result); return result; } /** * Försök utföra en åtgärd (med policy-kontroll) */ executeAction(action) { const validation = this.validateAction(action); if (!validation.allowed) { console.log('🔴 ÅTGÄRD BLOCKERAD\n'); for (const v of validation.violations) { console.log(` ${v.name}`); console.log(` ${v.reason}`); } console.log(); return { executed: false, validation }; } if (validation.requiresApproval) { console.log('🟡 ÅTGÄRD KRÄVER GODKÄNNANDE\n'); console.log(' Väntar på mänskligt godkännande...\n'); return { executed: false, pendingApproval: true, validation }; } console.log('✅ ÅTGÄRD GODKÄND\n'); return { executed: true, validation }; } logEnforcement(entry) { appendFileSync(ENFORCEMENT_LOG, JSON.stringify(entry) + '\n', 'utf8'); } printResult(result) { if (result.allowed && !result.requiresApproval) { console.log(`✅ ${result.action.type} — Godkänd`); } else if (result.requiresApproval) { console.log(`🟡 ${result.action.type} — Kräver godkännande`); } else { console.log(`🔴 ${result.action.type} — Blockerad`); } } /** * Visa policyer */ listPolicies() { console.log('📋 POLICYER\n'); for (const policy of this.policies) { console.log(` ${policy.name}`); console.log(` ID: ${policy.id}`); console.log(); } } /** * Visa enforcement-logg */ showLog() { if (!existsSync(ENFORCEMENT_LOG)) { console.log('Ingen enforcement-logg hittad\n'); return; } const entries = readFileSync(ENFORCEMENT_LOG, 'utf8') .split('\n') .filter(line => line.trim()) .map(line => { try { return JSON.parse(line); } catch { return null; } }) .filter(Boolean); console.log(`📊 ENFORCEMENT-LOGG (${entries.length} entries)\n`); const blocked = entries.filter(e => !e.allowed); console.log(` Blockerade: ${blocked.length}`); console.log(` Godkända: ${entries.length - blocked.length}`); console.log(); for (const entry of entries.slice(-10)) { const icon = entry.allowed ? '✅' : '🔴'; console.log(` ${icon} ${entry.action.type} ${entry.action.target || ''}`); if (entry.reason) { console.log(` ${entry.reason}`); } } console.log(); } } // ── Main ────────────────────────────────────────────────────────────────── const enforcement = new PolicyEnforcement(); const command = process.argv[2] || '--list'; if (command === '--list') { enforcement.listPolicies(); } else if (command === '--validate') { const action = { type: process.argv[3] || 'deploy', target: process.argv[4] || 'production' }; enforcement.validateAction(action); } else if (command === '--execute') { const action = { type: process.argv[3] || 'deploy', target: process.argv[4] || 'production' }; enforcement.executeAction(action); } else if (command === '--log') { enforcement.showLog(); } else { console.log('Användning:'); console.log(' node policy-enforcement.mjs --list # Lista policyer'); console.log(' node policy-enforcement.mjs --validate [target] # Validera åtgärd'); console.log(' node policy-enforcement.mjs --execute [target] # Utför med kontroll'); console.log(' node policy-enforcement.mjs --log # Visa logg'); }