new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Solana & Anchor Auditor · all subjects

Signer and owner checks

7 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

Where is the signer check on this privileged instruction?

The Solana runtime only guarantees `is_signer` reflects an actual signature; the PROGRAM must check it. Raw-program bug: an admin/withdraw instruction reads an `authority` pubkey from state and compares it to a passed account's key but never asserts `authority_info.is_signer` — anyone calls it with the real authority's pubkey (pubkeys are public) and moves funds. Anchor makes the common case easy (`authority: Signer<'info>` plus `has_one = authority`) and the wrong case silent: using `AccountInfo`/`UncheckedAccount` for the authority compiles fine and checks nothing. Detection signals: `ctx.accounts.authority.key() == state.authority` comparisons where `authority` isn't a `Signer`; raw `invoke` paths missing `if !authority.is_signer { return Err(...) }`. Every state-changing instruction that references a stored authority needs both the key match AND the signature.

Is this PDA's authority being lent to a hostile CPI?

`invoke_signed` lets a program sign with its PDA — and if the CPI target program or any account in that call is attacker-controlled, the attacker just got the PDA to authorize whatever they want. Patterns: a "router" instruction that takes an arbitrary `program_id` and invokes it signed with the protocol's vault PDA (instant drain); a program that CPIs into a user-supplied token program; a program that signs a transfer where the destination token account is unvalidated (PDA signs, funds go to the attacker). Rule: `invoke_signed` calls must pin the target program id against a constant/allowlist AND validate every account the signed authority touches — destination token accounts must be checked for correct mint and owner. Audit question: "if I control every account and program in this call except the signer PDA, what can I make it sign?"

Are stale token delegate approvals revoked before state changes?

SPL token accounts carry an optional `delegate` + `delegated_amount` that survives transfers of the account data across instructions. Programs that take custody semantics — escrows, staking, lending — often `approve` a delegate and later assume the approval is gone, or accept a user token account that already has a delegate set to a third party (who then pulls the deposited tokens back out via `transfer_checked` as delegate). Mirror-image bug: a program that closes/reassigns a token account without first `revoke`ing an outstanding delegate leaves a live allowance pointing at recycled state. Rule: custody instructions should reject incoming token accounts with `delegate.is_some()` (or explicitly revoke first), and close/finalize paths must revoke before closing. Detection signal: `approve` CPIs with no matching `revoke` on every exit path, including error paths.

Can authority rotation brick or hijack this account?

Programs that support `set_authority`-style updates (including SPL token's own `SetAuthority` for mints and accounts) have two failure modes. Hijack: the rotation instruction validates the OLD authority as signer but lets the caller set an arbitrary NEW authority without the new one signing — typo'd or attacker-substituted addresses become permanent owners; better pattern is two-step (propose + accept) or requiring the new authority to sign. Brick: rotating away a mint's `mint_authority` or `freeze_authority` permanently, or a program storing `authority` in state and letting it be set to a PDA nobody can sign for. Also check Anchor's `has_one = authority` still binds after rotation — if authority is stored in two places (state + token account authority field) they can desync. Audit both the rotation instruction and every consumer of the rotated field.

What does `#[account(signer)]` / Signer<'info> actually prove?

Exactly one thing: the transaction was signed by the private key for that account's public key. It says NOTHING about the account's owner, data, mutability, or relationship to your program — a `Signer` can be a zero-data system-program account, an account owned by a hostile program, or a program account itself. So `signer` alone never substitutes for an owner check (whose data are you reading?), a key match against stored state (`has_one = authority`), or a PDA derivation. The classic failure shape: `authority: Signer<'info>` with no binding to stored state — anyone signs and becomes "authority". Conversely, note what a signer can't be: PDAs have no private key and can never satisfy a runtime signer check outside `invoke_signed`. Match the check to the threat: a signature proves key control, not permission, data integrity, or ownership.

Can a PDA ever pass an is_signer check?

No — PDAs have no private key, so the runtime can never mark a PDA account `is_signer` from an external transaction. Two consequences auditors must hold simultaneously. First, the bricking bug: an authority-update or admin instruction requiring `authority.is_signer` where the stored authority IS (or may be rotated to) a PDA will always fail — the protocol locks itself out; a liveness finding, common when "multisig-safe" signer requirements meet PDA-governed configs. Second, the inverse rule: a program must never RELY on a PDA's signature as an authentication factor for its own instructions — PDA "signatures" exist only inside `invoke_signed`, authorized by the signing program's own logic. So instructions gated on PDA authority must authenticate via SEED VERIFICATION (`seeds` + `bump` re-derivation against stored state), not signer checks; if a design demands `is_signer` from an authority that could be a PDA, the design — not the check — is wrong.

Does `require!(account.owner == &my_program)` prove permission?

No — owner and signer checks answer different questions, and swapping them is a top-ten Solana finding. `owner == program_id` proves the account's DATA is controlled by your program, so deserializing it as your state type is meaningful: it validates the DATA SOURCE. It says nothing about WHO is calling. Permission requires a signer check bound to stored state: `authority.key() == state.authority && authority.is_signer` (Anchor: `Signer<'info>` + `has_one`). The failure shape: an admin instruction checks the config account's owner, reads `config.admin`, compares it to a passed `admin` account's key — but never requires `admin` to sign; anyone passes the real admin's pubkey (pubkeys are public!) and executes admin actions. Mirror-image mistake: checking the caller's signature but never the state account's owner, letting attacker-crafted fake state drive privileged logic. Every privileged instruction needs BOTH.

Give your agent this brain