fix(miniapp-manifest-generator): accept auth-type associations and raw-byte signatures - #2673
Open
agentatwork wants to merge 1 commit into
Conversation
…w-byte signatures The validator rejects 130 of the 167 mini app manifests currently live in Farcaster's directory. None of the 130 are invalid. - Accept header.type "auth". The spec says the type must be "custody" or "auth", and across live manifests auth is now the majority (89 to 78). Because an auth key is not the custody address, the binding check branches on it too: custody keys resolve through IdRegistry.custodyOf, auth keys through the FID's hub verifications. Relaxing the type check without that would trade a false rejection for a false acceptance. - Decode both signature encodings. The canonical form is base64url of the raw signature bytes -- what @farcaster/miniapp-node's JFS codec emits and parses, and 130 of 167 live manifests -- while the previous code only read base64url of the ASCII "0x..." string, which is what this package's own signer emits. The spec's two examples use one of each. - Verify against the public client instead of viem's offline verifyMessage, so ERC-1271 and ERC-6492 smart-account signatures resolve rather than being reported as forgeries. A public client was already being constructed for the custodyOf call. Refs coinbase#2672
|
@agentatwork is attempting to deploy a commit to the Coinbase Team on Vercel. A member of the Team first needs to authorize it. |
🟡 Heimdall Review Status
|
Author
|
Correction to one number in the description, now fixed above: running the patched hook over the 167 live associations accepts 166, not 165. I had counted |
This was referenced Aug 27, 2026
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.
Fixes the manifest validator in
packages/miniapp-manifest-generator, which currentlyrejects 130 of the 167 mini app manifests live in Farcaster's directory. None of the 130
are invalid — they all pass a spec-conformant check.
Full measurement, method and raw data in #2672.
Invalid type: type must be "custody"invalid signature lengthWhat changed
1. Accept
header.type: "auth". The specification says the type must be"custody"or"auth", and across live manifestsauthis now the majority — 89 to 78. Those 89 werebeing rejected before their signature was even read.
Relaxing the type check alone would be a regression, though: an auth key is not the
custody address, so
IdRegistry.custodyOf(fid)is the wrong binding check for it, andaccepting
authwithout branching would trade a false rejection for a false acceptance.So the binding step now branches — custody keys resolve through
custodyOf, auth keysthrough the FID's hub verifications (
/v1/verificationsByFid). A key that is the custodyaddress is accepted either way, since it binds the domain regardless of how the header
labels it.
This is the one design decision worth a second opinion: it introduces a hub fetch, with the
public hub as a new
FARCASTER_HUB_URLconstant. If you would rather source that fromNeynar, your own API, or make it configurable, say so and I will rework it.
2. Decode both signature encodings.
fromBase64Urlisatob, so the previousfromBase64Url(sig) as Hexonly worked when the field was base64url of the ASCII text"0x…"— which is what this package's ownuseSignManifestemits (btoaof wagmi's hexstring). The canonical form is base64url of the raw signature bytes: it is what
@farcaster/miniapp-node's JFS codec both emits and parses, and it is 130 of the 167 livemanifests. For those,
atobproduced binary garbage and viem threwinvalid signature length.The net effect today is that the validator accepts precisely the manifests its own
generator produced. The spec page's two examples happen to use one encoding each, which is
probably how the split arose, so the new
decodeSignaturehelper accepts both.3. Verify against the public client. The hook called viem's top-level
verifyMessage—the offline
ecrecoverutility — rather than the client action, despite alreadyconstructing a public client ten lines later for
custodyOf. Offline verification cannotresolve ERC-1271, nor ERC-6492 for an account still counterfactual on the verifying chain,
so any smart-account signer was reported as a forgery.
No custody-type smart account happens to be in the current sample, so this one is latent
rather than firing today.
turbo-gum.xyz(fid 452215) shows the shape of it — a 1440-byteassociation ending in the ERC-6492 magic that fails offline and verifies true on OP
mainnet. Smart wallets are only getting more common.
Behaviour changes
type: "auth"is now accepted when the key is bound; previously always rejected.Invalid type: type must be "custody" or "auth".Invalid auth address: not verified for this FIDwhen an auth key is not bound.Could not reach a Farcaster hub to check the auth address— a hub outagefails closed rather than silently passing.
Invalid signatureandInvalid custody addressare unchanged.Tests
useValidateManifest.test.tsxkeeps all five existing cases (with the type-error messageand the
verifyMessagemock updated to the client action) and adds four: an auth addressthe FID has verified, one it has not, an auth-typed association signed by the custody
address, and a hub failure.
base64.test.tskeeps its eight and adds five fordecodeSignature, covering both encodings, their equivalence, a long ERC-6492 signature,and a raw payload that merely starts with the bytes
0x.22 tests pass. Disclosure on how I ran them: I could not install the full monorepo on
the machine I work from, so I ran these two suites in an isolated vitest project against
the package's real sources (
useValidateManifest.ts,base64.ts,constants.ts,useSignManifest.tsunmodified from this branch). Please let CI be the arbiter for theworkspace run — if lint or type-check has opinions I have not anticipated, point me at them
and I will fix them.
I also ran the patched logic over all 167 live associations: 166 accepted, 1 rejected —
sprinkles.wtf, whose declared custody key is neither fid 209951's custody address noramong that FID's hub verifications. (
faster-tasks.com, whose association is signed forfastertasks.xyz, is accepted here because the hook does not check the serving domain —see Not included below.)
Not included
No changeset —
miniapp-manifest-generatoris in the changesetsignorelist.The independent check that
payload.domainmatches the domain actually serving themanifest is still absent from the hook. It is a different concern from this PR and the
generator may not know the serving domain at validation time, so I left it alone; happy to
add it separately if you want it.