finalizepsbt can return complete: true and a hex for a PSBT whose PSBT_IN_FINAL_SCRIPTWITNESS (or final scriptSig) is invalid.
FinalizePSBT counts an input as complete when SignPSBTInput succeeds. If the input already has final fields, PSBTInput::FillSignatureData marks the sigdata complete and ProduceSignature returns true early (sign.cpp:752), before the script is verified. So the existing final fields are trusted as-is.
To reproduce, take a signed P2WPKH v0 PSBT that has a non_witness_utxo and flip one bit of the signature inside the final witness:
finalizepsbtreturnscomplete: trueand a hexanalyzepsbtsays the input is not final,next: finalizertestmempoolaccepton the hex fails withmempool-script-verify-flag-failed (Signature must be zero for failed CHECK(MULTI)SIG operation)
Inputs that only have a witness_utxo already come out incomplete because of the require_witness_sig check, so this affects inputs with a non_witness_utxo.
#30357 fixed the same problem in FillPSBT by checking PSBTInputSignedAndVerified, and #33014 did it for descriptorprocesspsbt. This does the same in FinalizePSBT. Taproot verification needs every spent output. If one is missing, that input already fails in SignPSBTInput with MISSING_INPUTS and makes the PSBT incomplete, so the extra check doesn't change the result in that case.
The new test in rpc_psbt.py builds that PSBT and checks that finalizepsbt returns complete: false with no hex. It fails on master and passes with the fix. wallet_musig.py and the psbt_wallet_tests / script_tests unit tests still pass.
One behaviour change: a final script that is valid by consensus but non-standard now gives complete: false. FillPSBT and descriptorprocesspsbt already behave that way, since they check with the same standard flags.