Files
PaperClipAI/server
Jonathan Reyes 676e20a894 fix(routines): reject HMAC webhook replays (#9994)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Routines allow external systems to start recurring work through
authenticated public webhooks
> - Timestamped HMAC authentication currently verifies authenticity and
age but does not remember an accepted delivery
> - An exact signed request can therefore be reused within its replay
window, including through simultaneous duplicate delivery
> - Replay rejection must be atomic with run creation so concurrent
copies cannot both succeed
> - This pull request derives a non-secret replay identity from each
valid timestamped HMAC delivery and claims it under the existing routine
transaction lock
> - The benefit is at-most-once acceptance of an exact HMAC delivery
without changing ordinary caller-supplied idempotency semantics

## Linked Issues or Issue Description

Fixes: #9993

## What Changed

- Derive a stable, non-secret idempotency key after a timestamped HMAC
signature has been validated.
- Reject a previously claimed HMAC delivery with a conflict while
preserving coalescing for existing non-HMAC idempotency keys.
- Apply the same atomic replay claim when automatic worktree execution
is suppressed.
- Add regression coverage for sequential, concurrent, and suppressed-run
replays.

## Verification

- `pnpm exec vitest run server/src/__tests__/routines-service.test.ts` —
59 tests passed.
- `pnpm typecheck` — all workspace packages passed.
- The sequential test was observed failing on unmodified `master`: the
second identical request resolved and a second run was created.
- The concurrent regression test verifies exactly one request succeeds
and only one routine run exists.

## Risks

- Low migration risk: no schema change is required; the existing
nullable routine-run idempotency field is reused.
- The routine row lock serializes replay claims, adding a small amount
of contention only while a routine run is being created.
- Replay rejection applies only to `hmac_sha256`, which carries the
timestamp needed for a bounded replay policy. Existing `github_hmac`,
bearer, and unauthenticated trigger semantics are unchanged.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

OpenAI Codex (GPT-5 family) with reasoning, repository inspection, shell
execution, and test tooling.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] Documentation does not require an update because this restores the
documented replay-window security behavior without changing
configuration or APIs
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-08-12 16:09:00 -07:00
..