From 83bdbed9be7fcbe142242d2cb2e4c545f755f6a4 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 28 Jan 2026 12:31:11 +0000 Subject: [PATCH] feat(security): add fail-safe behavior for hardening initialization When MOLTBOT_HARDENING_ENABLED=1 is set but security modules fail to initialize, the system now throws HardeningInitError and refuses to start. This ensures the gateway never runs in an insecure state when security is explicitly requested. - Add HardeningInitError class with failedModules property - Throw on any module initialization failure when hardening is enabled - Log failed modules before throwing for debugging - Add 6 tests for fail-safe behavior https://claude.ai/code/session_01L2vY1VwmwN9x8WTT7cZdi4 --- src/security/hardening.test.ts | 110 ++++++++++++++++++++++++++++++++- src/security/hardening.ts | 55 ++++++++++++++++- 2 files changed, 163 insertions(+), 2 deletions(-) diff --git a/src/security/hardening.test.ts b/src/security/hardening.test.ts index 4306addf3..d30f3553d 100644 --- a/src/security/hardening.test.ts +++ b/src/security/hardening.test.ts @@ -31,7 +31,12 @@ import { isFsMonitorActive, isSensitivePath, } from "./fs-monitor.js"; -import { initHardening, isHardeningEnabled, teardownHardening } from "./hardening.js"; +import { + HardeningInitError, + initHardening, + isHardeningEnabled, + teardownHardening, +} from "./hardening.js"; // --------------------------------------------------------------------------- // Hardening Logger @@ -553,3 +558,106 @@ describe("hardening integration", () => { expect(result.fsMonitor).toBe(true); }); }); + +// --------------------------------------------------------------------------- +// Fail-Safe Behavior Tests +// --------------------------------------------------------------------------- + +describe("hardening fail-safe behavior", () => { + beforeEach(() => { + __resetSingleUserEnforcerForTest(); + __resetNetworkMonitorForTest(); + __resetFsMonitorForTest(); + __resetHardeningLoggerForTest(); + delete process.env.MOLTBOT_HARDENING_ENABLED; + delete process.env.CLAWDBOT_HARDENING_ENABLED; + delete process.env.MOLTBOT_AUTHORIZED_USER_HASH; + delete process.env.CLAWDBOT_AUTHORIZED_USER_HASH; + }); + + afterEach(() => { + teardownHardening(); + delete process.env.MOLTBOT_HARDENING_ENABLED; + delete process.env.CLAWDBOT_HARDENING_ENABLED; + delete process.env.MOLTBOT_AUTHORIZED_USER_HASH; + delete process.env.CLAWDBOT_AUTHORIZED_USER_HASH; + }); + + it("throws HardeningInitError when single-user enforcer fails with invalid hash", () => { + process.env.MOLTBOT_HARDENING_ENABLED = "1"; + process.env.MOLTBOT_AUTHORIZED_USER_HASH = "invalid-not-a-valid-sha256-hash"; + + expect(() => initHardening({})).toThrow(HardeningInitError); + + try { + initHardening({}); + } catch (err) { + expect(err).toBeInstanceOf(HardeningInitError); + const hardeningErr = err as HardeningInitError; + expect(hardeningErr.failedModules).toContain("single-user-enforcer"); + expect(hardeningErr.message).toContain("MOLTBOT_HARDENING_ENABLED=1"); + expect(hardeningErr.message).toContain("insecure state"); + } + }); + + it("does not throw when hardening is disabled even with invalid config", () => { + // Hardening disabled - should return inactive without throwing + process.env.MOLTBOT_AUTHORIZED_USER_HASH = "invalid-hash"; + + const result = initHardening({}); + expect(result.active).toBe(false); + }); + + it("HardeningInitError has correct name and properties", () => { + process.env.MOLTBOT_HARDENING_ENABLED = "1"; + process.env.MOLTBOT_AUTHORIZED_USER_HASH = "bad"; + + try { + initHardening({}); + expect.fail("Should have thrown HardeningInitError"); + } catch (err) { + expect(err).toBeInstanceOf(HardeningInitError); + const hardeningErr = err as HardeningInitError; + expect(hardeningErr.name).toBe("HardeningInitError"); + expect(Array.isArray(hardeningErr.failedModules)).toBe(true); + expect(hardeningErr.failedModules.length).toBeGreaterThan(0); + } + }); + + it("error message includes all failed module names", () => { + process.env.MOLTBOT_HARDENING_ENABLED = "1"; + process.env.MOLTBOT_AUTHORIZED_USER_HASH = "not-valid"; + + try { + initHardening({}); + expect.fail("Should have thrown"); + } catch (err) { + const hardeningErr = err as HardeningInitError; + expect(hardeningErr.message).toContain("single-user-enforcer"); + } + }); + + it("succeeds when all modules initialize correctly", () => { + const validHash = crypto.createHash("sha256").update("+1234567890").digest("hex"); + process.env.MOLTBOT_HARDENING_ENABLED = "1"; + process.env.MOLTBOT_AUTHORIZED_USER_HASH = validHash; + + // Should not throw + const result = initHardening({}); + expect(result.active).toBe(true); + expect(result.singleUser).toBe(true); + expect(result.networkMonitor).toBe(true); + expect(result.fsMonitor).toBe(true); + }); + + it("succeeds without single-user if no hash is provided (not a failure)", () => { + process.env.MOLTBOT_HARDENING_ENABLED = "1"; + // No hash provided - single-user is simply not enabled, not a failure + + const result = initHardening({}); + expect(result.active).toBe(true); + expect(result.singleUser).toBe(false); + expect(result.networkMonitor).toBe(true); + expect(result.fsMonitor).toBe(true); + }); +}); diff --git a/src/security/hardening.ts b/src/security/hardening.ts index 5528f55e8..6edb93327 100644 --- a/src/security/hardening.ts +++ b/src/security/hardening.ts @@ -84,9 +84,28 @@ export function isHardeningEnabled(cfg: MoltbotConfig): boolean { return envFlag === "1" || envFlag === "true"; } +/** + * Custom error class for security hardening initialization failures. + * Thrown when MOLTBOT_HARDENING_ENABLED=1 but modules fail to initialize (fail-safe). + */ +export class HardeningInitError extends Error { + constructor( + message: string, + public readonly failedModules: string[], + ) { + super(message); + this.name = "HardeningInitError"; + } +} + /** * Initialize all security hardening modules. * Should be called very early in gateway startup, before any I/O. + * + * FAIL-SAFE BEHAVIOR: When hardening is explicitly enabled (MOLTBOT_HARDENING_ENABLED=1 + * or config.security.hardening.enabled=true), any initialization failure will throw + * a HardeningInitError. The system must not start in an insecure state when security + * is explicitly requested. */ export function initHardening(cfg: MoltbotConfig): { active: boolean; @@ -106,9 +125,20 @@ export function initHardening(cfg: MoltbotConfig): { } const config = resolveHardeningConfig(cfg); + const failedModules: string[] = []; + const moduleErrors: Record = {}; // 1. Initialize audit logger first (all other modules depend on it). - initHardeningLogger(); + try { + initHardeningLogger(); + } catch (err) { + // Logger failure is critical - we can't even log what went wrong + throw new HardeningInitError( + `[hardening] FATAL: Audit logger failed to initialize: ${String(err)}. ` + + `Refusing to start with MOLTBOT_HARDENING_ENABLED=1 in insecure state.`, + ["hardening-logger"], + ); + } logSecurityEvent("hardening_init", { module: "hardening", @@ -127,6 +157,8 @@ export function initHardening(cfg: MoltbotConfig): { initSingleUserEnforcer({ authorizedUserHash: userHash }); singleUserActive = true; } catch (err) { + failedModules.push("single-user-enforcer"); + moduleErrors["single-user-enforcer"] = String(err); logSecurityEvent("hardening_error", { module: "single-user-enforcer", error: String(err), @@ -149,6 +181,8 @@ export function initHardening(cfg: MoltbotConfig): { }); networkActive = true; } catch (err) { + failedModules.push("network-monitor"); + moduleErrors["network-monitor"] = String(err); logSecurityEvent("hardening_error", { module: "network-monitor", error: String(err), @@ -166,12 +200,31 @@ export function initHardening(cfg: MoltbotConfig): { }); fsActive = true; } catch (err) { + failedModules.push("fs-monitor"); + moduleErrors["fs-monitor"] = String(err); logSecurityEvent("hardening_error", { module: "fs-monitor", error: String(err), }); } + // FAIL-SAFE: If any module failed to initialize when hardening is enabled, refuse to start + if (failedModules.length > 0) { + logSecurityEvent("hardening_error", { + module: "hardening", + message: "FATAL: Security hardening initialization failed", + failedModules, + errors: moduleErrors, + }); + closeHardeningLogger(); + throw new HardeningInitError( + `[hardening] FATAL: Failed to initialize security modules: ${failedModules.join(", ")}. ` + + `Refusing to start with MOLTBOT_HARDENING_ENABLED=1 in insecure state. ` + + `Errors: ${JSON.stringify(moduleErrors)}`, + failedModules, + ); + } + logSecurityEvent("hardening_init", { module: "hardening", message: "Security hardening initialized",