fix(hooks): gate methods before auth lockout accounting
This commit is contained in:
@@ -603,6 +603,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Models/provider config precedence: prefer exact `models.providers.<name>` matches before normalized provider aliases in embedded model resolution, preventing alias/canonical key collisions from applying the wrong provider `api`, `baseUrl`, or headers. (#35934) thanks @RealKai42.
|
||||
- Hooks/auth throttling: reject non-`POST` `/hooks/*` requests before auth-failure accounting so unsupported methods can no longer burn the hook auth lockout budget and block legitimate webhook delivery. Thanks @P1ck3d for reporting.
|
||||
- Network/fetch guard redirect auth stripping: switch cross-origin redirect handling in `fetchWithSsrFGuard` from a narrow sensitive-header denylist to a safe-header allowlist so custom auth headers like `X-Api-Key` and `Private-Token` no longer leak on origin changes. Thanks @Rickidevs for reporting.
|
||||
- Logging/Subsystem console timestamps: route subsystem console timestamp rendering through `formatConsoleTimestamp(...)` so `pretty` and timestamp-prefix output use local timezone formatting consistently instead of inline UTC `toISOString()` paths. (#25970) Thanks @openperf.
|
||||
- Feishu/Multi-account + reply reliability: add `channels.feishu.defaultAccount` outbound routing support with schema validation, prevent inbound preview text from leaking into prompt system events, keep quoted-message extraction text-first (post/interactive/file placeholders instead of raw JSON), route Feishu video sends as `msg_type: "file"`, and avoid websocket event blocking by using non-blocking event handling in monitor dispatch. Landed from contributor PRs #31209, #29610, #30432, #30331, and #29501. Thanks @stakeswky, @hclsys, @bmendonca3, @patrick-yingxi-pan, and @zwffff.
|
||||
|
||||
@@ -383,6 +383,14 @@ export function createHooksRequestHandler(
|
||||
return true;
|
||||
}
|
||||
|
||||
if (req.method !== "POST") {
|
||||
res.statusCode = 405;
|
||||
res.setHeader("Allow", "POST");
|
||||
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
||||
res.end("Method Not Allowed");
|
||||
return true;
|
||||
}
|
||||
|
||||
const token = extractHookToken(req);
|
||||
const clientKey = resolveHookClientKey(req);
|
||||
if (!safeEqualSecret(token, hooksConfig.token)) {
|
||||
@@ -404,14 +412,6 @@ export function createHooksRequestHandler(
|
||||
}
|
||||
hookAuthLimiter.reset(clientKey, AUTH_RATE_LIMIT_SCOPE_HOOK_AUTH);
|
||||
|
||||
if (req.method !== "POST") {
|
||||
res.statusCode = 405;
|
||||
res.setHeader("Allow", "POST");
|
||||
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
||||
res.end("Method Not Allowed");
|
||||
return true;
|
||||
}
|
||||
|
||||
const subPath = url.pathname.slice(basePath.length).replace(/^\/+/, "");
|
||||
if (!subPath) {
|
||||
res.statusCode = 404;
|
||||
|
||||
@@ -383,4 +383,24 @@ describe("gateway server hooks", () => {
|
||||
expect(failAfterSuccess.status).toBe(401);
|
||||
});
|
||||
});
|
||||
|
||||
test("rejects non-POST hook requests without consuming auth failure budget", async () => {
|
||||
testState.hooksConfig = { enabled: true, token: HOOK_TOKEN };
|
||||
await withGatewayServer(async ({ port }) => {
|
||||
let lastGet: Response | null = null;
|
||||
for (let i = 0; i < 21; i++) {
|
||||
lastGet = await fetch(`http://127.0.0.1:${port}/hooks/wake`, {
|
||||
method: "GET",
|
||||
headers: { Authorization: "Bearer wrong" },
|
||||
});
|
||||
}
|
||||
expect(lastGet?.status).toBe(405);
|
||||
expect(lastGet?.headers.get("allow")).toBe("POST");
|
||||
|
||||
const allowed = await postHook(port, "/hooks/wake", { text: "still works" });
|
||||
expect(allowed.status).toBe(200);
|
||||
await waitForSystemEvent();
|
||||
drainSystemEvents(resolveMainKey());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user