Cursor Security Review bot on a Sume webhook handler: what to find

Cursor added a Security Review bot on Sep 23. A webhook handler for Sume should pass seven checks: raw body, timestamp window, rotation, empty secret and more.

5 min readSume
All posts

A review bot checking a Sume webhook handler should confirm seven things: raw-body signing, a timestamp window, rotation-safe comparison, a refused empty secret, job_id idempotency, a fast 2xx after durable storage, and no trust in unverified events. The Cursor changelog lists Rollouts and Security Review bots on Sep 23, available on Teams and Enterprise plans.

Use this list as the brief when an agent writes the handler and a reviewer, human or bot, checks it.

The seven checks

Webhook handler review list from the Sume docs (read 2026-10-03)
CheckWhat the docs say
Sign over the raw bodyHMAC SHA-256 over <timestamp>.<raw_body>; a re-serialized body will not match.
Replay windowReject timestamps outside your tolerance; five minutes is a reasonable default.
RotationDuring rotation the header carries one sume-v1= entry per live secret; accept when any matches.
Secret presentThe secret is yours, read from the dashboard or GET /v1/webhooks/signing-secret.
IdempotencyUse job_id as the key; run webhooks dedupe on request_id.
Respond fastReturn 2xx after durably storing; attempts time out after 10 seconds.
Keep a fallbackTen failed attempts leave a failed delivery, so keep status polling.

A verifier that passes

This Python function refuses an empty secret, checks the timestamp window, and compares every sume-v1= entry in constant time.

import hashlib
import hmac
import time


def verify_sume_webhook(raw_body: bytes, timestamp: str, signature_header: str,
                        secret: str, tolerance_seconds: int = 300) -> bool:
    if not secret:
        raise ValueError("webhook signing secret is empty")
    try:
        ts = int(timestamp)
    except ValueError:
        return False
    if abs(int(time.time()) - ts) > tolerance_seconds:
        return False
    digest = hmac.new(
        secret.encode(), str(ts).encode() + b"." + raw_body, hashlib.sha256
    ).hexdigest()
    expected = ("sume-v1=" + digest).encode()
    matched = False
    for entry in signature_header.split(","):
        if hmac.compare_digest(entry.strip().encode(), expected):
            matched = True
    return matched

The easy mistakes

Review bots tend to flag the generic problems. These are the ones specific to this signature scheme.

  • Parsing the JSON and signing JSON.stringify of it. Sign the bytes you received.
  • Stopping at the first non-matching entry during rotation, which rejects a valid delivery.
  • Using a plain == on the signature rather than a constant-time comparison.
  • Reading the secret with a default of an empty string, so an unset variable silently disables verification.
  • Treating a redelivery as new work. Redeliver sends a fresh timestamp and signature for the same job.

Idempotency and retries

Sume retries network errors and non-2xx responses up to 10 attempts in total, with a fixed delay (30 seconds by default) rather than exponential backoff. The per-attempt timeout is 10 seconds. A handler that does slow work before replying burns its own budget.

Store the event first, answer 2xx, and process afterwards. Use job_id as the key so a retry or a manual redeliver does not repeat the side effect.

Test without a real job

The dashboard Send test action posts a dummy signed webhook.test payload to a URL you type, and it never replays a real job. Use it to prove the signature path. Redeliver, by contrast, re-sends a real job's terminal event, so use it to test the idempotency path.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume