Skip to content

fix(gateway): return default scopes when trusted HTTP request has no scope header#58603

Merged
fabianwilliams merged 1 commit into
openclaw:mainfrom
hclsys:fix/http-scopes-default-on-no-header
Apr 1, 2026
Merged

fix(gateway): return default scopes when trusted HTTP request has no scope header#58603
fabianwilliams merged 1 commit into
openclaw:mainfrom
hclsys:fix/http-scopes-default-on-no-header

Conversation

@hclsys

@hclsys hclsys commented Mar 31, 2026

Copy link
Copy Markdown

lobster-biscuit

Closes #58357

Problem

HTTP /v1/chat/completions returns 403 "missing scope: operator.write" when the gateway is configured with --auth none. The OpenAI-compatible API is completely broken for unauthenticated setups.

Root cause

src/gateway/http-utils.ts:138-140resolveTrustedHttpOperatorScopes() returns [] when the x-openclaw-scopes header is absent, even for trusted requests. PR #57783 (f0af18672) replaced the old scope resolution which had an explicit fallback to CLI_DEFAULT_OPERATOR_SCOPES when no header was present.

The new function treats absent header (undefined) the same as empty header ("") — both return [] → zero scopes → 403.

User impact

All --auth none HTTP API users get 403 on every request. OpenAI-compatible API completely broken for unauthenticated gateway setups.

Fix

Distinguish absent header (undefined → return CLI_DEFAULT_OPERATOR_SCOPES) from empty header ("" → return []). Uses existing CLI_DEFAULT_OPERATOR_SCOPES from method-scopes.ts (already imported at line 20).

1 file, +7/-1.

How to verify

  1. Start gateway with --auth none
  2. curl http://localhost:18789/v1/chat/completions -d ...
  3. Before: 403 "missing scope: operator.write". After: 200 OK.

Tests

The scope resolution is tested via the HTTP auth integration tests. The fix restores pre-#57783 behavior for the absent-header case — the same test expectations apply.

🤖 Generated with Claude Code

…scope header

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

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: HCL <chenglunhu@gmail.com>
@hclsys

This comment was marked as low quality.

@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime size: XS labels Mar 31, 2026
@greptile-apps

greptile-apps Bot commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a regression introduced in #57783 where resolveTrustedHttpOperatorScopes() incorrectly treated an absent x-openclaw-scopes header the same as an explicitly-empty one, both returning [] and causing 403 errors for all --auth none gateway users.

The fix is minimal and targeted: by splitting getHeader(...)?trim() into two steps, the function now correctly distinguishes:

  • Absent header (undefined) → return [...CLI_DEFAULT_OPERATOR_SCOPES] (full default access, restoring pre-gateway: ignore bearer-declared HTTP operator scopes #57783 behavior)
  • Explicitly empty header ("") → return [] (caller opted out of scopes)
  • Populated header → parse comma-separated scopes as before

The change is consistent with how resolveOpenAiCompatibleHttpOperatorScopes already handles the shared-secret path (line 163), which also returns CLI_DEFAULT_OPERATOR_SCOPES. The defensive spread [...CLI_DEFAULT_OPERATOR_SCOPES] correctly avoids exposing the mutable constant array to callers.

Confidence Score: 5/5

Safe to merge — single-file, minimal change that correctly restores the intended fallback for absent scope headers.

No P0 or P1 issues found. The fix correctly separates the absent-header case from the empty-header case, uses the already-imported constant, and is consistent with the existing pattern in resolveOpenAiCompatibleHttpOperatorScopes.

No files require special attention.

Important Files Changed

Filename Overview
src/gateway/http-utils.ts Correct fix that distinguishes absent header (→ default scopes) from empty header (→ no scopes) in resolveTrustedHttpOperatorScopes, restoring pre-#57783 behavior for --auth none setups.

Reviews (1): Last reviewed commit: "fix(gateway): return default scopes when..." | Re-trigger Greptile

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 110413e4af

ℹ️ 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".

Comment thread src/gateway/http-utils.ts
Comment on lines +139 to +142
if (headerValue === undefined) {
// No scope header present — trusted clients without an explicit header
// get the default operator scopes (matching pre-#57783 behavior).
return [...CLI_DEFAULT_OPERATOR_SCOPES];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restrict default scope fallback to shared-secret compat auth

This fallback now applies to every trusted HTTP request, not just shared-secret compat calls. In trusted-proxy or none modes, omitting x-openclaw-scopes now grants CLI_DEFAULT_OPERATOR_SCOPES (which includes operator.admin in src/gateway/method-scopes.ts), so compat requests are treated as owner via resolveOpenAiCompatibleHttpSenderIsOwner and can access owner-only tools without explicitly declared scopes. That regresses the explicit-scope boundary for identity-bearing proxy flows and can privilege-escalate authenticated users when the proxy does not inject a scopes header.

Useful? React with 👍 / 👎.

@fabianwilliams fabianwilliams left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — reviewed code, tests, and approach. Clean implementation.

@fabianwilliams fabianwilliams merged commit b8fea43 into openclaw:main Apr 1, 2026
41 of 43 checks passed
steipete pushed a commit to Mlightsnow/openclaw that referenced this pull request Apr 1, 2026
…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>
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
…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>
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
…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>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…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>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gateway Gateway runtime size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: HTTP /v1/chat/completions returns 'missing scope: operator.write' with --auth none (v2026.3.28)

2 participants