Revert message headers to their original state before auditing#7880
Open
warwickschroeder wants to merge 2 commits into
Open
Revert message headers to their original state before auditing#7880warwickschroeder wants to merge 2 commits into
warwickschroeder wants to merge 2 commits into
Conversation
This was referenced Jul 23, 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.
Closes: #7739
Built off PR: #7860
Problem
Mutations made to
IncomingMessage.Headersduring the incoming pipeline leak into the audit copy of the message. Anything that writes to the headers dictionary, e.g., a mutator, custom behavior, or core pipeline steps, mutates the same dictionary instance thatInvokeAuditPipelineBehaviorlater snapshots into theOutgoingMessagesent to the audit queue.The result is that the audit queue receives the processed headers rather than the headers as they arrived off the transport, so the audited message is not a faithful record of what was received.
IncomingMessagealready guards against exactly this for the body:UpdateBodystashes the original andRevertToOriginalBodyIfNeededrestores it before the audit copy is taken. Headers had no equivalent.Solution
IncomingMessagenow captures the headers as they were at construction time and exposes an internalRevertToOriginalHeadersIfNeeded()that restores the dictionary to that exact state, reverting changed values, dropping added keys, and reinstating removed ones.InvokeAuditPipelineBehaviorcalls it alongside the existingRevertToOriginalBodyIfNeeded(), immediately before building theOutgoingMessage, so the audit copy always reflects the headers as received.Note that the snapshot is taken after
GetOrSetMessageIdFromHeadersruns, so a message arriving without aNServiceBus.MessageIdheader still gets that header populated from the native ID in the audit copy, matching existing behavior.Considerations
IncomingMessageis constructed once per received message, so this adds a dictionary copy to the receive hot path unconditionally. An alternative is snapshotting at the top ofInvokeAuditPipelineBehavior.Invokeinstead of in the constructor. The audit fork is the only consumer, and it's only active when auditing is enabled.RevertToOriginalHeadersIfNeeded()method name has no condition, its just a no-op so the name potentially overstates what it does.Tests
IMutateIncomingTransportMessagesthat overwrites a header, and asserting the audit spy endpoint observes the original value.