Skip to content

fix(tempo): allow zero-amount session close for untouched channels (#291)#294

Open
EfeDurmaz16 wants to merge 1 commit into
tempoxyz:mainfrom
EfeDurmaz16:fix/close-at-settled-amount
Open

fix(tempo): allow zero-amount session close for untouched channels (#291)#294
EfeDurmaz16 wants to merge 1 commit into
tempoxyz:mainfrom
EfeDurmaz16:fix/close-at-settled-amount

Conversation

@EfeDurmaz16

@EfeDurmaz16 EfeDurmaz16 commented Jun 11, 2026

Copy link
Copy Markdown

What this fixes

Closes #291. validate_close_amount rejected a cooperative close at amount 0 on a funded but unspent channel, so an opened-but-unspent session left the deposit stranded with no recovery besides the forced-close grace period.

The change

validate_close_amount is aligned with the current mppx session validator (handleCloseCredential in tempo/session/server/CredentialVerification.ts), replacing the older single-carve-out shape. The gates, in order:

if cumulative_amount < spent            { /* reject: must be >= spent */ }
if on_chain_deposit == 0                { /* reject: unfunded or fully-settled channel */ }
if cumulative_amount < on_chain_settled { /* reject: must be >= settled */ }
let capture_amount = spent.max(on_chain_settled);
if capture_amount > on_chain_deposit    { /* reject: capture exceeds deposit */ }
  • A funded untouched channel (deposit > 0, settled == 0, spent == 0, cumulative == 0) now closes at 0 and refunds the deposit. This is the Session close fails on an unspent channel because validate_close_amount rejects a zero-amount close #291 case, and it falls out of the gates naturally rather than needing a special-case boolean.
  • cumulative == settled is now valid (it captures the settled amount); a close below a nonzero settled stays rejected.
  • The deposit bound now applies to the captured amount max(spent, settled), matching mppx, not the raw voucher cumulative.

Why it stays safe

A close below a nonzero on-chain settled amount remains rejected, preserving the strict anti-replay property behind CVE-2026-34209 / GHSA-mv9j-8jvg-j8mr (capture-replay, CWE-294), now expressed as cumulative >= settled. The new on_chain_deposit == 0 rejection means a zero-deposit channel is never a valid close target: with a None close_signer, handle_close can no longer mark a never-funded channel finalized and return success. This is the same guard handle_open applies when it treats deposit == 0 as "not funded on-chain".

Tests

validate_close_amount has 15 unit tests pinning the contract, including:

cargo fmt --check, cargo clippy -- -D warnings, the tempo/server/client test suite (996 passing), and the CI ast-grep gate all pass locally.


Reviewer note (Claude Fable 5)

Claude Fable 5 reviewed this PR. The change is one private function, so the thing to read is validate_close_amount and the close-test block in session_method.rs. Worth your attention:

  • It now mirrors current mppx, not the older carve-out. An earlier revision of this PR added an is_untouched_zero_close special case modelled on the legacy Session.ts. That has been replaced: the validator now follows tempo/session/server/CredentialVerification.ts line for line (the >= spent, deposit == 0 guard, >= settled, and capture = max(spent, settled) bound). The zero-untouched-funded case is a consequence of those gates, not a hand-coded exception.
  • The codex P2 is closed. The bot flagged that the previous carve-out accepted 0/0/0 before any funded-channel check, letting a None close_signer finalize an unfunded channel. The on_chain_deposit == 0 rejection now propagates through ? before the store marks the channel closing/finalized, so that path returns an error. Covered by test_close_rejects_zero_deposit_untouched and test_close_rejects_zero_deposit_with_value.
  • CVE intact. The only relaxation is cumulative == settled; a close below a nonzero settled is still rejected, which is the property CVE-2026-34209 turns on. No API, feature, or dependency surface moved.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 39717df280

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +216 to +217
let is_untouched_zero_close = cumulative_amount == 0 && spent == 0 && on_chain_settled == 0;
if !is_untouched_zero_close && cumulative_amount <= on_chain_settled {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject zero-close for unfunded on-chain channels

When the close request resolves to an escrow/channel whose getChannel returns the zero/default state, this carve-out now accepts 0/0/0 before any funded-channel check. handle_open treats on_chain.deposit == 0 as channel not funded on-chain, and before this change the same zero state was rejected by cumulative_amount <= on_chain_settled; with the default close_signer: None, handle_close can now mark the local channel finalized and return success even though no on-chain channel was closed. Please keep rejecting on_chain_deposit == 0 (or require a positive deposit in the carve-out).

Useful? React with 👍 / 👎.

@EfeDurmaz16
EfeDurmaz16 force-pushed the fix/close-at-settled-amount branch from 39717df to 47076ac Compare June 11, 2026 20:38
…empoxyz#291)

`validate_close_amount` rejected a cooperative close at amount 0 on a
funded but unspent channel, stranding the deposit with no recovery
besides the forced-close grace period (tempoxyz#291).

Align the check with the current mppx session validator
(handleCloseCredential in tempo/session/server/CredentialVerification):
require cumulative >= spent and cumulative >= on-chain settled, bound the
captured amount max(spent, settled) by the on-chain deposit, and reject a
zero on-chain deposit outright (an unfunded or fully-settled channel is
not a valid close target, matching how handle_open rejects deposit == 0).

A funded untouched channel (deposit > 0, settled == 0, spent == 0) now
closes at 0 and refunds the deposit. A close below a nonzero settled
amount stays rejected, preserving the strict anti-replay property from
CVE-2026-34209 / GHSA-mv9j-8jvg-j8mr, now expressed as cumulative >=
settled. A zero-deposit close is rejected, so a None close_signer can no
longer finalize a channel that was never funded on-chain.
@EfeDurmaz16
EfeDurmaz16 force-pushed the fix/close-at-settled-amount branch from 47076ac to 46ab8a8 Compare June 11, 2026 20:45
@EfeDurmaz16

Copy link
Copy Markdown
Author

@brendanjryan could I get a review here when you have a chance?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Session close fails on an unspent channel because validate_close_amount rejects a zero-amount close

1 participant