{"record":{"id":"080927864321b75e","repo":"risingwavelabs/risingwave","slug":"signature-verification-failed","errorCode":null,"errorMessage":"Signature verification failed","messagePattern":"Signature verification failed","errorType":"http","errorClass":null,"httpStatus":401,"severity":"error","filePath":"src/frontend/src/webhook/utils.rs","lineNumber":127,"sourceCode":"    payload: &[u8],\n    webhook_source_info: &risingwave_pb::catalog::WebhookSourceInfo,\n) -> Result<()> {\n    let is_valid = if let Some(signature_expr) = webhook_source_info.signature_expr.clone() {\n        let secret = if let Some(secret_ref) = webhook_source_info.secret_ref {\n            LocalSecretManager::global()\n                .fill_secret(secret_ref)\n                .map_err(|e| err(e, StatusCode::NOT_FOUND))?\n        } else {\n            String::new()\n        };\n        verify_signature(headers_jsonb, secret.as_str(), payload, signature_expr).await?\n    } else {\n        true\n    };\n\n    if !is_valid {\n        return Err(err(\n            anyhow!(\"Signature verification failed\"),\n            StatusCode::UNAUTHORIZED,\n        ));\n    }\n\n    Ok(())\n}\n\npub(crate) async fn verify_signature(\n    headers_jsonb: JsonbVal,\n    secret: &str,\n    payload: &[u8],\n    signature_expr: ExprNode,\n) -> Result<bool> {\n    let row = OwnedRow::new(vec![\n        Some(headers_jsonb.into()),\n        Some(secret.into()),\n        Some(payload.into()),\n    ]);","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/frontend/src/webhook/utils.rs#L109-L145","documentation":"The webhook payload's computed signature did not match the signature supplied by the client (after building the comparison expression and evaluating it). This returns HTTP 401 UNAUTHORIZED and aborts request processing. It protects webhook endpoints from unauthenticated or tampered payloads.","triggerScenarios":"POSTing to a webhook-protected table via handle_post_request or try_handle_connection with a missing, malformed, or incorrect signature header, or with a body that was modified after signing.","commonSituations":"Signing the wrong payload (e.g. signing pretty-printed JSON while sending compact), wrong secret configured, proxies altering the body, clock/algorithm mismatches, sending raw vs re-serialized body.","solutions":["Verify the client signs the exact raw request body bytes that are sent.","Confirm the webhook secret matches the one configured in RisingWave.","Check the signature algorithm and header name match RisingWave's expectations.","Ensure no intermediary (proxy/gateway) rewrites the body or headers.","Log both computed and provided signatures to diagnose mismatch."],"exampleFix":"// before\nconst signature = crypto.createHmac('sha256', secret).update(JSON.stringify(body)).digest('hex');\n// after\nconst signature = crypto.createHmac('sha256', secret).update(rawBodyBuffer).digest('hex'); // sign raw bytes","handlingStrategy":"try-catch","validationCode":"// client-side sanity check before sending\nif (!signature || !rawBody) throw new Error('signature and raw body are both required');","typeGuard":null,"tryCatchPattern":"// JS fetch\ntry {\n  const res = await fetch(url, { method: 'POST', body: rawBody, headers: { signature } });\n  if (res.status === 401) throw new Error('webhook signature rejected: check secret and raw-body signing');\n} catch (e) { /* log secret name + body hash for diagnosis */ }","preventionTips":["Always sign the exact raw request bytes, never re-serialized JSON.","Store the webhook secret in one shared config source to avoid drift.","Document and pin the signature algorithm and header name.","Log both computed and expected signature hashes in a debug mode."],"tags":["security","webhook","authentication","hmac"],"backgroundTag":"signature-verification-failed","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}