/sep41-token-safety
Trigger Pattern SEP-41 token patterns detected (approve/transfer/transfer_from/allowance/balance) - Inject Into Breadth agents, depth-token-flow, depth-edge-case
$ npx -y skills add PlamenTSV/plamen --skill sep41-token-safety --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/sep41-token-safety
Context preview
The summary Claude sees to decide when to auto-load this skill.
Trigger Pattern SEP-41 token patterns detected (approve/transfer/transfer_from/allowance/balance) - Inject Into Breadth agents, depth-token-flow, depth-edge-case
SKILL.md
sep41-token-safety.SKILL.mdname: "sep41-token-safety"
description: "Trigger Pattern SEP-41 token patterns detected (approve/transfer/transfer_from/allowance/balance) - Inject Into Breadth agents, depth-token-flow, depth-edge-case"
SEP41_TOKEN_SAFETY Skill (Soroban)
> **Trigger Pattern**: SEP-41 token patterns detected (`approve`/`transfer`/`transfer_from`/`allowance`/`balance`) > **Inject Into**: Breadth agents, depth-token-flow, depth-edge-case > **Finding prefix**: `[ST-N]` > **Rules referenced**: R4, R5, R10, R11, R13, R15
SEP-41 is Soroban's token interface standard, analogous to ERC-20. It introduces Soroban-specific behaviors that differ from ERC-20: allowances use Temporary storage with `expiration_ledger`, the `approve` function can be front-run similarly to ERC-20, and the Stellar Asset Contract (SAC) bridges Stellar classic assets into Soroban with additional accounting complexity.
1. Approve Race Condition
The `approve(from, spender, amount, expiration_ledger)` function overwrites the current allowance without checking the existing value. This is the ERC-20 approve race condition, present identically in SEP-41:
| Token Contract | `approve` Guarded? | Guard Type | Effective? | |---------------|-------------------|-----------|-----------| | `{contract}` | YES/NO | `require!(current == 0)` / none | YES/NO |
**Attack sequence**: 1. Owner approves spender for 100 tokens 2. Owner submits new approval for 50 tokens 3. Spender front-runs the new approval and spends the 100-token allowance 4. New 50-token approval is set 5. Spender spends 50 more tokens — net: 150 tokens spent, 50 intended
**Soroban-specific note**: Soroban does not have a traditional mempool — transaction ordering is determined by validators, not fee-based priority. However, multi-operation transactions and DEX routing can create ordering dependencies. The race condition is lower likelihood than on EVM but the vulnerability exists.
**Check for**:
- Does `approve` allow setting non-zero over non-zero without an intermediate zero-set step?
- Does the protocol documentation warn users about the race condition?
- Are there any off-chain tools (like `increaseAllowance` / `decreaseAllowance` equivalents) to safely adjust allowances?
2. Allowance Expiry
SEP-41 allowances include an `expiration_ledger` parameter stored alongside the allowance amount. When the ledger passes `expiration_ledger`, the allowance is treated as zero. Verify expiry is handled correctly:
| Contract | Allowance Expiry Checked Before Use? | Expired Allowance Returns 0 or Panics? | Protocol Communicates Expiry to Users? | |---------|--------------------------------------|---------------------------------------|---------------------------------------| | `{contract}` | YES/NO | `{behavior}` | YES/NO |
**Allowance storage**: SEP-41 standard stores allowances in Temporary storage with TTL linked to `expiration_ledger`. When `expiration_ledger` passes, the Temporary entry may be pruned, and the `allowance()` call returns zero automatically.
**Checks**:
- Does the calling contract handle the case where a previously valid allowance has expired and `transfer_from` now fails?
- Is `expiration_ledger` validated to be in the future when `approve` is called? Setting `expiration_ledger` in the past silently creates an already-expired allowance
- For protocol-controlled allowances (e.g., a contract that approves tokens on behalf of users): does the contract re-approve if the allowance has expired?
**Edge case**: `expiration_ledger = 0` behavior — verify whether the token contract treats 0 as "no expiry" or "expired at ledger 0" (already expired). Inconsistency here could cause silent allowance failures.
3. Transfer Auth Propagation
SEP-41 `transfer(from, to, amount)` requires `from.require_auth()`. `transfer_from(spender, from, to, amount)` requires `spender.require_auth()` and checks the allowance. Trace auth through the contract's transfer chains:
| Calling Function | Transfer Type | Auth Address | `require_auth` Called? | Correct Subject? | |-----------------|--------------|-------------|----------------------|-----------------| | `{fn}` | `transfer` / `transfer_from` | `{from or spender}` | YES/NO | YES/NO |
**Patterns to check**:
- A contract calling `token.transfer(user, contract, amount)` on behalf of a user — does it call `user.require_auth()` first, or does it rely on the token contract to enforce it?
- Sub-invocation auth propagation: if a function calls `token.transfer` inside a `invoke_contract` chain, is the `AuthorizedInvocation` tree constructed to include the token transfer?
- `transfer_from` where the spender is the calling contract itself (valid for vault patterns) — is the allowance actually set, or does the contract assume it already is?
4. SAC Interaction
The Stellar Asset Contract (SAC) wraps Stellar classic XLM and issued assets as SEP-41 tokens. Contracts that interact with SAC face unique considerations:
| Concern | Addressed? | Evidence | |---------|-----------|----------| | SAC balance includes trust line state (frozen/unauthorized) | YES/NO | `{fn:line or NONE}` | | Classic Stellar operations affecting SAC balance not reflected in Soroban state | YES/NO | `{fn:line or NONE}` | | Clawback feature of issued assets handled | YES/NO | `{fn:line or NONE}` | | Issuer authorization revocation handled | YES/NO | `{fn:line or NONE}` | | Token address permanence (classic re-issuance / migration) | YES/NO | `{fn:line or NONE}` |
**SAC-specific risks**:
- **Trust line authorization**: A classic Stellar account can have its trust line for an asset revoked by the issuer. After revocation, `transfer` to that account fails silently or panics. Contracts that assume all transfers succeed need to handle this.
- **Clawback**: Some Stellar assets have clawback enabled. An issuer can call `clawback` to reduce a Soroban contract's balance without any Soroban transaction. This means a contract's `balance()` can decrease between two Soroban transa
Read more
name: "sep41-token-safety" description: "Trigger Pattern SEP-41 token patterns detected (approve/transfer/transfer_from/allowance/balance) - Inject Into Breadth agents, depth-token-flow, depth-edge-case"
SEP41_TOKEN_SAFETY Skill (Soroban)
> **Trigger Pattern**: SEP-41 token patterns detected (`approve`/`transfer`/`transfer_from`/`allowance`/`balance`) > **Inject Into**: Breadth agents, depth-token-flow, depth-edge-case > **Finding prefix**: `[ST-N]` > **Rules referenced**: R4, R5, R10, R11, R13, R15
SEP-41 is Soroban's token interface standard, analogous to ERC-20. It introduces Soroban-specific behaviors that differ from ERC-20: allowances use Temporary storage with `expiration_ledger`, the `approve` function can be front-run similarly to ERC-20, and the Stellar Asset Contract (SAC) bridges Stellar classic assets into Soroban with additional accounting complexity.
1. Approve Race Condition
The `approve(from, spender, amount, expiration_ledger)` function overwrites the current allowance without checking the existing value. This is the ERC-20 approve race condition, present identically in SEP-41:
| Token Contract | `approve` Guarded? | Guard Type | Effective? | |---------------|-------------------|-----------|-----------| | `{contract}` | YES/NO | `require!(current == 0)` / none | YES/NO |
**Attack sequence**: 1. Owner approves spender for 100 tokens 2. Owner submits new approval for 50 tokens 3. Spender front-runs the new approval and spends the 100-token allowance 4. New 50-token approval is set 5. Spender spends 50 more tokens — net: 150 tokens spent, 50 intended
**Soroban-specific note**: Soroban does not have a traditional mempool — transaction ordering is determined by validators, not fee-based priority. However, multi-operation transactions and DEX routing can create ordering dependencies. The race condition is lower likelihood than on EVM but the vulnerability exists.
**Check for**:
- Does `approve` allow setting non-zero over non-zero without an intermediate zero-set step?
- Does the protocol documentation warn users about the race condition?
- Are there any off-chain tools (like `increaseAllowance` / `decreaseAllowance` equivalents) to safely adjust allowances?
2. Allowance Expiry
SEP-41 allowances include an `expiration_ledger` parameter stored alongside the allowance amount. When the ledger passes `expiration_ledger`, the allowance is treated as zero. Verify expiry is handled correctly:
| Contract | Allowance Expiry Checked Before Use? | Expired Allowance Returns 0 or Panics? | Protocol Communicates Expiry to Users? | |---------|--------------------------------------|---------------------------------------|---------------------------------------| | `{contract}` | YES/NO | `{behavior}` | YES/NO |
**Allowance storage**: SEP-41 standard stores allowances in Temporary storage with TTL linked to `expiration_ledger`. When `expiration_ledger` passes, the Temporary entry may be pruned, and the `allowance()` call returns zero automatically.
**Checks**:
- Does the calling contract handle the case where a previously valid allowance has expired and `transfer_from` now fails?
- Is `expiration_ledger` validated to be in the future when `approve` is called? Setting `expiration_ledger` in the past silently creates an already-expired allowance
- For protocol-controlled allowances (e.g., a contract that approves tokens on behalf of users): does the contract re-approve if the allowance has expired?
**Edge case**: `expiration_ledger = 0` behavior — verify whether the token contract treats 0 as "no expiry" or "expired at ledger 0" (already expired). Inconsistency here could cause silent allowance failures.
3. Transfer Auth Propagation
SEP-41 `transfer(from, to, amount)` requires `from.require_auth()`. `transfer_from(spender, from, to, amount)` requires `spender.require_auth()` and checks the allowance. Trace auth through the contract's transfer chains:
| Calling Function | Transfer Type | Auth Address | `require_auth` Called? | Correct Subject? | |-----------------|--------------|-------------|----------------------|-----------------| | `{fn}` | `transfer` / `transfer_from` | `{from or spender}` | YES/NO | YES/NO |
**Patterns to check**:
- A contract calling `token.transfer(user, contract, amount)` on behalf of a user — does it call `user.require_auth()` first, or does it rely on the token contract to enforce it?
- Sub-invocation auth propagation: if a function calls `token.transfer` inside a `invoke_contract` chain, is the `AuthorizedInvocation` tree constructed to include the token transfer?
- `transfer_from` where the spender is the calling contract itself (valid for vault patterns) — is the allowance actually set, or does the contract assume it already is?
4. SAC Interaction
The Stellar Asset Contract (SAC) wraps Stellar classic XLM and issued assets as SEP-41 tokens. Contracts that interact with SAC face unique considerations:
| Concern | Addressed? | Evidence | |---------|-----------|----------| | SAC balance includes trust line state (frozen/unauthorized) | YES/NO | `{fn:line or NONE}` | | Classic Stellar operations affecting SAC balance not reflected in Soroban state | YES/NO | `{fn:line or NONE}` | | Clawback feature of issued assets handled | YES/NO | `{fn:line or NONE}` | | Issuer authorization revocation handled | YES/NO | `{fn:line or NONE}` | | Token address permanence (classic re-issuance / migration) | YES/NO | `{fn:line or NONE}` |
**SAC-specific risks**:
- **Trust line authorization**: A classic Stellar account can have its trust line for an asset revoked by the issuer. After revocation, `transfer` to that account fails silently or panics. Contracts that assume all transfers succeed need to handle this.
- **Clawback**: Some Stellar assets have clawback enabled. An issuer can call `clawback` to reduce a Soroban contract's balance without any Soroban transaction. This means a contract's `balance()` can decrease between two Soroban transa
Autonomous Web3 security auditor for Claude Code and OpenAI Codex CLI. Orchestrates 18-100 AI agents across 40+ phases to produce audit reports with verified PoC exploits — for smart contracts and L1 node-client infrastructure.
Repo: PlamenTSV/plamen
Other skills on plamen.
- /ability-analysis
Trigger Pattern Always (Aptos Move) - foundational security check - Inject Into Breadth agents, depth agents
Open skill - /bit-shift-safety
Trigger Pattern Always (Aptos Move) - Move VM aborts on shift = bit width - Inject Into Breadth agents, depth-edge-case
Open skill - /centralization-risk
Trigger Protocol has privileged roles (admin, operator, governance, resource account owner) - Covers Single points of failure, privilege escalation, external governance dependen...
Open skill - /cross-chain-timing
Trigger Pattern wormhole|layerzero|ccip|bridge|cross_chain|vaa|guardian|emitter|relay|remote_chain|payload|nonce.sequence - Inject Into Breadth agents, depth-external
Open skill - /dependency-audit
Trigger EXTERNAL_LIB flag detected (protocol uses third-party Move dependencies) - Used by Breadth agents, depth-external
Open skill - /economic-design-audit
Trigger Pattern MONETARY_PARAMETER flag (required) - Inject Into Breadth agents (merged via M4 hierarchy)
Open skill

