-
Notifications
You must be signed in to change notification settings - Fork 473
Description
Describe the bug
StoredCredentials and StoredAuthorizationState in crates/rmcp/src/transport/auth.rs both derive #[derive(Debug)]. This causes secret fields — OAuth access tokens, refresh tokens, PKCE verifiers, and CSRF tokens — to be printed in plaintext whenever these types are formatted via a Debug formatter. There is no compiler warning or type-system protection against this.
To Reproduce
- Add the
authfeature to yourrmcpdependency. - Run an OAuth flow to populate a
StoredAuthorizationStateorStoredCredentials. - Format either type using
{:?}in any log call:
tracing::error!("auth state: {:?}", stored_state);- Observe that live PKCE verifiers, CSRF tokens, access tokens, and refresh tokens are emitted in plaintext to log output.
Expected behavior
Secret fields should be redacted in Debug output:
StoredAuthorizationState { pkce_verifier: [REDACTED], csrf_token: [REDACTED], created_at: 1234567890 }
Logs
Current {:?} output for StoredAuthorizationState:
StoredAuthorizationState {
pkce_verifier: "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
csrf_token: "xK9mP2nQ8rL4vT6wY1uZ3aB5cD7eF0gH",
created_at: 1741564800
}
Additional context
Affected types:
StoredCredentials (auth.rs line 62) — token_response contains access_token and refresh_token:
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoredCredentials {
pub client_id: String,
pub token_response: Option<OAuthTokenResponse>,
pub granted_scopes: Vec<String>,
pub token_received_at: Option<u64>,
}StoredAuthorizationState (auth.rs line 122):
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StoredAuthorizationState {
pub pkce_verifier: String,
pub csrf_token: String,
pub created_at: u64,
}The MCP spec's own auth guidance explicitly states: "Don't log credentials. Never log Authorization headers, tokens, codes, or secrets." The current type definitions make it easy to violate this by accident — including via anyhow/thiserror error chains that automatically format Debug representations of involved values.
Suggested fix: use the [secrecy](https://crates.io/crates/secrecy) crate. Wrap secret fields in SecretString, whose Debug impl prints [REDACTED] and requires .expose_secret() for access. Its serde feature keeps serialisation transparent so CredentialStore implementations are unaffected. Note that OAuthTokenResponse comes from the oauth2 crate and cannot be wrapped directly — Debug should be implemented manually on StoredCredentials to redact that field.