Skip to content

fix(oauth): remove base64 obfuscation from public OAuth client IDs#96537

Merged
steipete merged 1 commit into
openclaw:mainfrom
yangxiansheng:fix/issue-96490-oauth-client-id-cleanup
Jul 9, 2026
Merged

fix(oauth): remove base64 obfuscation from public OAuth client IDs#96537
steipete merged 1 commit into
openclaw:mainfrom
yangxiansheng:fix/issue-96490-oauth-client-id-cleanup

Conversation

@yangxiansheng

@yangxiansheng yangxiansheng commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Closes #96490

What Problem This Solves

Fixes an issue where Anthropic and GitHub Copilot OAuth client IDs were stored as Base64-encoded strings via atob(), creating a false impression that these public values are sensitive secrets. This security theatre misleads maintainers into treating them as credentials and adds unnecessary runtime indirection.

Why This Change Was Made

OAuth client IDs are public identifiers per RFC 6749 — they are sent in cleartext to authorization endpoints on every OAuth flow. The const decode = (s: string) => atob(s) wrapper provides zero security benefit while implying the values should be treated as confidential. This pattern was introduced unintentionally during a large-scale refactor (PR #85341) and was inconsistently applied: the sibling openai-codex.ts file already stores its client ID as a plain string literal in the same directory.

The fix removes the decode helper and replaces both Base64 literals with their decoded plaintext equivalents, matching the convention used by the rest of the OAuth module.

User Impact

No user-visible behavior change. Code is simpler, more transparent, and no longer misrepresents public OAuth identifiers as obfuscated values.

Evidence

All OAuth tests pass: 4 test files, 24 tests passed.

Test output
[openclaw]$ pnpm test src/llm/utils/oauth/
Scope: all 156 workspace projects
Lockfile is up to date, resolution step is skipped
Packages: +1210
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

   ╭─────────────────────────────────────────╮
   │                                         │
   │   Update available! 11.2.2 → 11.9.0.    │
   │   Changelog: https://pnpm.io/v/11.9.0   │
   │    To update, run: pnpm add -g pnpm     │
   │                                         │
   ╰─────────────────────────────────────────╯

Progress: resolved 0, reused 1174, downloaded 0, added 1210, done
node_modules/@matrix-org/matrix-sdk-crypto-nodejs: Running postinstall script, done in 5m 31.7s
node_modules/openclaw/node_modules/@google/genai: Running preinstall script, done in 52ms
node_modules/@google/genai: Running preinstall script, done in 60ms
node_modules/baileys: Running preinstall script, done in 87ms
node_modules/openclaw: Running preinstall script, done in 73ms
node_modules/openclaw: Running postinstall script, done in 4.4s
. preinstall$ node scripts/preinstall-package-manager-warning.mjs
└─ Done in 104ms
. postinstall$ node scripts/postinstall-bundled-plugins.mjs
└─ Done in 156ms
. prepare$ node scripts/prepare-git-hooks.mjs
└─ Done in 191ms
Done in 5m 53.2s using pnpm v11.2.2
$ node scripts/test-projects.mjs src/llm/utils/oauth/
[test] starting test/vitest/vitest.unit.config.ts

 RUN  v4.1.8 /media/vdb/outcoco/demo/openclaw

 ✓  unit  src/llm/utils/oauth/openai-chatgpt.test.ts (8 tests) 445ms
     ✓ routes legacy login callbacks through the OpenAI provider auth hook  431ms
 ✓  unit  src/llm/utils/oauth/github-copilot.test.ts (10 tests) 47ms
 ✓  unit  src/llm/utils/oauth/anthropic.test.ts (4 tests) 31ms
 ✓  unit  src/llm/utils/oauth/abort.test.ts (2 tests) 5ms

 Test Files  4 passed (4)
      Tests  24 passed (24)
   Start at  02:38:39
   Duration  3.02s (transform 2.89s, setup 1.63s, import 353ms, tests 527ms, environment 0ms)

[test] passed 1 Vitest shard in 14.89s
Real OAuth request path verification
1. Anthropic authorize URL (anthropic.ts:304-316)
[openclaw]$ node -e "
> const CLIENT_ID = '9d1c250a-e61b-44d9-88ed-5944d1962f5e';
> const url = new URL('https://claude.ai/oauth/authorize');
> url.searchParams.set('client_id', CLIENT_ID);
> console.log('Anthropic authorize URL:', url.toString());
> "
Anthropic authorize URL: https://claude.ai/oauth/authorize?client_id=9d1c250a-e61b-44d9-88ed-5944d1962f5e

2. Anthropic token exchange POST body (anthropic.ts:258-259)
[openclaw]$ node -e "
> const CLIENT_ID = '9d1c250a-e61b-44d9-88ed-5944d1962f5e';
> const body = new URLSearchParams({
>   grant_type: 'authorization_code',
>   client_id: CLIENT_ID,
>   code: 'test_auth_code',
>   redirect_uri: 'http://localhost:53692/callback',
>   code_verifier: 'test_verifier'
> });
> console.log('Anthropic token exchange POST body:', body.toString());
> "
Anthropic token exchange POST body: grant_type=authorization_code&client_id=9d1c250a-e61b-44d9-88ed-5944d1962f5e&code=test_auth_code&redirect_uri=http%3A%2F%2Flocalhost%3A53692%2Fcallback&code_verifier=test_verifier

3. GitHub Copilot device code POST body (github-copilot.ts:205-208)
[openclaw]$ node -e "
> const CLIENT_ID = 'Iv1.b507a08c87ecfe98';
> const body = new URLSearchParams({
>   client_id: CLIENT_ID,
>   scope: 'read:user'
> });
> console.log('Copilot device code POST body:', body.toString());
> "
Copilot device code POST body: client_id=Iv1.b507a08c87ecfe98&scope=read%3Auser

4. GitHub Copilot token poll POST body (github-copilot.ts:299-303)
[openclaw]$ node -e "
> const CLIENT_ID = 'Iv1.b507a08c87ecfe98';
> const body = new URLSearchParams({
>   client_id: CLIENT_ID,
>   device_code: 'test_device_code',
>   grant_type: 'urn:ietf:params:oauth:grant-type:device_code'
> });
> console.log('Copilot token poll POST body:', body.toString());
> "
Copilot token poll POST body: client_id=Iv1.b507a08c87ecfe98&device_code=test_device_code&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code

All four OAuth request paths (anthropic.ts:306, anthropic.ts:259, github-copilot.ts:206, github-copilot.ts:300) inject the plaintext CLIENT_ID correctly — the only change from main is removing the atob wrapper, the request construction code is untouched.

[openclaw]$ echo "=== Anthropic CLIENT_ID in Build Artifacts ===" && grep -o '"[^"]*9d1c250a[^"]*"' dist/oauth-DrNXzZ_D.js && echo "" && echo "=== Copilot CLIENT_ID in Build Artifacts ===" && grep -o '"[^"]*Iv1\.b507[^"]*"' dist/oauth-DrNXzZ_D.js && echo "" && echo "=== Check for Residual atob Decoding in Build Artifacts ===" && grep -c 'atob' dist/oauth-DrNXzZ_D.js && echo "(0 = Cleared, no atob decoding remaining)"
=== Anthropic CLIENT_ID in Build Artifacts ===
"9d1c250a-e61b-44d9-88ed-5944d1962f5e"

=== Copilot CLIENT_ID in Build Artifacts ===
"Iv1.b507a08c87ecfe98"

=== Check for Residual atob Decoding in Build Artifacts ===
0

The URL construction above uses the same code pattern as src/llm/utils/oauth/anthropic.ts:304-316 and github-copilot.ts:205-208 — the CLIENT_ID is injected at runtime by the OpenClaw OAuth module. The built artifact confirms that both values exist as plaintext literals in the compiled output and no atob decode path remains.

Ported from issue #96490 with AI-assisted implementation.

@clawsweeper

clawsweeper Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 25, 2026, 2:47 PM ET / 18:47 UTC.

Summary
The PR replaces Base64-decoded Anthropic and GitHub Copilot OAuth client ID constants with equivalent plaintext string literals.

PR surface: Source -2. Total -2 across 2 files.

Reproducibility: yes. Current main source shows the atob-based CLIENT_ID derivation in both OAuth helpers, and a focused decode check proves the proposed literals match the existing Base64 payloads exactly.

Review metrics: 1 noteworthy metric.

  • OAuth client IDs changed: 2 changed, 0 added, 0 removed. Both constants feed provider OAuth request payloads, so exact decoded-value preservation matters before merge.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #96490
Summary: This PR is the candidate fix for the open OAuth client-id cleanup issue; the broad runtime refactor is related provenance only.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🐚 platinum hermit
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • none.

Risk before merge

  • [P1] No live Anthropic or GitHub Copilot OAuth login was run; the merge decision relies on exact decoded-value equivalence, unchanged request construction, terminal proof, and CI.
  • [P1] The linked issue is security-labeled, so maintainers should explicitly accept that this is clarity cleanup rather than a secret-handling vulnerability fix.

Maintainer options:

  1. Accept Exact-Value Proof (recommended)
    Proceed if maintainers accept the decoded-value checks, PR-head source inspection, build-artifact output, and green OAuth tests as enough for this no-behavior-change auth constant cleanup.
  2. Request Live OAuth Trace
    Ask for redacted Anthropic authorize or GitHub Copilot device-code request output before merge if maintainers require live provider-path proof for changed auth constants.

Next step before merge

  • No automated repair candidate remains; maintainers should decide whether the exact-value and build-artifact proof is enough to merge this auth-provider cleanup.

Security
Cleared: No concrete security or supply-chain regression was found; the diff preserves the same public OAuth client ID values and does not touch secrets, dependencies, scripts, permissions, or token handling.

Review details

Best possible solution:

Merge the scoped constant cleanup after maintainers accept the exact-value and build-artifact proof, leaving OAuth request construction and config surfaces unchanged.

Do we have a high-confidence way to reproduce the issue?

Yes. Current main source shows the atob-based CLIENT_ID derivation in both OAuth helpers, and a focused decode check proves the proposed literals match the existing Base64 payloads exactly.

Is this the best way to solve the issue?

Yes. Replacing only the two constant initializers is the narrowest maintainable fix because the OAuth request call sites, provider ids, config, token handling, and refresh flows remain unchanged.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 66e2fcc6f83e.

Label changes

Label justifications:

  • P3: The PR is a focused auth-provider clarity cleanup with no claimed user-visible behavior change.
  • merge-risk: 🚨 auth-provider: A wrong CLIENT_ID literal would break Anthropic or GitHub Copilot OAuth login/token requests.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body includes terminal test output, request construction output, and build-artifact output showing the plaintext literals and no remaining atob path for the changed OAuth artifact.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes terminal test output, request construction output, and build-artifact output showing the plaintext literals and no remaining atob path for the changed OAuth artifact.
Evidence reviewed

PR surface:

Source -2. Total -2 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 2 2 4 -2
Tests 0 0 0 0
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 2 4 -2

What I checked:

  • Root policy applied: Root AGENTS.md was read fully; its auth-provider and whole-path PR review guidance applies to this OAuth cleanup. (AGENTS.md:1, 66e2fcc6f83e)
  • PR diff is narrowly scoped: The live PR patch only removes local atob decode helpers and replaces the two CLIENT_ID initializers with decoded literals. (src/llm/utils/oauth/anthropic.ts:45, 2f312a856169)
  • PR-head Anthropic request path: At the PR head, the Anthropic token exchange, authorize URL, and refresh request still pass the same CLIENT_ID constant into client_id fields. (src/llm/utils/oauth/anthropic.ts:259, 2f312a856169)
  • PR-head Copilot request path: At the PR head, GitHub Copilot device-code and token-poll request bodies still pass CLIENT_ID as client_id. (src/llm/utils/oauth/github-copilot.ts:206, 2f312a856169)
  • Decoded values match exactly: A local Node decode check printed the Anthropic and Copilot plaintext values and true for both comparisons against the PR literals.
  • Current main still has encoded constants: Current main derives both affected CLIENT_ID values through atob, so the requested cleanup is not already implemented on main. (src/llm/utils/oauth/anthropic.ts:45, 66e2fcc6f83e)

Likely related people:

  • Shakker: Current-line blame for both touched OAuth helper files points to 4ecb45b, which added these files in the current checkout history. (role: recent area contributor; confidence: medium; commits: 4ecb45bf7729; files: src/llm/utils/oauth/anthropic.ts, src/llm/utils/oauth/github-copilot.ts)
  • Vincent Koc: git log -S traces the same encoded constants through the v2026.6.10 release commit, which still contains the current encoded values. (role: release-lineage contributor; confidence: medium; commits: aa69b12d0086; files: src/llm/utils/oauth/anthropic.ts, src/llm/utils/oauth/github-copilot.ts)
  • steipete: The PR body cites the merged runtime internalization PR as provenance for the OAuth helper shape, and the provided live PR metadata names steipete as that PR author. (role: adjacent runtime refactor owner; confidence: low; commits: bb46b79d3c14; files: src/llm/utils/oauth/anthropic.ts, src/llm/utils/oauth/github-copilot.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. labels Jun 24, 2026
@clawsweeper

clawsweeper Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added the merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. label Jun 24, 2026
@yangxiansheng

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper clawsweeper Bot added the proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. label Jun 25, 2026
@yangxiansheng

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper clawsweeper Bot removed the proof: 📸 screenshot Contributor real behavior proof includes screenshot evidence. label Jun 25, 2026
@yangxiansheng

Copy link
Copy Markdown
Contributor Author

@clawsweeper re-review

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jun 25, 2026
@steipete steipete self-assigned this Jul 9, 2026
@steipete
steipete force-pushed the fix/issue-96490-oauth-client-id-cleanup branch from 2f312a8 to e64267a Compare July 9, 2026 15:33
@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Maintainer proof for prepared head e64267a34410e1f575c1ea5267f5251ea7f17747:

  • GitHub server-side rebased the contributor commit onto main at 3c048ef0529712e0d1fab86fba02b194cd245d2a; the resulting PR diff remains exactly the two OAuth files (+2/-4).
  • Focused OAuth tests: pnpm test -- src/llm/utils/oauth/anthropic.test.ts src/llm/utils/oauth/github-copilot.test.ts — 2 files, 25 tests passed.
  • Exact-value proof: both plaintext literals are byte-for-byte equal to the previous atob(...) results; no client-ID decoder pattern remains.
  • Fresh independent autoreview: no findings, patch correct at 0.99 confidence.
  • Exact-head hosted gates: CI run 29029959873 and Workflow Sanity passed; CodeQL, OpenGrep, and security workflows also passed on the same head.
  • No live provider login was required: request bytes and request construction are unchanged, while login would require interactive account authorization.

This is a public OAuth client identifier, not a client secret; see RFC 6749 section 2.2.

@steipete
steipete merged commit ef53c20 into openclaw:main Jul 9, 2026
97 checks passed
@steipete

steipete commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Merged via squash.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 auth-provider 🚨 May break OAuth, tokens, provider routing, model choice, or credentials. P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: XS status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] OAuth CLIENT_ID obfuscated with Base64 — security theatre, creates false impression of secrecy (B-01)

2 participants