Lumi/scripts/verify-production-diagnostics.js
2026-07-18 16:56:40 +02:00

94 lines
4.8 KiB
JavaScript

const assert = require("assert");
const fs = require("fs");
const path = require("path");
const root = path.join(__dirname, "..");
require("../src/services/db").migrate();
const diagnostics = require("../src/services/production-diagnostics");
assert.deepEqual(Object.keys(diagnostics.CHECKS), [
"system_health",
"update_state",
"plugins",
"recent_errors",
"benchmark"
]);
const key = "lumi_diag_verification-key_123";
const hash = diagnostics.hashAccessKey(key);
assert.match(hash, /^[a-f0-9]{64}$/);
assert.equal(diagnostics.verifyAccessKey(key, hash), true);
assert.equal(diagnostics.verifyAccessKey(`${key}x`, hash), false);
assert.equal(diagnostics.verifyAccessKey("wrong-prefix", hash), false);
assert.equal(diagnostics.isSecureDiagnosticRequest({ secure: true, ip: "203.0.113.10" }), true);
assert.equal(diagnostics.isSecureDiagnosticRequest({ secure: false, ip: "127.0.0.1" }), true);
assert.equal(diagnostics.isSecureDiagnosticRequest({ secure: false, ip: "203.0.113.10" }), false);
const redacted = diagnostics.redactDiagnosticValue({
token: "top-secret",
message: `authorization=Bearer-value ${key} /mnt/c/private/lumi/file.js user@example.com ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefgh`,
nested: { password: "also-secret", url: "https://example.test/?api_key=secret-value&ok=1" }
});
const redactedJson = JSON.stringify(redacted);
for (const forbidden of ["top-secret", "also-secret", key, "/mnt/c/private", "user@example.com", "secret-value", "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefgh"]) {
assert.equal(redactedJson.includes(forbidden), false, `diagnostic output leaked ${forbidden}`);
}
assert.match(redactedJson, /redacted|diagnostics key|local path|email/i);
const fingerprint = `verify-${Date.now()}`;
for (let index = 0; index < diagnostics.MAX_REQUESTS_PER_MINUTE; index += 1) {
const rate = diagnostics.consumeRateLimit(fingerprint, 120000);
assert.equal(rate.allowed, true);
}
assert.equal(diagnostics.consumeRateLimit(fingerprint, 120000).allowed, false);
assert.equal(diagnostics.consumeRateLimit(fingerprint, 180000).allowed, true);
for (const check of Object.keys(diagnostics.CHECKS)) {
const result = diagnostics.runDiagnosticCheck(check);
assert.equal(result.schema_version, 1);
assert.equal(result.check, check);
assert.equal(typeof result.duration_ms, "number");
assert.doesNotMatch(JSON.stringify(result), /\blumi_diag_[A-Za-z0-9_-]+\b/);
}
assert.throws(() => diagnostics.runDiagnosticCheck("shell"), /Unknown diagnostic check/);
const serviceSource = fs.readFileSync(path.join(root, "src", "services", "production-diagnostics.js"), "utf8");
for (const forbidden of [
/child_process/,
/\bexec(?:Sync)?\s*\(/,
/\bspawn(?:Sync)?\s*\(/,
/\beval\s*\(/,
/new\s+Function\s*\(/,
/\bfetch\s*\(/,
/require\(["'](?:https?|net|tls|dgram)["']\)/
]) {
assert.doesNotMatch(serviceSource, forbidden, "diagnostics must not expose execution or network primitives");
}
const serverSource = fs.readFileSync(path.join(root, "src", "web", "server.js"), "utf8");
const endpointIndex = serverSource.indexOf('app.post("/api/diagnostics/v1/run"');
const configuredIndex = serverSource.indexOf("app.use(requireConfigured)");
assert(endpointIndex > 0 && endpointIndex < configuredIndex, "diagnostics endpoint must remain available for production recovery");
assert.match(serverSource, /authenticateDiagnosticsRequest\(req\)/);
assert.match(serverSource, /app\.set\("trust proxy", "loopback"\)/);
assert.match(serverSource, /app\.get\("\/admin\/diagnostics", requireRole\("admin"\)/);
assert.match(serverSource, /app\.post\("\/admin\/diagnostics\/access\/renew", requireRole\("admin"\)/);
assert.match(serverSource, /app\.post\("\/admin\/diagnostics\/access\/revoke", requireRole\("admin"\)/);
const viewSource = fs.readFileSync(path.join(root, "src", "web", "views", "admin-diagnostics.ejs"), "utf8");
assert.match(viewSource, /no remote control/i);
assert.match(viewSource, /data-confirm-mode="modal"/);
assert.match(viewSource, /\.secrets/);
assert.match(viewSource, /diagnosticsEndpointUrl/);
assert.match(viewSource, /diagnosticsBaseUrl/);
assert.doesNotMatch(viewSource, /https:\/\/your-lumi-host/);
assert.match(serverSource, /const diagnosticsBaseUrl = `\$\{req\.protocol\}:\/\/\$\{req\.get\("host"\)\}`/);
const clientSource = fs.readFileSync(path.join(root, "scripts", "production-diagnostics-client.js"), "utf8");
assert.match(clientSource, /\.secrets["'], "production-diagnostics\.json"/);
assert.match(clientSource, /method: "POST"/);
assert.match(clientSource, /new URL\("\/api\/diagnostics\/v1\/run"/);
assert.doesNotMatch(clientSource, /console\.(?:log|error)\(\s*key\s*\)/, "client must not print its access key");
console.log("Production diagnostics verification passed: fixed read-only checks, redaction, key validation, rate limiting, HTTPS proxy trust, and admin-only controls.");