When you fix a vulnerability, first write a test that reproduces it and fails. Then make the fix and watch the test pass. The test proves the fix works, and it stays in the suite so the same bug cannot quietly return in a later refactor.
Why this order
Fixing first and testing after tends to produce a test that passes for the wrong reason. If you have never seen the test fail, you do not know it can.
A failing test also forces a precise statement of the bug. "Users can read other users' files" becomes a concrete request with a concrete wrong response.
The steps
- Reproduce the issue by hand and write down the exact request or input.
- Encode it as an automated test at the lowest level that still shows the failure.
- Run it and confirm it fails, for the right reason.
- Make the smallest change that fixes the cause.
- Run it again and confirm it passes, along with the rest of the suite.
- Add variations: neighbors of the same bug, such as the same missing check on a sibling route.
Where the test should live
Choose the lowest level that reproduces it. A missing escape in a template is a unit test. A missing owner check on a route is an integration test. A parser crash is a fuzz corpus entry. Go's fuzzing documentation says a failing fuzz input is written to the testdata directory and replayed by a plain test run, which makes it a regression test automatically.
Dependency vulnerabilities
For a vulnerable library, the equivalent is a test that exercises the vulnerable code path in your application, if one exists. Upgrading the package alone leaves no proof. If you cannot reach the vulnerable function, say so in the pull request and note why. Version bumps should still pass the existing suite plus any test that covers behavior the upgrade could change.
Pull request checklist
- Link to the report or advisory.
- A test that fails on the base commit. Some teams run the new test against the parent commit in CI to prove it.
- The fix and the test in the same pull request.
- A note on related places checked for the same flaw.
- No details in the public commit message that would help exploitation before users can upgrade, where disclosure is still coordinated.
Try it yourself
The fix available demo shows how a fix is surfaced for a finding. Look at what evidence it gives you, then ask what test would prove the fix in your own code.
Click through it at your own pace, or open it full screen.
Quick check
What if the bug is hard to reproduce in a test?
Narrow it. Extract the logic into a function you can call directly, or add a seam for time or randomness. If it truly cannot be automated, document a manual verification step and keep the ticket open until it is done.
Safeguard can open fix pull requests through autonomous remediation, which is off by default and starts with every category on Manual, so a person reviews each change and its tests.
Sources
Last reviewed: 19 October 2026.