Maren
GitHub
Security

Risk

What can go wrong in Maren, ordered by expected harm, including the risks we accept.

This states the risks in Maren plainly, including the ones that could cost users money and the ones the team has chosen to accept. It follows one principle: a security document that only lists successes is not a security document.

Maren is pre-audit. It should not be used with funds a user cannot afford to lose, and the interface says so rather than burying it in a terms page. No mainnet deployment exists, and a third-party audit is the deliberate gate.

Three things to take from this page:

One custodial-failure surface

The pooled vault is the only component that can lose user funds through a Maren design failure, but it is not the only place value sits inside a Maren contract: claimable payment links escrow MUSD until they are claimed, and Earn holds deposits in Mezo's Stability Pool where principal can fall. Direct payments, handles and direct troves move value wallet to wallet and hold nothing.

Inherited MUSD risk

Maren inherits risk from MUSD it cannot mitigate, only disclose: upgradeable proxies and a validator-set oracle.

Controls from real failures

The controls are derived from a real audit of the team's previous product, not from theory.

Risk inventory

Ordered by expected harm, not by likelihood.

#RiskWho bears itSeverityStatus
R-1Pooled vault socialises a loss in a fast BTC drawdownPooled usersCriticalPartly mitigated; reconciliation not yet deployed; paid audit pending
R-2MUSD proxy upgrade changes behaviour underneath usAll usersCriticalDisclosed, not mitigable
R-3Oracle failure or manipulationAll usersHighPartially mitigated
R-4Keeper fails to act in timePooled usersHighNot mitigated — keeper not yet built
R-5Relayer key compromiseMaren treasuryMediumMitigated by design
R-6Contract bug in Pay / Registry / EarnIndividual usersMediumOwn tests only; no third-party audit
R-7Sponsorship abuse drains the gas budgetMaren treasuryLowMitigated
R-8Handle squatting or impersonationIndividual usersMediumFront-running and homoglyphs mitigated; squatting not
R-9Front-end compromiseAll usersHighPartial — simulation yes, CSP and SRI not yet
R-10A pooled-trove liquidation freezes all exits, permanentlyPooled usersCriticalDisclosed, no wind-down path built
R-11Earn can lose principal, and its withdrawable figure is optimisticEarn depositorsHighDisclosed, not mitigated
R-12Earn withdrawals blocked by a trove nobody in the vault ownsEarn depositorsMediumDisclosed, not yet decoded
R-13Admin keys: one EOA holds every privileged roleAll usersHighDisclosed; multisig is a mainnet gate

R-1: The pooled vault

This is the risk that matters. Everything else is smaller. MarenVault maintains one MUSD trove and a per-user ledger inside it, so a user's collateral is segregated in accounting, not in custody.

If the pooled trove were ever liquidated by the MUSD protocol, the loss would fall across pooled users in proportion to their share, including users whose own position was perfectly healthy. Maren does not describe this as "your funds are safe". They are subject to a mechanism, and the mechanism can fail.

Why build it anyway. MUSD's 1,800 MUSD minimum net debt is a minimum loan size that prices small holders out entirely and pushes the few who try to the liquidation edge. The pooled vault removes that minimum, safely, which is the only honest reason to take on this risk.

Mitigations.

  • A 30-point buffer above the cliff: protocol liquidates at 110%, Maren internally at 140%, blocks borrowing below 180%, and manages to a 250% target.
  • Users who do not need pooling do not get it: assessBorrowRoute() sends anyone with enough collateral to a direct trove, which carries no pooling risk.
  • Borrowing pauses before withdrawal does, a hard contract requirement with a dedicated test.
  • Caps that widen on evidence, not schedule.
  • An insurance buffer funded from the 5% internal liquidation penalty.
  • A 0.001 BTC minimum deposit, keeping positions large enough that liquidating one is rational.
PhaseCapGate
Mainnet launch5 BTC14 days, no keeper failure
Post-audit25 BTCCritical/High findings resolved
Post-traction100 BTC60 days clean liquidation history

If the team cannot satisfy itself the pooled vault is safe, Maren ships without it. Direct troves, gasless payments, and Earn are valuable on their own. This commitment is in the roadmap and it is binding.

R-2: Inherited MUSD risk

Findings from reading the deployed contracts, offered without editorial.

MUSD's core contracts are upgradeable proxies

PCV.owner(), PriceFeed.owner(), and MUSD.owner() all resolve to the same Gnosis Safe, 0x98D8899c3030741925BE630C710A98B57F397C7a. That key can upgrade implementations, swap the oracle, and modify mint/burn lists. council() and treasury() both return the zero address, so effective control sits with a Thesis/Mezo multisig. Mezo documents immutability as a later-stage goal, so this is a disclosed choice. Maren cannot mitigate it, only disclose it, and avoid compounding it. Maren's own contracts take the opposite trade-off: they are not upgradeable at all — no proxy, no initialize, no admin implementation slot — so no key can change their logic. The cost of that choice is that a deployed bug is fixed only by deploying a new contract and migrating users, which is why the deployed vault still predates its own redemption reconciliation.

MUSD's backing is not what a casual reader assumes

Total supply is roughly 30.55M (measured 2026-09-07; it moves, so re-read it rather than citing this). Trove-backed debt is roughly 1.60M, plus an outstanding PCV bootstrap loan of roughly 14.97M. The remainder sits in protocol-owned strategies and the Wormhole NTT bridge. "100% Bitcoin-backed" is true of the CDP portion but not of most circulating MUSD today. Maren does not repeat a blanket backing claim.

Two places MUSD's docs and bytecode disagree

The docs state a borrower with an outstanding loan pays a zero redemption fee; isAccountFeeExempt appears only in BorrowerOperations, never in TroveManager, which applies the redemption rate unconditionally. Maren does not promise this waiver. The docs list the refinance fee as 0.1%; the code computes roughly 0.02% of old net debt. Maren follows the bytecode.

R-3: Oracle

MUSD's price feed reads a native Mezo precompile (Skip Connect, fed by the validator set), not Chainlink and not Pyth.

  • fetchPrice() reverts above 60 seconds of staleness and has no lastGoodPrice() fallback. Both apps have an explicit price-unavailable state and will not let a user borrow against a stale number.
  • The feed is secured by Mezo's validator set, a correlated risk: a consensus failure is simultaneously a price failure.
  • Pyth is deployed on Mezo and dead on both networks. Measured 2026-09-07: BTC/USD's publishTime was 41.9 days old and returned $65,238 against a live oracle price of $79,466 — 18% wrong — getPriceNoOlderThan(BTC/USD, 3600) reverts StalePrice, and ETH/USD reverts outright. The gap widens with time, so treat any Pyth figure on Mezo as unusable rather than merely old. Maren never reads Pyth for liquidation math.

Maren's keeper treats an oracle revert as a halt condition, not an error to retry through: it stops opening new debt and continues to permit withdrawals.

R-4 to R-9: Controls

R-4 Keeper

Not mitigated, and meant to be read as a single point of failure. liquidateUser is onlyRole(KEEPER_ROLE) by design: an open liquidation market pays the liquidator to push users over the line, so Maren runs it at cost and the 5% penalty goes to the insurance buffer rather than to the caller. The consequence is that if the keeper does not act, nobody else can. The keeper process is not yet built, and on matsnet KEEPER_ROLE is held by a single operator key. Exits stay open regardless — withdraw, repay and closePosition carry no pause modifier — so a dead keeper cannot trap collateral outside the frozen state described in R-1. If the keeper halts, borrowing stops; withdrawals do not.

R-5 Relayer

A full compromise of the relayer key costs Maren its BTC gas float and nothing else. The relayer is never an owner, approver, or custodian. It relays only to allowlisted Maren contracts, only for allowlisted selectors, and derives the payer from the recovered signature, not any client field. At Mezo gas prices a 100,000-gas transaction costs about $0.0092.

R-6 Contracts

Full unit and fuzz coverage; invariant tests asserting the vault's per-user ledger cannot drift from the trove's real debt, with any rounding error required to fall in the protocol's favour; fork tests against the real MUSD contracts on matsnet. MarenVault and the forwarder are the audit scope.

R-7 Sponsorship abuse

Per-address and per-IP rate limits, a global daily BTC spend cap that halts relaying, and a 25-action free tier after which a fee is taken in-band in MUSD. Exhaustion returns a typed error, never a silent fallback that spends Maren's money.

R-8 Handles

Commit-reveal registration prevents front-running, [a-z0-9_] eliminates homoglyph attacks, and the payment interface shows the resolved address alongside the handle before any confirmation. Squatting itself is not mitigated. Registration is free and unpriced, so nothing stops one party committing to many names up front; pricing and renewals belong in a layer above MarenRegistry and are not built. The mitigation today is that a handle is a convenience label and the resolved address is always shown before any signature.

R-9 Front-end

No third-party analytics anywhere in the app, and transaction simulation with a human-readable summary before every signature.

Not yet implemented: a strict Content-Security-Policy and subresource integrity. next.config.ts sets no security headers today. Both are mainnet gates, not shipped controls.

Known defect, disclosed rather than held back. The borrow slider's upper bound subtracts only drawn principal from Mezo's per-trove maxBorrowingCapacity, where the protocol checks against total trove debt — which includes the 200 MUSD gas compensation and accrued interest — and it omits the 0.1% origination fee gross-up. Measured on matsnet on 2026-09-07, maxBorrowable() quoted 1,556.98 MUSD for a position whose true limit simulated at 834.68 MUSD. Pressing a button the app itself labelled "Max" can therefore revert and cost gas.

R-10 A pooled-trove liquidation freezes every exit

A liquidation of the pooled trove is not only a pro-rata loss. When the trove leaves Mezo's active set, every mutation on the vault reverts — deposit, borrow, repay, withdraw, close, and every admin function except skim() and the pause switches. Collateral the protocol did not take sits in Mezo's CollSurplusPool, and MarenVault never calls claimCollateral, so it cannot be reached. Idle BTC held in the contract that never entered the trove is frozen alongside it, even though paying it out needs no trove, no oracle and no protocol call. There is no admin recovery path: closePooledTrove syncs state first and so reverts in exactly that state.

Freezing is the deliberate choice — paying claims first-come-first-served after a shortfall is worse — but the honest description is everyone is locked out until a wind-down mechanism is designed, deployed and migrated to, and that is a governance decision that does not exist yet. Letting a debt-free account withdraw from idle collateral while frozen is the minimal fix and is not built.

R-11 Earn can lose principal

Earn deposits MUSD into Mezo's Stability Pool. Cantina's mUSD audit §3.4.7 establishes — and Thesis explicitly agreed, marking it Acknowledged rather than fixed — that Stability Pool liquidations can be unprofitable: absorbing a trove whose ratio has fallen below about 100% returns depositors less value than the debt burned against them. A passive depositor, which is exactly what Earn is, takes that loss. A liquidation large enough to consume the pool zeroes deposits outright through the epoch mechanism. This is not a bug in Maren; it is what the Stability Pool is.

Two Maren-side gaps compound it. maxWithdraw and maxRedeem are not overridden, so the figure the app shows can exceed what the pool will actually pay. And BTC liquidation gains are counted in totalAssets() at the oracle price while swapRouter is address(0), so there is currently no path converting that BTC back to MUSD. Fixing both is a prerequisite to Earn holding real money.

R-12 Earn withdrawals can be blocked by a stranger's trove

Mezo's Stability Pool refuses every non-zero withdrawal while any trove on the whole chain sits below 110% ICR — the string is in the deployed bytecode verbatim: StabilityPool: Cannot withdraw while there are troves with ICR < MCR. This is precisely the moment a depositor most wants out, it is caused by a stranger, and no Earn depositor can clear it. Maren does not yet decode this revert, so today it surfaces as a generic failure.

R-13 Admin keys

Maren's contracts are not upgradeable, so no key can change their logic. Three roles still carry real power. DEFAULT_ADMIN_ROLE can raise or zero either deposit cap, set an Earn performance fee up to 20% effective immediately, point Earn's harvest at an arbitrary swap router, pull Earn out of the Stability Pool, and grant itself every other role. KEEPER_ROLE liquidates users under 140% and manages the pooled trove. GUARDIAN_ROLE pauses deposits and borrowing — and only those: withdraw, repay, closePosition and liquidateUser carry no pause modifier, so no guardian key can trap collateral, pinned by test_exitsWorkWhilePaused. MarenPay, MarenRegistry and MarenForwarder have no privileged functions at all.

MarenRelayHub's RELAY_ADMIN_ROLE can allowlist any target contract, not only Maren's — the contract enforces "allowlisted", not "Maren". Combined with the absence of a fee-token allowlist on executeWithFee, that key is more powerful than the relayer key in R-5.

Today all of these are held by one externally owned account on matsnet. No multisig, no timelock, no separation of duties. Only testnet funds are exposed. Splitting the roles across a Safe with a timelock is a hard mainnet gate.

Rules inherited from a real audit

The controls above are not theoretical. The team's previous product, Talise, was audited on 24 July 2026: 7 Critical, 13 High, 20 Medium, 24 Low. The core custody model held and the IDOR sweep came back clean, but each finding is now a hard rule in this repository. The load-bearing ones:

  • Bind the funding source to the caller; a relayer that trusts a client-asserted source can be made to fund an attacker.
  • Every value-moving parameter lives inside the signed struct: recipient, amount, fee, deadline, nonce.
  • Never key escrow by a shared address.
  • Verify funding on-chain; never trust a client-supplied digest.
  • Gates fail closed. Maren's equivalent has a test that boots with empty configuration and asserts the service refuses to operate.
  • Put every privileged role behind a multisig and a timelock before the contract holds value that matters. Not yet done: on matsnet a single externally owned account holds DEFAULT_ADMIN_ROLE, KEEPER_ROLE and GUARDIAN_ROLE on the vault and DEFAULT_ADMIN_ROLE on the relay hub. Moving them to a Safe with a timelock is a mainnet gate.
  • A transaction hash is not success; only a receipt with status === 'success' marks a payment delivered.
  • Do not claim an attestation you have not implemented.

Disclosure: exorbilabs@gmail.com for vulnerability reports, never a public issue. Acknowledgement within 48 hours, assessment within 5 working days. security@maren.finance replaces it once the domain is live; it does not resolve today, so it is not a channel to use.

The contracts and the audit reports in docs/audit/ are not public yet — the repository is private during the grant review. They are published in full, with every fix and its regression test, when it opens. Incidents are disclosed with a timeline, including ones caught before user harm.