mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-29 01:52:04 +00:00
Security: expand audit checks for mDNS and real-IP fallback
This commit is contained in:
committed by
Peter Steinberger
parent
b13fc7eccd
commit
bc78b343ba
@@ -973,6 +973,102 @@ describe("security audit", () => {
|
||||
expect(finding?.detail).toContain("tools.exec.applyPatch.workspaceOnly=false");
|
||||
});
|
||||
|
||||
it("scores X-Real-IP fallback risk by gateway exposure", async () => {
|
||||
const cases: Array<{
|
||||
name: string;
|
||||
cfg: OpenClawConfig;
|
||||
expectedSeverity: "warn" | "critical";
|
||||
}> = [
|
||||
{
|
||||
name: "loopback gateway",
|
||||
cfg: {
|
||||
gateway: {
|
||||
bind: "loopback",
|
||||
allowRealIpFallback: true,
|
||||
trustedProxies: ["127.0.0.1"],
|
||||
auth: {
|
||||
mode: "token",
|
||||
token: "very-long-token-1234567890",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedSeverity: "warn",
|
||||
},
|
||||
{
|
||||
name: "lan gateway",
|
||||
cfg: {
|
||||
gateway: {
|
||||
bind: "lan",
|
||||
allowRealIpFallback: true,
|
||||
trustedProxies: ["10.0.0.1"],
|
||||
auth: {
|
||||
mode: "token",
|
||||
token: "very-long-token-1234567890",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedSeverity: "critical",
|
||||
},
|
||||
];
|
||||
|
||||
for (const testCase of cases) {
|
||||
const res = await audit(testCase.cfg);
|
||||
expect(
|
||||
hasFinding(res, "gateway.real_ip_fallback_enabled", testCase.expectedSeverity),
|
||||
testCase.name,
|
||||
).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("scores mDNS full mode risk by gateway bind mode", async () => {
|
||||
const cases: Array<{
|
||||
name: string;
|
||||
cfg: OpenClawConfig;
|
||||
expectedSeverity: "warn" | "critical";
|
||||
}> = [
|
||||
{
|
||||
name: "loopback gateway with full mDNS",
|
||||
cfg: {
|
||||
gateway: {
|
||||
bind: "loopback",
|
||||
auth: {
|
||||
mode: "token",
|
||||
token: "very-long-token-1234567890",
|
||||
},
|
||||
},
|
||||
discovery: {
|
||||
mdns: { mode: "full" },
|
||||
},
|
||||
},
|
||||
expectedSeverity: "warn",
|
||||
},
|
||||
{
|
||||
name: "lan gateway with full mDNS",
|
||||
cfg: {
|
||||
gateway: {
|
||||
bind: "lan",
|
||||
auth: {
|
||||
mode: "token",
|
||||
token: "very-long-token-1234567890",
|
||||
},
|
||||
},
|
||||
discovery: {
|
||||
mdns: { mode: "full" },
|
||||
},
|
||||
},
|
||||
expectedSeverity: "critical",
|
||||
},
|
||||
];
|
||||
|
||||
for (const testCase of cases) {
|
||||
const res = await audit(testCase.cfg);
|
||||
expect(
|
||||
hasFinding(res, "discovery.mdns_full_mode", testCase.expectedSeverity),
|
||||
testCase.name,
|
||||
).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("evaluates trusted-proxy auth guardrails", async () => {
|
||||
const cases: Array<{
|
||||
name: string;
|
||||
|
||||
@@ -270,6 +270,8 @@ function collectGatewayConfigFindings(
|
||||
(auth.mode === "token" && hasToken) || (auth.mode === "password" && hasPassword);
|
||||
const hasTailscaleAuth = auth.allowTailscale && tailscaleMode === "serve";
|
||||
const hasGatewayAuth = hasSharedSecret || hasTailscaleAuth;
|
||||
const allowRealIpFallback = cfg.gateway?.allowRealIpFallback === true;
|
||||
const mdnsMode = cfg.discovery?.mdns?.mode ?? "minimal";
|
||||
|
||||
// HTTP /tools/invoke is intended for narrow automation, not session orchestration/admin operations.
|
||||
// If operators opt-in to re-enabling these tools over HTTP, warn loudly so the choice is explicit.
|
||||
@@ -334,6 +336,35 @@ function collectGatewayConfigFindings(
|
||||
});
|
||||
}
|
||||
|
||||
if (allowRealIpFallback) {
|
||||
const exposed = bind !== "loopback" || auth.mode === "trusted-proxy";
|
||||
findings.push({
|
||||
checkId: "gateway.real_ip_fallback_enabled",
|
||||
severity: exposed ? "critical" : "warn",
|
||||
title: "X-Real-IP fallback is enabled",
|
||||
detail:
|
||||
"gateway.allowRealIpFallback=true trusts X-Real-IP when trusted proxies omit X-Forwarded-For. " +
|
||||
"Misconfigured proxies that forward client-supplied X-Real-IP can spoof source IP and local-client checks.",
|
||||
remediation:
|
||||
"Keep gateway.allowRealIpFallback=false (default). Only enable this when your trusted proxy " +
|
||||
"always overwrites X-Real-IP and cannot provide X-Forwarded-For.",
|
||||
});
|
||||
}
|
||||
|
||||
if (mdnsMode === "full") {
|
||||
const exposed = bind !== "loopback";
|
||||
findings.push({
|
||||
checkId: "discovery.mdns_full_mode",
|
||||
severity: exposed ? "critical" : "warn",
|
||||
title: "mDNS full mode can leak host metadata",
|
||||
detail:
|
||||
'discovery.mdns.mode="full" publishes cliPath/sshPort in local-network TXT records. ' +
|
||||
"This can reveal usernames, filesystem layout, and management ports.",
|
||||
remediation:
|
||||
'Prefer discovery.mdns.mode="minimal" (recommended) or "off", especially when gateway.bind is not loopback.',
|
||||
});
|
||||
}
|
||||
|
||||
if (tailscaleMode === "funnel") {
|
||||
findings.push({
|
||||
checkId: "gateway.tailscale_funnel",
|
||||
|
||||
Reference in New Issue
Block a user