windmill-labs/windmill · error

invalid secret

Error message

invalid secret

What it means

SlackVerifier::new initializes an HMAC-SHA256 machine from the Slack signing secret. HMAC accepts any key length in practice, so new_from_slice failing is unexpected; the constructor maps any failure to 'invalid secret' rather than returning a verifier.

Source

Thrown at backend/windmill-oauth/src/lib.rs:1114

    cookie.set_http_only(true);
    cookie.set_path("/");
    if COOKIE_DOMAIN.is_some() {
        cookie.set_domain(COOKIE_DOMAIN.clone().unwrap());
    }
    cookies.add(cookie);
}

/// Slack signature verifier for webhook authentication
#[derive(Clone, Debug)]
pub struct SlackVerifier {
    mac: HmacSha256,
}

impl SlackVerifier {
    pub fn new<S: AsRef<[u8]>>(secret: S) -> anyhow::Result<SlackVerifier> {
        HmacSha256::new_from_slice(secret.as_ref())
            .map(|mac| SlackVerifier { mac })
            .map_err(|_| anyhow::anyhow!("invalid secret"))
    }

    pub fn verify(&self, ts: &str, body: &str, exp_sig: &str) -> anyhow::Result<()> {
        let basestring = format!("v0:{}:{}", ts, body);
        let mut mac = self.mac.clone();

        mac.update(basestring.as_bytes());
        let sig = format!("v0={}", hex::encode(mac.finalize().into_bytes()));
        if sig != exp_sig {
            Err(anyhow::anyhow!("signature mismatch"))?;
        }
        Ok(())
    }
}

/// Fetch user info from OAuth provider
pub async fn http_get_user_info<T: DeserializeOwned>(
    http_client: &reqwest::Client,

View on GitHub (pinned to e474e8803c)

Solutions

  1. Ensure the Slack signing secret is configured (from Slack app 'Signing Secret' under Basic Information) and non-empty
  2. Check the env var / config key actually reaches the code that constructs SlackVerifier
  3. If storing the secret in the DB or a file, verify no empty-string fallback is being used

Example fix

// before
let verifier = SlackVerifier::new("")?; // fails
// after
let verifier = SlackVerifier::new(std::env::var("SLACK_SIGNING_SECRET")?)?;
Defensive patterns

Strategy: validation

Validate before calling

let secret = std::env::var("SLACK_SIGNING_SECRET")?;
if secret.is_empty() { anyhow::bail!("SLACK_SIGNING_SECRET is not set"); }
let verifier = SlackVerifier::new(&secret)?;

Type guard

fn usable_signing_secret(s: &str) -> bool { !s.trim().is_empty() }

Try / catch

match SlackVerifier::new(&secret) {
    Ok(v) => v,
    Err(e) if e.to_string() == "invalid secret" => {
        anyhow::bail!("Signing secret missing/empty — configure SLACK_SIGNING_SECRET");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: SlackVerifier::new called with a secret that HmacSha256::new_from_slice rejects — in practice only when the secret is empty or the HMAC construction fails internally.

Common situations: Empty SLACK_SIGNING_SECRET / signing secret env var not set; a None/empty string passed through from config instead of the real secret.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/c4b4ab85b8c8893b. Report an issue: GitHub.