How to prevent Block Editor from adding id to block markup in save function?

You don’t, rather the approach taken is incorrect, and this particular situation is not fixable.

The group blocks anchor attribute for the ID is stored in the markup itself not the HTML comment, so by introducing an ID at the top level you are changing its value. Once this is done, the output of the save component of the group block changes and that is where your ID is coming from.

Fundamentally, what you’re doing is not how it should be done, and this approach is the wrong approach.

Instead, you should use containing blocks and compose your structure. Much in the same way that you can group blocks together in a group block, and give it an ID, you would be better off creating a dedicated block with a title and icon selection that has an ID.

If a container block isn’t suitable, a step heading block would also work just as well.

The bonus would be that you could build the navigation trivially in PHP by parsing the blocks, either through parse_blocks then looping through to find these “step” blocks, or by adding a filter to the_content, or even a template function if you wanted to do it that way. Likewise, any question about building outliners for block based content would also be super useful to you.

Likewise a filter similar to what you have right now in PHP could be implemented using the block rendering filters at runtime to achieve this.