Security review #325

Closed
opened 2026-06-14 21:58:50 +00:00 by PlasticDigits · 12 comments
PlasticDigits commented 2026-06-14 21:58:50 +00:00 (Migrated from gitlab.com)
No description provided.
PlasticDigits commented 2026-06-14 22:08:36 +00:00 (Migrated from gitlab.com)

Internal Full-Stack Security Audit — GitLab #325

Field Value
Audit file audits/INTERNAL_COMPOSER_1781474786.md
Date 2026-06-14
Reviewer Cursor Cloud agent (Composer)
Commit d2913027b4d67d05b45a924f04d172ce75d39843
Issue #325 — Security review
Scope Arena v2 full stack: contracts, indexer, frontend, bots, CI/ops, dependencies

Executive Summary

No Critical or High finding was identified that lets an unprivileged attacker steal DOUB, CRED, or CL8Y from protocol vaults, bypass TimeArena access control, or execute SQL/RCE against a correctly configured production deployment.

The codebase shows mature hardening from prior audits: nonReentrant on 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:

  1. Governance / upgrade authority — TimeArena and ReferralRegistry are UUPS proxies with single-step OwnableUpgradeable (no timelock / Ownable2Step in-contract).
  2. Misconfiguration — Indexer permissive CORS when INDEXER_PRODUCTION is unset; mock-wallet or dev-key artifacts in a production frontend build.
  3. User-facing DeFi hazards — Kumbaya ingress swaps use sqrtPriceLimitX96 = 0; MEV and sandwich risk on swaps is borne by buyers (mitigated by amountInMaximum + deadline).
  4. Availability — Public read API has no rate limiting; expensive list endpoints can be abused for DB/CPU load.

Governance onlyOwner powers (setEpochCharmAnchorWad, setPaused, grandfatherProgression, UUPS upgrades) are trusted by design but documented below as operational risks.

Areas Analyzed

Layer Components reviewed
Contracts TimeArena, PodiumVaults, TimeArenaBuyRouter, ReferralRegistry, PlayCred, Doubloon, DoubAirdrop, ArenaCharmPriceTwap, ArenaBuyRouting, deploy scripts
Indexer api.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)
Frontend Arena buy/session flows, referral registration, wallet guards, approvals, indexerApi.ts, revertMessage.ts, wagmi-config.ts, XSS tests
Bots / ops bots/timearena/, scripts/deploy-megaeth-contracts.sh, scripts/start-local-anvil-stack.sh, AGENTS.md dev-key policy
CI .github/workflows/unit-tests.yml, contracts/slither.config.json
Tests run forge test (Arena/Prize/Router/Referral/PlayCred/Doubloon/NonStandard — PASS); cargo test --lib in indexer/ (65 passed)

Assumptions & Trust Boundaries

  • Governance / deployer onlyOwner and MINTER_ROLE holders are trusted.
  • Canonical addresses are ERC-1967 proxies, not implementations.
  • Production DOUB/CL8Y/USDm/WETH behave as standard ERC-20 (fee-on-transfer DOUB is rejected by TimeArena).
  • Indexer is a read model; onchain state is authoritative for funds.
  • INDEXER_PRODUCTION=1 and strong DATABASE_URL / CORS_ALLOWED_ORIGINS are set in real deployments.

Findings

F-01 — UUPS upgrade + single-step ownership on core proxies

Severity Medium (operational / governance)
Location contracts/src/arena/TimeArena.sol (UUPSUpgradeable, OwnableUpgradeable, _authorizeUpgrade → onlyOwner); contracts/src/ReferralRegistry.sol (same pattern). Contrast: TimeArenaBuyRouter uses Ownable2Step.
Issue Compromised or mistyped owner key can immediately upgrade implementation or transfer ownership in one transaction. No in-contract timelock or two-step accept.
Impact Full logic replacement (drain routes, malicious mint hooks if combined with token admin), registry behavior change, or permanent bricking.
Attack path Attacker obtains owner private key → upgradeToAndCall malicious impl → proxy delegates to attacker code on all user calls.
Evidence 257:257:contracts/src/arena/TimeArena.sol · DeployProduction.s.sol sets admin as Ownable owner at deploy.
Recommendation Operate owner via multisig + timelock off-chain; consider Ownable2StepUpgradeable for transfers; emit scheduled upgrade events; run slither / external audit before each upgrade.

F-02 — Owner break-glass charm anchor during live arena

Severity Medium (governance / fairness)
Location TimeArena.setEpochCharmAnchorWad, _setEpochCharmAnchor
Issue Owner can reset epochCharmAnchorWad and growth clock during an active arena, affecting DOUB price for subsequent buys outside the automated hard-reset TWAP path.
Impact Participants may pay materially different DOUB/CHARM than TWAP/market expectation; reputational and economic harm; not a direct theft vector if owner is honest.
Attack path Malicious or coerced owner calls setEpochCharmAnchorWad with extreme value before their own buys / referrals.
Evidence 269:273:contracts/src/arena/TimeArena.sol
Recommendation Timelock owner actions; restrict setEpochCharmAnchorWad to pre-startArena or document as emergency-only; monitor onchain events in ops dashboards.

F-03 — Kumbaya buy-router swaps allow unbounded price limit (MEV / sandwich)

Severity Medium (user economic)
Location TimeArenaBuyRouter._swapExactOut — sqrtPriceLimitX96: 0 on exactOutputSingle
Issue Exact-output swaps with zero sqrt price limit offer no on-pool slippage protection beyond amountInMaximum and swapDeadline. Searchers can sandwich CL8Y/ETH/stable → DOUB legs.
Impact Buyer pays up to amountInMaximum; surplus DOUB dust goes to doubSurplusRecipient (immutable). User overpays on ingress asset; not a protocol vault drain.
Attack path User submits buyViaKumbaya → attacker frontrun/backrun on Kumbaya pool → user’s amountInMaximum consumed.
Evidence 152:161:contracts/src/arena/TimeArenaBuyRouter.sol
Recommendation Document MEV risk in product copy; consider conservative sqrtPriceLimitX96 from off-chain quote; keep amountInMaximum tight; optional private-RPC / batching guidance for whales.

F-04 — Indexer permissive CORS when not in production mode

Severity Medium (misconfiguration)
Location indexer/src/cors_config.rs — CorsLayer::permissive() when INDEXER_PRODUCTION unset
Issue Any browser origin can read the indexer API if an operator exposes the service publicly without INDEXER_PRODUCTION=1 and CORS_ALLOWED_ORIGINS.
Impact Enables third-party sites to embed live arena data and wallet-scoped public stats in phishing UIs; combined with address poisoning, increases social-engineering surface. Does not bypass onchain auth (API is read-only).
Attack path Operator deploys staging indexer on public URL with default env → attacker site calls API from victim browser (no cookies needed).
Evidence 32:35:indexer/src/cors_config.rs
Recommendation Treat INDEXER_PRODUCTION=1 as mandatory for any internet-facing deploy; add startup warning when listening on 0.0.0.0 without production CORS; document in indexer/README.md.

F-05 — Production frontend must never ship mock wallet (VITE_E2E_MOCK_WALLET)

Severity Medium (build / deploy)
Location frontend/src/wagmi-config.ts, scripts/e2e-anvil.sh, scripts/qa/build-frontend-for-rabby.sh
Issue VITE_E2E_MOCK_WALLET=1 enables 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.
Impact Catastrophic if combined with users believing they use a real wallet; on mainnet the mock is inert without matching keys, but UX could mislead QA/staging users to treat staging as real.
Attack path Mis-run e2e-anvil.sh build artifacts deployed to production CDN.
Evidence 37:37:frontend/src/wagmi-config.ts · Rabby build script omits mock flag (good).
Recommendation CI gate: fail npm run build if VITE_E2E_MOCK_WALLET=1 and NODE_ENV=production; runtime console banner already partial — add build-time assert.

F-06 — Unlimited CL8Y approval to TimeArena (documented tradeoff)

Severity Low
Location frontend/src/lib/arenaDoubApprovalPreference.ts — readArenaDoubUnlimitedApproval() always true; arenaDoubApprovalAmountWei returns maxUint256
Issue Wallets grant max allowance to the TimeArena proxy for CL8Y/DOUB spend paths (#143). If the proxy is upgraded maliciously or users approve a phishing address, entire CL8Y balance is at risk.
Impact Full CL8Y balance spendable by approved spender contract.
Attack path Phishing site tricks user to approve fake contract; or malicious UUPS upgrade (see F-01).
Evidence 22:25:frontend/src/lib/arenaDoubApprovalPreference.ts
Recommendation Keep address checksum UI; optional exact-amount path for security-conscious users; monitor proxy implementation upgrades.

F-07 — No HTTP rate limiting on public indexer API

Severity Low
Location indexer/src/api.rs, api_arena.rs — all GET routes
Issue Endpoints such as /v1/arena/buys, /v1/arena/activity, /v1/arena/platform-usage allow limit up to 200 with unauthenticated access. No per-IP throttling.
Impact DB CPU/memory pressure, RPC amplification via chain-timer poller indirectly; availability degradation (not data integrity).
Attack path Scripted pagination over large tables from many IPs.
Evidence 46:48:indexer/src/api.rs (clamp_limit 1–200)
Recommendation Reverse-proxy rate limits (Cloudflare/nginx); optional tower-governor in-process; cap lower limits for unauthenticated tier.

F-08 — /v1/status exposes internal operational metrics

Severity Low (information disclosure)
Location indexer/src/api.rs — status handler
Issue Public JSON includes rpc_metrics (call rates, method breakdown), chain_pointer, max_indexed_block, ingestion_alive.
Impact Aids reconnaissance for timing ingestion lag or RPC abuse; no secrets directly leaked.
Attack path curl /v1/status on public indexer.
Evidence 116:150:indexer/src/api.rs
Recommendation Split public health (/healthz) from operator metrics; protect detailed status behind auth or internal network.

F-09 — Weaker wallet address validation on arena wallet stats

Severity Low
Location indexer/src/api_arena.rs — arena_wallet_stats
Issue Validates 0x + length 42 only; does not require 40 hex digits (unlike valid_0x_address20 in api.rs).
Impact Non-hex “addresses” return empty stats (200 OK); minor inconsistency; no SQL injection ($1 binds).
Attack path GET /v1/arena/wallet/0xZZZZ.../stats
Evidence 835:841:indexer/src/api_arena.rs vs 163:168:indexer/src/api.rs
Recommendation Reuse valid_0x_address20 for all wallet path params.

F-10 — DoubAirdrop.disperseToken lacks fee-on-transfer parity

Severity Low
Location contracts/src/DoubAirdrop.sol
Issue Pulls total via safeTransferFrom but does not verify received amount matches sum when token charges transfer fees.
Impact Last recipients may receive less than values[i] or tx reverts mid-loop; caller-funded grief only.
Attack path Use non-standard ERC-20 as token parameter.
Evidence 40:44:contracts/src/DoubAirdrop.sol
Recommendation Balance-delta check after transferFrom; or document “standard ERC-20 only”.

F-11 — Referral code registration race (documented)

Severity Low (economic / fairness)
Location ReferralRegistry.registerCode
Issue First mined registration wins; public observers can race the same normalized slug (documented in natspec and docs/product/referrals.md).
Impact Victim burns CL8Y but loses code to faster tx; not protocol fund loss.
Attack path Mempool monitoring of registerCode("popular") → higher gas competitor tx.
Evidence 53:57:contracts/src/ReferralRegistry.sol
Recommendation UX warning before burn; optional commit-reveal (product change).

F-12 — MEV / ordering on Last Buy and podiums (accepted game mechanic)

Severity Informational
Location TimeArena._finishBuy, _trackLastBuyer, docs/onchain/security-and-threat-model.md
Issue Same-block transaction order determines last buyer and timer extensions; no onchain commit-reveal.
Impact Searchers with ordering advantage gain game edge; by design for competitive arena.
Mitigations present buyCooldownSec per wallet; hard-reset TWAP sampled at tx execution for price (#315); deterministic podium math.
Recommendation Maintain threat-model doc; UI labels “confirmed” vs recent blocks when showing winners.

F-13 — Static analysis not in default CI gate

Severity Informational
Location contracts/slither.config.json; .github/workflows/unit-tests.yml
Issue Slither config exists but no CI job runs slither or cargo audit / npm audit.
Impact Regressions in new contract code may slip until manual audit.
Recommendation Optional CI job on contracts/src; periodic dependency audit workflow.

F-14 — Local dev secrets in repo scripts (accepted for Anvil)

Severity Informational
Location scripts/start-local-anvil-stack.sh, AGENTS.md, scripts/lib/evm_dev_keys.sh
Issue Well-known Anvil keys and postgres://yieldomega:password@ defaults are embedded for local QA.
Impact None if confined to localhost; catastrophic if same keys/passwords used on public networks.
Mitigations INDEXER_PRODUCTION rejects :password@ in DATABASE_URL; docs warn never use Anvil keys on mainnet.
Recommendation Continue enforcing production env guards; no change required for local path.

Positive Controls Observed

Control Evidence
Reentrancy guards on value paths TimeArena buy/claim/WarBow/top-up; TimeArenaBuyRouter.buyViaKumbaya; DoubAirdrop
ERC-20 parity on DOUB ingress _pullDoubExact · NonStandardERC20.t.sol
Router-only buyFor 338:344:contracts/src/arena/TimeArena.sol
PodiumVaults arena-only payouts payPodiumWinners, creditTranche modifiers
PlayCred non-transferable _update mint/burn only
TWAP guards for production charm init ArenaCharmPriceTwap cardinality + spot/TWAP deviation + USD band
Wallet session drift guard walletBuySessionGuard.ts used in arena buy + referral register
Wrong-network write barrier chainMismatchWriteGuard.ts
Indexer SQL parameterization sqlx::query + .bind() throughout persist.rs / APIs
DB error redaction internal_db_error_response → generic "internal server error"
Production DB + registry fail-closed ensure_production_database_url, ensure_production_address_registry
XSS tests on wallet UI WalletProfileModal.test.tsx, AddressInline
RPC URL redaction in errors revertMessage.ts

Test Evidence

Command Result
forge test --match-contract "TimeArena|ArenaPrize|TimeArenaBuy|Referral|PlayCred|Doubloon|NonStandard" PASS (exit 0)
cargo test --lib in indexer/ 65 passed

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):

  1. Operate UUPS owner via multisig + timelock; schedule external audit before mainnet upgrade.
  2. Mandatory INDEXER_PRODUCTION=1 checklist for any public indexer URL.
  3. Build pipeline guard against VITE_E2E_MOCK_WALLET in production artifacts.
  4. Add edge rate limiting and tighten /v1/status exposure.
  5. Optional: valid_0x_address20 on all wallet path params; Ownable2StepUpgradeable for future proxy versions.

End of report — audits/INTERNAL_COMPOSER_1781474786.md

# Internal Full-Stack Security Audit — GitLab #325 | Field | Value | |-------|-------| | **Audit file** | `audits/INTERNAL_COMPOSER_1781474786.md` | | **Date** | 2026-06-14 | | **Reviewer** | Cursor Cloud agent (Composer) | | **Commit** | `d2913027b4d67d05b45a924f04d172ce75d39843` | | **Issue** | [#325 — Security review](https://gitlab.com/PlasticDigits/yieldomega/-/issues/325) | | **Scope** | Arena v2 full stack: contracts, indexer, frontend, bots, CI/ops, dependencies | ## Executive Summary No **Critical** or **High** finding was identified that lets an **unprivileged** attacker steal DOUB, CRED, or CL8Y from protocol vaults, bypass `TimeArena` access control, or execute SQL/RCE against a correctly configured production deployment. The codebase shows mature hardening from prior audits: `nonReentrant` on 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: 1. **Governance / upgrade authority** — `TimeArena` and `ReferralRegistry` are UUPS proxies with **single-step** `OwnableUpgradeable` (no timelock / `Ownable2Step` in-contract). 2. **Misconfiguration** — Indexer **permissive CORS** when `INDEXER_PRODUCTION` is unset; mock-wallet or dev-key artifacts in a production frontend build. 3. **User-facing DeFi hazards** — Kumbaya ingress swaps use **`sqrtPriceLimitX96 = 0`**; MEV and sandwich risk on swaps is borne by buyers (mitigated by `amountInMaximum` + deadline). 4. **Availability** — Public read API has **no rate limiting**; expensive list endpoints can be abused for DB/CPU load. Governance `onlyOwner` powers (`setEpochCharmAnchorWad`, `setPaused`, `grandfatherProgression`, UUPS upgrades) are **trusted by design** but documented below as operational risks. ## Areas Analyzed | Layer | Components reviewed | |-------|---------------------| | **Contracts** | `TimeArena`, `PodiumVaults`, `TimeArenaBuyRouter`, `ReferralRegistry`, `PlayCred`, `Doubloon`, `DoubAirdrop`, `ArenaCharmPriceTwap`, `ArenaBuyRouting`, deploy scripts | | **Indexer** | `api.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) | | **Frontend** | Arena buy/session flows, referral registration, wallet guards, approvals, `indexerApi.ts`, `revertMessage.ts`, `wagmi-config.ts`, XSS tests | | **Bots / ops** | `bots/timearena/`, `scripts/deploy-megaeth-contracts.sh`, `scripts/start-local-anvil-stack.sh`, `AGENTS.md` dev-key policy | | **CI** | `.github/workflows/unit-tests.yml`, `contracts/slither.config.json` | | **Tests run** | `forge test` (Arena/Prize/Router/Referral/PlayCred/Doubloon/NonStandard — **PASS**); `cargo test --lib` in `indexer/` (**65 passed**) | ## Assumptions & Trust Boundaries - Governance / deployer `onlyOwner` and `MINTER_ROLE` holders are trusted. - Canonical addresses are ERC-1967 **proxies**, not implementations. - Production DOUB/CL8Y/USDm/WETH behave as standard ERC-20 (fee-on-transfer DOUB is **rejected** by `TimeArena`). - Indexer is a **read model**; onchain state is authoritative for funds. - `INDEXER_PRODUCTION=1` and strong `DATABASE_URL` / `CORS_ALLOWED_ORIGINS` are set in real deployments. --- ## Findings ### F-01 — UUPS upgrade + single-step ownership on core proxies | | | |---|---| | **Severity** | **Medium** (operational / governance) | | **Location** | `contracts/src/arena/TimeArena.sol` (`UUPSUpgradeable`, `OwnableUpgradeable`, `_authorizeUpgrade` → `onlyOwner`); `contracts/src/ReferralRegistry.sol` (same pattern). Contrast: `TimeArenaBuyRouter` uses `Ownable2Step`. | | **Issue** | Compromised or mistyped owner key can **immediately** upgrade implementation or transfer ownership in one transaction. No in-contract timelock or two-step accept. | | **Impact** | Full logic replacement (drain routes, malicious mint hooks if combined with token admin), registry behavior change, or permanent bricking. | | **Attack path** | Attacker obtains owner private key → `upgradeToAndCall` malicious impl → proxy delegates to attacker code on all user calls. | | **Evidence** | ```257:257:contracts/src/arena/TimeArena.sol``` · `DeployProduction.s.sol` sets `admin` as `Ownable` owner at deploy. | | **Recommendation** | Operate owner via **multisig + timelock** off-chain; consider `Ownable2StepUpgradeable` for transfers; emit scheduled upgrade events; run `slither` / external audit before each upgrade. | --- ### F-02 — Owner break-glass charm anchor during live arena | | | |---|---| | **Severity** | **Medium** (governance / fairness) | | **Location** | `TimeArena.setEpochCharmAnchorWad`, `_setEpochCharmAnchor` | | **Issue** | Owner can reset `epochCharmAnchorWad` and growth clock **during an active arena**, affecting DOUB price for subsequent buys outside the automated hard-reset TWAP path. | | **Impact** | Participants may pay materially different DOUB/CHARM than TWAP/market expectation; reputational and economic harm; not a direct theft vector if owner is honest. | | **Attack path** | Malicious or coerced owner calls `setEpochCharmAnchorWad` with extreme value before their own buys / referrals. | | **Evidence** | ```269:273:contracts/src/arena/TimeArena.sol``` | | **Recommendation** | Timelock owner actions; restrict `setEpochCharmAnchorWad` to pre-`startArena` or document as emergency-only; monitor onchain events in ops dashboards. | --- ### F-03 — Kumbaya buy-router swaps allow unbounded price limit (MEV / sandwich) | | | |---|---| | **Severity** | **Medium** (user economic) | | **Location** | `TimeArenaBuyRouter._swapExactOut` — `sqrtPriceLimitX96: 0` on `exactOutputSingle` | | **Issue** | Exact-output swaps with **zero sqrt price limit** offer no on-pool slippage protection beyond `amountInMaximum` and `swapDeadline`. Searchers can sandwich CL8Y/ETH/stable → DOUB legs. | | **Impact** | Buyer pays up to `amountInMaximum`; surplus DOUB dust goes to `doubSurplusRecipient` (immutable). User overpays on ingress asset; not a protocol vault drain. | | **Attack path** | User submits `buyViaKumbaya` → attacker frontrun/backrun on Kumbaya pool → user’s `amountInMaximum` consumed. | | **Evidence** | ```152:161:contracts/src/arena/TimeArenaBuyRouter.sol``` | | **Recommendation** | Document MEV risk in product copy; consider conservative `sqrtPriceLimitX96` from off-chain quote; keep `amountInMaximum` tight; optional private-RPC / batching guidance for whales. | --- ### F-04 — Indexer permissive CORS when not in production mode | | | |---|---| | **Severity** | **Medium** (misconfiguration) | | **Location** | `indexer/src/cors_config.rs` — `CorsLayer::permissive()` when `INDEXER_PRODUCTION` unset | | **Issue** | Any browser origin can read the indexer API if an operator exposes the service publicly **without** `INDEXER_PRODUCTION=1` and `CORS_ALLOWED_ORIGINS`. | | **Impact** | Enables third-party sites to embed live arena data and wallet-scoped **public** stats in phishing UIs; combined with address poisoning, increases social-engineering surface. Does not bypass onchain auth (API is read-only). | | **Attack path** | Operator deploys staging indexer on public URL with default env → attacker site calls API from victim browser (no cookies needed). | | **Evidence** | ```32:35:indexer/src/cors_config.rs``` | | **Recommendation** | Treat `INDEXER_PRODUCTION=1` as mandatory for any internet-facing deploy; add startup warning when listening on `0.0.0.0` without production CORS; document in `indexer/README.md`. | --- ### F-05 — Production frontend must never ship mock wallet (`VITE_E2E_MOCK_WALLET`) | | | |---|---| | **Severity** | **Medium** (build / deploy) | | **Location** | `frontend/src/wagmi-config.ts`, `scripts/e2e-anvil.sh`, `scripts/qa/build-frontend-for-rabby.sh` | | **Issue** | `VITE_E2E_MOCK_WALLET=1` enables 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. | | **Impact** | Catastrophic if combined with users believing they use a real wallet; on mainnet the mock is inert without matching keys, but UX could mislead QA/staging users to treat staging as real. | | **Attack path** | Mis-run `e2e-anvil.sh` build artifacts deployed to production CDN. | | **Evidence** | ```37:37:frontend/src/wagmi-config.ts``` · Rabby build script omits mock flag (good). | | **Recommendation** | CI gate: fail `npm run build` if `VITE_E2E_MOCK_WALLET=1` and `NODE_ENV=production`; runtime console banner already partial — add build-time assert. | --- ### F-06 — Unlimited CL8Y approval to TimeArena (documented tradeoff) | | | |---|---| | **Severity** | **Low** | | **Location** | `frontend/src/lib/arenaDoubApprovalPreference.ts` — `readArenaDoubUnlimitedApproval()` always `true`; `arenaDoubApprovalAmountWei` returns `maxUint256` | | **Issue** | Wallets grant **max allowance** to the TimeArena proxy for CL8Y/DOUB spend paths (#143). If the proxy is upgraded maliciously or users approve a **phishing address**, entire CL8Y balance is at risk. | | **Impact** | Full CL8Y balance spendable by approved spender contract. | | **Attack path** | Phishing site tricks user to approve fake contract; or malicious UUPS upgrade (see F-01). | | **Evidence** | ```22:25:frontend/src/lib/arenaDoubApprovalPreference.ts``` | | **Recommendation** | Keep address checksum UI; optional exact-amount path for security-conscious users; monitor proxy implementation upgrades. | --- ### F-07 — No HTTP rate limiting on public indexer API | | | |---|---| | **Severity** | **Low** | | **Location** | `indexer/src/api.rs`, `api_arena.rs` — all `GET` routes | | **Issue** | Endpoints such as `/v1/arena/buys`, `/v1/arena/activity`, `/v1/arena/platform-usage` allow `limit` up to **200** with unauthenticated access. No per-IP throttling. | | **Impact** | DB CPU/memory pressure, RPC amplification via chain-timer poller indirectly; availability degradation (not data integrity). | | **Attack path** | Scripted pagination over large tables from many IPs. | | **Evidence** | ```46:48:indexer/src/api.rs``` (`clamp_limit` 1–200) | | **Recommendation** | Reverse-proxy rate limits (Cloudflare/nginx); optional `tower-governor` in-process; cap lower limits for unauthenticated tier. | --- ### F-08 — `/v1/status` exposes internal operational metrics | | | |---|---| | **Severity** | **Low** (information disclosure) | | **Location** | `indexer/src/api.rs` — `status` handler | | **Issue** | Public JSON includes `rpc_metrics` (call rates, method breakdown), `chain_pointer`, `max_indexed_block`, `ingestion_alive`. | | **Impact** | Aids reconnaissance for timing ingestion lag or RPC abuse; no secrets directly leaked. | | **Attack path** | `curl /v1/status` on public indexer. | | **Evidence** | ```116:150:indexer/src/api.rs``` | | **Recommendation** | Split public health (`/healthz`) from operator metrics; protect detailed status behind auth or internal network. | --- ### F-09 — Weaker wallet address validation on arena wallet stats | | | |---|---| | **Severity** | **Low** | | **Location** | `indexer/src/api_arena.rs` — `arena_wallet_stats` | | **Issue** | Validates `0x` + length 42 only; does **not** require 40 hex digits (unlike `valid_0x_address20` in `api.rs`). | | **Impact** | Non-hex “addresses” return empty stats (200 OK); minor inconsistency; no SQL injection (`$1` binds). | | **Attack path** | `GET /v1/arena/wallet/0xZZZZ.../stats` | | **Evidence** | ```835:841:indexer/src/api_arena.rs``` vs ```163:168:indexer/src/api.rs``` | | **Recommendation** | Reuse `valid_0x_address20` for all wallet path params. | --- ### F-10 — `DoubAirdrop.disperseToken` lacks fee-on-transfer parity | | | |---|---| | **Severity** | **Low** | | **Location** | `contracts/src/DoubAirdrop.sol` | | **Issue** | Pulls `total` via `safeTransferFrom` but does not verify received amount matches sum when token charges transfer fees. | | **Impact** | Last recipients may receive less than `values[i]` or tx reverts mid-loop; caller-funded grief only. | | **Attack path** | Use non-standard ERC-20 as `token` parameter. | | **Evidence** | ```40:44:contracts/src/DoubAirdrop.sol``` | | **Recommendation** | Balance-delta check after `transferFrom`; or document “standard ERC-20 only”. | --- ### F-11 — Referral code registration race (documented) | | | |---|---| | **Severity** | **Low** (economic / fairness) | | **Location** | `ReferralRegistry.registerCode` | | **Issue** | First **mined** registration wins; public observers can race the same normalized slug (documented in natspec and `docs/product/referrals.md`). | | **Impact** | Victim burns CL8Y but loses code to faster tx; not protocol fund loss. | | **Attack path** | Mempool monitoring of `registerCode("popular")` → higher gas competitor tx. | | **Evidence** | ```53:57:contracts/src/ReferralRegistry.sol``` | | **Recommendation** | UX warning before burn; optional commit-reveal (product change). | --- ### F-12 — MEV / ordering on Last Buy and podiums (accepted game mechanic) | | | |---|---| | **Severity** | **Informational** | | **Location** | `TimeArena._finishBuy`, `_trackLastBuyer`, `docs/onchain/security-and-threat-model.md` | | **Issue** | Same-block transaction order determines last buyer and timer extensions; no onchain commit-reveal. | | **Impact** | Searchers with ordering advantage gain game edge; by design for competitive arena. | | **Mitigations present** | `buyCooldownSec` per wallet; hard-reset TWAP sampled at **tx execution** for price (#315); deterministic podium math. | | **Recommendation** | Maintain threat-model doc; UI labels “confirmed” vs recent blocks when showing winners. | --- ### F-13 — Static analysis not in default CI gate | | | |---|---| | **Severity** | **Informational** | | **Location** | `contracts/slither.config.json`; `.github/workflows/unit-tests.yml` | | **Issue** | Slither config exists but **no** CI job runs `slither` or `cargo audit` / `npm audit`. | | **Impact** | Regressions in new contract code may slip until manual audit. | | **Recommendation** | Optional CI job on `contracts/src`; periodic dependency audit workflow. | --- ### F-14 — Local dev secrets in repo scripts (accepted for Anvil) | | | |---|---| | **Severity** | **Informational** | | **Location** | `scripts/start-local-anvil-stack.sh`, `AGENTS.md`, `scripts/lib/evm_dev_keys.sh` | | **Issue** | Well-known Anvil keys and `postgres://yieldomega:password@` defaults are embedded for local QA. | | **Impact** | None if confined to localhost; catastrophic if same keys/passwords used on public networks. | | **Mitigations** | `INDEXER_PRODUCTION` rejects `:password@` in `DATABASE_URL`; docs warn never use Anvil keys on mainnet. | | **Recommendation** | Continue enforcing production env guards; no change required for local path. | --- ## Positive Controls Observed | Control | Evidence | |---------|----------| | Reentrancy guards on value paths | `TimeArena` buy/claim/WarBow/top-up; `TimeArenaBuyRouter.buyViaKumbaya`; `DoubAirdrop` | | ERC-20 parity on DOUB ingress | `_pullDoubExact` · `NonStandardERC20.t.sol` | | Router-only `buyFor` | ```338:344:contracts/src/arena/TimeArena.sol``` | | `PodiumVaults` arena-only payouts | `payPodiumWinners`, `creditTranche` modifiers | | PlayCred non-transferable | `_update` mint/burn only | | TWAP guards for production charm init | `ArenaCharmPriceTwap` cardinality + spot/TWAP deviation + USD band | | Wallet session drift guard | `walletBuySessionGuard.ts` used in arena buy + referral register | | Wrong-network write barrier | `chainMismatchWriteGuard.ts` | | Indexer SQL parameterization | `sqlx::query` + `.bind()` throughout `persist.rs` / APIs | | DB error redaction | `internal_db_error_response` → generic `"internal server error"` | | Production DB + registry fail-closed | `ensure_production_database_url`, `ensure_production_address_registry` | | XSS tests on wallet UI | `WalletProfileModal.test.tsx`, `AddressInline` | | RPC URL redaction in errors | `revertMessage.ts` | ## Test Evidence | Command | Result | |---------|--------| | `forge test --match-contract "TimeArena\|ArenaPrize\|TimeArenaBuy\|Referral\|PlayCred\|Doubloon\|NonStandard"` | **PASS** (exit 0) | | `cargo test --lib` in `indexer/` | **65 passed** | ## 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):** 1. Operate UUPS owner via multisig + timelock; schedule external audit before mainnet upgrade. 2. Mandatory `INDEXER_PRODUCTION=1` checklist for any public indexer URL. 3. Build pipeline guard against `VITE_E2E_MOCK_WALLET` in production artifacts. 4. Add edge rate limiting and tighten `/v1/status` exposure. 5. Optional: `valid_0x_address20` on all wallet path params; `Ownable2StepUpgradeable` for future proxy versions. --- *End of report — `audits/INTERNAL_COMPOSER_1781474786.md`*
PlasticDigits commented 2026-06-14 22:29:06 +00:00 (Migrated from gitlab.com)

Recommendation 1 rejected until $100k monthly volume.
Recommendations 2, 3, 4, 5 approved.
Tower governor approved. Cloudflare rejected (reliance on monopoly)

Recommendation 1 rejected until $100k monthly volume. Recommendations 2, 3, 4, 5 approved. Tower governor approved. Cloudflare rejected (reliance on monopoly)
PlasticDigits commented 2026-06-14 22:31:58 +00:00 (Migrated from gitlab.com)

mentioned in issue #326

mentioned in issue #326
PlasticDigits commented 2026-06-14 22:32:00 +00:00 (Migrated from gitlab.com)

mentioned in issue #327

mentioned in issue #327
PlasticDigits commented 2026-06-14 22:32:00 +00:00 (Migrated from gitlab.com)

mentioned in issue #328

mentioned in issue #328
PlasticDigits commented 2026-06-14 22:32:01 +00:00 (Migrated from gitlab.com)

mentioned in issue #329

mentioned in issue #329
PlasticDigits commented 2026-06-14 22:32:09 +00:00 (Migrated from gitlab.com)

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

Issue Bundle Audit refs
#326 Internet-facing indexer production checklist + startup guard when binding non-loopback without INDEXER_PRODUCTION F-04, rec #2
#327 Production build fail-closed when VITE_E2E_MOCK_WALLET=1 (CI + Vite gate) F-05, rec #3
#328 In-process tower-governor rate limits + trim/gate public /v1/status ops metrics F-07, F-08, rec #4
#329 Shared valid_0x_address20 on arena wallet paths + Ownable2StepUpgradeable for TimeArena / ReferralRegistry F-09, F-01 (partial), rec #5

Skipped — rejected

Item Reason
Rec #1 — UUPS owner via multisig + timelock + pre-mainnet external audit Rejected until $100k monthly volume (parent comment)
Cloudflare / nginx edge rate limiting Rejected — monopoly reliance; tower-governor approved instead (#328)

Skipped — not approved (informational / accepted / out of scope)

Finding Severity Notes
F-02 Owner setEpochCharmAnchorWad break-glass Medium Not in approved rec list — governance/trusted owner
F-03 Kumbaya sqrtPriceLimitX96 = 0 MEV Medium Document-only in audit; no approved implementation rec
F-06 Unlimited CL8Y approval Low Documented tradeoff (#143)
F-10 DoubAirdrop fee-on-transfer parity Low Caller-funded grief only
F-11 Referral code registration race Low Documented product behavior
F-12 Last Buy MEV / ordering Informational Accepted game mechanic
F-13 Slither not in default CI Informational Optional CI noted in audit
F-14 Local dev secrets in scripts Informational Accepted for Anvil; production guards exist

Bundling rationale

  • #326 — single deploy-safety workstream (docs + bind/CORS guard).
  • #327 — frontend build/CI only.
  • #328 — indexer availability + status disclosure (one router/middleware pass).
  • #329 — small hardening pair (indexer validation + contract ownership pattern) from the same approved rec #5.

No child issues existed before this pass. agent:open_issues label removed.

## 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 | Issue | Bundle | Audit refs | |-------|--------|------------| | [#326](https://gitlab.com/PlasticDigits/yieldomega/-/issues/326) | Internet-facing indexer production checklist + startup guard when binding non-loopback without `INDEXER_PRODUCTION` | F-04, rec **#2** | | [#327](https://gitlab.com/PlasticDigits/yieldomega/-/issues/327) | Production build fail-closed when `VITE_E2E_MOCK_WALLET=1` (CI + Vite gate) | F-05, rec **#3** | | [#328](https://gitlab.com/PlasticDigits/yieldomega/-/issues/328) | In-process **`tower-governor`** rate limits + trim/gate public `/v1/status` ops metrics | F-07, F-08, rec **#4** | | [#329](https://gitlab.com/PlasticDigits/yieldomega/-/issues/329) | Shared `valid_0x_address20` on arena wallet paths + `Ownable2StepUpgradeable` for `TimeArena` / `ReferralRegistry` | F-09, F-01 (partial), rec **#5** | ### Skipped — rejected | Item | Reason | |------|--------| | Rec **#1** — UUPS owner via multisig + timelock + pre-mainnet external audit | **Rejected** until $100k monthly volume (parent comment) | | Cloudflare / nginx edge rate limiting | **Rejected** — monopoly reliance; **tower-governor** approved instead ([#328](https://gitlab.com/PlasticDigits/yieldomega/-/issues/328)) | ### Skipped — not approved (informational / accepted / out of scope) | Finding | Severity | Notes | |---------|----------|-------| | F-02 Owner `setEpochCharmAnchorWad` break-glass | Medium | Not in approved rec list — governance/trusted owner | | F-03 Kumbaya `sqrtPriceLimitX96 = 0` MEV | Medium | Document-only in audit; no approved implementation rec | | F-06 Unlimited CL8Y approval | Low | Documented tradeoff (#143) | | F-10 `DoubAirdrop` fee-on-transfer parity | Low | Caller-funded grief only | | F-11 Referral code registration race | Low | Documented product behavior | | F-12 Last Buy MEV / ordering | Informational | Accepted game mechanic | | F-13 Slither not in default CI | Informational | Optional CI noted in audit | | F-14 Local dev secrets in scripts | Informational | Accepted for Anvil; production guards exist | ### Bundling rationale - **#326** — single deploy-safety workstream (docs + bind/CORS guard). - **#327** — frontend build/CI only. - **#328** — indexer availability + status disclosure (one router/middleware pass). - **#329** — small hardening pair (indexer validation + contract ownership pattern) from the same approved rec #5. No child issues existed before this pass. `agent:open_issues` label removed.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-06-14 22:47:57 +00:00
PlasticDigits commented 2026-06-14 22:50:40 +00:00 (Migrated from gitlab.com)

mentioned in merge request !499

mentioned in merge request !499
PlasticDigits commented 2026-06-14 22:56:55 +00:00 (Migrated from gitlab.com)

mentioned in merge request !500

mentioned in merge request !500
PlasticDigits commented 2026-06-14 23:01:28 +00:00 (Migrated from gitlab.com)

mentioned in merge request !501

mentioned in merge request !501
PlasticDigits commented 2026-06-14 23:04:18 +00:00 (Migrated from gitlab.com)

mentioned in merge request !502

mentioned in merge request !502
PlasticDigits commented 2026-06-21 03:43:53 +00:00 (Migrated from gitlab.com)

mentioned in issue #341

mentioned in issue #341
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
code/yieldomega#325
No description provided.