Security review #325
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#325
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?
Internal Full-Stack Security Audit — GitLab #325
audits/INTERNAL_COMPOSER_1781474786.mdd2913027b4d67d05b45a924f04d172ce75d39843Executive Summary
No Critical or High finding was identified that lets an unprivileged attacker steal DOUB, CRED, or CL8Y from protocol vaults, bypass
TimeArenaaccess control, or execute SQL/RCE against a correctly configured production deployment.The codebase shows mature hardening from prior audits:
nonReentranton value-moving paths, ERC-20 balance-delta parity on DOUB ingress (#123), production fail-closed guards on indexer DB URL and address registry (#142, #156), wallet session drift guards on multi-step writes (#144/#155), RPC URL redaction in frontend errors (#145), and generic 500 bodies on indexer DB failures.Primary residual risks are operational and economic:
TimeArenaandReferralRegistryare UUPS proxies with single-stepOwnableUpgradeable(no timelock /Ownable2Stepin-contract).INDEXER_PRODUCTIONis unset; mock-wallet or dev-key artifacts in a production frontend build.sqrtPriceLimitX96 = 0; MEV and sandwich risk on swaps is borne by buyers (mitigated byamountInMaximum+ deadline).Governance
onlyOwnerpowers (setEpochCharmAnchorWad,setPaused,grandfatherProgression, UUPS upgrades) are trusted by design but documented below as operational risks.Areas Analyzed
TimeArena,PodiumVaults,TimeArenaBuyRouter,ReferralRegistry,PlayCred,Doubloon,DoubAirdrop,ArenaCharmPriceTwap,ArenaBuyRouting, deploy scriptsapi.rs,api_arena.rs,arena_platform_usage.rs,arena_wallet_stats.rs,ingestion.rs,reorg.rs,persist.rs,config.rs,cors_config.rs,rpc_http.rs, migrations (by test mapping)indexerApi.ts,revertMessage.ts,wagmi-config.ts, XSS testsbots/timearena/,scripts/deploy-megaeth-contracts.sh,scripts/start-local-anvil-stack.sh,AGENTS.mddev-key policy.github/workflows/unit-tests.yml,contracts/slither.config.jsonforge test(Arena/Prize/Router/Referral/PlayCred/Doubloon/NonStandard — PASS);cargo test --libinindexer/(65 passed)Assumptions & Trust Boundaries
onlyOwnerandMINTER_ROLEholders are trusted.TimeArena).INDEXER_PRODUCTION=1and strongDATABASE_URL/CORS_ALLOWED_ORIGINSare set in real deployments.Findings
F-01 — UUPS upgrade + single-step ownership on core proxies
contracts/src/arena/TimeArena.sol(UUPSUpgradeable,OwnableUpgradeable,_authorizeUpgrade→onlyOwner);contracts/src/ReferralRegistry.sol(same pattern). Contrast:TimeArenaBuyRouterusesOwnable2Step.upgradeToAndCallmalicious impl → proxy delegates to attacker code on all user calls.257:257:contracts/src/arena/TimeArena.sol·DeployProduction.s.solsetsadminasOwnableowner at deploy.Ownable2StepUpgradeablefor transfers; emit scheduled upgrade events; runslither/ external audit before each upgrade.F-02 — Owner break-glass charm anchor during live arena
TimeArena.setEpochCharmAnchorWad,_setEpochCharmAnchorepochCharmAnchorWadand growth clock during an active arena, affecting DOUB price for subsequent buys outside the automated hard-reset TWAP path.setEpochCharmAnchorWadwith extreme value before their own buys / referrals.269:273:contracts/src/arena/TimeArena.solsetEpochCharmAnchorWadto pre-startArenaor document as emergency-only; monitor onchain events in ops dashboards.F-03 — Kumbaya buy-router swaps allow unbounded price limit (MEV / sandwich)
TimeArenaBuyRouter._swapExactOut—sqrtPriceLimitX96: 0onexactOutputSingleamountInMaximumandswapDeadline. Searchers can sandwich CL8Y/ETH/stable → DOUB legs.amountInMaximum; surplus DOUB dust goes todoubSurplusRecipient(immutable). User overpays on ingress asset; not a protocol vault drain.buyViaKumbaya→ attacker frontrun/backrun on Kumbaya pool → user’samountInMaximumconsumed.152:161:contracts/src/arena/TimeArenaBuyRouter.solsqrtPriceLimitX96from off-chain quote; keepamountInMaximumtight; optional private-RPC / batching guidance for whales.F-04 — Indexer permissive CORS when not in production mode
indexer/src/cors_config.rs—CorsLayer::permissive()whenINDEXER_PRODUCTIONunsetINDEXER_PRODUCTION=1andCORS_ALLOWED_ORIGINS.32:35:indexer/src/cors_config.rsINDEXER_PRODUCTION=1as mandatory for any internet-facing deploy; add startup warning when listening on0.0.0.0without production CORS; document inindexer/README.md.F-05 — Production frontend must never ship mock wallet (
VITE_E2E_MOCK_WALLET)frontend/src/wagmi-config.ts,scripts/e2e-anvil.sh,scripts/qa/build-frontend-for-rabby.shVITE_E2E_MOCK_WALLET=1enables a deterministic Anvil mock connector (account #0). If baked into a production static build, users could sign with a known dev key mindset — the connector does not add chain security.e2e-anvil.shbuild artifacts deployed to production CDN.37:37:frontend/src/wagmi-config.ts· Rabby build script omits mock flag (good).npm run buildifVITE_E2E_MOCK_WALLET=1andNODE_ENV=production; runtime console banner already partial — add build-time assert.F-06 — Unlimited CL8Y approval to TimeArena (documented tradeoff)
frontend/src/lib/arenaDoubApprovalPreference.ts—readArenaDoubUnlimitedApproval()alwaystrue;arenaDoubApprovalAmountWeireturnsmaxUint25622:25:frontend/src/lib/arenaDoubApprovalPreference.tsF-07 — No HTTP rate limiting on public indexer API
indexer/src/api.rs,api_arena.rs— allGETroutes/v1/arena/buys,/v1/arena/activity,/v1/arena/platform-usageallowlimitup to 200 with unauthenticated access. No per-IP throttling.46:48:indexer/src/api.rs(clamp_limit1–200)tower-governorin-process; cap lower limits for unauthenticated tier.F-08 —
/v1/statusexposes internal operational metricsindexer/src/api.rs—statushandlerrpc_metrics(call rates, method breakdown),chain_pointer,max_indexed_block,ingestion_alive.curl /v1/statuson public indexer.116:150:indexer/src/api.rs/healthz) from operator metrics; protect detailed status behind auth or internal network.F-09 — Weaker wallet address validation on arena wallet stats
indexer/src/api_arena.rs—arena_wallet_stats0x+ length 42 only; does not require 40 hex digits (unlikevalid_0x_address20inapi.rs).$1binds).GET /v1/arena/wallet/0xZZZZ.../stats835:841:indexer/src/api_arena.rsvs163:168:indexer/src/api.rsvalid_0x_address20for all wallet path params.F-10 —
DoubAirdrop.disperseTokenlacks fee-on-transfer paritycontracts/src/DoubAirdrop.soltotalviasafeTransferFrombut does not verify received amount matches sum when token charges transfer fees.values[i]or tx reverts mid-loop; caller-funded grief only.tokenparameter.40:44:contracts/src/DoubAirdrop.soltransferFrom; or document “standard ERC-20 only”.F-11 — Referral code registration race (documented)
ReferralRegistry.registerCodedocs/product/referrals.md).registerCode("popular")→ higher gas competitor tx.53:57:contracts/src/ReferralRegistry.solF-12 — MEV / ordering on Last Buy and podiums (accepted game mechanic)
TimeArena._finishBuy,_trackLastBuyer,docs/onchain/security-and-threat-model.mdbuyCooldownSecper wallet; hard-reset TWAP sampled at tx execution for price (#315); deterministic podium math.F-13 — Static analysis not in default CI gate
contracts/slither.config.json;.github/workflows/unit-tests.ymlslitherorcargo audit/npm audit.contracts/src; periodic dependency audit workflow.F-14 — Local dev secrets in repo scripts (accepted for Anvil)
scripts/start-local-anvil-stack.sh,AGENTS.md,scripts/lib/evm_dev_keys.shpostgres://yieldomega:password@defaults are embedded for local QA.INDEXER_PRODUCTIONrejects:password@inDATABASE_URL; docs warn never use Anvil keys on mainnet.Positive Controls Observed
TimeArenabuy/claim/WarBow/top-up;TimeArenaBuyRouter.buyViaKumbaya;DoubAirdrop_pullDoubExact·NonStandardERC20.t.solbuyFor338:344:contracts/src/arena/TimeArena.solPodiumVaultsarena-only payoutspayPodiumWinners,creditTranchemodifiers_updatemint/burn onlyArenaCharmPriceTwapcardinality + spot/TWAP deviation + USD bandwalletBuySessionGuard.tsused in arena buy + referral registerchainMismatchWriteGuard.tssqlx::query+.bind()throughoutpersist.rs/ APIsinternal_db_error_response→ generic"internal server error"ensure_production_database_url,ensure_production_address_registryWalletProfileModal.test.tsx,AddressInlinerevertMessage.tsTest Evidence
forge test --match-contract "TimeArena|ArenaPrize|TimeArenaBuy|Referral|PlayCred|Doubloon|NonStandard"cargo test --libinindexer/Conclusion
Under the stated trust boundaries, Arena v2’s unprivileged attack surface is narrow: contracts enforce parity and reentrancy; the indexer is read-only with parameterized SQL; the frontend guards multi-step wallet flows. Residual Medium items concentrate on governance key hygiene, production configuration, and user-level DeFi MEV on Kumbaya ingress — not on missing access checks in core buy/payout logic.
Recommended next steps (no code changes in this audit):
INDEXER_PRODUCTION=1checklist for any public indexer URL.VITE_E2E_MOCK_WALLETin production artifacts./v1/statusexposure.valid_0x_address20on all wallet path params;Ownable2StepUpgradeablefor future proxy versions.End of report —
audits/INTERNAL_COMPOSER_1781474786.mdRecommendation 1 rejected until $100k monthly volume.
Recommendations 2, 3, 4, 5 approved.
Tower governor approved. Cloudflare rejected (reliance on monopoly)
mentioned in issue #326
mentioned in issue #327
mentioned in issue #328
mentioned in issue #329
Child issues opened from security audit triage
Reviewed audit comment (2026-06-14) against parent #325 findings. Opened 4 issues for approved recommendations only.
Opened
INDEXER_PRODUCTIONVITE_E2E_MOCK_WALLET=1(CI + Vite gate)tower-governorrate limits + trim/gate public/v1/statusops metricsvalid_0x_address20on arena wallet paths +Ownable2StepUpgradeableforTimeArena/ReferralRegistrySkipped — rejected
Skipped — not approved (informational / accepted / out of scope)
setEpochCharmAnchorWadbreak-glasssqrtPriceLimitX96 = 0MEVDoubAirdropfee-on-transfer parityBundling rationale
No child issues existed before this pass.
agent:open_issueslabel removed.mentioned in merge request !499
mentioned in merge request !500
mentioned in merge request !501
mentioned in merge request !502
mentioned in issue #341