Skip to content

fix(#2505,#2468): bind the registration parameters into the keeper-register proof - #2532

Merged
dcccrypto merged 1 commit into
playgroundfrom
fix/keeper-register-auth-binding
Sep 2, 2026
Merged

fix(#2505,#2468): bind the registration parameters into the keeper-register proof#2532
dcccrypto merged 1 commit into
playgroundfrom
fix/keeper-register-auth-binding

Conversation

@dcccrypto

@dcccrypto dcccrypto commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Closes #2505 and #2468 — one root cause, fixed together.

The H1v2 "stateless deployer proof" signed:

keeper-register:<slabAddress>:<unix-minute>

That authorises the slab and nothing else. It says nothing about the pool being registered, the mainnet CA, the dex type, the symbol or the label — so one captured signature authorised registering that slab against any pool (#2468), and the parameters the route acts on were never covered by the thing verifying them (#2505).

The message now binds a canonical encoding of every acted-on parameter, built by a shared module that both the client and the route import, so the two cannot drift. The sibling route POST /api/markets already binds its complete canonical payload this way; this brings keeper-register to the same standard.

What this deliberately does not fix

The signature is still valid across the ~6-minute window and is still not single-use.

Closing that needs a server-side nonce store — which is exactly what H1v2 removed to fix a serverless race. Reintroducing it here would trade a replay window for a correctness bug.

Payload binding shrinks the consequence instead: a captured signature can now only replay the same registration, which is idempotent, rather than authorising a substituted pool. This is stated in the module header rather than left to be rediscovered.

Two details that would have broken it quietly

The client sends the normalized dexType, not the raw DexScreener id. The signature binds the normalized value, because that is what the body carries and therefore what the route binds. Signing the raw id would fail verification on only those markets whose dexId differs from the keeper vocabulary — so it would have looked intermittent rather than broken.

Absent optionals encode as empty, not omitted. Omitting them would let {symbol: "X", label: absent} and {symbol: absent, label: "X"} produce the same message — two different registrations sharing one signature.

Two existing tests pinned the vulnerability

Inverted, not deleted:

  • useCreateMarket-keeper-register.test.ts asserted the signed message matched ^keeper-register:<slab>:<minute>$the unbound format itself. It now asserts the pool is covered, which is the keeper-register deployer proof is not bound to the pool it registers #2468 attack stated as an assertion.
  • useCreateMarket-fresh-batched-registration.test.ts searched the source for the literal "keeper-register:". It now asserts the shared builder is used.

Verification

  • Negative control: reverting the message to slab+minute fails 4 of the 8 new tests
  • launch suite 3136 passed / 16 skipped / 0 failed; tsc clean

🤖 Generated with Claude Code

https://claude.ai/code/session_01NgoNgagkvw7i5SSRC3FJ8D

Summary by CodeRabbit

  • Security

    • Keeper registration proofs now securely bind all registration details, including the slab, pool, token, DEX, symbol, label, and timestamp.
    • Registration requests with mismatched signed parameters are rejected, helping prevent proof reuse or tampering.
  • Bug Fixes

    • Replaced the previous proof format that authenticated only the slab address and time.
    • Added consistent handling for optional registration fields and parameter ordering.

…gister proof

The H1v2 "stateless deployer proof" signed:

    keeper-register:<slabAddress>:<unix-minute>

That authorises the SLAB and nothing else. It says nothing about the pool being
registered, the mainnet CA, the dex type, the symbol or the label — so one
captured signature authorised registering that slab against ANY pool (#2468),
and the parameters the route actually acts on were never covered by the thing
verifying them (#2505).

Both issues are one root cause and are fixed together.

The message now binds a canonical encoding of every acted-on parameter, built by
a shared module (lib/keeper-register-proof.ts) that BOTH the client and the route
import — so the two cannot drift. The sibling route POST /api/markets already
binds its complete canonical payload this way; this brings keeper-register to the
same standard.

WHAT THIS DELIBERATELY DOES NOT FIX. The signature is still valid across the
~6-minute tolerance window and is still not single-use. Closing that needs a
server-side nonce store — which is exactly what H1v2 REMOVED to fix a serverless
race, so reintroducing it here would trade a replay window for a correctness bug.
Payload binding shrinks the CONSEQUENCE instead: a captured signature can now
only replay the same registration, which is idempotent, rather than authorising a
substituted pool. Stated in the module header rather than left for someone to
discover.

Two details that would have broken this quietly:

  * The client sends the NORMALIZED dexType (normalizeDexType(...)), not the raw
    DexScreener id. The signature binds the normalized value, because that is what
    the body carries and therefore what the route binds. Signing the raw id would
    have failed verification on every market whose dexId differs from the keeper
    vocabulary — and only those, so it would have looked intermittent.
  * Absent optionals encode as EMPTY rather than being omitted. Omitting them
    would let {symbol: "X", label: absent} and {symbol: absent, label: "X"} produce
    the same message — two different registrations sharing one signature.

TWO EXISTING TESTS PINNED THE VULNERABILITY and are inverted, not deleted:

  * useCreateMarket-keeper-register.test.ts asserted the signed message matched
    `^keeper-register:<slab>:<minute>$` — the unbound format itself. It now asserts
    the pool is covered, which is the #2468 attack stated as an assertion.
  * useCreateMarket-fresh-batched-registration.test.ts searched the source for the
    literal "keeper-register:". It now asserts the shared builder is used.

Negative control: reverting the message to slab+minute fails 4 of the 8 new tests.

Launch suite: 3136 passed / 16 skipped / 0 failed.

Refs: #2505, #2468

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NgoNgagkvw7i5SSRC3FJ8D
@vercel

vercel Bot commented Sep 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
percolator-launch Ready Ready Preview Sep 2, 2026 9:40pm UTC
percolator-mainnet Building Building Preview Sep 2, 2026 9:40pm UTC
percolator-playground Ready Ready Preview Sep 2, 2026 9:40pm UTC

Request Review

@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: ce838b20-0146-4c18-87d2-d6a068731624

📥 Commits

Reviewing files that changed from the base of the PR and between aa20158 and 34544a2.

📒 Files selected for processing (6)
  • app/__tests__/hooks/useCreateMarket-fresh-batched-registration.test.ts
  • app/__tests__/hooks/useCreateMarket-keeper-register.test.ts
  • app/__tests__/lib/keeper-register-proof.test.ts
  • app/app/api/playground/keeper-register/route.ts
  • app/hooks/useCreateMarket.ts
  • app/lib/keeper-register-proof.ts

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The keeper registration proof now binds the slab, pool, CA, DEX type, symbol, label, and Unix minute. A shared canonical builder is used by both registration client paths and the server route. Tests cover binding, canonicalization, delimiter handling, and message format.

Changes

Keeper registration proof

Layer / File(s) Summary
Canonical proof format
app/lib/keeper-register-proof.ts
Defines the proof parameters, canonical field encoding, domain prefix, minute binding, and Uint8Array message construction.
Client proof signing paths
app/hooks/useCreateMarket.ts
Uses the shared proof builder in regular and fresh batched keeper registration flows.
Server proof verification
app/app/api/playground/keeper-register/route.ts
Rebuilds and verifies proofs from the complete registration parameter tuple. The authentication error text describes the new format.
Proof regression coverage
app/lib/keeper-register-proof.ts, app/__tests__/lib/keeper-register-proof.test.ts, app/__tests__/hooks/useCreateMarket-fresh-batched-registration.test.ts, app/__tests__/hooks/useCreateMarket-keeper-register.test.ts
Tests parameter binding, canonicalization, optional values, delimiter handling, minute changes, and client use of the shared builder.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: 🟠 High · up to 34544

The registration proof now binds the main target identifiers, but the endpoint still accepts and stores unsigned market metadata and authority-related configuration. A valid proof can therefore be reused for the authorized target while changing the stored market record, leaving a high-impact authorization gap that should be fixed before merge.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant buildKeeperRegisterProofMessage
  participant KeeperRegisterRoute
  Client->>buildKeeperRegisterProofMessage: Build proof from registration parameters
  Client->>KeeperRegisterRoute: Submit parameters and signature
  KeeperRegisterRoute->>buildKeeperRegisterProofMessage: Rebuild proof from request parameters
  KeeperRegisterRoute->>KeeperRegisterRoute: Verify signature
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR satisfies payload binding, canonical client-server message construction, existing validation preservation, and regression coverage for parameter tampering. It does not eliminate or reduce the a… Reduce or eliminate the replay window, preferably with durable single-use replay protection that remains safe in the serverless environment. Add regression tests for replay prevention and clock skew while preserving the implemented payload-…
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 6 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: binding registration parameters into the keeper-register proof.
Out of Scope Changes check ✅ Passed The changed helper, route, client logic, and tests directly support the keeper-register proof-binding security fix and its regression coverage. No unrelated code changes are identified.
Full details: Linked Issues check

Explanation

The PR satisfies payload binding, canonical client-server message construction, existing validation preservation, and regression coverage for parameter tampering. It does not eliminate or reduce the approximately six-to-seven-minute replay window, add durable single-use replay protection, or provide replay-prevention and clock-skew regression coverage required by issue #2505.

Resolution

Reduce or eliminate the replay window, preferably with durable single-use replay protection that remains safe in the serverless environment. Add regression tests for replay prevention and clock skew while preserving the implemented payload-binding changes. [#2505]

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/keeper-register-auth-binding

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant