fix: reject zero/expired ValidBefore on co-signed fee payer transactions - #123
Open
chalomdev wants to merge 1 commit into
Open
fix: reject zero/expired ValidBefore on co-signed fee payer transactions#123chalomdev wants to merge 1 commit into
chalomdev wants to merge 1 commit into
Conversation
validateFeePayerTransaction only checks the sponsor-policy upper bound on ValidBefore when it's non-zero, so the check is silently skipped for a co-signed tx with ValidBefore == 0. The standalone zero/expired guard applied to the original client-submitted transaction before co-signing was never re-applied after co-signing, so a remote fee payer response with ValidBefore reset to 0 could bypass the sponsor's validity-window policy entirely. Re-apply the same guard immediately after the second validateFeePayerTransaction call.
This was referenced Aug 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
In
pkg/tempo/server/charge.go'sverifyTransaction, the fee-payer flow validates the transaction'sValidBeforefield twice: once on the original client-submitted transaction (before co-signing), and once again after co-signing. But these two checks aren't equivalent:Before co-signing (line ~286):
After co-signing (line ~328, before this fix): only
validateFeePayerTransaction(tx, ...)is called, which internally does:i.e. it silently skips its check entirely when
ValidBefore == 0. There is no standalone re-check afterward.For the local
feePayerSignerpath this is harmless, sincetxis mutated in place and itsValidBeforewas already validated pre-signing. But for the remote fee payer path (request.MethodDetails.FeePayerURL),txis replaced wholesale:coSignedRawcomes from an external HTTP response (signWithRemoteFeePayer). If that response contains a transaction withValidBeforereset to0, neither the post-co-signvalidateFeePayerTransactioncall nor any other check catches it, and the transaction proceeds to signature verification, preflight, and broadcast.Why it matters / precedent
The pre-signing check already establishes that
ValidBefore == 0is treated as invalid (grouped with "already expired"), not as "never expires." The gap is that this same invariant isn't re-enforced on the transaction that actually gets broadcast when it originates from a remote fee payer response.feePayerMaxValidityWindow(15 minutes) is meant to bound how long a sponsor is on the hook for a transaction; aValidBeforeof 0 defeats that bound entirely for the remote co-signing path.Fix
Re-apply the same
ValidBefore == 0 || expiredguard immediately after the secondvalidateFeePayerTransactioncall, mirroring the pre-signing check.Tests
Added
TestChargeFlow_FeePayerTransactionViaRemoteSignerRejectsZeroValidBeforeinpkg/tempo/server/charge_feepayer_validbefore_test.go, modeled directly on the existingTestChargeFlow_FeePayerTransactionViaRemoteSignerRejectsTamperedFeeTokentest. It runs a fake remote fee payer HTTP server that co-signs the transaction but zeroesValidBefore, and assertsVerify()rejects it with an "expired" error before any broadcast (rpc.sentRawTxsstays empty).I was not able to run the full
go test ./pkg/tempo/server/...suite in my sandbox — network egress there blocksproxy.golang.org(this repo'sgo.modrequires Go 1.26, and dependencies likego-ethereum/tempo-gocouldn't be fetched), the same limitation noted on #122. I traced the fix and test logic by hand against the existingTestChargeFlow_FeePayerTransactionViaRemoteSigner*tests' structure, but I'd appreciate CI or a maintainer confirming the new test passes (and fails without the fix) before merge.Scope / trade-off
This only affects the
FeePayerURL(remote fee payer) path. The localfeePayerSignerpath was never affected, since it mutates the original, already-validatedtxin place rather than replacing it. I have not audited whethertempotx.DeserializeorVerifyDualSignaturesindependently enforce a non-zeroValidBeforeat the transaction-format level — this fix adds the check at the mpp-go verification layer regardless, consistent with the existing pre-signing check.Changeset
Added
.changelog/fee-payer-validbefore-guard.mdwith apatchbump.