Synced from monorepo
Changes: - Non-blocking coding-data sharing upsell banner - Consolidate remediation in Doctor - Auto mode defers fail-closed gate asks to the classifier - Coalesce marketplace list fetches - Allow removing a marketplace source by name - Contain hung git marketplace sources (timeouts, non-blocking refresh, unbrick modal) - Label failed workspace RPCs with error_kind - Drop redundant explicit tonic/prost deps from xai-grok-shell - Report real exit codes for completed background shells - Narrow the date-rollover reminder to date-bearing templates - Wire toolOverrides through the session and agent - Security: Bash(git:*) allowlist matches whole command chain by prefix - Split prompt-trigger telemetry and record classifier provenance - Raise connectors-manager timeout to 60s - Auto classifier honors recorded approvals for repeat actions - Apply doctor fixes in the TUI - Auto-mode classifier timeouts prompt instead of silently denying - Scope subagent completion drains to the owning session - Add the toolOverrides wire types - Set client_identifier=grok-agent-sdk - Accept both spellings of the workspace-teleport kill switch - Persist one-shot occurrence journal - Stop turns that poll the exact same tool call 16x in a row - Copy compaction checkpoint files when forking sessions - Auto-focus permission prompt from scrollback - Esc cancels the running turn in non-vim and minimal modes - List Ctrl+Z undo and redo in keyboard shortcuts - Out-of-process macOS mic capture - Show active auth mode on session-info - Install the npm binary under $GROK_HOME - Remove hover/click dead zones between dashboard items - Route startup warnings to doctor - Document [feedback.user] author identity config - Extend bang command timeout - Close combine-queued edit-hold race - Integrate relocation recovery - Expose privacy notice rollout flag - Break harness discovery ref cycle so connections can idle-evict - Shift/Alt+Enter inserts newline when editing a queued prompt - Gate project Claude permissions on folder trust - Echo response.create.event_id on response.created - Toast when session creation fails from disk full - Add shared test process lifecycle - Enable dynamic workflows by default - Add relocation transaction state machine - Add shared test sandbox - Surface auth failures on model-switch compact - Persist durable scheduler expiry - Confirm before removing extensions-modal items - Re-run compact and prompt after login when compact hit expired auth - Recap sends hosted tools under backend search
This commit is contained in:
parent
3af4d5d398
commit
a5727c5960
482 changed files with 37627 additions and 13402 deletions
|
|
@ -194,7 +194,8 @@ pub(crate) fn enforce_login_principal(
|
|||
format!("one of teams: {}", allowed.join(", "))
|
||||
};
|
||||
tracing::warn!(
|
||||
expected = % expected, actual = ? actual,
|
||||
expected = %expected,
|
||||
actual = ?actual,
|
||||
"OIDC: login principal does not satisfy required policy; rejecting"
|
||||
);
|
||||
Err(anyhow::Error::new(OidcError::PinnedPrincipalMismatch {
|
||||
|
|
@ -303,7 +304,7 @@ fn discovery_retry_policy() -> backon::ExponentialBuilder {
|
|||
}
|
||||
async fn discover_once(issuer_key: &str) -> anyhow::Result<Discovery> {
|
||||
let url = format!("{issuer_key}/.well-known/openid-configuration");
|
||||
tracing::debug!(url = % url, "OIDC: fetching discovery document");
|
||||
tracing::debug!(url = %url, "OIDC: fetching discovery document");
|
||||
let resp = with_alpha_test_key(
|
||||
crate::http::shared_client()
|
||||
.get(&url)
|
||||
|
|
@ -320,9 +321,11 @@ async fn discover_once(issuer_key: &str) -> anyhow::Result<Discovery> {
|
|||
}
|
||||
let doc: Discovery = resp.json().await?;
|
||||
tracing::debug!(
|
||||
authorization_endpoint = % doc.authorization_endpoint, token_endpoint = % doc
|
||||
.token_endpoint, jwks_uri = ? doc.jwks_uri, id_token_algs = ? doc
|
||||
.id_token_signing_alg_values_supported, "OIDC: discovery complete"
|
||||
authorization_endpoint = %doc.authorization_endpoint,
|
||||
token_endpoint = %doc.token_endpoint,
|
||||
jwks_uri = ?doc.jwks_uri,
|
||||
id_token_algs = ?doc.id_token_signing_alg_values_supported,
|
||||
"OIDC: discovery complete"
|
||||
);
|
||||
Ok(doc)
|
||||
}
|
||||
|
|
@ -405,9 +408,7 @@ pub(super) async fn exchange_code(
|
|||
client_id: &str,
|
||||
code_verifier: &str,
|
||||
) -> anyhow::Result<TokenResponse> {
|
||||
tracing::debug!(
|
||||
token_endpoint = % token_endpoint, "OIDC: exchanging code for tokens"
|
||||
);
|
||||
tracing::debug!(token_endpoint = %token_endpoint, "OIDC: exchanging code for tokens");
|
||||
let resp = with_alpha_test_key(
|
||||
crate::http::shared_client()
|
||||
.post(token_endpoint)
|
||||
|
|
@ -472,8 +473,10 @@ pub(super) async fn refresh_tokens(
|
|||
) -> anyhow::Result<TokenResponse> {
|
||||
use backon::Retryable;
|
||||
tracing::debug!(
|
||||
token_endpoint = % token_endpoint, principal_type = ? principal_type,
|
||||
principal_id = ? principal_id, "OIDC: refreshing token"
|
||||
token_endpoint = %token_endpoint,
|
||||
principal_type = ?principal_type,
|
||||
principal_id = ?principal_id,
|
||||
"OIDC: refreshing token"
|
||||
);
|
||||
(|| {
|
||||
refresh_tokens_once(
|
||||
|
|
@ -525,9 +528,12 @@ async fn refresh_tokens_once(
|
|||
.ok()
|
||||
.and_then(|v| v.get("error")?.as_str().map(str::to_owned));
|
||||
tracing::warn!(
|
||||
http_status = status, oauth2_error = ? error_code, rt_prefix = crate
|
||||
::auth::token_suffix(refresh_token), client_id = % client_id, principal_type
|
||||
= ? principal_type, "OIDC: token refresh HTTP error"
|
||||
http_status = status,
|
||||
oauth2_error = ?error_code,
|
||||
rt_prefix = crate::auth::token_suffix(refresh_token),
|
||||
client_id = %client_id,
|
||||
principal_type = ?principal_type,
|
||||
"OIDC: token refresh HTTP error"
|
||||
);
|
||||
return Err(anyhow::Error::new(OidcError::TokenRefreshHttp {
|
||||
status,
|
||||
|
|
@ -566,9 +572,7 @@ pub(super) fn aud_matches(aud: &serde_json::Value, expected: &str) -> bool {
|
|||
}
|
||||
pub(super) fn validate_state(expected: &str, received: &str) -> anyhow::Result<()> {
|
||||
if received != expected {
|
||||
tracing::warn!(
|
||||
expected = % expected, received = % received, "OIDC: state mismatch"
|
||||
);
|
||||
tracing::warn!(expected = %expected, received = %received, "OIDC: state mismatch");
|
||||
return Err(anyhow::Error::new(OidcError::StateMismatch));
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -993,23 +997,31 @@ mod tests {
|
|||
)
|
||||
.unwrap()
|
||||
}
|
||||
let team_jwt = make_jwt(serde_json::json!(
|
||||
{ "sub" : "user-42", "iss" : "https://auth.x.ai", "aud" : "test-client",
|
||||
"exp" : 9999999999u64, "iat" : 1000000000u64, "scope" :
|
||||
"offline_access grok-cli:access api:access", "principal_type" : "Team",
|
||||
"principal_id" : "team-abc-123", "client_id" : "test-client", "jti" :
|
||||
"token-1", }
|
||||
));
|
||||
let team_jwt = make_jwt(serde_json::json!({
|
||||
"sub": "user-42",
|
||||
"iss": "https://auth.x.ai",
|
||||
"aud": "test-client",
|
||||
"exp": 9999999999u64,
|
||||
"iat": 1000000000u64,
|
||||
"scope": "offline_access grok-cli:access api:access",
|
||||
"principal_type": "Team",
|
||||
"principal_id": "team-abc-123",
|
||||
"client_id": "test-client",
|
||||
"jti": "token-1",
|
||||
}));
|
||||
let (pt, pid, tid) = peek_access_token_principal(&team_jwt).expect("team principal");
|
||||
assert_eq!(pt, "Team");
|
||||
assert_eq!(pid, "team-abc-123");
|
||||
assert_eq!(tid, None);
|
||||
assert!(peek_access_token_principal("not-a-jwt-token").is_none());
|
||||
assert!(peek_access_token_principal("").is_none());
|
||||
let no_principal = make_jwt(serde_json::json!(
|
||||
{ "sub" : "user-42", "iss" : "https://auth.x.ai", "aud" : "test-client",
|
||||
"exp" : 9999999999u64, "iat" : 1000000000u64, }
|
||||
));
|
||||
let no_principal = make_jwt(serde_json::json!({
|
||||
"sub": "user-42",
|
||||
"iss": "https://auth.x.ai",
|
||||
"aud": "test-client",
|
||||
"exp": 9999999999u64,
|
||||
"iat": 1000000000u64,
|
||||
}));
|
||||
assert!(peek_access_token_principal(&no_principal).is_none());
|
||||
}
|
||||
/// `peek_access_token_principal_id` extracts the id even when
|
||||
|
|
@ -1026,7 +1038,7 @@ mod tests {
|
|||
)
|
||||
.unwrap()
|
||||
}
|
||||
let id_only = make_jwt(serde_json::json!({ "principal_id" : "team-abc", "sub" : "u" }));
|
||||
let id_only = make_jwt(serde_json::json!({ "principal_id": "team-abc", "sub": "u" }));
|
||||
assert_eq!(
|
||||
peek_access_token_principal_id(&id_only).as_deref(),
|
||||
Some("team-abc"),
|
||||
|
|
@ -1035,7 +1047,7 @@ mod tests {
|
|||
peek_access_token_principal(&id_only).is_none(),
|
||||
"the strict peek still needs principal_type",
|
||||
);
|
||||
let none = make_jwt(serde_json::json!({ "sub" : "u" }));
|
||||
let none = make_jwt(serde_json::json!({ "sub": "u" }));
|
||||
assert!(peek_access_token_principal_id(&none).is_none());
|
||||
assert!(peek_access_token_principal_id("not-a-jwt").is_none());
|
||||
}
|
||||
|
|
@ -1113,10 +1125,10 @@ mod tests {
|
|||
let counter = hits_for_handler.clone();
|
||||
async move {
|
||||
counter.fetch_add(1, Ordering::SeqCst);
|
||||
axum::Json(serde_json::json!(
|
||||
{ "authorization_endpoint" : format!("{b}/authorize"),
|
||||
"token_endpoint" : format!("{b}/token"), }
|
||||
))
|
||||
axum::Json(serde_json::json!({
|
||||
"authorization_endpoint": format!("{b}/authorize"),
|
||||
"token_endpoint": format!("{b}/token"),
|
||||
}))
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue