fix(gateway): reject unknown agent IDs on OpenAI + OpenResponses HTTP paths#45344
fix(gateway): reject unknown agent IDs on OpenAI + OpenResponses HTTP paths#45344spence27 wants to merge 1 commit into
Conversation
… paths Unknown agent IDs supplied via x-openclaw-agent-id / x-openclaw-agent or model selectors like openclaw:ghost / agent:ghost now return 400 invalid_request_error instead of silently routing to main. - Add InvalidGatewayAgentIdError to http-utils - resolveAgentIdForRequest validates explicit selections against knownAgentIds when provided; implicit fallback to main unchanged - openai-http and openresponses-http pass listAgentIds(loadConfig()) as knownAgentIds and catch the error as sendInvalidRequest(400) - Tests: 7 unit tests in request-context test, 9 e2e tests across both HTTP handler test files; all 16 pass
Greptile SummaryThis PR fixes a security gap in the OpenClaw gateway where unknown agent IDs supplied via Key changes:
One logic concern: Confidence Score: 4/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/gateway/http-utils.ts
Line: 80-88
Comment:
**`knownAgentIds: undefined` silently rejects all explicit agent selections**
When `knownAgentIds` is `undefined`, the optional-chaining expression `knownAgentIds?.includes(selection.agentId)` evaluates to `undefined`, so `!undefined` is `true` and the function **always throws `InvalidGatewayAgentIdError`** — even for a perfectly valid agent ID.
This contradicts the implied contract of the `?` optional parameter in `resolveAgentIdForRequest` and `resolveGatewayRequestContext`: a caller who omits `knownAgentIds` expecting "no validation" will instead get an unexpected error thrown for any request that carries an explicit `x-openclaw-agent-id` header or an `openclaw:<id>` / `agent:<id>` model selector.
The two current production callers always pass `listAgentIds(loadConfig())`, so nothing breaks today. However, any future gateway handler (or unit test) that omits `knownAgentIds` while handling agent-scoped requests will silently break.
Suggested fix — treat `undefined` as "skip validation":
```ts
function assertKnownExplicitAgentSelection(
selection: ExplicitAgentSelection,
knownAgentIds: readonly string[] | undefined,
): string {
if (knownAgentIds !== undefined && !knownAgentIds.includes(selection.agentId)) {
throw new InvalidGatewayAgentIdError(selection.rawAgentId);
}
return selection.agentId;
}
```
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: 3eec497 |
| function assertKnownExplicitAgentSelection( | ||
| selection: ExplicitAgentSelection, | ||
| knownAgentIds: readonly string[] | undefined, | ||
| ): string { | ||
| if (!knownAgentIds?.includes(selection.agentId)) { | ||
| throw new InvalidGatewayAgentIdError(selection.rawAgentId); | ||
| } | ||
| return selection.agentId; | ||
| } |
There was a problem hiding this comment.
knownAgentIds: undefined silently rejects all explicit agent selections
When knownAgentIds is undefined, the optional-chaining expression knownAgentIds?.includes(selection.agentId) evaluates to undefined, so !undefined is true and the function always throws InvalidGatewayAgentIdError — even for a perfectly valid agent ID.
This contradicts the implied contract of the ? optional parameter in resolveAgentIdForRequest and resolveGatewayRequestContext: a caller who omits knownAgentIds expecting "no validation" will instead get an unexpected error thrown for any request that carries an explicit x-openclaw-agent-id header or an openclaw:<id> / agent:<id> model selector.
The two current production callers always pass listAgentIds(loadConfig()), so nothing breaks today. However, any future gateway handler (or unit test) that omits knownAgentIds while handling agent-scoped requests will silently break.
Suggested fix — treat undefined as "skip validation":
function assertKnownExplicitAgentSelection(
selection: ExplicitAgentSelection,
knownAgentIds: readonly string[] | undefined,
): string {
if (knownAgentIds !== undefined && !knownAgentIds.includes(selection.agentId)) {
throw new InvalidGatewayAgentIdError(selection.rawAgentId);
}
return selection.agentId;
}Prompt To Fix With AI
This is a comment left during a code review.
Path: src/gateway/http-utils.ts
Line: 80-88
Comment:
**`knownAgentIds: undefined` silently rejects all explicit agent selections**
When `knownAgentIds` is `undefined`, the optional-chaining expression `knownAgentIds?.includes(selection.agentId)` evaluates to `undefined`, so `!undefined` is `true` and the function **always throws `InvalidGatewayAgentIdError`** — even for a perfectly valid agent ID.
This contradicts the implied contract of the `?` optional parameter in `resolveAgentIdForRequest` and `resolveGatewayRequestContext`: a caller who omits `knownAgentIds` expecting "no validation" will instead get an unexpected error thrown for any request that carries an explicit `x-openclaw-agent-id` header or an `openclaw:<id>` / `agent:<id>` model selector.
The two current production callers always pass `listAgentIds(loadConfig())`, so nothing breaks today. However, any future gateway handler (or unit test) that omits `knownAgentIds` while handling agent-scoped requests will silently break.
Suggested fix — treat `undefined` as "skip validation":
```ts
function assertKnownExplicitAgentSelection(
selection: ExplicitAgentSelection,
knownAgentIds: readonly string[] | undefined,
): string {
if (knownAgentIds !== undefined && !knownAgentIds.includes(selection.agentId)) {
throw new InvalidGatewayAgentIdError(selection.rawAgentId);
}
return selection.agentId;
}
```
How can I resolve this? If you propose a fix, please make it concise.
xkonjin
left a comment
There was a problem hiding this comment.
Code Review — Reject Unknown Agent IDs
Overall: Clean, well-tested security fix. The pattern of validating agent IDs at the HTTP boundary before routing is the right approach. Good test coverage with both header and model-based selection paths.
✅ Strengths
- Custom error class (
InvalidGatewayAgentIdError) with actionable CLI hint is great UX - Tests cover all four paths: header-known, header-unknown, model-known, model-unknown
- Consistent application across both OpenAI and OpenResponses HTTP handlers
🟡 Potential issue: loadConfig() called per-request
Both openai-http.ts and openresponses-http.ts call listAgentIds(loadConfig()) inside the request handler. If loadConfig() re-reads from disk on every call, this adds I/O per request. If it's cached/memoized, no issue — worth confirming.
🟡 Missing: test for empty knownAgentIds
What happens when knownAgentIds is undefined or an empty array? Looking at assertKnownExplicitAgentSelection:
if (!knownAgentIds?.includes(selection.agentId)) {If knownAgentIds is undefined, the optional chain returns undefined (falsy), which means ALL explicit agent selections would throw. That seems intentional (strict mode), but a test confirming this behavior would be valuable.
💡 Minor: duplicate expect blocks in unit tests
The unit tests for unknown agent IDs call the same function twice — once to check toThrowError(InvalidGatewayAgentIdError) and once to check .toThrow(/Unknown agent id/). These could be combined into a single assertion block since toThrowError accepts both a class and a message pattern.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3eec49784d
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| selection: ExplicitAgentSelection, | ||
| knownAgentIds: readonly string[] | undefined, | ||
| ): string { | ||
| if (!knownAgentIds?.includes(selection.agentId)) { |
There was a problem hiding this comment.
Allow explicit main agent selection
Explicitly selecting main now returns a 400 in multi-agent configs that do not list a main entry, because this check only accepts IDs present in knownAgentIds; however, the same request with no selector still falls back to "main" later in this function. That creates a behavior regression where x-openclaw-agent-id: main / model: "openclaw:main" fail even though implicit default routing still targets main, which can break existing clients and documented examples that pin main explicitly.
Useful? React with 👍 / 👎.
|
Reviewed the diff — solid security fix. The silent fallback to Agreeing with xkonjin's note on Also +1 on the |
|
Thanks for the context here. I swept through the related work, and this is now duplicate or superseded. Keep open. The PR addresses a real source-reproducible gateway bug, but the branch is now conflicting and stale, lacks after-fix real behavior proof, and would regress current OpenAI-compatible gateway routing contracts if merged as submitted. Canonical path: Close this stale PR. The latest review rated it F, the branch still lacks merge-ready proof, and there has been no human follow-up after the durable review. So I’m closing this here because the remaining work is already tracked in the canonical issue. Review detailsBest possible solution: Close this stale PR. The latest review rated it F, the branch still lacks merge-ready proof, and there has been no human follow-up after the durable review. Do we have a high-confidence way to reproduce the issue? Yes, source-level. Current main resolves explicit header/model agent IDs without configured-agent validation before dispatch; I did not run a live gateway in this read-only review. Is this the best way to solve the issue? No as submitted. HTTP-boundary validation is the right direction, but this branch must be rebased and preserve current gateway contracts before it is a maintainable merge shape. Security review: Security review cleared: No dependency, workflow, secret-handling, package-resolution, download, or lifecycle-hook changes were found; gateway routing risks are covered as functional merge risks. What I checked:
Likely related people:
Codex review notes: model gpt-5.5, reasoning high; reviewed against cd1cae5be9ba. |
|
ClawSweeper PR egg 🎁 Pass real behavior proof to wake the egg and unlock a hatchable treat. Where did the egg go?
|
|
ClawSweeper applied the proposed close for this PR.
|
Problem
Unknown agent IDs passed via
x-openclaw-agent-id,x-openclaw-agent, or model selectors likeopenclaw:ghost/agent:ghostwere silently accepted and routed tomaininstead of being rejected.Repro on the installed gateway:
Fix
src/gateway/http-utils.ts— addsInvalidGatewayAgentIdErrorand wires it intoresolveAgentIdForRequest/resolveGatewayRequestContextwhen aknownAgentIdslist is supplied. Implicit fallback tomain(no header, no model selector) is unchanged.src/gateway/openai-http.ts— passeslistAgentIds(loadConfig())asknownAgentIdsand mapsInvalidGatewayAgentIdError→sendInvalidRequest(400).src/gateway/openresponses-http.ts— same change for/v1/responses.Tests
All 16 tests pass locally (
vitest.gateway.config.ts):http-utils.request-context.test.tsopenai-http.test.tsopenresponses-http.test.tsError shape (consistent with existing gateway errors)
{ "error": { "type": "invalid_request_error", "message": "Unknown agent id \"ghost\". Use \"openclaw agents list\" to see configured agents." } }