gateway: ignore bearer-declared HTTP operator scopes#57783
Conversation
Greptile SummaryThis PR closes a bearer-token scope-bypass: previously, HTTP clients authenticated with a gateway bearer token could self-assert operator scopes via the Key changes:
Two minor observations: Confidence Score: 5/5Safe to merge — all remaining findings are P2 style suggestions; the security fix is logically correct and well-covered by new and updated tests The core invariant (bearer requests cannot self-assert scopes) is correct, consistently applied across every active HTTP endpoint, and directly exercised by the PoC regression tests. The two P2 findings (orphaned export and a missing bearer test case for resolveHttpSenderIsOwner) do not affect correctness or safety. src/gateway/http-auth-helpers.ts — resolveGatewayRequestedOperatorScopes is now dead code and could be removed
|
| Filename | Overview |
|---|---|
| src/gateway/http-utils.ts | Adds isGatewayBearerHttpRequest, resolveTrustedHttpOperatorScopes, and resolveHttpSenderIsOwner helpers; the core security fix that prevents bearer clients from self-asserting operator scopes |
| src/gateway/http-endpoint-helpers.ts | Swaps resolveGatewayRequestedOperatorScopes for resolveTrustedHttpOperatorScopes; bearer requests now always get empty scopes and are rejected at the scope check |
| src/gateway/tools-invoke-http.ts | Adds an explicit bearer-token block after the auth pass, preventing any bearer-authenticated client from invoking tools over HTTP regardless of scope headers |
| src/gateway/openai-http.ts | Replaces hardcoded senderIsOwner: true with resolveHttpSenderIsOwner(req); bearer callers now correctly receive senderIsOwner: false |
| src/gateway/openresponses-http.ts | Same senderIsOwner fix as openai-http.ts applied to both the create-response and re-run-response paths |
| src/gateway/server.openai-compatible-http-write-scope-bypass.poc.test.ts | PoC tests updated to expect 403 instead of 200 for bearer scope self-assertion, and a new test added for /tools/invoke denial; regression coverage is thorough |
| src/gateway/http-auth-helpers.ts | resolveGatewayRequestedOperatorScopes is now exported dead code; all active callers have been migrated to the new trusted helper in http-utils.ts |
Comments Outside Diff (1)
-
src/gateway/http-auth-helpers.ts, line 33-42 (link)Exported dead code remains after migration
resolveGatewayRequestedOperatorScopesis now fully orphaned — no file in the repository imports it since all active call sites have been migrated toresolveTrustedHttpOperatorScopes. The PR notes acknowledge this as a known residual, but the exported function is a confusion risk: future contributors may not realise the safe version lives inhttp-utils.tsand could accidentally reach for this one instead. Consider removing it (or, if kept for a future-use reason, adding a@deprecatedJSDoc pointing to the replacement).Prompt To Fix With AI
This is a comment left during a code review. Path: src/gateway/http-auth-helpers.ts Line: 33-42 Comment: **Exported dead code remains after migration** `resolveGatewayRequestedOperatorScopes` is now fully orphaned — no file in the repository imports it since all active call sites have been migrated to `resolveTrustedHttpOperatorScopes`. The PR notes acknowledge this as a known residual, but the exported function is a confusion risk: future contributors may not realise the safe version lives in `http-utils.ts` and could accidentally reach for this one instead. Consider removing it (or, if kept for a future-use reason, adding a `@deprecated` JSDoc pointing to the replacement). How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/gateway/http-auth-helpers.ts
Line: 33-42
Comment:
**Exported dead code remains after migration**
`resolveGatewayRequestedOperatorScopes` is now fully orphaned — no file in the repository imports it since all active call sites have been migrated to `resolveTrustedHttpOperatorScopes`. The PR notes acknowledge this as a known residual, but the exported function is a confusion risk: future contributors may not realise the safe version lives in `http-utils.ts` and could accidentally reach for this one instead. Consider removing it (or, if kept for a future-use reason, adding a `@deprecated` JSDoc pointing to the replacement).
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/gateway/http-utils.request-context.test.ts
Line: 77-83
Comment:
**`resolveHttpSenderIsOwner` bearer case not tested**
The current suite tests two non-bearer scenarios (with `operator.admin` → `true`, with `operator.write` → `false`), but there is no assertion that a bearer-authenticated request with `operator.admin` in the scope header still returns `false`. Given that the suppression of self-asserted scopes for bearer requests is the core security invariant being added here, a dedicated test case would pin the contract explicitly:
```typescript
it("returns false for bearer requests even with operator.admin in headers", () => {
expect(
resolveHttpSenderIsOwner(
createReq({
authorization: "Bearer secret",
"x-openclaw-scopes": "operator.admin",
}),
),
).toBe(false);
});
```
`resolveTrustedHttpOperatorScopes` does cover this for the scope-stripping behaviour, but `resolveHttpSenderIsOwner` is the direct API that downstream callers use.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "gateway: ignore bearer-declared HTTP ope..." | Re-trigger Greptile
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1363658b53
ℹ️ 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".
|
Follow-up on review feedback: I addressed the direct bearer-owner/bearer-guard suggestions in code and tests, and pushed commit |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 20f5bd6671
ℹ️ 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".
20f5bd6 to
12c41ea
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 12c41ea24e
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cec05db8c3
ℹ️ 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".
cec05db to
a2074b1
Compare
🔒 Aisle Security AnalysisWe found 1 potential security issue(s) in this PR:
1. 🟠 Privilege escalation via client-controlled x-openclaw-scopes header when using trusted-proxy auth
Description
This enables privilege escalation if the reverse proxy does not reliably strip/overwrite
Vulnerable code: return {
authMethod: authResult.method,
trustDeclaredOperatorScopes: !usesSharedSecretGatewayMethod(authResult.method),
};
const raw = getHeader(req, "x-openclaw-scopes")?.trim();
...
return raw.split(",").map(...);If an attacker can reach the gateway through a configured trusted proxy, they may be able to add RecommendationDo not accept operator scopes directly from an untrusted, forwardable client header. Recommended fixes (choose one):
Example (derive from auth result rather than request headers): // In authorizeGatewayHttpRequestOrReply
return {
authMethod: authResult.method,
trustDeclaredOperatorScopes: false,
operatorScopes: authResult.ok ? resolveScopesForUser(authResult.user) : [],
};
// And remove use of x-openclaw-scopes for authorization decisions.At minimum, ensure the edge proxy explicitly strips Analyzed PR: #57783 at commit Last updated on: 2026-03-30T16:44:48Z Latest run failed. Keeping previous successful results. Trace ID: Last updated on: 2026-03-30T18:20:21Z |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5f595821a1
ℹ️ 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".
* gateway: ignore bearer-declared HTTP operator scopes * gateway: key HTTP bearer guards to auth mode * gateway: refresh rebased HTTP regression expectations * gateway: honor resolved HTTP auth method * gateway: remove duplicate openresponses owner flags
* gateway: ignore bearer-declared HTTP operator scopes * gateway: key HTTP bearer guards to auth mode * gateway: refresh rebased HTTP regression expectations * gateway: honor resolved HTTP auth method * gateway: remove duplicate openresponses owner flags
…scope header (#58603) resolveTrustedHttpOperatorScopes() returns [] when the x-openclaw-scopes header is absent, even for trusted requests (--auth none). This causes 403 "missing scope: operator.write" on /v1/chat/completions. Root cause: src/gateway/http-utils.ts:138-140. PR #57783 (f0af186) replaced the old resolveGatewayRequestedOperatorScopes which had an explicit fallback to CLI_DEFAULT_OPERATOR_SCOPES when no header was present. The new function treats absent header the same as empty header — both return []. Fix: distinguish absent header (undefined → return defaults) from empty header ("" → return []). Trusted clients without an explicit scope header get the default operator scopes, matching pre-#57783 behavior. Closes #58357 Signed-off-by: HCL <chenglunhu@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…scope header (openclaw#58603) resolveTrustedHttpOperatorScopes() returns [] when the x-openclaw-scopes header is absent, even for trusted requests (--auth none). This causes 403 "missing scope: operator.write" on /v1/chat/completions. Root cause: src/gateway/http-utils.ts:138-140. PR openclaw#57783 (f0af186) replaced the old resolveGatewayRequestedOperatorScopes which had an explicit fallback to CLI_DEFAULT_OPERATOR_SCOPES when no header was present. The new function treats absent header the same as empty header — both return []. Fix: distinguish absent header (undefined → return defaults) from empty header ("" → return []). Trusted clients without an explicit scope header get the default operator scopes, matching pre-openclaw#57783 behavior. Closes openclaw#58357 Signed-off-by: HCL <chenglunhu@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* gateway: ignore bearer-declared HTTP operator scopes * gateway: key HTTP bearer guards to auth mode * gateway: refresh rebased HTTP regression expectations * gateway: honor resolved HTTP auth method * gateway: remove duplicate openresponses owner flags
…scope header (openclaw#58603) resolveTrustedHttpOperatorScopes() returns [] when the x-openclaw-scopes header is absent, even for trusted requests (--auth none). This causes 403 "missing scope: operator.write" on /v1/chat/completions. Root cause: src/gateway/http-utils.ts:138-140. PR openclaw#57783 (b77a5fb) replaced the old resolveGatewayRequestedOperatorScopes which had an explicit fallback to CLI_DEFAULT_OPERATOR_SCOPES when no header was present. The new function treats absent header the same as empty header — both return []. Fix: distinguish absent header (undefined → return defaults) from empty header ("" → return []). Trusted clients without an explicit scope header get the default operator scopes, matching pre-openclaw#57783 behavior. Closes openclaw#58357 Signed-off-by: HCL <chenglunhu@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* gateway: ignore bearer-declared HTTP operator scopes * gateway: key HTTP bearer guards to auth mode * gateway: refresh rebased HTTP regression expectations * gateway: honor resolved HTTP auth method * gateway: remove duplicate openresponses owner flags
* gateway: ignore bearer-declared HTTP operator scopes * gateway: key HTTP bearer guards to auth mode * gateway: refresh rebased HTTP regression expectations * gateway: honor resolved HTTP auth method * gateway: remove duplicate openresponses owner flags
…scope header (openclaw#58603) resolveTrustedHttpOperatorScopes() returns [] when the x-openclaw-scopes header is absent, even for trusted requests (--auth none). This causes 403 "missing scope: operator.write" on /v1/chat/completions. Root cause: src/gateway/http-utils.ts:138-140. PR openclaw#57783 (cc4ce76) replaced the old resolveGatewayRequestedOperatorScopes which had an explicit fallback to CLI_DEFAULT_OPERATOR_SCOPES when no header was present. The new function treats absent header the same as empty header — both return []. Fix: distinguish absent header (undefined → return defaults) from empty header ("" → return []). Trusted clients without an explicit scope header get the default operator scopes, matching pre-openclaw#57783 behavior. Closes openclaw#58357 Signed-off-by: HCL <chenglunhu@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* gateway: ignore bearer-declared HTTP operator scopes * gateway: key HTTP bearer guards to auth mode * gateway: refresh rebased HTTP regression expectations * gateway: honor resolved HTTP auth method * gateway: remove duplicate openresponses owner flags
…scope header (openclaw#58603) resolveTrustedHttpOperatorScopes() returns [] when the x-openclaw-scopes header is absent, even for trusted requests (--auth none). This causes 403 "missing scope: operator.write" on /v1/chat/completions. Root cause: src/gateway/http-utils.ts:138-140. PR openclaw#57783 (d089836) replaced the old resolveGatewayRequestedOperatorScopes which had an explicit fallback to CLI_DEFAULT_OPERATOR_SCOPES when no header was present. The new function treats absent header the same as empty header — both return []. Fix: distinguish absent header (undefined → return defaults) from empty header ("" → return []). Trusted clients without an explicit scope header get the default operator scopes, matching pre-openclaw#57783 behavior. Closes openclaw#58357 Signed-off-by: HCL <chenglunhu@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* gateway: ignore bearer-declared HTTP operator scopes * gateway: key HTTP bearer guards to auth mode * gateway: refresh rebased HTTP regression expectations * gateway: honor resolved HTTP auth method * gateway: remove duplicate openresponses owner flags
…scope header (openclaw#58603) resolveTrustedHttpOperatorScopes() returns [] when the x-openclaw-scopes header is absent, even for trusted requests (--auth none). This causes 403 "missing scope: operator.write" on /v1/chat/completions. Root cause: src/gateway/http-utils.ts:138-140. PR openclaw#57783 (f727495) replaced the old resolveGatewayRequestedOperatorScopes which had an explicit fallback to CLI_DEFAULT_OPERATOR_SCOPES when no header was present. The new function treats absent header the same as empty header — both return []. Fix: distinguish absent header (undefined → return defaults) from empty header ("" → return []). Trusted clients without an explicit scope header get the default operator scopes, matching pre-openclaw#57783 behavior. Closes openclaw#58357 Signed-off-by: HCL <chenglunhu@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Summary
x-openclaw-scopesfor bearer-authenticated HTTP requestssenderIsOwnerfrom trusted admin scope instead of hardcoding owner access/tools/invokecalls and updates the gateway HTTP regression coverageChanges
/tools/invokedenialValidation
pnpm test -- src/gateway/http-endpoint-helpers.test.ts src/gateway/openai-http.test.ts src/gateway/openai-http.message-channel.test.ts src/gateway/openresponses-http.test.ts src/gateway/models-http.test.ts src/gateway/sessions-history-http.test.ts src/gateway/tools-invoke-http.test.ts src/gateway/server.openai-compatible-http-write-scope-bypass.poc.test.tspnpm test -- src/gateway/http-utils.request-context.test.tspnpm buildpnpm checkclaude -p "/review"and added direct unit coverage for the trusted HTTP scope helpersNotes
http-auth-helpersscope parser remains in place because this fix stayed on the unrestricted HTTP utility path; the active HTTP endpoints now route through the trusted-scope helper added here.