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:
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.
Maren inherits risk from MUSD it cannot mitigate, only disclose: upgradeable proxies and a validator-set oracle.
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.
| # | Risk | Who bears it | Severity | Status |
|---|---|---|---|---|
| R-1 | Pooled vault socialises a loss in a fast BTC drawdown | Pooled users | Critical | Partly mitigated; reconciliation not yet deployed; paid audit pending |
| R-2 | MUSD proxy upgrade changes behaviour underneath us | All users | Critical | Disclosed, not mitigable |
| R-3 | Oracle failure or manipulation | All users | High | Partially mitigated |
| R-4 | Keeper fails to act in time | Pooled users | High | Not mitigated — keeper not yet built |
| R-5 | Relayer key compromise | Maren treasury | Medium | Mitigated by design |
| R-6 | Contract bug in Pay / Registry / Earn | Individual users | Medium | Own tests only; no third-party audit |
| R-7 | Sponsorship abuse drains the gas budget | Maren treasury | Low | Mitigated |
| R-8 | Handle squatting or impersonation | Individual users | Medium | Front-running and homoglyphs mitigated; squatting not |
| R-9 | Front-end compromise | All users | High | Partial — simulation yes, CSP and SRI not yet |
| R-10 | A pooled-trove liquidation freezes all exits, permanently | Pooled users | Critical | Disclosed, no wind-down path built |
| R-11 | Earn can lose principal, and its withdrawable figure is optimistic | Earn depositors | High | Disclosed, not mitigated |
| R-12 | Earn withdrawals blocked by a trove nobody in the vault owns | Earn depositors | Medium | Disclosed, not yet decoded |
| R-13 | Admin keys: one EOA holds every privileged role | All users | High | Disclosed; 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.
| Phase | Cap | Gate |
|---|---|---|
| Mainnet launch | 5 BTC | 14 days, no keeper failure |
| Post-audit | 25 BTC | Critical/High findings resolved |
| Post-traction | 100 BTC | 60 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 nolastGoodPrice()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
publishTimewas 41.9 days old and returned $65,238 against a live oracle price of $79,466 — 18% wrong —getPriceNoOlderThan(BTC/USD, 3600)revertsStalePrice, 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_ROLEandGUARDIAN_ROLEon the vault andDEFAULT_ADMIN_ROLEon 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.
