Pre-auditThree days of senior audit time for $1,500, credited in full toward your Zealynx audit.3 audit days, credited toward your auditSee how →
Back to Blog
When Operator Approval Stops Meaning Consent
SolidityWeb3 SecurityAuditTesting

When Operator Approval Stops Meaning Consent

18 min
An NFT approval can be valid and still authorize the wrong action.
That happens because isApprovedForAll answers a narrow question: whether one operator may manage one owner's tokens. Protocols often treat that answer as proof of something larger. They infer that a particular caller may trigger a particular transfer, through a particular route, under current holder intent.
Those are separate claims.
This article compares one Zealynx ERC-1155 finding with four public technical records and the relevant token standards. The shared mechanism is not simply "too much privilege." It is an authorization binding failure: the system checks an approval, but fails to bind that approval to the subject, caller, asset scope, action, or lifecycle state that gives it meaning.
The anchor is the Plakxio case study. The case study describes the engagement. This research removes the client name from the lesson and tests the mechanism against a wider public record.

Methodology and evidence boundary

The comparison is mechanism-led. A record enters the core set when its public source describes an NFT operator approval that is consumed at the wrong authorization boundary.
The core records are:
  1. the Plakxio ERC-1155 operator finding;
  2. Y2K Finance finding #434;
  3. Party Protocol finding #129;
  4. reNFT finding #430; and
  5. Tapioca finding #44, an ERC-721 analogue.
ERC-1155, OpenZeppelin Contracts, and EIP-712 supply the standards context.[1][2][3]
Four statement types are kept distinct:
  • Source fact: the cited source reports the behavior, status, or label.
  • Zealynx classification: this article groups records by authorization mechanism. It is not a new severity assessment.
  • Inference: a design conclusion derived across records.
  • Limitation: the sources do not establish prevalence, production exposure, exploitation, or monetary impact unless they say so explicitly.
This is a purposive comparison, not a representative sample of NFT contracts or audit findings. No frequency claim follows from it. Public contest submissions also carry different review histories. The source status is preserved rather than normalized into one score.

What an operator approval actually says

ERC-1155 defines setApprovalForAll as enabling or disabling an operator to manage all of the caller's tokens. It defines isApprovedForAll(owner, operator) as the query for that relationship. Both single and batch transfers require the caller to be approved to manage the holder's tokens.[1]
OpenZeppelin's implementation makes the dependency direct:
1function _checkAuthorized(address operator, address owner) internal view {
2 if (owner != operator && !isApprovedForAll(owner, operator)) {
3 revert ERC1155MissingApprovalForAll(operator, owner);
4 }
5}
The standard relation is therefore:
1owner grants authority to operator
2operator initiates transfer from owner
It does not independently prove:
1this caller may invoke this workflow
2this receiver is the approved operator
3this token subset is covered
4this signed order exists and is valid
5this approval has not been effectively revoked
6this intermediary cannot redelegate authority
A protocol that needs those properties must encode them outside, or more narrowly than, the base approval relation.

A five-part authorization model

The selected records become easier to compare when approval is decomposed into five bindings.
BindingQuestion the contract must answerTypical failure
SubjectWhose assets are covered?Global privilege overrides per-holder choice
ActorWhich caller may exercise authority?Approval is checked against receiver or intermediary instead of msg.sender
ObjectWhich assets or token scopes are covered?A guard blocks one approval interface but misses another
ActionWhich operation may occur?Generic execution can create or redelegate approval
LifecycleIs authority active, observable, and revocable now?Grant activates immediately or revocation changes storage but not effective authority
This is a Zealynx classification. The sources do not use one shared taxonomy.
A useful authorization predicate is:
1AUTHORIZED =
2 subject_consented
3 AND caller_is_the_authorized_actor
4 AND asset_is_within_scope
5 AND action_is_permitted
6 AND authority_is_active
7 AND authority_is_not_revoked
Every term matters. A true approval bit cannot compensate for a false caller check. A correct caller check cannot compensate for an uncovered scoped-approval interface. A valid signature cannot help if the token contract also grants a parallel transfer path that ignores it.

Anchor: global operator privilege overrode holder state

The Plakxio finding is the clearest subject and lifecycle binding failure in the set.
The public report states that an ERC-1155 override returned true when the operator was whitelisted, independently of the holder's stored approval:[4]
1function isApprovedForAll(address account, address operator)
2 public view override returns (bool)
3{
4 return isSettlement[operator] || super.isApprovedForAll(account, operator);
5}
The expected settlement implementation verified EIP-712 orders. EIP-712 defines typed structured data and domain separation, but its security considerations also make clear that application-specific authorization behavior remains the implementer's responsibility.[3]
In this design, the badge contract did not structurally require a whitelisted operator to be the expected consent-checking implementation. The grant accepted a bare address. The class mask constrained which badge classes an operator could move, but not whose badges it could move. A holder could call setApprovalForAll(operator, false), emit the expected event, and still receive true from the effective authorization query because the whitelist branch resolved first.[4]
Zealynx classifies this as two connected failures:
  1. Subject bypass: an administrative operator grant applied against every holder.
  2. Ineffective revocation: the holder changed stored approval state but not effective transfer authority.
The remediation record reports code-only operator grants, delayed activation, holder opt-out enforcement, and matching behavior across single and batch transfer paths. It also records limitations: code presence does not prove correct behavior, the delay can be configured to zero, and consent remains opt-out.[4]
The source marks the finding fixed against the reviewed remediation. That does not establish production deployment or exploitation.

Comparison 1: approval checked against the receiver, not the caller

Y2K Finance finding #434 reports withdrawal paths that checked isApprovedForAll(owner, receiver) while allowing an arbitrary msg.sender to initiate the withdrawal.[5]
That swaps two distinct roles:
1caller: who causes the state transition
2receiver: who receives the output
If the receiver is approved, that does not imply every caller may decide when the withdrawal occurs. The public issue describes third parties triggering withdrawals for approved owner-receiver pairs and recommends checking approval for the caller instead. A project participant commented that the change would be implemented.[5]
Zealynx classifies this as an actor binding failure. The approval relation was real. The contract asked it about the wrong address.
The broader lesson is not limited to withdrawals. Any function with owner, operator, receiver, beneficiary, and msg.sender in the same flow should identify which one is the authority-bearing actor before consulting approval state.

Comparison 2: generic execution created an uncovered approval path

Party Protocol finding #129 reports that arbitrary proposal execution filtered dangerous ERC-721 approval operations but did not equivalently account for ERC-1155 and ERC-20 approval calls. The submission describes a proposal creating operator authority over ERC-1155 assets held by the party.[6]
The issue thread later identifies the submission as a duplicate of another record. That history matters. This article uses it only as public technical evidence of the described selector-coverage mechanism, not as an independent severity result.
Zealynx classifies the mechanism as an action binding failure:
1proposal authority to execute a call
2 is not automatically
3permission to create durable token operator authority
Filtering one familiar selector is not a complete policy. A generic executor must reason about the postcondition of a call, including new approvals, ownership changes, module installation, and delegation. Otherwise, the executor can convert short-lived execution rights into persistent asset control.

Comparison 3: the guard covered global approval but missed scoped approval

ERC-1155 intentionally keeps the base approval model broad. Its rationale says narrower token ID, quantity, or rule-based approval may be supplied through an additional interface, and it points to ERC-1761 as one example.[1]
reNFT finding #430 reports a guard that rejected known transfer and approval selectors but did not reject setApprovalForScope(address,bytes32,bool). For an ERC-1155 implementation supporting the scoped extension, the safe owner could approve an operator for a scope and create a transfer route the guard did not cover.[7]
The issue history records several duplicate-status changes before the submission was marked satisfactory. It does not support treating every scoped extension as present in every token.
Zealynx classifies this as an object and interface coverage failure. The policy attempted to protect rented assets by enumerating forbidden actions. The token's authority surface was larger than the guard's selector model.
The durable review question is:
Does the policy control every way authority can be created for this asset, or only the base standard functions known when the policy was written?
This matters for extensions, permits, wrappers, modules, proxies, and custom transfer APIs. An allowlist or denylist is only as complete as its authority inventory.

Comparison 4: a trusted router could redelegate an owner's ERC-721

Tapioca finding #44 is an ERC-721 analogue. The public record reports that users could approve a router with isApprovedForAll, while another user could route an approve call through that intermediary and assign approval for a token to themselves.[8]
The issue history records duplicate changes, later selection for the report, a final High label, and sponsor confirmation added by Code4rena staff.[8]
The mechanism differs from the Plakxio anchor. The token owner's operator approval was genuine. The failure was that the approved intermediary exposed a public operation that let an unrelated caller choose how that authority was redelegated.
Zealynx classifies this as a combined actor and action binding failure:
1owner trusts router as operator
2 does not imply
3any caller may use router to appoint a new spender
This is why "the router is approved" is not an endpoint. Review must continue through every public function that can consume the router's authority.

Comparative matrix

RecordSource-reported mechanismZealynx classificationWhat the approval failed to bindEvidence limit
Plakxio F-2026-0001Whitelisted operator read as approved for every holder; holder refusal was ineffectiveSubject and lifecycleHolder consent and effective revocationFixed in reviewed remediation; no deployment or exploitation claim [4]
Y2K #434Withdrawal checked receiver approval rather than caller approvalActorInitiating callerPublic issue and implementation comment; no independent reproduction here [5]
Party #129Generic execution could create ERC-1155 approval outside ERC-721-specific filteringAction and interface coverageDurable approval side effectSubmission was linked as a duplicate; no normalized severity claim [6]
reNFT #430Guard missed an ERC-1761 scoped approval selectorObject and interface coverageToken scope and extension surfaceConditional on token supporting the extension [7]
Tapioca #44Approved router exposed a path for another caller to assign token approvalActor and actionWho may consume and redelegate router authorityERC-721 analogue, not an ERC-1155 exact match [8]
The records do not show one universal patch. They show one review discipline: model approval as a relation with explicit dimensions, then verify that every consuming path uses the same dimensions.

Why events and views are part of the security boundary

The Plakxio anchor exposes a subtle control-plane problem. A revocation transaction succeeded and emitted ApprovalForAll, while the effective query remained true through a separate branch.[4]
That creates three possible truths:
1stored approval state
2event-reported approval state
3effective transfer authority
A secure integration needs them to agree, or needs any intentional difference to be explicit and queryable. Wallets, indexers, marketplaces, and monitoring systems use views and events to represent authority to humans. If transfer code consumes a broader rule than those systems display, the user sees a revocation that the contract does not honor.
This produces a stronger invariant than "revocation writes false":
1After revocation becomes effective,
2all transfer paths that depend on the revoked relation must reject,
3and the canonical authority view must report false.

Working auditors in your corner, all year

Zealynx Insiders: weekly live sessions, 1:1 advisory, pair-auditing, and Krait runs on your code, from an audit firm that publishes its reports.

Test the invariant on single transfers, batch transfers, burns, custom movement functions, generic executors, and delegated settlement routes.

Code presence and delays are constraints, not proof of consent

Requiring operator.code.length > 0 blocks a bare EOA at grant time. It does not establish that the code verifies signatures, remains immutable, or cannot expose public methods that misuse its authority.
A contract address may be:
  • a proxy whose implementation can change;
  • a router with overly broad public entry points;
  • a contract that verifies the wrong signer or order domain;
  • a contract that can redelegate approvals; or
  • an address whose code lifecycle differs across chains or deployment assumptions.
Likewise, an activation delay does not prove safety. It creates time for detection and cancellation. That is useful only if the grant is observable, revocation is immediate, monitoring exists, and the configured delay cannot collapse to an unsafe value.
These are layered controls:
1identity constraint blocks obviously wrong operator types
2activation delay creates reaction time
3holder opt-out preserves individual agency
4behavioral verification enforces signed intent
5surface consistency closes alternate transfer paths
6monitoring makes the reaction window usable
No single layer carries the complete consent property.

Design patterns that preserve holder consent

1. Keep restriction separate from grant

A protocol whitelist can restrict which already-approved operators may act:
1return protocolAllowed[operator]
2 && super.isApprovedForAll(owner, operator);
That is easier to reason about than making protocol membership itself a grant. If gasless UX requires a different model, name and expose it as distinct delegated authority rather than silently folding it into the holder approval query.

2. Bind signed intent at the consuming boundary

Typed data can bind the holder, operator, token IDs, quantities, receiver, price, nonce, expiry, chain, and verifying contract.[3] The transfer route must actually require and consume that signed artifact. A signature check in the expected operator does not protect a parallel operator that can skip it.

3. Check the authority-bearing actor

For every state transition, label:
1owner
2msg.sender
3operator
4receiver
5beneficiary
6relayer
7verifying contract
Then state which identity holds the permission. Do not infer it from naming or destination.

4. Inventory every authority-creating interface

Review base standards, extensions, permits, custom approvals, module installation, ownership changes, and arbitrary-call paths. For denylist-based guards, test selectors that are semantically equivalent but syntactically different.

5. Make revocation behaviorally complete

A revocation should change:
  • the canonical authority view;
  • every standard transfer path;
  • every custom movement path;
  • every batch variant; and
  • the signals consumed by wallets and monitors.

6. Treat intermediaries as authorization systems

An approved router is not safe merely because its address is expected. Enumerate its public methods, caller checks, upgrade controls, call targets, delegatecall behavior, and ability to create downstream approvals.

An audit test matrix

The following matrix turns the five bindings into concrete tests.
TestExpected property
Holder explicitly revokes protocol operatorEffective query and every transfer path reject
Unrelated caller supplies an approved receiverCaller cannot trigger the action unless separately authorized
EOA is granted settlement authorityGrant rejects if contract behavior is required
Unexpected contract is granted authorityDelay and cancellation controls remain effective; behavior is not assumed from code presence
Grant and transfer occur in one transactionTransfer rejects until activation conditions are satisfied
Single transfer rejectsBatch and custom transfer paths reject for the same reason
Base setApprovalForAll is blockedScoped approvals, permits, and custom approval interfaces are also evaluated
Router is globally approvedUnrelated caller cannot use router to redelegate or move another owner's asset
Approval event says falseCanonical view reports false and consuming code behaves as false
Operator is upgraded or implementation changesAuthority policy detects, delays, or explicitly accepts the change
Property-based testing can express the central rule:
1For every successful movement from owner:
2 owner == caller
3 OR effective_authority(owner, caller, asset, action, now) == true
For signature-mediated systems, strengthen it:
1Every privileged movement must correspond to
2one unexpired, unconsumed holder authorization
3bound to this asset, action, destination, domain, and execution policy.
The exact fields vary. The need to bind them does not.

What this comparison does not establish

First, it does not estimate how common operator-consent failures are. The records were selected because they illuminate the mechanism.
Second, it does not claim equal severity or exploitability. The sources span a direct audit, public contest submissions, duplicate histories, extension-dependent conditions, and an ERC-721 analogue.
Third, it does not independently reproduce every proof of concept. It analyzes the behavior reported in the cited public records.
Fourth, a source-reported fix, implementation comment, sponsor confirmation, or report-selection label is not proof that every deployment contains the change.
Fifth, code-only operators, timelocks, EIP-712 signatures, and opt-outs are not universal prescriptions. Their usefulness depends on the protocol's trust model and whether the complete transfer path enforces them.
Finally, this article makes no claim that any described path was exploited unless the cited source explicitly establishes that. None of the core claims here requires an exploitation or monetary-impact assertion.

The durable rule: approval must bind the whole action

isApprovedForAll is a primitive, not a complete consent model.
The Plakxio anchor shows a global operator branch overriding holder revocation.[4] Y2K shows an approval query aimed at the receiver rather than the caller.[5] Party shows generic execution crossing into durable approval creation.[6] reNFT shows an extension expanding the authority surface beyond a selector guard.[7] Tapioca shows an approved intermediary exposing its authority to an unrelated caller.[8]
The common rule is:
An approval is safe only when the contract binds it to the correct owner, actor, asset scope, action, and lifecycle state at the moment authority is consumed.
Reviewing the boolean is not enough. Follow it into the transfer, withdrawal, router, proposal executor, extension, and revocation path. Consent lives in the complete state transition.
If your protocol uses gasless NFT settlement, privileged operators, routers, or custom approval logic, request a scoped smart contract review.

FAQ

1. Is ERC-1155 setApprovalForAll unsafe?
No. ERC-1155 defines a broad operator relationship deliberately.[1] Risk appears when an application interprets that relationship as proof of narrower consent it does not encode, or when an intermediary exposes the granted authority to the wrong caller.
2. Does requiring an operator to be a contract solve the problem?
No. It excludes a bare EOA at grant time, but does not prove the contract verifies holder intent, cannot be upgraded, or has safe public entry points. It is one layer in an access-control design.
3. What is the difference between an operator and a receiver?
The operator exercises authority over the owner's asset. The receiver gets the asset or output. They may be the same address, but approval of one role must not be silently used as authorization for the other.
4. How should a protocol test NFT revocation?
After revocation, assert that the canonical approval view returns false and that single, batch, custom, router-mediated, and burn paths all reject the former operator. Also verify that events and monitoring reflect the effective state.
5. Can EIP-712 signatures replace token approvals?
They can authorize a gasless workflow, but only if the consuming contract binds the signed fields correctly and no parallel transfer route bypasses signature verification. EIP-712 specifies typed signing, not the application's complete authorization policy.[3]

Glossary

TermDefinition
Access ControlRules determining which identities may perform privileged actions.
Approval BypassA failure where an apparent approval does not constrain the action's real security parameters.
Role-Based AccessPermission assignment through explicit roles and role administration.
Trust BoundaryThe point where data, identity, or authority crosses between components with different trust assumptions.
TransactionAn authenticated state-transition request processed by the network.

Sources

[1] https://eips.ethereum.org/EIPS/eip-1155 - ERC-1155 Multi Token Standard [2] https://docs.openzeppelin.com/contracts/5.x/api/token/erc1155 - OpenZeppelin Contracts ERC-1155 documentation [3] https://eips.ethereum.org/EIPS/eip-712 - EIP-712 Typed Structured Data Hashing and Signing [4] https://zealynx.io/audits/plakxio/badge-marketplace-founder-sprint-q3-2026/findings/F-2026-0001 - Zealynx, Plakxio operator-consent finding and remediation [5] https://github.com/code-423n4/2022-09-y2k-finance-findings/issues/434 - Code4rena, Y2K Finance finding #434 [6] https://github.com/code-423n4/2022-09-party-findings/issues/129 - Code4rena, Party Protocol finding #129 [7] https://github.com/code-423n4/2024-01-renft-findings/issues/430 - Code4rena, reNFT finding #430 [8] https://github.com/code-423n4/2024-02-tapioca-findings/issues/44 - Code4rena, Tapioca finding #44 [9] https://raw.githubusercontent.com/ZealynxSecurity/audits/main/web3/2026-08-zealynx-plakxio-founder-sprint.pdf - Zealynx, canonical Plakxio Founder Security Sprint report

Working auditors in your corner, all year

Zealynx Insiders: weekly live sessions, 1:1 advisory, pair-auditing, and Krait runs on your code, from an audit firm that publishes its reports.