Algorand contract detectors

16 built-in detectors targeting real Algorand AVM attack vectors. Each detector maps to documented exploit patterns from mainnet incidents and audit findings.

CLI Scan Workbench

Basic static scan commands are free. AI workflow commands require paid access.

Website exposes basic scan workflows. For full access commands (on-chain scans, deployment helpers, serve mode), install and run local CLI: pip install algo-secure then algosec --help.

Core Detectors (16)

Reentrancy via Inner Transactions

critical

Detects contract state mutations after InnerTxnBuilder.Execute() calls. In AVM, inner transactions execute atomically, but if global state is updated after an inner call that transfers control, the called contract may re-enter and exploit stale state. This mirrors the classic reentrancy pattern adapted for Algorand's inner transaction model.

Vulnerable PatternSeq([InnerTxnBuilder.Execute(), App.globalPut(Bytes('balance'), new_val)])

Unrestricted UpdateApplication

critical

Flags OnComplete.UpdateApplication handlers that lack sender authorization. Without a check like Assert(Txn.sender() == Global.creator_address()), any account can replace the approval program TEAL bytecode, fully compromising the contract. This is a real attack vector seen in unaudited Algorand DeFi contracts.

Vulnerable PatternCond([Txn.on_completion() == OnComplete.UpdateApplication, Return(Int(1))])

Unrestricted DeleteApplication

critical

Detects OnComplete.DeleteApplication handlers without proper auth. If any user can delete the app, all escrowed ALGO and ASAs in the app account are permanently lost. This must always be gated by creator or admin check.

Vulnerable PatternCond([Txn.on_completion() == OnComplete.DeleteApplication, Approve()])

Missing CloseRemainderTo Validation

high

Identifies payment or asset transfer transactions where Txn.close_remainder_to() or Txn.asset_close_to() is not validated against Global.zero_address(). An attacker can drain the full account balance by setting close-to to their own address. This is one of the most common Algorand exploits.

Vulnerable PatternAssert(Txn.close_remainder_to() == Global.zero_address()) # MISSING

Missing RekeyTo Validation

high

Flags transactions that do not assert Txn.rekey_to() == Global.zero_address(). Without this, a malicious transaction can rekey the contract's auth address, granting permanent control to an attacker. Every Algorand stateful contract must validate this.

Vulnerable PatternAssert(Txn.rekey_to() == Global.zero_address()) # MISSING

Unchecked Group Transaction Index

high

Warns when Txn.group_index() is not validated, allowing atomic group reordering attacks. An attacker can submit a valid group with transactions in a different order, bypassing sequential validation logic.

Vulnerable PatternAssert(Txn.group_index() == Int(0)) # MISSING

Integer Overflow in uint64 Arithmetic

high

AVM uint64 arithmetic wraps on overflow and panics on underflow. Detects operations like subtraction without prior bounds checking (App.globalGet(key) - amount) where amount could exceed the stored value, causing a panic or exploitable wrap-around in older TEAL versions.

Vulnerable PatternApp.globalGet(Bytes('balance')) - Btoi(Txn.application_args[1])

Missing Fee Validation

high

Detects contracts that don't validate Txn.fee() against a maximum threshold. Without Assert(Txn.fee() <= Int(max_fee)), an attacker can set an excessively high fee to drain the contract's ALGO balance through miner fee extraction.

Vulnerable PatternAssert(Txn.fee() <= Int(10000)) # MISSING

Opcode Budget Exhaustion

medium

Warns when contract logic paths may exceed the 700 opcode budget per transaction. Complex loops, recursive Subroutine calls, or multiple byte operations can exceed the budget, causing transaction failure. Suggests using OpUp inner transactions for budget increases.

Vulnerable PatternWhile(counter.load() < limit): ... # may exceed 700 opcodes

Timestamp Manipulation

medium

Flags business logic that depends on Global.latest_timestamp() for critical decisions. Block proposers on Algorand can manipulate the timestamp within a ±25 second window, making time-dependent logic like auction deadlines or vesting schedules exploitable.

Vulnerable PatternAssert(Global.latest_timestamp() > Int(deadline))

Global State Key Collision

medium

Detects when multiple code paths write to the same global state key with different types or semantics, creating data corruption risks. Also flags contracts approaching the 64 global state key limit.

Vulnerable PatternApp.globalPut(Bytes('data'), value) # written in 2 branches with different types

Unsafe Scratch Space Usage

low

Identifies scratch space usage (ScratchVar) where values are read before being written in a given execution path, which returns a default zero value. This can lead to logic errors when the zero is used in arithmetic or conditionals.

Vulnerable PatternScratchVar().load() # before .store() in this path

Missing App Argument Bounds Check

high

Detects direct access to Txn.application_args[N] without asserting the argument count first. Missing bounds checks can panic execution and create denial-of-service surfaces under malformed calls.

Vulnerable PatternBtoi(Txn.application_args[2]) # without Assert(Txn.application_args.length() > Int(2))

Unvalidated Inner Transaction Receiver

high

Flags inner payment or asset transfer builders where receiver/asset_receiver is not constrained to a trusted address or argument policy. This can route funds to attacker-controlled accounts.

Vulnerable PatternInnerTxnBuilder.SetFields({TxnField.receiver: Txn.accounts[1], ...}) # no allowlist/assert

Box Storage Access Control Gap

medium

Warns when box read/write operations are performed without sender authorization for sensitive namespaces. Attackers may overwrite business-critical box state when access checks are absent.

Vulnerable PatternApp.box_put(Bytes('cfg'), value) # no role/owner check

Missing ABI Selector Validation

low

Identifies ABI dispatch handlers that do not validate the first 4-byte selector before executing a branch. This increases ambiguity and may route malformed calls into unintended logic paths.

Vulnerable PatternCond([Txn.on_completion() == OnComplete.NoOp, handler]) # selector check missing

Key Features

🌳

PyTeal AST Analysis

Deep abstract syntax tree parsing for PyTeal contracts. Understands Seq, Cond, If, While, Subroutine, and all PyTeal constructs at the Python level before TEAL compilation.

⚙️

TEAL Opcode Coverage

Direct TEAL bytecode analysis covering all AVM v10 opcodes including box_create, box_put, vrf_verify, block, and state proof operations.

🔗

Inner Transaction Tracing

Tracks inner transaction flows including itxn_begin/itxn_submit sequences and InnerTxnBuilder chains to detect composability vulnerabilities across contract-to-contract calls.

⚛️

Atomic Group Validation

Analyzes grouped transaction logic to verify correct group_index assertions, prevent reordering attacks, and validate cross-transaction dependencies in atomic swaps and DeFi operations.

📋

ARC Compliance Checking

Validates contracts against Algorand Request for Comments standards including ARC-4 (ABI), ARC-28 (Events), and ARC-72 (NFTs) for interface correctness.

🚀

CI/CD Integration

Exit codes and JSON/SARIF output for GitHub Actions, GitLab CI, and custom pipelines. Set severity thresholds to fail builds before mainnet deployment.

Installation

pippip install algo-secure
scanalgosec analyze contracts/contract.py --severity high
servealgosec serve

Quick start

algosec-cli
$ algosec analyze contracts/AMM.py --severity high
  ⠿ Loading detectors...
  ✓ 16 detectors loaded (3 critical, 7 high, 4 medium, 2 low)
  ⠿ Parsing PyTeal AST...
  ⠿ Running analysis...
  ✗ CRITICAL Unrestricted UpdateApplication handler (line 47)
    → OnComplete.UpdateApplication returns Approve() without sender check
    → Fix: Add Assert(Txn.sender() == Global.creator_address())
  ✗ HIGH Missing CloseRemainderTo validation (line 82)
    → Payment inner txn does not assert close_remainder_to == zero
    → Fix: Add Assert(Txn.close_remainder_to() == Global.zero_address())
  ✗ HIGH Missing RekeyTo validation (line 15)
    → No rekey_to guard in approval program entry
    → Fix: Add Assert(Txn.rekey_to() == Global.zero_address())

  # Score: 62 / 100
  # Findings: 3 (1 critical, 2 high)
  # Report saved: ./report-amm.json

Commands

analyzealgosec analyze <path> [options]

Analyze contract security posture (free basic workflow).

--type <type>Detector scope
--format <fmt>console, json, sarif, markdown, html
-o <file>Write report output
statsalgosec stats <path>

Show concise security stats with visual summary.

--graphShow graph output when available
scanalgosec scan <path> [options]

Scan contracts from a file or folder (basic workflow).

--type <type>Detector scope
--format <fmt>console, json, sarif, markdown, html

Web UI intentionally exposes only basic commands (`analyze`, `scan`, `stats`). For full command set use local CLI: `pip install algo-secure` and run `algosec --help`.