mirror of
https://github.com/farcasclaudiu/openclaw.git
synced 2026-06-28 23:02:02 +03:00
feat(security): warn when gateway.tools.allow re-enables dangerous HTTP tools
This commit is contained in:
@@ -153,6 +153,53 @@ describe("security audit", () => {
|
|||||||
).toBe(true);
|
).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("warns when gateway.tools.allow re-enables dangerous HTTP /tools/invoke tools (loopback)", async () => {
|
||||||
|
const cfg: OpenClawConfig = {
|
||||||
|
gateway: {
|
||||||
|
bind: "loopback",
|
||||||
|
auth: { token: "secret" },
|
||||||
|
tools: { allow: ["sessions_spawn"] },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await runSecurityAudit({
|
||||||
|
config: cfg,
|
||||||
|
env: {},
|
||||||
|
includeFilesystem: false,
|
||||||
|
includeChannelSecurity: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
res.findings.some(
|
||||||
|
(f) => f.checkId === "gateway.tools_invoke_http.dangerous_allow" && f.severity === "warn",
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("flags dangerous gateway.tools.allow over HTTP as critical when gateway binds beyond loopback", async () => {
|
||||||
|
const cfg: OpenClawConfig = {
|
||||||
|
gateway: {
|
||||||
|
bind: "lan",
|
||||||
|
auth: { token: "secret" },
|
||||||
|
tools: { allow: ["sessions_spawn", "gateway"] },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await runSecurityAudit({
|
||||||
|
config: cfg,
|
||||||
|
env: {},
|
||||||
|
includeFilesystem: false,
|
||||||
|
includeChannelSecurity: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
res.findings.some(
|
||||||
|
(f) =>
|
||||||
|
f.checkId === "gateway.tools_invoke_http.dangerous_allow" && f.severity === "critical",
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it("does not warn for auth rate limiting when configured", async () => {
|
it("does not warn for auth rate limiting when configured", async () => {
|
||||||
const cfg: OpenClawConfig = {
|
const cfg: OpenClawConfig = {
|
||||||
gateway: {
|
gateway: {
|
||||||
|
|||||||
@@ -258,6 +258,33 @@ function collectGatewayConfigFindings(
|
|||||||
(auth.mode === "token" && hasToken) || (auth.mode === "password" && hasPassword);
|
(auth.mode === "token" && hasToken) || (auth.mode === "password" && hasPassword);
|
||||||
const hasTailscaleAuth = auth.allowTailscale && tailscaleMode === "serve";
|
const hasTailscaleAuth = auth.allowTailscale && tailscaleMode === "serve";
|
||||||
const hasGatewayAuth = hasSharedSecret || hasTailscaleAuth;
|
const hasGatewayAuth = hasSharedSecret || hasTailscaleAuth;
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
const gatewayToolsAllowRaw = Array.isArray(cfg.gateway?.tools?.allow)
|
||||||
|
? cfg.gateway?.tools?.allow
|
||||||
|
: [];
|
||||||
|
const gatewayToolsAllow = new Set(
|
||||||
|
gatewayToolsAllowRaw
|
||||||
|
.map((v) => (typeof v === "string" ? v.trim().toLowerCase() : ""))
|
||||||
|
.filter(Boolean),
|
||||||
|
);
|
||||||
|
const defaultHttpDeniedTools = ["sessions_spawn", "sessions_send", "gateway", "whatsapp_login"];
|
||||||
|
const reenabledOverHttp = defaultHttpDeniedTools.filter((name) => gatewayToolsAllow.has(name));
|
||||||
|
if (reenabledOverHttp.length > 0) {
|
||||||
|
const extraRisk = bind !== "loopback" || tailscaleMode === "funnel";
|
||||||
|
findings.push({
|
||||||
|
checkId: "gateway.tools_invoke_http.dangerous_allow",
|
||||||
|
severity: extraRisk ? "critical" : "warn",
|
||||||
|
title: "Gateway HTTP /tools/invoke re-enables dangerous tools",
|
||||||
|
detail:
|
||||||
|
`gateway.tools.allow includes ${reenabledOverHttp.join(", ")} which removes them from the default HTTP deny list. ` +
|
||||||
|
"This can allow remote session spawning / control-plane actions via HTTP and increases RCE blast radius if the gateway is reachable.",
|
||||||
|
remediation:
|
||||||
|
"Remove these entries from gateway.tools.allow (recommended). " +
|
||||||
|
"If you keep them enabled, keep gateway.bind loopback-only (or tailnet-only), restrict network exposure, and treat the gateway token/password as full-admin.",
|
||||||
|
});
|
||||||
|
}
|
||||||
if (bind !== "loopback" && !hasSharedSecret && auth.mode !== "trusted-proxy") {
|
if (bind !== "loopback" && !hasSharedSecret && auth.mode !== "trusted-proxy") {
|
||||||
findings.push({
|
findings.push({
|
||||||
checkId: "gateway.bind_no_auth",
|
checkId: "gateway.bind_no_auth",
|
||||||
|
|||||||
Reference in New Issue
Block a user