fix(auth): tolerate device clock skew in ChatGPT verification

Allow the existing 60-second network time tolerance on ChatGPT identity-token
expiry and not-before checks while keeping signature, issuer, audience, subject,
nonce and required-claim checks exact. A token with a 15-second future nbf now
verifies; the existing one-hour-invalid case still fails.

Verification: cargo test -p codewhale-runtime --lib
siwc_signed_identity_requires_signature_issuer_audience_expiry_nonce_and_account
passed (1 passed, 0 failed). Refs #7203.
This commit is contained in:
CodeWhale Bot committed 2026-11-06 15:06:35 -08:00
1 parent b0b2b9a335
commit 4ca5880be8
2 files changed
+9 -1

No files matched your search

+1
View File
@@ -193,6 +193,7 @@ until the matching tag, packages, checksums, and release assets exist.
### Fixed
- **ChatGPT sign-in tolerates a small device clock skew:** identity-token expiry and not-before checks allow the same 60-second tolerance already used for ordinary network time drift, while signature, issuer, audience, subject and nonce checks remain exact. The diagnostic text still names the exact failed check when a token is genuinely invalid.
- **Remaining long-blocking work moved off async callers (#6553):** route
credential and certificate preflight, worktree creation, Runtime Chat relay
and remote-control journal transactions, and the remaining session, branch,
+8 -1
View File
@@ -2436,6 +2436,7 @@ pub const CHATGPT_OAUTH_CLIENT_ID: &str = "dynamic_agent_client";
pub const CHATGPT_OAUTH_RESOURCE: &str = "https://api.openai.com/v1";
pub const CHATGPT_OAUTH_SCOPE: &str =
"openid profile email offline_access resource.invoke chatgpt.tokens.use.direct";
const CHATGPT_CLOCK_SKEW_LEEWAY_SECS: u64 = 60;
/// Verified registration identity. The client ID also binds the selected workspace.
/// One active registration is supported; use CODEWHALE_CHATGPT_NEW_ACCOUNT=1
@@ -2579,7 +2580,7 @@ fn verify_chatgpt_jwt<T: serde::de::DeserializeOwned + Clone>(
validation.set_audience(&[audience]);
validation.set_required_spec_claims(&["exp", "iss", "aud", "sub"]);
validation.validate_nbf = true;
validation.leeway = 0;
validation.leeway = CHATGPT_CLOCK_SKEW_LEEWAY_SECS;
let claims = decode::<Value>(token, &key, &validation)
.map_err(|error| match error.kind() {
ErrorKind::InvalidSignature => {
@@ -8384,6 +8385,12 @@ consent_version = 1
let error = verify(&signer.token(changed)).unwrap_err().to_string();
assert!(error.contains(expected), "invalid {field}: {error}");
}
let mut skewed = claims.clone();
skewed["nbf"] = Value::from(now_unix_secs().unwrap() + 15);
assert!(
verify(&signer.token(skewed)).is_ok(),
"a small clock skew must not reject a valid identity token"
);
for required in ["iss", "aud", "exp", "sub", "nonce"] {
let mut changed = claims.clone();
changed.as_object_mut().unwrap().remove(required);