{"record":{"id":"9e364bd6a3641e7b","repo":"windmill-labs/windmill","slug":"signature-mismatch","errorCode":null,"errorMessage":"signature mismatch","messagePattern":"signature mismatch","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/windmill-api/src/oauth2_oss.rs","lineNumber":193,"sourceCode":"#[cfg(not(feature = \"private\"))]\npub struct SlackVerifier {\n    mac: HmacSha256,\n}\n#[cfg(not(feature = \"private\"))]\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        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","sourceCodeStart":175,"sourceCodeEnd":198,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/backend/windmill-api/src/oauth2_oss.rs#L175-L198","documentation":"`AuthedClient::verify` computes an HMAC-SHA256 over `v0:<timestamp>:<body>` with the shared secret and compares it hex-encoded (`v0=<hex>`) against the provided signature. A mismatch means either the body bytes differ from what was signed, the timestamp differs, or the wrong secret/algorithm was used. Thrown from `validate_view_token` when verifying a signed view-token payload.","triggerScenarios":"Calling validate_view_token (which invokes verify at backend/windmill-api/src/oauth2_oss.rs:193) with an exp_sig that does not equal HMAC(secret, \"v0:{ts}:{body}\") — altered body, different ts, wrong secret, or re-signed payload with a different key.","commonSituations":"Payload mutated after signing (proxy adding/removing fields, whitespace, key reordering); clock/timestamp string mismatch between signer and verifier; rotating the secret on one side only; base64-vs-hex confusion on the client; encoding differences (JSON serialization order).","solutions":["Recompute the signature over the EXACT byte string that will be verified: sign \"v0:{ts}:{body}\" with the same secret and hex-encode as v0=<hex>.","Confirm both sides use the same secret and HMAC-SHA256.","Ensure the ts string is byte-identical (no reformatting of timestamps).","Regenerate the view token rather than hand-editing its body; re-check any intermediary that could rewrite the body."],"exampleFix":"// before\nconst sig = 'v0=' + hmacSha256(otherSecret, ts + body); // wrong format & secret\n// after\nconst base = `v0:${ts}:${JSON.stringify(body)}`; // exact serialized body sent\nconst sig = 'v0=' + crypto.createHmac('sha256', sharedSecret).update(base).digest('hex');","handlingStrategy":"try-catch","validationCode":"const base = `v0:${ts}:${bodyString}`;\nconst expected = 'v0=' + crypto.createHmac('sha256', secret).update(base).digest('hex');\nif (expected !== expSig) throw new Error('local signature check failed before sending');","typeGuard":"function looksLikeSig(s) { return typeof s === 'string' && /^v0=[0-9a-f]{64}$/.test(s); }","tryCatchPattern":"try {\n  await validateViewToken(token);\n} catch (e) {\n  if (String(e).includes('signature mismatch')) {\n    // regenerate token with the current secret and exact body bytes\n  } else throw e;\n}","preventionTips":["Sign the exact serialized body you transmit; never re-serialize after signing","Keep the HMAC secret in sync across signer and verifier","Use hex-encoded v0=<hex> HMAC-SHA256 output","Never hand-edit signed payloads"],"tags":["security","hmac","signature","oauth"],"backgroundTag":"signature-mismatch","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"}