v0.14 is out: a mobile auth pack for Swift/iOS and Android, catching insecure token storage, cleartext traffic, and OAuth in embedded WebViews. Read more →
HIGH AI PREVALENCE: HIGH auth.rust.oauth.hardcoded-client-secret

An OAuth client_secret is hardcoded as a string literal and passed to the oauth2 crate's ClientSecret::new(...).

Why AI tools produce this: AI coding tools generate this anti-pattern by default, it appears in a large share of AI-written auth code.

Why this matters

The client secret authenticates your application to the authorization server; committed to source control it is one grep/git-history search away from compromise, letting an attacker impersonate your client, mint tokens, and exchange authorization codes (CWE-798). AI-generated snippets inline the secret to make the sample "just work" and it ships unchanged.

Load the secret at runtime from the environment or a secret manager: let secret = std::env::var("OAUTH_CLIENT_SECRET")?; let client = BasicClient::new(client_id) .set_client_secret(ClientSecret::new(secret)); Never commit client secrets to source control.

VULNERABLE
vulnerable.rs
use oauth2::basic::BasicClient;
use oauth2::{ClientId, ClientSecret};

// Client secret hardcoded as a string literal — one grep away from compromise.
fn build_client_to_string() -> BasicClient {
    // ruleid: auth.rust.oauth.hardcoded-client-secret
    BasicClient::new(ClientId::new("my_client_id".to_string()))
        .set_client_secret(ClientSecret::new("s3cr3t-abcdef-123456".to_string()))
}

// Hardcoded via .to_owned().
fn build_client_to_owned() {
    // ruleid: auth.rust.oauth.hardcoded-client-secret
    let _ = ClientSecret::new("s3cr3t-abcdef-123456".to_owned());
}

// Hardcoded via String::from.
fn build_client_string_from() {
    // ruleid: auth.rust.oauth.hardcoded-client-secret
    let _ = ClientSecret::new(String::from("s3cr3t-abcdef-123456"));
}

// Hardcoded via .into().
fn build_client_into() {
    // ruleid: auth.rust.oauth.hardcoded-client-secret
    let _ = ClientSecret::new("s3cr3t-abcdef-123456".into());
}

fn main() {}
SAFE
safe.rs
use oauth2::basic::BasicClient;
use oauth2::{ClientId, ClientSecret};

// Safe: secret loaded from the environment at runtime.
fn build_client_from_env() -> Result<(), Box<dyn std::error::Error>> {
    let secret = std::env::var("OAUTH_CLIENT_SECRET")?;
    // ok: auth.rust.oauth.hardcoded-client-secret
    let _ = ClientSecret::new(secret);
    Ok(())
}

// Safe: env var read inline and converted, still not a literal.
fn build_client_inline_env() -> Result<(), Box<dyn std::error::Error>> {
    // ok: auth.rust.oauth.hardcoded-client-secret
    let _ = ClientSecret::new(std::env::var("OAUTH_CLIENT_SECRET")?);
    Ok(())
}

// Safe: derived from a variable, not a string literal.
fn build_client_from_var(secret: String) {
    // ok: auth.rust.oauth.hardcoded-client-secret
    let _ = ClientSecret::new(secret);
}

fn main() {}

Suppressing this rule

If a finding is a genuine false positive, scope the suppression to the exact line and leave a reason, never disable the rule project-wide. Disable directives are line-scoped by design.

// oauthlint-disable-next-line auth.rust.oauth.hardcoded-client-secret -- <reason>

References

https://docs.rs/oauth2/latest/oauth2/struct.ClientSecret.html ↗https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1 ↗https://cwe.mitre.org/data/definitions/798.html ↗