{"record":{"id":"fa7a671645ea5807","repo":"windmill-labs/windmill","slug":"invalid-signature","errorCode":null,"errorMessage":"Invalid signature","messagePattern":"Invalid signature","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/windmill-api/src/jobs.rs","lineNumber":5239,"sourceCode":"}\n\nasync fn verify_suspended_secret(\n    w_id: &String,\n    db: &DB,\n    job_id: Uuid,\n    resume_id: u32,\n    approver: &QueryApprover,\n    secret: String,\n) -> Result<(), Error> {\n    let key = get_workspace_key(w_id, db).await?;\n    let mut mac = HmacSha256::new_from_slice(key.as_bytes()).map_err(to_anyhow)?;\n    mac.update(job_id.as_bytes());\n    mac.update(resume_id.to_be_bytes().as_ref());\n    if let Some(approver) = approver.approver.clone() {\n        mac.update(approver.as_bytes());\n    }\n    mac.verify_slice(hex::decode(secret)?.as_ref())\n        .map_err(|_| anyhow::anyhow!(\"Invalid signature\"))?;\n    Ok(())\n}\n\n/* If the flow is currently waiting to be resumed (`FlowStatusModule::WaitingForEvents`)\n * the suspend column must be set to the number of resume messages waited on.\n *\n * The flow's queue row is locked in this transaction because to avoid race conditions around\n * the suspend column.\n * That is, a job needs one event but it hasn't arrived, a worker counts zero events before\n * entering WaitingForEvents.  Then this message arrives but the job isn't in WaitingForEvents\n * yet so the suspend counter isn't updated.  Then the job enters WaitingForEvents expecting\n * one event to arrive based on the count that is no longer correct. */\nasync fn resume_immediately_if_relevant<'c>(\n    flow: FlowInfo,\n    job_id: Uuid,\n    tx: &mut Transaction<'c, Postgres>,\n) -> error::Result<()> {\n    Ok(","sourceCodeStart":5221,"sourceCodeEnd":5257,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/backend/windmill-api/src/jobs.rs#L5221-L5257","documentation":"Resume secrets (approval/approver links) are HMAC-signed; this error is thrown when mac.verify_slice fails, i.e. the provided signature does not match a freshly computed HMAC of job_id, resume_id and optional approver with the server's secret. It means the resume link or token is forged, corrupted, or signed with a different secret.","triggerScenarios":"Calling the anonymous approval/resume endpoint with a tampered signature parameter; using a resume link generated before the server secret was rotated; truncation/corruption of the hex-encoded signature in an email client.","commonSituations":"Email clients line-wrapping or HTML-escaping the signed link; rotating WINDMILL secrets in a running deployment so old links invalidate; manually constructing approval URLs.","solutions":["Request a fresh approval/resume link (re-trigger the resume request)","Verify the URL's signature parameter was not truncated or escaped by the email client","Check that the instance's HMAC secret was not changed after the link was generated","Never hand-craft the URL; always use the link emitted by the server"],"exampleFix":"// before\nconst sig = url.searchParams.get('s').slice(0, 32); // corrupted\n// after\nconst sig = url.searchParams.get('s'); // pass full hex signature verbatim","handlingStrategy":"try-catch","validationCode":"// ensure the URL carries a complete hex signature before calling\nconst sig = new URL(link).searchParams.get('signature');\nif (!sig || !/^[0-9a-f]+$/i.test(sig)) throw new Error('malformed resume link');","typeGuard":"function hasValidSignature(params) {\n  return typeof params.signature === 'string' && /^[0-9a-f]{40,}$/i.test(params.signature);\n}","tryCatchPattern":"try {\n  await client.resume(flowId, resumeId, payload);\n} catch (e) {\n  if (/Invalid signature/.test(e.message)) {\n    // link tampered/stale: request a fresh one, do not retry with the same link\n    await requestNewApprovalLink(flowId);\n  } else throw e;\n}","preventionTips":["Always use the exact link emitted by the server","Rotate HMAC secrets knowing all outstanding links invalidate","Strip mail-client line wrapping before extracting the URL"],"tags":["hmac","signature","security","resume"],"backgroundTag":"invalid-signature","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"}