Security review #341
Labels
No labels
agent:fix_bugfix
agent:fix_conflicts
agent:fix_security
agent:gap_analysis
agent:implement
agent:open_issues
agents
agent:security_audit
agent:verify
area
timecurve
arena
arena-v2
audit
block:feature_review
block:security
bug
contracts
design
devops
docs
documentation
enhancement
epic
feature
frontend
indexer
low priority
megaeth
operations
ops
performance
QA
qa checklist
referrals
removal
security
smart-contracts
tech-debt
testing
time-arena
timecurve
visual-qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
code/yieldomega#341
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Full stack security review.
made the item visible to everyone
Internal Full-Stack Security Audit — GitLab #341
File:
audits/INTERNAL_COMPOSER_1782013334.mdDate: 2026-06-21
Reviewer: Cursor Cloud agent (Composer)
Commit:
8f695247562c29822c912a83052bfa5d01101f56Issue: #341 Security review
Executive summary
Arena v2 (
TimeArena,PodiumVaults,ReferralRegistry,TimeArenaBuyRouter,PlayCred,Doubloon) is the current onchain authority. The indexer and frontend are derived read/signing layers.Under standard non-rebasing ERC-20 assumptions and correct deployment configuration, no Critical unprivileged path was found that would let an arbitrary attacker drain protocol custody (podium vault ledgers, prize pools, or mint authority outside intended game rules).
The codebase shows material hardening aligned with prior audits and GitLab invariants:
nonReentranton value-moving entrypoints, balance-delta ERC-20 parity on DOUB ingress (#123),Ownable2StepUpgradeableon UUPS proxies (#329), production indexer fail-closed guards (#142, #156, #326), parameterized SQL, per-IP rate limiting (#328), Slither CI, and Gitleaks.Highest residual risks are cross-layer and operational:
type(uint256).max.gasoverride when RPC simulation fails.INDEXER_PRODUCTIONis unset on a non-loopback bind.grandfatherProgression(accepted trust model; high impact if keys compromised).Scope and methodology
In scope
TimeArena,PodiumVaults,ReferralRegistry,TimeArenaBuyRouter,PlayCred,Doubloon,DoubAirdrop,ArenaCharmPriceTwap, routing/timer/XP libraries,DeployDev/DeployProductionapi.rs,api_arena.rs,api_validate.rs,config.rs,cors_config.rs,rate_limit.rs,ingestion.rs,reorg.rs,persist.rs, migrationswriteContractWithGasBuffer, chain-mismatch guard, indexer API client, referral storagebots/timearenaconfig and key handling.github/workflows/slither.yml,gitleaks.yml, deploy scripts,AGENTS.mddev-key policydocs/onchain/security-and-threat-model.md, invariants mapTrust assumptions (per maintainer / prior audits)
Doubloonand configured pay tokens are standard ERC-20 (no fee-on-transfer / rebasing in production).Automated verification (this audit)
forge testexcludingDoubAirdropMegaethFork+WarbowBenchmarkcargo test --libcd frontend && npm test -- --runNotes: Four
DoubAirdropMegaethForktests require gitignoredairdrop/doub.csv(expected).TimeArenaWarbowBenchmarktests fail in this VM without benchmark fixtures — not a security regression signal.Findings
F-01 — Medium — Unlimited ERC-20 approvals hardcoded on Arena spend paths
frontend/src/lib/arenaDoubApprovalPreference.ts(readArenaDoubUnlimitedApprovalalwaystrue); consumers:useArenaSaleSession.ts,useArenaWarbowHero.ts,useArenaProtocolDonatePools.ts,ensureDoubTimeArenaAllowance.ts,ensureCl8yKumbayaAllowance.tsmaxUint256toTimeArena/ router. The prior opt-in unlimited toggle (#143/#277) is effectively removed —useCl8yArenaUnlimitedApprovalis a no-op stub.TimeArenaproxy is upgraded maliciously, compromised at the implementation layer, or the user is phished into approving a wrong spender address via a fake frontend, the entire token balance approved can be drained in one transfer. Industry pattern (e.g. BadgerDAO-style allowance drainers) targets long-lived unlimited approvals.VITE_TIME_ARENA_ADDRESSpointing at attacker contract. 2) User approvesmaxUint256DOUB. 3) Attacker callstransferFrom. Even with correct addresses, a future malicious UUPS implementation retains allowance until revoked.F-02 — Medium — Gas estimation failure falls through to wallet default estimator
frontend/src/lib/writeContractWithGasBuffer.ts— defaultonEstimateRevert: "submit-without-override"; Arena buy/WarBow/flag paths inuseArenaSaleSession.ts,useArenaWarbowHero.tsdo not overrideestimateContractGasfails (RPC error, simulation revert, flaky MegaETH RPC), the helper submits viawriteContractAsyncwithout agasoverride.VITE_RPC_URLat a broken endpoint; attemptbuy— observe submission proceeds after estimate failure instead of blocking with a clear error.onEstimateRevert: "rethrow"for all user-value txs (buys, WarBow,claimCred, donations). Reserve"submit-without-override"only for known-safe retry paths with explicit user messaging.F-03 — Medium — Public indexer bind without
INDEXER_PRODUCTIONenables cross-origin data readsindexer/src/cors_config.rs,indexer/src/config.rs(warn_public_bind_without_production)INDEXER_PRODUCTION=1, CORS is fully permissive (Access-Control-Allow-Origin: *). A startup warning is logged for non-loopback binds, but the process still serves.LISTEN_ADDR=0.0.0.0:3100and noINDEXER_PRODUCTION. Fromevil.example,fetch('http://indexer:3100/v1/arena/timers')succeeds with ACAO*.INDEXER_PRODUCTION=1+CORS_ALLOWED_ORIGINSin deployment templates (Render/systemd). Optional: fail-fast (not just warn) whenLISTEN_ADDRis non-loopback and production mode is off.F-04 — Low —
INDEXER_TRUST_PROXYenables rate-limit bypass if reverse proxy is misconfiguredindexer/src/rate_limit.rs—SmartIpKeyExtractorwhenINDEXER_TRUST_PROXY=1X-Forwarded-Forwhen trust-proxy is enabled. If the edge proxy does not overwrite client-suppliedX-Forwarded-For, attackers can rotate spoofed headers to get fresh buckets.INDEXER_TRUST_PROXY=1requires a trusted proxy that strips inboundX-Forwarded-For. Prefer connecting limiter toConnectInfopeer when indexer is loopback-only behind nginx.F-05 — Low — Indexer-first Arena display trusts offchain head for UX-critical timing
INV-FRONTEND-301-INDEXER-FIRST-DISPLAY;frontendarena hooks pollingGET /v1/arena/*VITE_INDEXER_URLis set. Onchain state remains authoritative, but users may act on stale indexer rows after reorgs, lag, or indexer compromise.ingestion_alive/ lag badge (#168). For high-stakes actions, optional client-sideeth_callsanity check againstTimeArena.deadline()/doubOwedForBuy.F-06 — Low — Owner break-glass charm re-anchor (
setEpochCharmAnchorWad)contracts/src/arena/TimeArena.sol—setEpochCharmAnchorWad,_reanchorEpochCharmPriceon hard-reset buys_sampleCharmAnchor()on MegaETH (#303/#315).ReferralRegistryreadsepochCharmAnchorWad). TWAP path mitigates same-block sandwich for hard-reset buys viadoubOwedForBuysampling at tx time (#315).LastBuyEpochCharmAnchoredevents.F-07 — Low — UUPS upgrade authority concentrated in single owner
TimeArena,ReferralRegistry—_authorizeUpgrade→onlyOwner;Ownable2StepUpgradeable(#329)__gappreserved.F-08 — Informational — Referral code registration is MEV-competitive (documented)
contracts/src/ReferralRegistry.sol—registerCode;docs/product/referrals.mdF-09 — Informational — MEV and same-block ordering are inherent to game design
TimeArenalast-buyer ring, podium scoring, WarBow BPdocs/onchain/security-and-threat-model.md).F-10 — Informational —
PodiumVaultsusesIERC20.transfernotSafeERC20contracts/src/arena/PodiumVaults.sol—payPodiumWinners,rollEpochTranchestransfer. Safe for standardDoubloon; would revert/fail on non-standard tokens.Doubloonremains standard; prize payout could fail silently on exotic tokens if pools were repointed.Doubloonstandard; if external pool addresses are ever wired viasetActivePool, document ERC-20 requirements.F-11 — Informational —
DoubAirdroplacks per-recipient fee-on-transfer paritycontracts/src/DoubAirdrop.soltotalonce, thensafeTransferper recipient — FoT tokens could cause later recipients to receive less thanvalues[i].F-12 — Informational —
GET /healthzexcluded from rate limitingindexer/src/api.rs— health router merged outside rate-limited subtreeF-13 — Informational — Reorg handling capped at 128 blocks
indexer/src/reorg.rs—MAX_REORG_DEPTH = 128rollback_after+ reindex; monitorchain_pointervia ops metrics (#306).Positive controls observed
TimeArena,TimeArenaBuyRouter,DoubAirdrop—nonReentranton external value pathsTimeArena._pullDoubExact,ReferralRegistry.registerCode, router stable ingressbuyForgated totimeArenaBuyRouter; path validation in_validatePathreorg.rstable names from staticARENA_INDEX_TABLESallowlistvalid_0x_address20,normalize_tx_hash_paramon sensitive routes (#329)PUBLIC_INTERNAL_DB_ERROR— no SQL echo (#157)INDEXER_PRODUCTIONCORS, DB URL, registry validation (#142, #156)fail-on: high), Gitleaks on push/PRdeploy-megaeth-contracts.shuses hidden promptYIELDOMEGA_PRIVATE_KEYfrom env only; dry-run defaultAreas reviewed — no material issue
_update; mint/burn onlyMINTER_ROLE(granted toTimeArenain production deploy).MINTER_ROLEcannotburnFromothers (#132).claimCred— requires settled epoch (epoch < lastBuyEpoch); clears weight before mint.dangerouslySetInnerHTML/evalinfrontend/src.chainMismatchWriteMessagegates contract writes (#95).Recommendations summary (priority)
docs/onchain/security-and-threat-model.md).End of report.
No items block launch or need tl be addressed at the expected volume/tvl. Closing.