Skip to content

feat: verify agent identity JWTs with JWKS#19764

Open
efrazer-oai wants to merge 2 commits intodev/efrazer/agent-identity-eager-runtimefrom
dev/efrazer/agent-identity-jwt-verify
Open

feat: verify agent identity JWTs with JWKS#19764
efrazer-oai wants to merge 2 commits intodev/efrazer/agent-identity-eager-runtimefrom
dev/efrazer/agent-identity-jwt-verify

Conversation

@efrazer-oai
Copy link
Copy Markdown
Contributor

@efrazer-oai efrazer-oai commented Apr 27, 2026

Summary

AgentIdentity auth could previously read the JWT payload but did not verify that the token was signed by the backend. This PR verifies AgentIdentity JWTs by fetching the backend JWKS and passing the matching key into the existing jsonwebtoken verification path.

The existing login/storage write path still performs a payload-shape check before saving the token. The actual auth load path fetches JWKS, verifies issuer/audience/signature/expiry, then constructs the same AgentIdentity auth record from the verified claims.

Stack

  1. refactor: make auth loading async
  2. refactor: load AgentIdentity runtime eagerly
  3. This PR: feat: verify AgentIdentity JWTs with JWKS

Important call sites

Area Change
codex-agent-identity Adds JWKS fetch, path-style URL construction, and jsonwebtoken verification using kid.
codex-login auth load AgentIdentity JWT auth now fetches JWKS before constructing AgentIdentityAuth.
codex login status Passes the configured ChatGPT base URL so status uses the same JWKS endpoint as the running app.
AgentIdentity env-auth test Uses a signed JWT and mocked JWKS endpoint, then verifies task registration still happens.

Design decisions

JWT verification stays in codex-agent-identity, next to the claim type and existing JWT parsing helper. The implementation delegates signature and claim validation to jsonwebtoken; this code only fetches the JWKS, selects the trusted key by kid, and supplies issuer/audience requirements.

The JWKS URL follows existing Codex backend path style: /backend-api bases use /wham/agent-identities/jwks, while direct Codex API bases use /api/codex/agent-identities/jwks.

Testing

Tests: targeted AgentIdentity tests, targeted AgentIdentity env-auth test, targeted Rust checks for touched crates, formatter, scoped Clippy fix, and Bazel lock check.

@efrazer-oai
Copy link
Copy Markdown
Contributor Author

@codex review

@efrazer-oai efrazer-oai force-pushed the dev/efrazer/agent-identity-eager-runtime branch 2 times, most recently from ee7d9b3 to d69ac63 Compare April 27, 2026 04:55
Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

💡 Codex Review

if let Some(stripped) = base_url.strip_suffix("/codex") {
base_url = stripped.to_string();

P1 Badge Preserve /api/codex when normalizing base URL

normalize_chatgpt_base_url removes any trailing /codex. With a configured base like http://host/api/codex, this truncates to http://host/api; then JWKS URL construction becomes .../api/api/codex/agent-identities/jwks. That breaks JWKS fetch and causes valid AgentIdentity JWTs to fail verification on direct Codex API deployments.

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread codex-rs/login/src/auth/manager.rs Outdated
/*enable_codex_api_key_env*/ true,
config.auth_credentials_store_mode,
)?
/*chatgpt_base_url*/ None,
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.

P1 Badge Use configured base URL in login restriction auth load

enforce_login_restrictions always calls load_auth(..., None) for chatgpt_base_url. After this change, AgentIdentity auth verification fetches JWKS using that value, so this path falls back to https://chatgpt.com/backend-api instead of the runtime-configured backend. In non-default/staging/self-hosted setups, restriction checks can fail and abort startup even with valid credentials.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added this, also simplified he normalize_base_url thing it shouldn't really have existed in the way it did.

Comment thread codex-rs/agent-identity/src/lib.rs
Comment thread codex-rs/agent-identity/src/lib.rs
@efrazer-oai efrazer-oai force-pushed the dev/efrazer/agent-identity-eager-runtime branch 2 times, most recently from 64a404b to c21a966 Compare April 27, 2026 05:09
@efrazer-oai efrazer-oai force-pushed the dev/efrazer/agent-identity-jwt-verify branch 2 times, most recently from 4a656a8 to 8c73abd Compare April 27, 2026 05:47
@efrazer-oai
Copy link
Copy Markdown
Contributor Author

@codex review

@efrazer-oai efrazer-oai force-pushed the dev/efrazer/agent-identity-jwt-verify branch from 8c73abd to 464097e Compare April 27, 2026 05:50
@efrazer-oai
Copy link
Copy Markdown
Contributor Author

@codex review

@efrazer-oai efrazer-oai force-pushed the dev/efrazer/agent-identity-jwt-verify branch from 464097e to f085622 Compare April 27, 2026 05:53
@efrazer-oai
Copy link
Copy Markdown
Contributor Author

@codex review

Copy link
Copy Markdown
Contributor

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

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: f085622f31

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread codex-rs/agent-identity/src/lib.rs Outdated
Comment on lines 348 to 351
{
base_url = stripped.to_string();
}
if (base_url.starts_with("https://chatgpt.com")
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.

P1 Badge Preserve Codex API base URLs on ChatGPT hosts

Do not append /backend-api when the configured base URL already targets /api/codex. In normalize_chatgpt_base_url, a value like https://chatgpt.com/api/codex is rewritten to .../api/codex/backend-api, which makes agent_identity_jwks_url build a WHAM JWKS path that does not match Codex-API style routing and causes JWT verification to fail.

Useful? React with 👍 / 👎.

@efrazer-oai efrazer-oai force-pushed the dev/efrazer/agent-identity-jwt-verify branch from f085622 to ec92ff1 Compare April 27, 2026 06:21
@efrazer-oai
Copy link
Copy Markdown
Contributor Author

@codex review

Copy link
Copy Markdown
Contributor

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

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: ec92ff1eae

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

let decoding_key = DecodingKey::from_jwk(jwk).context("failed to build JWT decoding key")?;
let mut validation = Validation::new(Algorithm::RS256);
validation.set_audience(&[AGENT_IDENTITY_JWT_AUDIENCE]);
validation.set_issuer(&[AGENT_IDENTITY_JWT_ISSUER]);
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.

P1 Badge Validate issuer against the configured backend, not a fixed host

decode_agent_identity_jwt verifies signatures using JWKS fetched from the caller-provided chatgpt_base_url, but still hard-codes issuer to https://chatgpt.com/.... In non-prod/custom deployments (e.g. localhost or staging), correctly signed tokens from that backend can be rejected solely due to issuer mismatch, breaking AgentIdentity auth load/login flows.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

codex-backend currently always sets issuer to chatgpt.com regardless of the deployment, this should be fine.

Comment thread codex-rs/agent-identity/src/lib.rs
efrazer-oai added a commit that referenced this pull request Apr 27, 2026
## Summary

Auth loading used to expose synchronous construction helpers in several
places even though some auth sources now need async work. This PR makes
the auth-loading surface async and updates the callers to await it.

This is intentionally only plumbing. It does not change how
AgentIdentity tokens are decoded, how task runtime ids are allocated, or
how JWT signatures are verified.

## Stack

1. **This PR:** [refactor: make auth loading
async](#19762)
2. [refactor: load AgentIdentity runtime
eagerly](#19763)
3. [feat: verify AgentIdentity JWTs with
JWKS](#19764)

## Important call sites

| Area | Change |
| --- | --- |
| `codex-login` auth loading | `CodexAuth` and `AuthManager`
construction paths now await auth loading. |
| app-server startup | Auth manager construction is awaited during
initialization. |
| CLI/TUI/exec/MCP/chatgpt callers | Existing auth-loading calls now
await the same behavior. |
| cloud requirements storage loader | The loader becomes async so it can
share the same auth construction path. |
| auth tests | Tests that load auth now run in async contexts. |

## Testing

Tests: targeted Rust auth test compilation, formatter, scoped Clippy
fix, and Bazel lock check.
@efrazer-oai efrazer-oai force-pushed the dev/efrazer/agent-identity-eager-runtime branch 2 times, most recently from dfb08d0 to 381d0dc Compare April 27, 2026 18:09
@efrazer-oai efrazer-oai force-pushed the dev/efrazer/agent-identity-eager-runtime branch from 381d0dc to 248b00b Compare April 27, 2026 20:01
@efrazer-oai efrazer-oai force-pushed the dev/efrazer/agent-identity-jwt-verify branch from ec92ff1 to 415cb77 Compare April 27, 2026 20:01
@efrazer-oai efrazer-oai force-pushed the dev/efrazer/agent-identity-jwt-verify branch from 3d6fc02 to da21113 Compare April 27, 2026 21:59
Comment thread codex-rs/agent-identity/src/lib.rs
.find(&kid)
.with_context(|| format!("agent identity JWT kid {kid} is not trusted"))?;
let decoding_key = DecodingKey::from_jwk(jwk).context("failed to build JWT decoding key")?;
let mut validation = Validation::new(Algorithm::RS256);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

👍

@efrazer-oai efrazer-oai marked this pull request as ready for review April 27, 2026 22:54
@efrazer-oai efrazer-oai requested a review from a team as a code owner April 27, 2026 22:54
Copy link
Copy Markdown
Contributor

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

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: da211139fe

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


pub fn agent_identity_jwks_url(chatgpt_base_url: &str) -> String {
let trimmed = chatgpt_base_url.trim_end_matches('/');
format!("{trimmed}/wham/agent-identities/jwks")
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.

P1 Badge Build JWKS URL for /api/codex bases correctly

agent_identity_jwks_url always appends /wham/agent-identities/jwks to the configured base. With a Codex-API base like https://.../api/codex, this produces .../api/codex/wham/agent-identities/jwks, but JWKS is served at .../api/codex/agent-identities/jwks. AgentIdentity JWT verification will fail in Codex-API style deployments, breaking auth load/login.

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants