No. Test coverage reports which lines executed while tests ran. It does not show whether the tests checked for hostile input, broken authorisation or unsafe defaults. High coverage can sit beside serious flaws. Use coverage as a floor for risky modules and add tests that assert security behaviour.
What coverage counts
Coverage tools mark each line, branch or function as executed or not. A line that ran inside a test counts as covered even if the test never looked at its result. A test that calls a login handler with valid credentials and checks nothing else will cover that handler.
So coverage answers "was this code exercised?" and not "was this code verified against abuse?"
The gap between executed and checked
Think of three layers of confidence:
- The line ran.
- The line produced the expected result for normal input.
- The line behaves safely for hostile input.
Coverage measures the first. Most unit tests reach the second. Security failures live in the third: an ID that belongs to another user, a path with parent directory segments, an oversized payload, a missing authentication check on one route.
Why a target on the percentage backfires
If a team must hit a number, the quickest route is tests that call code and assert little. This is Goodhart's law again: when a measure becomes a target it stops being a good measure. The result is a high figure and a false sense of safety.
It also directs effort to easy code. Getters and plain data classes are cheap to cover. Authorisation checks, parsers and serialisers are harder and matter more.
Where coverage is useful for security
Use it narrowly:
- As a floor on sensitive modules. Authentication, access control, input parsing and cryptographic wrappers should not be untested.
- As a diff check. Flag pull requests that add security relevant code with no tests at all.
- As a hint, not a verdict. A module with very low coverage and a history of defects deserves review.
Tests that add security value
| Test type | What it asserts |
|---|---|
| Negative authorisation | User A cannot read or change user B's record |
| Boundary input | Empty, huge, malformed and unexpected types are rejected |
| Default safety | New endpoints require authentication unless explicitly public |
| Regression | A past vulnerability stays fixed |
A short list of these for each service tells you more than a coverage figure does.
Steps to improve the signal
- List the five riskiest code paths per service: login, permissions, uploads, parsers, payments.
- Write at least one negative test for each.
- Add a rule that any fixed vulnerability ships with a regression test.
- Track the count of negative tests on those paths rather than the overall percentage.
- Complement tests with static and dependency analysis, since tests rarely cover third party code.
Safeguard offers SBOM and dependency scanning, reachability analysis and policy gates. You can run a free scan at gold.safeguard.sh/scan.
Quick check
Is 100 percent coverage a security guarantee?
No. It means every line ran during tests, not that any test checked for abuse.
What should we measure instead?
Count negative and regression tests on the riskiest paths, and keep coverage as a floor for sensitive modules.
Last reviewed: 19 October 2026.