feat(security): warn on dangerous config flags at startup

This commit is contained in:
Peter Steinberger
2026-02-22 10:11:03 +01:00
parent de2e5c7b74
commit f101d59d57
4 changed files with 81 additions and 25 deletions

View File

@@ -0,0 +1,45 @@
import { describe, expect, it, vi } from "vitest";
import { logGatewayStartup } from "./server-startup-log.js";
describe("gateway startup log", () => {
it("warns when dangerous config flags are enabled", () => {
const info = vi.fn();
const warn = vi.fn();
logGatewayStartup({
cfg: {
gateway: {
controlUi: {
dangerouslyDisableDeviceAuth: true,
},
},
},
bindHost: "127.0.0.1",
port: 18789,
log: { info, warn },
isNixMode: false,
});
expect(warn).toHaveBeenCalledTimes(1);
expect(warn).toHaveBeenCalledWith(expect.stringContaining("dangerous config flags enabled"));
expect(warn).toHaveBeenCalledWith(
expect.stringContaining("gateway.controlUi.dangerouslyDisableDeviceAuth=true"),
);
expect(warn).toHaveBeenCalledWith(expect.stringContaining("openclaw security audit"));
});
it("does not warn when dangerous config flags are disabled", () => {
const info = vi.fn();
const warn = vi.fn();
logGatewayStartup({
cfg: {},
bindHost: "127.0.0.1",
port: 18789,
log: { info, warn },
isNixMode: false,
});
expect(warn).not.toHaveBeenCalled();
});
});

View File

@@ -3,6 +3,7 @@ import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js";
import { resolveConfiguredModelRef } from "../agents/model-selection.js";
import type { loadConfig } from "../config/config.js";
import { getResolvedLoggerSettings } from "../logging.js";
import { collectEnabledInsecureOrDangerousFlags } from "../security/dangerous-config-flags.js";
export function logGatewayStartup(params: {
cfg: ReturnType<typeof loadConfig>;
@@ -10,7 +11,7 @@ export function logGatewayStartup(params: {
bindHosts?: string[];
port: number;
tlsEnabled?: boolean;
log: { info: (msg: string, meta?: Record<string, unknown>) => void };
log: { info: (msg: string, meta?: Record<string, unknown>) => void; warn: (msg: string) => void };
isNixMode: boolean;
}) {
const { provider: agentProvider, model: agentModel } = resolveConfiguredModelRef({
@@ -37,4 +38,12 @@ export function logGatewayStartup(params: {
if (params.isNixMode) {
params.log.info("gateway: running in Nix mode (config managed externally)");
}
const enabledDangerousFlags = collectEnabledInsecureOrDangerousFlags(params.cfg);
if (enabledDangerousFlags.length > 0) {
const warning =
`security warning: dangerous config flags enabled: ${enabledDangerousFlags.join(", ")}. ` +
"Run `openclaw security audit`.";
params.log.warn(warning);
}
}

View File

@@ -39,6 +39,7 @@ import {
formatPermissionRemediation,
inspectPathPermissions,
} from "./audit-fs.js";
import { collectEnabledInsecureOrDangerousFlags } from "./dangerous-config-flags.js";
import { DEFAULT_GATEWAY_HTTP_TOOL_DENY } from "./dangerous-tools.js";
import type { ExecFn } from "./windows-acl.js";
@@ -119,30 +120,6 @@ function normalizeAllowFromList(list: Array<string | number> | undefined | null)
return list.map((v) => String(v).trim()).filter(Boolean);
}
function collectEnabledInsecureOrDangerousFlags(cfg: OpenClawConfig): string[] {
const enabledFlags: string[] = [];
if (cfg.gateway?.controlUi?.allowInsecureAuth === true) {
enabledFlags.push("gateway.controlUi.allowInsecureAuth=true");
}
if (cfg.gateway?.controlUi?.dangerouslyDisableDeviceAuth === true) {
enabledFlags.push("gateway.controlUi.dangerouslyDisableDeviceAuth=true");
}
if (cfg.hooks?.gmail?.allowUnsafeExternalContent === true) {
enabledFlags.push("hooks.gmail.allowUnsafeExternalContent=true");
}
if (Array.isArray(cfg.hooks?.mappings)) {
for (const [index, mapping] of cfg.hooks.mappings.entries()) {
if (mapping?.allowUnsafeExternalContent === true) {
enabledFlags.push(`hooks.mappings[${index}].allowUnsafeExternalContent=true`);
}
}
}
if (cfg.tools?.exec?.applyPatch?.workspaceOnly === false) {
enabledFlags.push("tools.exec.applyPatch.workspaceOnly=false");
}
return enabledFlags;
}
async function collectFilesystemFindings(params: {
stateDir: string;
configPath: string;

View File

@@ -0,0 +1,25 @@
import type { OpenClawConfig } from "../config/config.js";
export function collectEnabledInsecureOrDangerousFlags(cfg: OpenClawConfig): string[] {
const enabledFlags: string[] = [];
if (cfg.gateway?.controlUi?.allowInsecureAuth === true) {
enabledFlags.push("gateway.controlUi.allowInsecureAuth=true");
}
if (cfg.gateway?.controlUi?.dangerouslyDisableDeviceAuth === true) {
enabledFlags.push("gateway.controlUi.dangerouslyDisableDeviceAuth=true");
}
if (cfg.hooks?.gmail?.allowUnsafeExternalContent === true) {
enabledFlags.push("hooks.gmail.allowUnsafeExternalContent=true");
}
if (Array.isArray(cfg.hooks?.mappings)) {
for (const [index, mapping] of cfg.hooks.mappings.entries()) {
if (mapping?.allowUnsafeExternalContent === true) {
enabledFlags.push(`hooks.mappings[${index}].allowUnsafeExternalContent=true`);
}
}
}
if (cfg.tools?.exec?.applyPatch?.workspaceOnly === false) {
enabledFlags.push("tools.exec.applyPatch.workspaceOnly=false");
}
return enabledFlags;
}