Pre-auditThree days of senior audit time for $1,500, credited in full toward your Zealynx audit.3 audit days, credited toward your auditSee how →
M-08·audit-finding

Concurrent deployments can promote another run's unverified artifact

Fixednfterc-1155marketplace
TL;DR

Concurrent deployments can promote another run's unverified artifact. Final disposition: Fixed.

Severity
MEDIUM
Method
MManual review
CAT.

Tracker: issue #29 · Status: Fixed · Verified at: audit-v3-rereview-handoff

02Section · Affected files

Affected files

script/deploy_base.sh#L51-L293 script/DeployBase.s.sol#L243-L295

03Section · Description

Description

All deployment runs using the same NETWORK_KEY share deployments/<network>/addresses.staging.json. The wrapper deletes this file at startup, the Solidity script recreates it during simulation and broadcast, the wrapper reads it throughout verification, and the final mv promotes whichever file occupies that path. There is no same-network lock or run identifier binding the verified candidate to the promoted candidate.

Consequently, two authorized jobs running with REDEPLOY=true can replace each other's staging artifact. An ordinary overlap is more likely to produce a visible failure, but a narrower ordering silently publishes the wrong candidate: run A completes verification, run B replaces the staging file and is interrupted, then A promotes B's unverified file and reports success.

Vulnerable Scenario:

The deterministic regression harness ran the real wrapper in separate Bash processes. Its forge and cast shims controlled only the external CLI responses so the relevant filesystem ordering could be reproduced safely. It produced:

code
single_run_promoted= A
interrupted_B_staging= B
race_promoted_by_A= B
different_key_A_promoted= A
harm_assertion= True
✅ Deployed and verified. Artifacts written to deployments/concurrent-race/ (addresses.json + abi/).

The single-run and different-network controls both promoted A. Under the same-network interleaving, A exited successfully but its final addresses.json was byte-for-byte B's interrupted staging candidate.

04Section · Impact

Impact

The authoritative artifact consumed by clients, relayers, and indexers can temporarily point to contracts that the successful run did not verify. This can make clients construct signatures for the wrong EIP-712 verifying contract and disrupt routing or indexing until operators identify and roll back the artifact. No fund loss was demonstrated. Exploitation requires two authorized same-network redeployments and a narrow verify-to-promote ordering, placing this finding at the low edge of Medium severity.

05Section · Recommendations

Recommendations

Acquire an atomic exclusive lock scoped to NETWORK_KEY before checking, deleting, or creating any deployment artifact. Hold it through broadcast, all verification, ABI export, address promotion, and cleanup. A second same-network run should fail closed before touching staging, while different network keys should remain independent.

Do not automatically remove an existing lock. Record enough ownership information for diagnosis and require an explicit stale-lock recovery procedure after confirming no deployment is active. Cleanup should release a lock owned by the current process on normal exit and TERM.

Add regressions confirming that a concurrent same-key run is rejected, different-key runs remain concurrent, the first run promotes its own candidate, and TERM releases the lock. This serializes deployments for one network, but it removes the cross-run publication window without delaying deployments to other networks.

06Section · Remediation

Remediation

Fixed. An exclusive per-NETWORK_KEY lock, taken by atomic mkdir and recording its owner before the artifact is touched. A lock the run did not create is never removed by it, including on the trap path, so a crashed run cannot delete a live run's lock.

07Section · Verification provenance

Verification provenance

Final disposition: Fixed. Verified at audit-v3-rereview-handoff, commit f020448976eb7c4895e1626ef3228876cff2b7d2. This earlier remediation disposition is carried forward from the prior re-review, not independently re-verified in the generation-d pass. The report-wide scope and evidence limitations remain applicable; see the final report overview.

Status
Fixed
M-08