Use "not reachable" when the vulnerable code cannot be executed in your build. Use "compensating control" when it can be executed but something else stands in the way of exploitation. The first is a fact about code. The second depends on a control that can fail, so it needs its own owner, test and review date.
Two situations that look alike on a ticket
A finding is closed with the note "not a risk". Is that because nothing calls the function, or because a gateway filters the malicious input? From the ticket you cannot tell, and an auditor will ask.
The distinction matters because the evidence differs and the failure modes differ.
| Question | Not reachable | Compensating control |
|---|---|---|
| What is the claim? | No call path to the vulnerable code in this build | Code can run, but exploitation is blocked or limited |
| What proves it? | Analysis result, build and commit | Control configuration plus a test that shows it works |
| What breaks it? | A code change that adds a call | A configuration change or a bypass |
| Who owns it? | Application team | Whoever owns the control |
| Review trigger | Release or dependency change | Control change or new bypass technique |
What the standards vocabulary says
The OpenVEX specification lists a set of justification labels for a not affected claim. One is vulnerable code not in execute path, used when the product includes the vulnerable component but does not call it. Another is inline mitigations already exist, described as built in protections that cannot be subverted by the attacker and cannot be configured or disabled by the user.
The specification adds a caution on that last label: it could be difficult to prove conclusively, and mitigation bypasses are common. That caution is the reason to treat an external control differently from an absent call path. A web application firewall in front of the service is not a built in protection of the product.
How to record each
For not reachable, keep the analysis date, tool, scope and the absence of a path. Re-run it on each release.
For a compensating control, record:
- The control and where it runs.
- The specific attack it is expected to stop.
- A test that demonstrates it stopping a safe sample.
- The owner and the review date.
- What happens if the control is removed.
The last point is the one teams skip. If the control is turned off during a migration, the finding is live again.
Do not stack labels to look safer
Some records say "not reachable, and also mitigated by the firewall". If the code is truly not reachable, the second claim adds noise. If you are unsure about reachability, say so and rely on the control openly. A clear single reason beats two vague ones.
Choosing quickly
Ask: if the control vanished tomorrow, would the finding still be harmless? If yes, the reason is reachability. If no, the reason is the control, and you should label it that way.
Try it yourself
Click through it at your own pace, or open it full screen. Look at which facts change the verdict.
Quick check
Can a compensating control justify a VEX not affected statement?
Only if the mitigation is built into the product and cannot be turned off by the user. An external control is better described in an action or impact statement.
How often should a compensating control be reviewed?
On any change to the control, and on a fixed schedule your policy names.
Safeguard runs reachability analysis and keeps decision records with dates and owners. You can try the free scan.
Sources
Last reviewed: 19 October 2026.