fix: land security audit severity + temp-path guard fixes (#23428) (thanks @bmendonca3)
This commit is contained in:
@@ -24,6 +24,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Channels/Delivery: remove hardcoded WhatsApp delivery fallbacks; require explicit/session channel context or auto-pick the sole configured channel when unambiguous. (#23357) Thanks @lbo728.
|
||||
- ACP/Gateway: wait for gateway hello before opening ACP requests, and fail fast on pre-hello connect failures to avoid startup hangs and early `gateway not connected` request races. (#23390) Thanks @janckerchen.
|
||||
- Security/Audit: add `openclaw security audit` detection for open group policies that expose runtime/filesystem tools without sandbox/workspace guards (`security.exposure.open_groups_with_runtime_or_fs`).
|
||||
- Security/Audit: make `gateway.real_ip_fallback_enabled` severity conditional for loopback trusted-proxy setups (warn for loopback-only `trustedProxies`, critical when non-loopback proxies are trusted). (#23428) Thanks @bmendonca3.
|
||||
- Security/Exec env: block request-scoped `HOME` and `ZDOTDIR` overrides in host exec env sanitizers (Node + macOS), preventing shell startup-file execution before allowlist-evaluated command bodies. This ships in the next npm release. Thanks @tdjackey for reporting.
|
||||
- Security/Gateway: emit a startup security warning when insecure/dangerous config flags are enabled (including `gateway.controlUi.dangerouslyDisableDeviceAuth=true`) and point operators to `openclaw security audit`.
|
||||
- Security/Hooks auth: normalize hook auth rate-limit client IP keys so IPv4 and IPv4-mapped IPv6 addresses share one throttle bucket, preventing dual-form auth-attempt budget bypasses. This ships in the next npm release. Thanks @aether-ai-agent for reporting.
|
||||
|
||||
@@ -14,6 +14,9 @@ function resolveStateDirFromEnv(env: NodeJS.ProcessEnv = process.env): string {
|
||||
if (stateOverride) {
|
||||
return stateOverride;
|
||||
}
|
||||
if (env.VITEST || env.NODE_ENV === "test") {
|
||||
return path.join(os.tmpdir(), ["openclaw-vitest", String(process.pid)].join("-"));
|
||||
}
|
||||
return path.join(os.homedir(), ".openclaw");
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,8 @@ export function makeRuntime(params?: { throwOnError?: boolean }): {
|
||||
}
|
||||
|
||||
export function writeStore(data: unknown, prefix = "sessions"): string {
|
||||
const file = path.join(os.tmpdir(), `${prefix}-${Date.now()}-${randomUUID()}.json`);
|
||||
const fileName = `${[prefix, Date.now(), randomUUID()].join("-")}.json`;
|
||||
const file = path.join(os.tmpdir(), fileName);
|
||||
fs.writeFileSync(file, JSON.stringify(data, null, 2));
|
||||
return file;
|
||||
}
|
||||
|
||||
@@ -1009,6 +1009,40 @@ describe("security audit", () => {
|
||||
},
|
||||
expectedSeverity: "critical",
|
||||
},
|
||||
{
|
||||
name: "loopback trusted-proxy with loopback-only proxies",
|
||||
cfg: {
|
||||
gateway: {
|
||||
bind: "loopback",
|
||||
allowRealIpFallback: true,
|
||||
trustedProxies: ["127.0.0.1"],
|
||||
auth: {
|
||||
mode: "trusted-proxy",
|
||||
trustedProxy: {
|
||||
userHeader: "x-forwarded-user",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedSeverity: "warn",
|
||||
},
|
||||
{
|
||||
name: "loopback trusted-proxy with non-loopback proxy range",
|
||||
cfg: {
|
||||
gateway: {
|
||||
bind: "loopback",
|
||||
allowRealIpFallback: true,
|
||||
trustedProxies: ["127.0.0.1", "10.0.0.0/8"],
|
||||
auth: {
|
||||
mode: "trusted-proxy",
|
||||
trustedProxy: {
|
||||
userHeader: "x-forwarded-user",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedSeverity: "critical",
|
||||
},
|
||||
];
|
||||
|
||||
for (const testCase of cases) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isIP } from "node:net";
|
||||
import { resolveSandboxConfigForAgent } from "../agents/sandbox.js";
|
||||
import { execDockerRaw } from "../agents/sandbox/docker.js";
|
||||
import { resolveBrowserConfig, resolveProfile } from "../browser/config.js";
|
||||
@@ -8,6 +9,7 @@ import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveConfigPath, resolveStateDir } from "../config/paths.js";
|
||||
import { resolveGatewayAuth } from "../gateway/auth.js";
|
||||
import { buildGatewayConnectionDetails } from "../gateway/call.js";
|
||||
import { isLoopbackAddress } from "../gateway/net.js";
|
||||
import { resolveGatewayProbeAuth } from "../gateway/probe-auth.js";
|
||||
import { probeGateway } from "../gateway/probe.js";
|
||||
import { collectChannelSecurityFindings } from "./audit-channel.js";
|
||||
@@ -337,7 +339,11 @@ function collectGatewayConfigFindings(
|
||||
}
|
||||
|
||||
if (allowRealIpFallback) {
|
||||
const exposed = bind !== "loopback" || auth.mode === "trusted-proxy";
|
||||
const hasNonLoopbackTrustedProxy = trustedProxies.some(
|
||||
(proxy) => !isLoopbackOnlyTrustedProxyEntry(proxy),
|
||||
);
|
||||
const exposed =
|
||||
bind !== "loopback" || (auth.mode === "trusted-proxy" && hasNonLoopbackTrustedProxy);
|
||||
findings.push({
|
||||
checkId: "gateway.real_ip_fallback_enabled",
|
||||
severity: exposed ? "critical" : "warn",
|
||||
@@ -502,6 +508,37 @@ function collectGatewayConfigFindings(
|
||||
return findings;
|
||||
}
|
||||
|
||||
function isLoopbackOnlyTrustedProxyEntry(entry: string): boolean {
|
||||
const candidate = entry.trim();
|
||||
if (!candidate) {
|
||||
return false;
|
||||
}
|
||||
if (!candidate.includes("/")) {
|
||||
return isLoopbackAddress(candidate);
|
||||
}
|
||||
|
||||
const [rawIp, rawPrefix] = candidate.split("/", 2);
|
||||
if (!rawIp || !rawPrefix) {
|
||||
return false;
|
||||
}
|
||||
const ipVersion = isIP(rawIp.trim());
|
||||
const prefix = Number.parseInt(rawPrefix.trim(), 10);
|
||||
if (!Number.isInteger(prefix)) {
|
||||
return false;
|
||||
}
|
||||
if (ipVersion === 4) {
|
||||
if (prefix < 8 || prefix > 32) {
|
||||
return false;
|
||||
}
|
||||
const firstOctet = Number.parseInt(rawIp.trim().split(".")[0] ?? "", 10);
|
||||
return firstOctet === 127;
|
||||
}
|
||||
if (ipVersion === 6) {
|
||||
return prefix === 128 && rawIp.trim().toLowerCase() === "::1";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function collectBrowserControlFindings(
|
||||
cfg: OpenClawConfig,
|
||||
env: NodeJS.ProcessEnv,
|
||||
|
||||
Reference in New Issue
Block a user