MarenDocs
Security

Risk

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

On this page
  1. Risk inventory
  2. R-1: The pooled vault
  3. R-2: Inherited MUSD risk
  4. R-3: Oracle
  5. R-4 to R-14: Controls
  6. Incident log
  7. 22 September 2026 — the pooled trove hit its borrowing ceiling and all borrowing stopped
  8. Rules inherited from a real audit

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.

Warning

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 actions run by hand; automation not 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-14The pooled trove exhausts its fixed maxBorrowingCapacity and borrowing stops for every user at oncePooled borrowersHighOccurred 22 Sep 2026, cleared manually; detection and automation not built

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.

Warning

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
Note

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-14: 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 still not built as automation, and on matsnet KEEPER_ROLE is held by a single operator key. Keeper actions have now been run by hand in production: the trove refinance of 22 September 2026 in the incident log below was executed by an operator with tools/ops/refinance-trove.mjs. A human with a keystore is not a keeper; nothing watches the trove between those runs. 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.

The borrow-ceiling defect recorded here is fixed in the shipped clients. borrowCeiling() in packages/sdk/src/borrow.ts now subtracts the whole trove's debt — gas compensation and accrued interest included — from Mezo's maxBorrowingCapacity, grosses the result up for the 0.1% origination fee, and additionally clamps to the pooled trove's headroom against the 180% floor. Both apps bound the slider by that figure, so "Max" no longer quotes an amount the protocol will refuse. The on-chain maxBorrowable() getter is unchanged and remains pool-blind by design — see Borrow for why a reviewer calling it directly still sees a higher number.

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.

R-14 The pooled trove runs out of borrowing ceiling

On Mezo a trove's maxBorrowingCapacity is frozen at the BTC price the trove was opened at. Adding collateral does not raise it; refinance() is the only thing that does. A pooled trove held across a BTC rally therefore walks into a ceiling that has nothing to do with how well collateralised it is, and when the trove's debt reaches it every borrow reverts for every pooled user at once, however healthy their own position.

This is not hypothetical: it happened on 22 September 2026, and the incident log below gives the timeline. Nothing detects or clears it automatically today. MarenVault.refinancePooledTrove is deployed and works, but calling it is a manual keeper step, so the failure recurs the next time BTC moves far enough. Deposits, repayments and withdrawals are unaffected when it happens, which is the same direction as R-4: borrowing pauses, exits do not.

Incident log

Incidents are recorded here with a timeline, including the ones caught before any user was harmed.

22 September 2026 — the pooled trove hit its borrowing ceiling and all borrowing stopped

Impact. Every borrow through the pooled vault was refused, for every user, for as long as the condition lasted. No funds were lost, no position was liquidated, and deposits, repayments and withdrawals kept working throughout. Only testnet funds were exposed.

Cause. The pooled trove's maxBorrowingCapacity is fixed at the price the trove opened at, and its debt had caught up with it. Read from the deployed TroveManager at block 15,695,380 — the block before the fix — the ceiling was 2,749.20 MUSD against a trove debt of 2,749.57 MUSD (2,747.27 principal plus 2.30 accrued interest): about 0.37 MUSD over. Mezo checks maxBorrowingCapacity >= netDebtChange + getTroveDebt(trove), so no borrow of any size could pass. The trove itself was healthy; this was a ceiling, not a collateral shortfall. From inside the app it looked like a broken relayer, because the fail-closed relayer correctly declined to sponsor a call it had simulated as reverting.

Fix. An operator holding KEEPER_ROLE called MarenVault.refinancePooledTrove by hand using tools/ops/refinance-trove.mjs. Confirmed in block 15,695,381, 07:36:12 UTC on 22 September 2026, transaction 0x163e5f3d…86c7. The ceiling moved from 2,749.20 MUSD to 5,348.05 MUSD and borrowing resumed. Every figure here was re-read from matsnet at those two blocks on 2026-09-23.

Still open. Nothing watches for this condition and nothing clears it automatically. refinancePooledTrove had been deployed since day one and had never been called outside unit tests before this. It is tracked as R-14 above, and it stays open until a keeper process exists.

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.
Info

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.