How to wrap all titles generated by Gutenberg “Heading” block with tag

This can be done using the blocks.getSaveElement filter as shown in the example below. I would STRONGLY caution that this has the potential for bad times. Core blocks are static blocks. This means that the output is saved to the database. If you want to remove these spans, you’ll need to basically redo all of them. If the theme/plugin that introduces this is removed, then you’ll have new headers without the span but the old ones will still have them.

    wp.hooks.addFilter(
    'blocks.getSaveElement',
    'wpse-368511',
    ( el, type, attributes ) => {
        if ( 'core/heading' === type.name ) {
            const { level, content } = attributes;
            switch ( level ) {
                case 2:
                    return ( <h2><span>{ content }</span></h2> );
                case 3:
                    return ( <h3><span>{ content }</span></h3> );
                case 4:
                    return ( <h4><span>{ content }</span></h4> );
                case 5:
                    return ( <h5><span>{ content }</span></h5> );
                default:
            }
        }
        return el;
    }
);

Hope this helps!