Two Frontend Compromises in 48 Hours: A Security Architecture for Onchain Apps
Back to Blog

Two Frontend Compromises in 48 Hours: A Security Architecture for Onchain Apps

Users lost funds on two prediction market platforms in the past 48 hours.

Fermah
Platform Reported date Est. loss Initial vector
Predict.fun Oct 28, 2026 ~$400K USDT Compromised third-party documentation site
Polymarket Oct 29, 2026 ~$3M (est.) Malicious third-party script in the frontend

Figures and dates are as reported and may be revised. In both incidents the smart contracts were not the point of failure. The compromise occurred in the web and delivery layer — the third-party code a modern app loads into the user's browser alongside the wallet session.

This is a structural property of onchain applications, and it is not specific to prediction markets. Any product that settles real value onchain exposes the same surface. Fermah builds infrastructure in this category, and this document sets out the controls we apply and the general practices we recommend to other teams. For a product that settles in USDC, funds can be lost in three locations — market contracts, the platform treasury, and the user's wallet — plus a fourth concern: detection after a control fails. Each is covered below.

1. Web layer: the primary loss vector

Smart contracts are rarely the point of failure. Losses typically originate elsewhere in the stack, after which the wallet signs a transaction it should not have.

A production web app loads code from multiple origins: analytics, monitoring, support widgets, hosted docs, vendor scripts. Any one of them, once compromised, executes in the same browser context as the user's wallet session. Embedded wallets reduce signing friction, which also reduces the cost to an attacker of obtaining a malicious signature. Both incidents above are instances of this single pattern.

The working assumption at Fermah is that any component can be compromised, and the system is designed for that case rather than against it.

Recommendation. Treat every third-party dependency in the frontend as untrusted, and design on the assumption that one is already compromised.

2. Market contracts

The strongest guarantee is enforced by code that cannot be changed. Each Fermah market is a minimal clone of a fixed, audited implementation, with no admin backdoor and no upgrade path. The code a user stakes into is the code that pays out; there is no later upgrade that can alter the terms.

Operator powers over a live market are limited to three actions: shorten the close time, pause trading, or void the market and return every stake at par. None of these can send user funds to any destination other than the user. Every transfer out of a market goes to a position holder or to a recipient fixed at market creation. No caller can specify a new destination at call time.

The contracts have completed an external security audit.

Recommendation. Make market contracts immutable, constrain operator powers so none can redirect funds, fix payout destinations at creation, and complete an external audit before funds are accepted.

3. Platform treasury

A platform treasury is a common single point of failure. A single key on a single server — exposed through a stolen device or a leaked environment variable — is sufficient to move all funds that pay for gas and seed new markets. This is the failure mode behind multiple incidents this year.

We do not publish our treasury configuration. The governing principle is that no single key, held in any single location, can move platform funds. A transfer requires multiple independent approvals from distinct parties on distinct devices, and the component that submits the transaction has no authority to originate or modify one. Draining the treasury requires simultaneous compromise of several specific parties.

Recommendation. Require multiple independent approvals to move treasury funds, and separate the authority to submit a transaction from the authority to approve it.

4. User wallets: the layer that cannot be fully guaranteed

Most reported losses occur here, and no platform can state a hard guarantee for it. A wallet signs what the browser presents. If frontend code is hostile, the wallet is the final control. Fermah applies three measures in front of it, each reproducible by any team:

No custody of keys. User wallet keys are unlocked by the user's own login and never reach our servers. A compromise of our backend does not expose user wallets.

Independent re-derivation of every signed action. Before signing, the app reconstructs the exact transaction from the market and amount and halts if it does not match what is displayed. The server then independently reconstructs the same transaction and verifies both signatures against the user's wallet. An authenticated session alone cannot move funds.

Bounded exposure. Per-signature, per-session, and per-day limits cap the maximum movable amount, and higher-risk actions require explicit user confirmation rather than a silent signature.

The trust boundary is the wallet prompt. Everything after the signature is controlled and re-verified server-side. Everything before it executes in a browser outside our control. No system is fully invulnerable; the objective is to bound the loss from any single failure and surface it quickly, so a fully compromised page can move only a limited amount before detection.

Recommendation. Hold no user keys, re-derive and re-verify every signature server-side, and enforce per-signature, per-session, and per-day transfer caps.

5. Detection after a control fails

Backend signing keys — for settlement, deployment, and gas — are held in a managed key management service. A production build does not start if supplied a raw private key via config file or environment variable.

Every platform wallet has a declared role. Wallets expected to remain idle are monitored onchain, and any transaction from such a wallet triggers an alert. A stolen key therefore produces an observable event on its first use.

Recommendation. Store signing keys in a managed KMS, assign every wallet a defined role, and alert on any activity from a wallet expected to be idle.

Summary: failure modes and controls

Layer Primary failure mode Control Residual risk
Market contracts Upgrade or fund redirection Immutable clones, no upgrade path, constrained operator, external audit Undiscovered pre-audit bug
Treasury Single compromised key Multi-party independent approval; submitter cannot author Simultaneous multi-party compromise
User wallet Hostile frontend obtains signature No key custody; server re-verifies every signature; transfer caps Bounded loss within caps before detection
Detection Stolen backend key Managed KMS; role-tagged wallets; idle-wallet alerts Detection latency

Design summary

The common factor in incidents that cost users money is a single trusted component whose compromise is sufficient to move funds. This architecture removes that property at each layer: the frontend is not trusted because the server re-derives every signature; the servers are not trusted because they hold no user keys; no individual is trusted because the treasury requires multiple approvals; the operator is not trusted because the contracts expose no redirection path.

These are reproducible defaults, not platform-specific mechanisms. Compromising any one layer is insufficient to move funds without also compromising another.

Responsible disclosure

Security researchers who identify a vulnerability in any Fermah product can report it to whitehat@fermah.xyz. We review every report and acknowledge receipt.