@@ -644,6 +644,89 @@ async fn test_get_header_bid_validation_matrix() -> Result<()> {
644644 Ok ( ( ) )
645645}
646646
647+ /// PBS must accept relay `Content-Type` values that include MIME parameters
648+ /// (e.g. `application/octet-stream; charset=binary`). The audit fix for C2
649+ /// switched `EncodingType::from_str` to parse via the `mediatype` crate;
650+ /// this test exercises the full relay→PBS→BN path to guard against
651+ /// regressions at the wire boundary.
652+ #[ tokio:: test]
653+ async fn test_get_header_tolerates_mime_params_in_content_type ( ) -> Result < ( ) > {
654+ setup_test_env ( ) ;
655+ let signer = random_secret ( ) ;
656+ let pubkey = signer. public_key ( ) ;
657+ let chain = Chain :: Holesky ;
658+ let pbs_listener = get_free_listener ( ) . await ;
659+ let relay_listener = get_free_listener ( ) . await ;
660+ let pbs_port = pbs_listener. local_addr ( ) . unwrap ( ) . port ( ) ;
661+ let relay_port = relay_listener. local_addr ( ) . unwrap ( ) . port ( ) ;
662+
663+ let mut mock_state = MockRelayState :: new ( chain, signer)
664+ . with_response_content_type ( "application/octet-stream; charset=binary" ) ;
665+ mock_state. supported_content_types = Arc :: new ( HashSet :: from ( [ EncodingType :: Ssz ] ) ) ;
666+ let mock_state = Arc :: new ( mock_state) ;
667+ let mock_relay = generate_mock_relay ( relay_port, pubkey) ?;
668+ tokio:: spawn ( start_mock_relay_service_with_listener ( mock_state. clone ( ) , relay_listener) ) ;
669+
670+ let pbs_config = get_pbs_config ( pbs_port) ;
671+ let config = to_pbs_config ( chain, pbs_config, vec ! [ mock_relay] ) ;
672+ let state = PbsState :: new ( config, PathBuf :: new ( ) ) ;
673+ drop ( pbs_listener) ;
674+ tokio:: spawn ( PbsService :: run :: < ( ) , DefaultBuilderApi > ( state) ) ;
675+
676+ tokio:: time:: sleep ( Duration :: from_millis ( 100 ) ) . await ;
677+
678+ let mock_validator = MockValidator :: new ( pbs_port) ?;
679+ let res =
680+ mock_validator. do_get_header ( None , vec ! [ EncodingType :: Ssz ] , ForkName :: Electra ) . await ?;
681+ assert_eq ! ( res. status( ) , StatusCode :: OK , "PBS should tolerate `; charset=binary` MIME param" ) ;
682+ assert_eq ! ( mock_state. received_get_header( ) , 1 ) ;
683+
684+ let fork = get_consensus_version_header ( res. headers ( ) ) . expect ( "missing fork version header" ) ;
685+ let bytes = res. bytes ( ) . await ?;
686+ let data = SignedBuilderBid :: from_ssz_bytes_by_fork ( & bytes, fork) . unwrap ( ) ;
687+ assert_eq ! ( data. message. header( ) . block_hash( ) . 0 [ 0 ] , 1 ) ;
688+ Ok ( ( ) )
689+ }
690+
691+ /// Same guarantee on the JSON path: `application/json; charset=utf-8` (the
692+ /// value some production relays actually emit) must be accepted as JSON.
693+ #[ tokio:: test]
694+ async fn test_get_header_tolerates_json_charset_param ( ) -> Result < ( ) > {
695+ setup_test_env ( ) ;
696+ let signer = random_secret ( ) ;
697+ let pubkey = signer. public_key ( ) ;
698+ let chain = Chain :: Holesky ;
699+ let pbs_listener = get_free_listener ( ) . await ;
700+ let relay_listener = get_free_listener ( ) . await ;
701+ let pbs_port = pbs_listener. local_addr ( ) . unwrap ( ) . port ( ) ;
702+ let relay_port = relay_listener. local_addr ( ) . unwrap ( ) . port ( ) ;
703+
704+ let mut mock_state = MockRelayState :: new ( chain, signer)
705+ . with_response_content_type ( "application/json; charset=utf-8" ) ;
706+ mock_state. supported_content_types = Arc :: new ( HashSet :: from ( [ EncodingType :: Json ] ) ) ;
707+ let mock_state = Arc :: new ( mock_state) ;
708+ let mock_relay = generate_mock_relay ( relay_port, pubkey) ?;
709+ tokio:: spawn ( start_mock_relay_service_with_listener ( mock_state. clone ( ) , relay_listener) ) ;
710+
711+ let pbs_config = get_pbs_config ( pbs_port) ;
712+ let config = to_pbs_config ( chain, pbs_config, vec ! [ mock_relay] ) ;
713+ let state = PbsState :: new ( config, PathBuf :: new ( ) ) ;
714+ drop ( pbs_listener) ;
715+ tokio:: spawn ( PbsService :: run :: < ( ) , DefaultBuilderApi > ( state) ) ;
716+
717+ tokio:: time:: sleep ( Duration :: from_millis ( 100 ) ) . await ;
718+
719+ let mock_validator = MockValidator :: new ( pbs_port) ?;
720+ let res =
721+ mock_validator. do_get_header ( None , vec ! [ EncodingType :: Json ] , ForkName :: Electra ) . await ?;
722+ assert_eq ! ( res. status( ) , StatusCode :: OK , "PBS should tolerate `; charset=utf-8` MIME param" ) ;
723+ assert_eq ! ( mock_state. received_get_header( ) , 1 ) ;
724+
725+ let body: GetHeaderResponse = serde_json:: from_slice ( & res. bytes ( ) . await ?) ?;
726+ assert_eq ! ( body. data. message. header( ) . block_hash( ) . 0 [ 0 ] , 1 ) ;
727+ Ok ( ( ) )
728+ }
729+
647730/// Standard mode rejects a bid whose embedded pubkey does not match the relay's
648731/// configured pubkey; None mode forwards it unchecked, proving the bypass works
649732/// for the signature/pubkey validation check.
0 commit comments