Skip to content

security: gate pda-mint-authority's mint_token behind the token's original creator - #692

Merged
dev-jodee merged 1 commit into
solana-foundation:mainfrom
NikkiAung:fix/pda-mint-authority-unrestricted-mint
Aug 21, 2026
Merged

security: gate pda-mint-authority's mint_token behind the token's original creator#692
dev-jodee merged 1 commit into
solana-foundation:mainfrom
NikkiAung:fix/pda-mint-authority-unrestricted-mint

Conversation

@NikkiAung

@NikkiAung NikkiAung commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Summary

tokens/pda-mint-authority (anchor) has an unrestricted-minting vulnerability: mint_token
performs no authorization check whatsoever. Its only accounts are payer: Signer (any
wallet) and the mint/ATA accounts — the mint's authority is a program-derived PDA
(seeds = [b"mint"], set up in create_token) that signs the MintTo CPI unconditionally
on every call, for whoever happens to call the instruction.

create_token uses Anchor's init constraint, so the mint is created exactly once,
globally, as a plain SPL fungible token (mint::decimals = 9, no supply cap, no Metaplex
Master Edition). Combined with the missing check on mint_token, any wallet can mint an
arbitrary amount of this token to itself, repeatedly, forever
— unrestricted token supply
inflation, with no cap and no gate.

Why this went unnoticed

  • The README says only "This example is exactly the same as the NFT Minter example, but it
    changes the mint authority account from the payer to a PDA"
    — no caveat about missing
    access control (unlike this repo's compression/cnft-vault, which explicitly discloses
    its own analogous gap as an intentional proof-of-concept limitation).
  • create.rs's own comment frames the PDA-as-signer mechanic as the sole teaching point:
    "demonstrate that the same PDA can be used for both the address of an account and CPI
    signing."
    Access control was never part of the design discussion.
  • The sibling native and pinocchio implementations of this same example mint an NFT
    (supply 1), then hand the mint/freeze authority to a Metaplex Master Edition PDA — which
    incidentally caps them at one mint per account, even without an explicit check. The
    anchor implementation departs from this (plain fungible token, no edition, no cap) and
    is the only one of the three with unbounded, repeatable minting.
  • The existing test only ever calls mint_token as the same wallet that called
    create_token — there is no test with a second, unrelated caller, so the missing check
    had zero coverage.

Fix

Adds a MintConfig PDA (seeds = [b"mint_config"]) that records the admin (the wallet that
called create_token) and gates mint_token behind it via an Anchor account-level
constraint (mint_config.admin == payer.key()). The existing "PDA is both the mint account
address and the CPI signer" demonstration — the example's actual teaching point — is
completely untouched; this only adds a small, separate, read-only-checked account
controlling who may trigger the existing mint logic.

Test changes

Added a negative test in tests/litesvm.test.ts: a second, freshly-generated wallet
attempts mint_token after the first wallet's create_token, asserting it fails with
Unauthorized. Verified this reproduces against the unpatched code (reverted the fix,
rebuilt, confirmed the unrelated wallet's mint call succeeds), then reapplied the fix and
confirmed it's correctly rejected. The two pre-existing tests pass unchanged — Anchor's
client-side PDA auto-resolution picks up the new mint_config account without needing
explicit wiring in .accountsPartial(...).

Out of scope

  • The native/pinocchio siblings have a different, lower-severity, related concern: since
    create and mint_to are separate instructions, any caller can crank the one-time
    mint_to before the intended recipient, directing a single NFT to themselves instead.
    This is bounded to one NFT per mint account and requires timing/front-running, unlike the
    anchor bug's unbounded, un-timed value creation — flagging it here for visibility, not
    fixing it in this PR.
  • compression/cnft-vault's missing access control is a disclosed, intentional limitation
    per its own README and not something this audit should "fix" against the authors' stated
    intent.

Verification

  • cargo check, anchor build, cargo fmt -p pda-mint-authority-anchor (scoped — this
    crate is listed in .github/.workspace-ignore), pnpm exec tsc --noEmit, root
    prettier --check all pass.
  • Full litesvm.test.ts suite: 3/3 passing.
  • Red/green proof: captured the fix as a patch, reverted the program source only, rebuilt,
    confirmed the new negative test fails against the unpatched code (an unrelated wallet's
    mint succeeds), reapplied the patch, confirmed the full suite passes green.

…ginal creator

mint_token has no authorization check: any signer can mint an arbitrary
amount to itself, since the mint's authority is a program-derived PDA
(seeds = [b"mint"]) that signs the MintTo CPI unconditionally for whoever
calls the instruction. create_token uses Anchor's `init` constraint, so the
mint is created exactly once, globally, as a plain fungible token (decimals
9, no supply cap, no Metaplex Master Edition) — the result is unrestricted,
repeatable token inflation by any wallet.

This is undocumented, not an intentional simplification: the README only
describes the PDA-as-signer mechanic, and the sibling native/pinocchio
implementations of this same example mint a supply-1 NFT and hand the mint
authority to a Master Edition PDA afterward, which incidentally caps them —
the anchor implementation is the only one of the three with no cap and no
gate. The existing test only ever calls mint_token as the same wallet that
created the token, so the missing check had zero coverage.

Adds a MintConfig PDA (seeds = [b"mint_config"]) recording the admin at
create_token time, and an Anchor account-level constraint on mint_token
requiring the caller to match it. The existing "PDA is both the mint
account address and CPI signer" demonstration is untouched.

Verified: reverted the fix, rebuilt, and confirmed an unrelated wallet's
mint_token call succeeds against the unpatched code; reapplied the fix and
confirmed the same call is rejected with Unauthorized.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@NikkiAung
NikkiAung requested a review from dev-jodee as a code owner August 21, 2026 03:58
@greptile-apps

greptile-apps Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a program-owned MintConfig PDA that records the original token creator and requires that creator to sign every subsequent mint operation.

  • Initializes the global authorization configuration alongside the mint.
  • Rejects mint attempts whose signer does not match the stored administrator.
  • Adds an Unauthorized program error and a LiteSVM regression test using an unrelated wallet.

Confidence Score: 5/5

The PR appears safe to merge, with the new creator-only mint authorization consistently initialized, enforced, and covered by a negative test.

The canonical program-owned configuration and mint use fixed global PDA seeds, are initialized atomically, and the mint instruction validates the stored administrator against the required signer before reaching the token-program CPI.

Important Files Changed

Filename Overview
tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/create.rs Atomically initializes the global MintConfig PDA with the mint and records the creating payer as administrator.
tokens/pda-mint-authority/anchor/programs/token-minter/src/instructions/mint.rs Requires the canonical MintConfig PDA and verifies its administrator matches the signing payer before executing the mint CPI.
tokens/pda-mint-authority/anchor/programs/token-minter/src/state.rs Defines the discriminator-inclusive, correctly sized MintConfig account containing one administrator public key.
tokens/pda-mint-authority/anchor/programs/token-minter/src/errors.rs Defines the dedicated Unauthorized error returned by the new administrator constraint.
tokens/pda-mint-authority/anchor/tests/litesvm.test.ts Adds a negative test confirming that an unrelated funded signer cannot mint tokens to its associated token account.

Reviews (1): Last reviewed commit: "security: gate pda-mint-authority's mint..." | Re-trigger Greptile

@NikkiAung

Copy link
Copy Markdown
Contributor Author

Hey @dev-jodee — this is ready for review whenever you get a chance. CI is green and Greptile's automated pass found no blocking issues.

@dev-jodee
dev-jodee merged commit 491e195 into solana-foundation:main Aug 21, 2026
19 checks passed
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.

2 participants