mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-28 04:26:16 +00:00
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:
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user