webhook: compare secret in constant time

payload.secret != expected_secret short-circuits on the first
differing byte, which leaks timing information about how many
leading bytes of a guess are correct. Use subtle::ConstantTimeEq
instead.
This commit is contained in:
Claude 2026-08-13 02:10:09 +00:00 committed by Jonas Rabenstein
commit 1473e726f3
2 changed files with 5 additions and 1 deletions

View file

@ -18,6 +18,7 @@ reqwest = {
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sha2 = "0.10"
subtle = "2"
sqlx = {
version = "0.8",
features = [

View file

@ -1,5 +1,6 @@
use axum::{extract::State, http::StatusCode, response::IntoResponse};
use serde::Deserialize;
use subtle::ConstantTimeEq;
use crate::AppState;
@ -28,7 +29,9 @@ pub async fn receive(
.into_response();
};
if payload.secret != expected_secret {
let secret_matches = payload.secret.as_bytes().ct_eq(expected_secret.as_bytes());
if !bool::from(secret_matches) {
tracing::warn!("received webhook with invalid secret");
return (StatusCode::UNAUTHORIZED, "invalid webhook secret").into_response();