Before merging a fix to a parser, serializer or validator, replay a sample of real inputs through both the old and new versions and compare the results. Unit tests prove the code works on cases you thought of. A replay shows whether the stricter fix now rejects or changes data your users already send.
Why these fixes break things quietly
Many vulnerabilities in parsers come from being too forgiving. The library accepted malformed input, followed an unexpected reference, or expanded something without limit. The fix makes it stricter. That is correct, and it also means any real input that relied on the lenient behavior now fails.
Semantic Versioning defines a bug fix as an internal change that fixes incorrect behavior, which is why such fixes ship as patch releases. But your application may have depended on the incorrect behavior without knowing. Your tests encode what you intended. Your users encode what actually happens.
Build a replay corpus
You need a set of inputs that looks like production without exposing it.
- Sample from logs or captured requests, if you have them, and strip or replace personal data. Keep structure, size and odd corners.
- Add the weird ones. Empty values, very long strings, unusual encodings, deeply nested structures, unexpected types, duplicate keys.
- Add inputs from bug reports. Every past parsing bug is a free test case.
- Add the attack input. Include the proof of concept for the vulnerability the fix addresses. It should now be rejected.
- Keep it small enough to run in a minute. A few hundred inputs often covers more ground than thousands of near duplicates.
Run both versions and diff
Write a small script that feeds each input to the old and new version and records one of three outcomes: accepted with the same result, accepted with a different result, or rejected. Then compare.
| Result | What to do |
|---|---|
| Same on both | Nothing to do |
| New rejects the attack input | Expected, this is the fix working |
| New rejects an ordinary input | Find out why before merging, decide if clients need to change |
| Same input, different output | Check carefully, especially numbers, dates, ordering and whitespace |
| New accepts something old rejected | Rare, investigate, it may be a regression |
The valuable lines are the second and fourth rows. A rejected ordinary input means some client will break. A changed output means data may be silently altered, which is harder to notice later.
Decide how to handle breakage
If real inputs are now rejected, you have choices. Fix the producer of the bad input if it is yours. Add a documented normalization step before the parser if the input is legitimate but odd. Ask the library whether a compatibility option exists, and weigh whether turning it on reopens the hole. Or notify the clients who send the input, with a date.
Do not fall back to "pin the old version" without a recorded exception, since the old version is the vulnerable one.
After the merge
Log rejection counts for the first few days. A jump in parse errors right after the deploy is the signal that your replay corpus missed something. Keep a rollback ready for the first release.
Safeguard offers reachability analysis to show whether a vulnerable parser function is called by your code, which helps you decide how urgently to run this check. You can run a free scan at gold.safeguard.sh/scan.
Quick check
Can I use production data for the replay?
Avoid raw production data. Use a sanitized sample with personal data removed or replaced, and keep the structure and size characteristics that matter.
Is this worth doing for every parser bump?
For fixes in libraries that handle untrusted input, such as JSON, XML, YAML, image or archive handlers, yes. For a patch to a logging helper, probably not.
Sources
Last reviewed: 19 October 2026.