Importing a package means your build includes it. Calling a function means your program can actually run that code. Reachability analysis measures the gap between the two. A vulnerability in an imported package you never call into is a different risk from one sitting on a path your code uses.
Three levels of "present"
Think of three questions, each narrower than the last.
- Is the package in your dependency tree? This is what a basic inventory shows.
- Is it imported by code that is built into your program? Some packages sit in the tree but are never compiled in.
- Can execution reach the vulnerable function? This is reachability.
Many alerts come from level one. Reachability moves a finding to level three only when a path exists.
What the Go tooling shows
The Go team's govulncheck tutorial gives a clear picture because the tool reports all three states. In the tutorial, the tool prints a call stack when the sample code calls a vulnerable function, for example a line showing the main function calling a function in a language parsing package. For another advisory, the output says there is a vulnerability in a package that the code imports, but the code does not call the affected function, and tells you no action is needed for that one, though it may be worth a look. The tutorial labels such cases as informational.
The command documentation adds that the tool analyses source code or a binary's symbol table to narrow reports, and that results depend on the build configuration, such as the Go version and platform.
This is a language specific example, but the idea carries over. A good report separates "present", "imported", and "called".
Why the gap exists so often
- Libraries are wide. You use three functions from a package of three hundred.
- Transitive dependencies are pulled in for features you do not use.
- Vulnerabilities usually live in one function or parser, not the whole package.
What to do with each level
| Level | Typical action |
|---|---|
| In tree, not imported | Low urgency, clean up when convenient |
| Imported, not called | Schedule fix, note the evidence, recheck on change |
| Called | Prioritise, check exposure of the entry point |
Imported but not called is the tempting category to ignore forever. Do not. Code changes, and a new call can appear in a refactor. Keep the finding open with a lower priority and let a re-run close it when the dependency is updated.
Try it yourself
Click through it at your own pace, or open it full screen. Compare the full list of findings with the list after the reachability filter.
Safeguard includes reachability analysis alongside SBOM and dependency scanning, and you can run a free scan at gold.safeguard.sh/scan to see the output on your own repository.
Quick check
Does an unused import count as risk?
It is lower risk than a called function, but not zero. Some packages run code at import time, and a later change could add a call.
Can a tool see calls made through reflection?
Often not. Reflection decides what to call at runtime, which is a known gap for static analysis.
Sources
- Tutorial: Find and fix vulnerable dependencies with govulncheck, go.dev
- govulncheck command documentation, pkg.go.dev
- Reflective programming, Wikipedia
Last reviewed: 19 October 2026.