Repository navigation
fix(wallet): stop giving Veil names away for free - #1329
Merged
Merged
Conversation
`POST /api/names/claim` is deployed, open to every origin, and granted a name
on the strength of a `paymentTxHash` that was never checked against the
ledger. The registry's default verifiers were stubs that accepted:
paymentVerifier ?? (async (txHash) => Boolean(txHash && txHash.trim().length > 0))
signatureVerifier ?? (async () => true)
and `globalNamesRegistry` is constructed with no options, so those stubs were
the production policy. Any caller could take any unreserved name by sending a
one-character hash, bound to whatever `passkeyPublicKey` they chose. The
signature was only checked `if (signature)`, so omitting it skipped the check
rather than failing it — nothing proved possession of the passkey the name was
being bound to.
The route could not have honoured a real payment either. The registry is a set
of `Map`s behind a module singleton: every serverless instance gets its own and
instances are recycled, so a claim is invisible to the next request and gone
after a deploy.
Three changes:
- **The verifier defaults now refuse.** A stub standing in for money has to
fail closed, or the day something is wired to it is the day names become
free. Callers that want permissive behaviour — the unit tests covering
charset, reserved names and claim concurrency — now pass their own verifiers
and say so at the construction site.
- **Both routes return 503**, via `lib/namesApiGate.ts`. Deliberately not an
env flag: a flag is a switch somebody eventually flips, and what is behind it
hands out names. `GET /api/names/available` is shut for its own reason — it
answered from the always-empty registry, so every name came back
`available: true`, with `Cache-Control: public, max-age=10` inviting clients
to cache that.
- **The route tests assert the gate.** They previously asserted a 201 for
`paymentTxHash: 'tx_valid_jupiter'`, which encoded the hole as intended
behaviour. Reserved/taken/charset coverage already lives in
`lib/__tests__/namesRegistry.test.ts`, so nothing unique left coverage.
The real registry is `contracts/veil-names`, where `register` requires `buyer`
auth so the ledger proves possession, and a name is rented by the year in
length bands rather than sold once for a flat 5 USDC. `namesApiGate.ts`
records what lifting the gate requires.
Not fixed, and noted in place instead: the optional-signature gap above.
Hardening a module that the on-chain registry retires is wasted work.
No caller anywhere in the repo reaches these routes — only their own tests —
so nothing regresses.
`npx tsc --noEmit` clean; wallet suite 100 suites / 1563 passed.
Claude-Session: https://claude.ai/code/session_01USgemLt4Rnz4SGB1Srf3GB
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
POST /api/names/claimis live in production, open to every origin, andgrants a name on the strength of a
paymentTxHashthat is never checkedagainst the ledger.
The registry's default verifiers are stubs that accept:
and
globalNamesRegistry = new NamesRegistry()passes no options — so thosestubs are the production policy. Any caller can take any unreserved name by
sending a one-character
paymentTxHash, bound to whateverpasskeyPublicKeythey supply. The signature is only checked
if (signature), so omitting itskips the check rather than failing it, and nothing proves possession of the
passkey the name is bound to.
The route could not have honoured a real payment either: the registry is a set
of
Maps behind a module singleton, so each serverless instance has its own,instances are recycled, and a claim is invisible to the next request and gone
after a deploy.
Verified deployed with a non-mutating
OPTIONS(204,Allow-Origin: *) and aGET /api/names/available?name=adebayoreturningavailable: true. I did notPOST to production to demonstrate the grant.
Changes
closed, or the day something is wired to it is the day names become free.
The unit tests that want permissive behaviour now pass their own verifiers at
the construction site and say why.
lib/namesApiGate.ts. Deliberately not anenv flag — a flag is a switch somebody eventually flips, and what is behind
it hands out names.
GET /api/names/availableis shut for its own reason:it answered from the always-empty registry, so every name returned
available: truewithCache-Control: public, max-age=10inviting clientsto cache it.
paymentTxHash: 'tx_valid_jupiter'— the hole encoded as intendedbehaviour. Reserved/taken/charset coverage already lives in
lib/__tests__/namesRegistry.test.ts, so nothing unique left coverage.Not fixed, noted in place
The optional-signature gap. The on-chain registry (
contracts/veil-names,#1328) requires
buyerauth forregister, so the ledger proves possession;hardening a module that registry retires is wasted work.
Scope
No caller anywhere in the repo reaches these routes — only their own tests —
so nothing regresses. Targets
mainbecause that is what the three Vercelprojects deploy from, so the hole stays open in production until this lands.
npx tsc --noEmit— cleanhttps://claude.ai/code/session_01USgemLt4Rnz4SGB1Srf3GB