05ed037fe8
- DNS: pilot.landvex.com -> 16.170.83.169 - TLS: Let's Encrypt certificate (expires 2026-09-30) - Nginx: reverse proxy with SSL termination - API: https://pilot.landvex.com/api/v1/missions - UI: https://pilot.landvex.com/ - Upload: POST /api/v1/missions/import (multipart/form-data) Verified: ✅ https://pilot.landvex.com/health ✅ https://pilot.landvex.com/version ✅ https://pilot.landvex.com/api/v1/missions (list) ✅ https://pilot.landvex.com/api/v1/missions/:id (get) ✅ POST /api/v1/missions/import (video upload) ✅ UI loads with title 'LandveX Intelligence Lab' Next: Pilot 001 — Break the system!
208 lines
5.2 KiB
JavaScript
208 lines
5.2 KiB
JavaScript
#!/usr/bin/env node
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// EOS Integration — Gör EOS obligatoriskt i agent-flödet
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
import { readFileSync } from 'fs';
|
|
|
|
/**
|
|
* EOS Integration säkerställer att:
|
|
* 1. Ingen agent kan hoppa över EOS-policyer
|
|
* 2. Varje förändring loggas
|
|
* 3. Blockeringar är tydliga och motiverade
|
|
* 4. Undantag kräver mänsklig granskning
|
|
*/
|
|
|
|
class EOSIntegration {
|
|
constructor() {
|
|
this.violations = [];
|
|
this.exemptions = [];
|
|
}
|
|
|
|
/**
|
|
* Kör EOS-policyer för en förändring
|
|
*/
|
|
async check(change) {
|
|
const checks = [
|
|
this.checkNoHardcodedSecrets(change),
|
|
this.checkNoDirectProd(change),
|
|
this.checkTestsRequired(change),
|
|
this.checkGitRequired(change),
|
|
this.checkNoUncommittedChanges(change),
|
|
this.checkIaCRequired(change)
|
|
];
|
|
|
|
const results = await Promise.all(checks);
|
|
const failed = results.filter(r => !r.passed);
|
|
|
|
if (failed.length > 0) {
|
|
return {
|
|
passed: false,
|
|
blocked: true,
|
|
reason: 'EOS Policy Check failed',
|
|
failures: failed,
|
|
action: 'Fix failures or request exemption'
|
|
};
|
|
}
|
|
|
|
return {
|
|
passed: true,
|
|
blocked: false,
|
|
reason: 'All checks passed',
|
|
results
|
|
};
|
|
}
|
|
|
|
async checkNoHardcodedSecrets(change) {
|
|
const secrets = this.findSecrets(change);
|
|
|
|
return {
|
|
rule: 'no-hardcoded-secrets',
|
|
passed: secrets.length === 0,
|
|
evidence: secrets.length > 0
|
|
? `Found secrets: ${secrets.join(', ')}`
|
|
: 'No secrets found',
|
|
severity: 'CRITICAL'
|
|
};
|
|
}
|
|
|
|
async checkNoDirectProd(change) {
|
|
const isProd = change.target?.includes('production');
|
|
const hasPipeline = change.pipeline !== undefined;
|
|
|
|
return {
|
|
rule: 'no-direct-prod',
|
|
passed: !isProd || hasPipeline,
|
|
evidence: isProd && !hasPipeline
|
|
? 'Production change without pipeline'
|
|
: 'OK',
|
|
severity: 'HIGH'
|
|
};
|
|
}
|
|
|
|
async checkTestsRequired(change) {
|
|
const hasTests = change.tests?.length > 0;
|
|
const isConfig = change.type === 'config';
|
|
|
|
return {
|
|
rule: 'test-required',
|
|
passed: hasTests || isConfig,
|
|
evidence: !hasTests && !isConfig
|
|
? 'No tests for code change'
|
|
: 'OK',
|
|
severity: 'MEDIUM'
|
|
};
|
|
}
|
|
|
|
async checkGitRequired(change) {
|
|
const hasGit = change.git?.commit !== undefined;
|
|
|
|
return {
|
|
rule: 'git-required',
|
|
passed: hasGit,
|
|
evidence: !hasGit
|
|
? 'No git commit'
|
|
: 'OK',
|
|
severity: 'HIGH'
|
|
};
|
|
}
|
|
|
|
async checkNoUncommittedChanges(change) {
|
|
// TODO: Kolla git status
|
|
return {
|
|
rule: 'no-uncommitted-changes',
|
|
passed: true, // TODO: Verklig kontroll
|
|
evidence: 'TODO',
|
|
severity: 'MEDIUM'
|
|
};
|
|
}
|
|
|
|
async checkIaCRequired(change) {
|
|
const isInfrastructure = change.type === 'infrastructure';
|
|
const hasTerraform = change.terraform !== undefined;
|
|
|
|
return {
|
|
rule: 'iac-required',
|
|
passed: !isInfrastructure || hasTerraform,
|
|
evidence: isInfrastructure && !hasTerraform
|
|
? 'Infrastructure change without Terraform'
|
|
: 'OK',
|
|
severity: 'HIGH'
|
|
};
|
|
}
|
|
|
|
findSecrets(change) {
|
|
const secrets = [];
|
|
const patterns = [
|
|
/password\s*=\s*["'][^"']+["']/i,
|
|
/secret\s*=\s*["'][^"']+["']/i,
|
|
/token\s*=\s*["'][^"']+["']/i,
|
|
/api_key\s*=\s*["'][^"']+["']/i,
|
|
/JWT_SECRET\s*=\s*["'][^"']+["']/i
|
|
];
|
|
|
|
for (const file of change.files || []) {
|
|
for (const pattern of patterns) {
|
|
if (pattern.test(file.content)) {
|
|
secrets.push(file.path);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return [...new Set(secrets)];
|
|
}
|
|
|
|
/**
|
|
* Begär undantag från EOS-regel
|
|
*/
|
|
requestExemption(rule, reason, requester) {
|
|
const exemption = {
|
|
id: `exemption-${Date.now()}`,
|
|
rule,
|
|
reason,
|
|
requester,
|
|
requestedAt: new Date().toISOString(),
|
|
status: 'pending',
|
|
approvedBy: null,
|
|
approvedAt: null
|
|
};
|
|
|
|
this.exemptions.push(exemption);
|
|
return exemption;
|
|
}
|
|
|
|
/**
|
|
* Godkänn undantag
|
|
*/
|
|
approveExemption(exemptionId, approver) {
|
|
const exemption = this.exemptions.find(e => e.id === exemptionId);
|
|
if (exemption) {
|
|
exemption.status = 'approved';
|
|
exemption.approvedBy = approver;
|
|
exemption.approvedAt = new Date().toISOString();
|
|
}
|
|
return exemption;
|
|
}
|
|
}
|
|
|
|
// CLI
|
|
if (process.argv[1] === new URL(import.meta.url).pathname) {
|
|
const eos = new EOSIntegration();
|
|
|
|
// Testa med en förändring
|
|
eos.check({
|
|
id: 'test-change',
|
|
type: 'code',
|
|
files: [
|
|
{ path: 'test.mjs', content: 'const x = 1;' }
|
|
],
|
|
git: { commit: 'abc123' },
|
|
tests: ['test.mjs']
|
|
}).then(result => {
|
|
console.log(JSON.stringify(result, null, 2));
|
|
});
|
|
}
|
|
|
|
export { EOSIntegration };
|