Background
Obsidian Rollup settles to Ethereum. Its Portal holds deposited assets and finalises withdrawals once a dispute game has resolved. The portal is an upgradeable proxy; upgrades are scheduled by a 2-of-3 upgrade multisig through an UpgradeTimelock, and a 4-of-7 security council holds the canceller role. A guardian can pause withdrawal finalisation.
The research assessment had already flagged the upgrade path (OR-02) and moved routine upgrades behind a timelock. What it could not see was where the multisig’s keys were kept.
Architecture
Portal, OutputOracle and DisputeGame are proxies administered by a proxy admin owned by the UpgradeTimelock. Whoever can schedule a timelock operation can schedule an upgrade of the contract that releases funds; the delay and the canceller are the only checks after that.
The case covers the governance path around the portal, not the proof system.
Figure 1Obsidian Rollup: components and paths involved in this case
Elements:
- CI-connected signing host (external party): held 2 of 3 owner keys
- Unauthorised key user (adversary): fresh deployer address
- Upgrade multisig (control, involved in this case): 2-of-3
- UpgradeTimelock (control, involved in this case): 48 h delay at the time
- Security council (external party): 4-of-7 canceller
- Portal (protocol component, involved in this case): Deposits, withdrawal proving and finalisation
- OutputOracle (protocol component): Stores proposed L2 state roots
- DisputeGame (protocol component): Bisection fault proofs over disputed outputs
Connections:
- CI-connected signing host to Upgrade multisig: signs (data or call flow)
- Unauthorised key user to Upgrade multisig: execTransaction (2 sigs) (attack path)
- Upgrade multisig to UpgradeTimelock: schedule() (attack path)
- Security council to UpgradeTimelock: cancel(id) (control path)
- UpgradeTimelock to Portal: proxy upgrade (control path)
- Portal to OutputOracle: output roots (data or call flow)
- Portal to DisputeGame: game result (data or call flow)
Components in the Obsidian Rollup profile
BatchInbox
Receives compressed batches from the sequencer
OutputOracle
Stores proposed L2 state roots
DisputeGame
Bisection fault proofs over disputed outputs
Portal(involved in this case)
Deposits, withdrawal proving and finalisation
Sequencer (Go)
Orders transactions and builds batches
Security challenge
At 02:14 UTC on 14 September 2026 the rule for upgrades to unapproved implementations raised a critical alert: a timelock operation had been scheduled to point the Portal proxy at a contract deployed eighteen minutes earlier by a single-use address, with bytecode that matched no release.
The operation would become executable 48 hours later. The response had to confirm the operation was unauthorised, protect withdrawals in the meantime, cancel it, and find out how two multisig signatures had been produced outside the change window.
Attack surface
The response mapped every key and process that could cause an upgrade.
| Surface | Exposure | Control |
|---|---|---|
| Upgrade multisig owners | Two of three keys on the same CI-connected signing host | 3-of-5 with hardware-backed keys, one per keyholder |
| Signing host | Reachable from build jobs | No upgrade keys on CI-reachable systems |
| Implementation code | Any address could be scheduled as the new implementation | Approved-release hash check in monitoring (R-01) |
| Timelock delay | 48 hours, shorter than the 7-day withdrawal window | 8 days for withdrawal-path contracts |
| Canceller and guardian | Security council 4-of-7; guardian pause | Unchanged; both used within hours |
Technical investigation
The scheduled implementation was decompiled within the first hour. Its withdrawal finalisation skipped the dispute-game check and paid any withdrawal submitted by one hard-coded address. Executed, it would have let that address withdraw the portal’s balance.
The multisig transaction carried valid signatures from owners one and three. Both keys sat on the same signing host, which build jobs could reach, so the 2-of-3 threshold had in practice become one compromised machine. The second owner, held separately, had not signed.
The guardian paused withdrawal finalisation 48 minutes after the alert as a precaution. The security council cancelled the operation 3 hours 26 minutes after it was scheduled, with more than 44 hours of the delay still to run, and both exposed owners were replaced that morning.
Finalisation resumed the next day once the state roots for the paused period had been re-verified. No other operation had been scheduled with the exposed keys.
Figure 2Malicious upgrade scheduled, detected and cancelled
- Unauthorised key user (adversary) to Portal proxy: CREATE implementation (fresh deployer). bytecode matches no release (attack step)
- Unauthorised key user (adversary) to UpgradeTimelock: schedule(upgrade(Portal, newImpl), 172800 s). 2-of-3 multisig (attack step)
- UpgradeTimelock (on-chain) to Monitoring (R-01): CallScheduled (normal step)
- Monitoring (R-01) (off-chain) to Guardian and council: critical: unapproved implementation (defensive step)
- Guardian and council (participant) to Portal proxy: guardian: pause finalisation (defensive step)
- Guardian and council (participant) to UpgradeTimelock: council: cancel(id). upgrade never executes (defensive step)
| Step | Time (UTC) | Since scheduling | Delay remaining |
|---|---|---|---|
| Malicious implementation deployed | 2026-09-14 01:56 | −18 min | — |
| Upgrade multisig signs outside the change window | 2026-09-14 02:13 | −18 s | — |
| Upgrade scheduled; R-01 alert | 2026-09-14 02:14 | 0 s | 48 h |
| Guardian pauses withdrawal finalisation | 2026-09-14 03:02 | 49 min | 47 h 11 min |
| Security council cancels the operation | 2026-09-14 05:40 | 3 h 26 min | 44 h 34 min |
| Operation would have become executable | 2026-09-16 02:14 | 48 h | 0 s |
Monitoring record
Open in Security Monitoring| Time (UTC) | Rule | Observation | Measured | Alert |
|---|---|---|---|---|
| 2026-09-14 01:56:30 | R-16Activity above baseline | Contract deployed by a fresh, single-use deployerMedium | 4 (threshold 3 × 30-day median) | Alert |
| 2026-09-14 02:13:51 | R-02Privileged role change | Upgrade multisig executed outside the change windowHigh | Condition matched | Alert |
| 2026-09-14 02:14:09 | R-01Upgrade to unapproved implementation | Portal upgrade scheduled to an unapproved implementationCritical | Condition matched | Alert |
| 2026-09-14 03:02:44 | R-15Pause state changed | Withdrawal finalisation paused by the guardianMedium | Condition matched | Alert |
| 2026-09-14 05:40:18 | R-08Governance lifecycle | Security council cancels the scheduled upgradeLow | Condition matched | Alert |
| 2026-09-14 09:25:52 | R-02Privileged role change | Upgrade multisig owners rotatedHigh | Recorded for the timeline | Logged |
| 2026-09-15 16:00:07 | R-15Pause state changed | Withdrawal finalisation resumedMedium | Condition matched | Alert |
| 2026-09-16 12:00:00 | R-09Protocol parameter change | Portal upgrade delay raised from 48 hours to 8 daysMedium | Recorded for the timeline | Logged |
Timeline
Exploitation
Unreleased Portal implementation deployed by a fresh address
Upgrade multisig executes outside the change window
Detection
Portal upgrade scheduled; R-01 critical alert
Containment
Guardian pauses withdrawal finalisation
Security council cancels the operation
Fix
Exposed owners rotated; threshold 3-of-5
Verification
Finalisation resumed after state roots re-verified
Portal upgrade delay raised to 8 days
Root cause
The contracts behaved as designed. The failure was operational: a multisig threshold counts keys, not machines, and two of three keys shared a host that build jobs could reach. A threshold of two had collapsed to a single point of compromise.
The delay was also shorter than the withdrawal window, so had the operation gone unnoticed, users could not have exited before it executed — the condition OR-02 had warned about.
- Class
- Key management
- Severity
- Critical
- Components involved
- Portal
- Findings
- OR-02
# keys served by the release signing host- id: upgrade-owner-1 # Upgrade multisig owner[0] backend: local-file # same host reachable_from: [ci-runners] # build jobs can call the signer- id: upgrade-owner-2 # owner[1] backend: hardware # held separately reachable_from: []- id: upgrade-owner-3 # owner[2] backend: local-file # same host as owner[0] reachable_from: [ci-runners]threshold: 2-of-3Risk evaluation
Operational Security drops sharply while two owner keys are exposed, and Governance Risk reflects that one compromised host could start an upgrade of the fund-holding contract. No code finding is open in this window: the contracts behaved as designed.
The score moves from 75.2 (BBB) to 83.0 (A), a change of +7.8 points. The largest single contribution is Operational Security: restoring it from 50.0 to 80.0 alone moves the score from 75.2 to 81.2.
| Component | Weight | Range | During case | Today | Score points |
|---|---|---|---|---|---|
| Code Security | 30% | 86.0 | 86.0 | 0.0 | |
| Operational Security | 20% | 50.0 | 80.0 | +6.0 | |
| Governance Risk | 15% | 64.0 | 76.0 | +1.8 | |
| Economic Security | 15% | 84.0 | 84.0 | 0.0 | |
| Infrastructure Resilience | 10% | 82.0 | 82.0 | 0.0 | |
| Transparency | 10% | 90.0 | 90.0 | 0.0 | |
| Quantix Risk Index | 100% | 75.2 | 83.0 | +7.8 |
Scores use the six weighted components of the Quantix Risk Index. Open findings reduce Code Security by a fixed penalty per severity; the current profile is the protocol’s entry in Quantix Intelligence.
Mitigation strategy
Both exposed owners were replaced with hardware-backed keys held by separate people, and the threshold rose to 3-of-5. No upgrade key remains on any system that build jobs can reach; the release pipeline now produces an unsigned proposal for keyholders to review.
The delay for upgrades to withdrawal-path contracts was raised from 48 hours to 8 days, longer than the 7-day withdrawal window, so users can always exit before an upgrade they disagree with takes effect.
- 01Pause withdrawal finalisation and cancel the operation
- 02Rotate the exposed owners to hardware-backed keys held by separate people
- 03Raise the multisig threshold to 3-of-5
- 04Remove every upgrade key from CI-reachable systems
- 05Raise the withdrawal-path upgrade delay above the withdrawal window
-upgradeMultisig.threshold = 2 of 3 // owners 0 and 2 on one signing host+upgradeMultisig.threshold = 3 of 5 // hardware-backed keys, one per keyholder -UpgradeTimelock.delay(Portal) = 172800 // 48 hours+UpgradeTimelock.delay(Portal) = 691200 // 8 days, longer than the 7-day withdrawal window -release pipeline: signs and submits schedule()+release pipeline: prepares an unsigned proposal for keyholder reviewSecurity outcome
The malicious upgrade never executed and no funds left the portal. Withdrawal finalisation was paused for about a day and a half and resumed after re-verification.
The incident completed the open part of OR-02: withdrawal-path upgrades now wait longer than the withdrawal window.
- Detection to containment
- 49 min
- Detection to first fix
- 7 h 12 min
- Detection to verified fix
- 37 h 46 min
- Risk Index change
- BBB → A (+7.8)
Findings this case expands on
Lessons learned
- 01
A timelock buys time only if someone watches the queue.
- 02
A multisig threshold counts keys; count the machines and people behind them as well.
- 03
Keep signing keys off systems that run build jobs or third-party code.
- 04
Make the upgrade delay longer than the time users need to exit.

