{"record":{"id":"c4b4ab85b8c8893b","repo":"windmill-labs/windmill","slug":"invalid-secret","errorCode":null,"errorMessage":"invalid secret","messagePattern":"invalid secret","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/windmill-oauth/src/lib.rs","lineNumber":1114,"sourceCode":"    cookie.set_http_only(true);\n    cookie.set_path(\"/\");\n    if COOKIE_DOMAIN.is_some() {\n        cookie.set_domain(COOKIE_DOMAIN.clone().unwrap());\n    }\n    cookies.add(cookie);\n}\n\n/// Slack signature verifier for webhook authentication\n#[derive(Clone, Debug)]\npub struct SlackVerifier {\n    mac: HmacSha256,\n}\n\nimpl SlackVerifier {\n    pub fn new<S: AsRef<[u8]>>(secret: S) -> anyhow::Result<SlackVerifier> {\n        HmacSha256::new_from_slice(secret.as_ref())\n            .map(|mac| SlackVerifier { mac })\n            .map_err(|_| anyhow::anyhow!(\"invalid secret\"))\n    }\n\n    pub fn verify(&self, ts: &str, body: &str, exp_sig: &str) -> anyhow::Result<()> {\n        let basestring = format!(\"v0:{}:{}\", ts, body);\n        let mut mac = self.mac.clone();\n\n        mac.update(basestring.as_bytes());\n        let sig = format!(\"v0={}\", hex::encode(mac.finalize().into_bytes()));\n        if sig != exp_sig {\n            Err(anyhow::anyhow!(\"signature mismatch\"))?;\n        }\n        Ok(())\n    }\n}\n\n/// Fetch user info from OAuth provider\npub async fn http_get_user_info<T: DeserializeOwned>(\n    http_client: &reqwest::Client,","sourceCodeStart":1096,"sourceCodeEnd":1132,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/backend/windmill-oauth/src/lib.rs#L1096-L1132","documentation":"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.","triggerScenarios":"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.","commonSituations":"Empty SLACK_SIGNING_SECRET / signing secret env var not set; a None/empty string passed through from config instead of the real secret.","solutions":["Ensure the Slack signing secret is configured (from Slack app 'Signing Secret' under Basic Information) and non-empty","Check the env var / config key actually reaches the code that constructs SlackVerifier","If storing the secret in the DB or a file, verify no empty-string fallback is being used"],"exampleFix":"// before\nlet verifier = SlackVerifier::new(\"\")?; // fails\n// after\nlet verifier = SlackVerifier::new(std::env::var(\"SLACK_SIGNING_SECRET\")?)?;","handlingStrategy":"validation","validationCode":"let secret = std::env::var(\"SLACK_SIGNING_SECRET\")?;\nif secret.is_empty() { anyhow::bail!(\"SLACK_SIGNING_SECRET is not set\"); }\nlet verifier = SlackVerifier::new(&secret)?;","typeGuard":"fn usable_signing_secret(s: &str) -> bool { !s.trim().is_empty() }","tryCatchPattern":"match SlackVerifier::new(&secret) {\n    Ok(v) => v,\n    Err(e) if e.to_string() == \"invalid secret\" => {\n        anyhow::bail!(\"Signing secret missing/empty — configure SLACK_SIGNING_SECRET\");\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Fail fast at startup if the Slack signing secret env var is missing or empty","Store secrets in a secrets manager rather than optional config fields that default to empty strings"],"tags":["slack","hmac","webhook","configuration"],"backgroundTag":"invalid-signing-secret","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}