fix(gateway): enable default auth rate limiting (#87148)

* fix(gateway): enable default auth rate limiting

* fix(gateway): update auth rate limit changelog
This commit is contained in:
Agustin Rivera
2026-05-26 22:29:33 -07:00
committed by GitHub
parent 9772cf202c
commit ae972fe1fe
3 changed files with 39 additions and 3 deletions

View File

@@ -44,6 +44,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Memory/security: reject prompt-like text submitted through the explicit `memory_store` tool before embedding or storage, matching the existing auto-capture prompt-injection filter. (#87142)
- Gateway/security: enable the default auth rate limiter for remote non-browser and HTTP gateway auth failures when `gateway.auth.rateLimit` is unset, while preserving the loopback exemption. (#87148)
- Security/content boundaries: validate Browser snapshot tab URLs against SSRF policy before ChromeMCP or direct CDP reads, sanitize queued system-event text so untrusted plugin/channel labels cannot spoof nested prompt markers, wrap fetched file text and metadata as external content, apply ClickClack `allowFrom` sender allowlists before agent dispatch, reject RPCs from invalidated device-token clients during rotation, require staged sandbox media refs, and scrub serialized tool-call text from replies. (#78526, #87094, #87062, #83741, #70707, #86924) Thanks @zsxsoft, @ttzero25, and @mmaps.
- Transcripts/user turns: persist CLI, WebChat, media, follow-up, hook, and Codex-mirror user turns to the admitted session target; keep cleaned transcript text, inline image routing, provenance metadata, replay hooks, and fallback paths idempotent when runtimes fail or restart.
- TUI/status/onboarding/UI: queue busy TUI prompts instead of dropping them, preserve the configured default model during onboarding, show failed tool results as errors, show config-open failures in Control UI, keep status JSON plugin scans healthy, preserve xAI usage-limit errors locally, and expose explicit fast-mode/systemd state. (#86722, #87000, #85786, #87108, #87001, #86614, #87115, #86976)

View File

@@ -280,6 +280,39 @@ describe("gateway auth browser hardening", () => {
});
});
test("rate-limits non-browser remote auth failures by default", async () => {
const { writeConfigFile } = await import("../config/config.js");
testState.gatewayAuth = { mode: "token", token: "secret" };
await writeConfigFile({
gateway: {
trustedProxies: ["127.0.0.1"],
},
});
await withGatewayServer(async ({ port }) => {
const remoteHeaders = { "x-forwarded-for": "203.0.113.50" };
for (let attempt = 1; attempt <= 10; attempt += 1) {
const ws = await openWs(port, remoteHeaders);
try {
const res = await connectReq(ws, { token: "wrong", device: null });
expect(res.ok).toBe(false);
expect(res.error?.message ?? "").not.toContain("retry later");
} finally {
ws.close();
}
}
const lockedWs = await openWs(port, remoteHeaders);
try {
const locked = await connectReq(lockedWs, { token: "wrong", device: null });
expect(locked.ok).toBe(false);
expect(locked.error?.message ?? "").toContain("retry later");
} finally {
lockedWs.close();
}
});
});
test("isolates loopback browser-origin auth lockouts per origin", async () => {
testState.gatewayAuth = {
mode: "token",

View File

@@ -446,10 +446,12 @@ async function stopTaskRegistryMaintenanceOnDemand(): Promise<void> {
type AuthRateLimitConfig = Parameters<typeof createAuthRateLimiter>[0];
function createGatewayAuthRateLimiters(rateLimitConfig: AuthRateLimitConfig | undefined): {
rateLimiter?: AuthRateLimiter;
rateLimiter: AuthRateLimiter;
browserRateLimiter: AuthRateLimiter;
} {
const rateLimiter = rateLimitConfig ? createAuthRateLimiter(rateLimitConfig) : undefined;
// Keep remote non-browser and HTTP auth attempts throttled by default while
// preserving the normal loopback exemption unless operators configure otherwise.
const rateLimiter = createAuthRateLimiter(rateLimitConfig ?? {});
// Browser-origin WS auth attempts always use loopback-non-exempt throttling.
const browserRateLimiter = createAuthRateLimiter({
...rateLimitConfig,
@@ -984,7 +986,7 @@ export async function startGatewayServer(
runtimeState.skillsRefreshTimer = null;
},
skillsChangeUnsub: runtimeState.skillsChangeUnsub,
...(authRateLimiter ? { disposeAuthRateLimiter: () => authRateLimiter.dispose() } : {}),
disposeAuthRateLimiter: () => authRateLimiter.dispose(),
disposeBrowserAuthRateLimiter: () => browserAuthRateLimiter.dispose(),
stopModelPricingRefresh: runtimeState.stopModelPricingRefresh,
stopChannelHealthMonitor: () => runtimeState?.channelHealthMonitor?.stop(),