Trivy filesystem mode: where dependency checking breaks
Dependency checking during build breaks more often not on the vulnerability itself, but on how it is searched for.
Dependency composition scanning is usually attached after image build, as a separate job and without blocking — the build pipeline is formally green, but a package with a known issue goes into release.
The opposite option is also bad: full image scanning on every commit adds minutes to every build, and the step is disabled so as not to wait.
Trivy filesystem mode removes both extremes: it reads manifests and lock files directly from the working copy — package-lock.json, go.mod together with go.sum, pom.xml, requirements.txt or poetry.lock — without a built image, registry access, or container daemon permissions.
A single executable and a local vulnerability database cache are enough, and the cache is updated by a separate step and reused between runs, including in an air-gapped environment.
This breaks on multi-module repositories. In a monorepo there are dozens of modules with their own lock files, some packages come in transitively through the parent manifest, and some are tooling and test dependencies.
Without an explicit list of targets and exclusion rules, scanning either becomes noisy on unsupported dependencies or silently skips a module that the tree did not reach. The result is predictable: the team gets used to ignoring a red status.
We look at how this is closed — how to set scanning targets, severity thresholds, and exit behavior so that the step remains fast and actually stops the build.
What changes after integrating the check into the build
Integrating dependency checking into the build produces measurable shifts, not a feeling of calm. Below is a checklist we use to verify the result at acceptance: each item is counted from pipeline logs and scanner reports, not from words.
- Build time — Trivy in filesystem mode parses manifests and the dependency tree without starting containers, so the increase in total time is tens of seconds, not minutes.
- Share of caught vulnerabilities — the check runs on every commit, and a finding is recorded before rollout; the report shows a breakdown by severity levels and dynamics from release to release.
- Eliminated class of failures — rollouts no longer contain crashes due to incompatible or revoked library versions: they are cut off by the build, not by an on-call engineer at night.
- Cost of downtime — measured in hours of unavailability and the number of manual rollbacks; when a problem is caught before rollout, recovery is not required at all.
- Report readability — we limit output to new findings relative to the baseline, so the report includes only what appeared in the current change.
- Reproducibility — an engineer runs the same check locally before committing, and the result matches what the pipeline sees.
We configure thresholds separately: block the build on critical levels and warn on the rest. Without this, noise in the report quickly leads to the check being bypassed, and it stops catching anything.
Scanning modes: filesystem, image, registry
The verification point determines what the scanner can even see. Trivy in filesystem mode reads manifests and lock files directly in the working directory: this is the fastest option, seconds per run, suitable for pre-commit and early build stages.
Checking a finished image gives the full picture — layers, system libraries, packages installed on top of the base image. A remote image registry checks what has already been published and is running in the environment.
| Verification point | When suitable | Limitations |
|---|---|---|
| Filesystem | Development, pre-commit, manifest checks before build | Does not see packages from the base layer and system libraries |
| Finished image | Gate before publication, checking what will actually run | Requires a built artifact and a local daemon, run takes longer |
| Remote registry | Audit of published tags, regular review without rebuilding | No access to local layers, depends on registry availability |
Scanning modes do not replace but complement each other: filesystem catches the problem at the input, the finished image before publication, the registry after.
A discrepancy between results is normal because some dependencies come from the base layer and are not visible in project files.
A common limitation is the freshness of the vulnerability database. Without regular index updates, any mode gives an unreliable picture, and the database cache should be pinned by version, otherwise runs between builds are incomparable.
Choosing the point is choosing where to place the gate: where failure is cheaper, that is where we put the check.
Stages of implementing dependency composition checking
We structure the work so that dependency composition checking is integrated on top of the existing pipeline, not instead of it.
First we record the current state, then enable scanning in report mode, and only after a trial run do we make the check blocking — this way the build will not stop on day one, and the team has time to triage accumulated findings.
- Pipeline audit. We look at how artifacts are built, where manifests and dependency files are stored, which stages already exist, and which one the check will fit into without rebuilding the build.
- Check scheme. We determine the scope — repository trees and built images — and the launch point for Trivy filesystem mode: before build, so as not to waste time on a known problematic commit.
- Setting blocking thresholds. We divide findings by levels: what stops the build, what goes into the report, what is added to the accepted risks list with a reason and owner.
- Trial run in report mode. The first runs do not block but collect statistics: how many triggers, which repeat, where false ones are. We calibrate thresholds based on it.
- Switching to blocking mode. We enable stopping on critical levels and formulate the message to the developer so that it is clear what exactly to update or replace.
- Handover of settings to the team. We hand over the rules, launch scheme, procedure for updating the exception list, and escalation point when triggered — after that the pipeline runs without us.
What remains in the repository after implementation
After implementation, what remains in the repository is not a set of one-off commands but a set of artifacts. Trivy in filesystem scanning mode is launched from a configuration file, so the result is reproduced identically both on a local machine and in the pipeline.
- Check configuration — a single file at the root: scanning targets, severity levels, report formats, build behavior on a finding.
- Trigger suppression rules — an exception list where each entry has a rationale: why the vulnerability does not apply to the service's usage profile.
- Access policies — who has the right to edit exceptions and approve new ones, how the edit history is maintained, and who reviews it.
- Database update procedure — synchronization frequency, build behavior when the source is unavailable, the person responsible for updates.
- Check documentation — a section in the README: how to run the check locally, what a failed build means, where to view the report.
- Severity matrix — which levels block the build, which go into the report and enter the work plan.
- Template for new services — a base set of files so that a new repository starts with checks rather than catching up with the rest.
Such a set survives engineer turnover and project migration: checks are set up from documentation, not from a colleague's memory. We separately make sure the exception list does not grow — we review it on a schedule and remove rules that have lost their basis.
Case study: share of builds with vulnerable dependencies decreased
The project is a platform of several dozen services with a shared build pipeline. Dependency scanning here was done after image build: the result came in 10–12 minutes, after artifact publication, and was treated as reference information. The share of builds in the main branch with high-severity vulnerable dependencies held around 27%.
What we did
We moved the check to the beginning of the pipeline and switched it to Trivy in filesystem scanning mode. The run goes over the source tree — manifests and dependency lock files — before image build, so it does not wait for a finished artifact or require registry access.
The vulnerability database is cached between runs, thresholds and exceptions are set by a configuration file, and the report is returned in markup that the version control system shows as comments directly in the merge request. Only a critical vulnerability with an available fix blocks; everything else goes to the task queue.
Result
After the transition, checking one service takes about 40 seconds instead of 12 minutes, and the developer sees the result in the first minute.
The share of builds with vulnerable dependencies decreased from 27% to 4%, and eight out of ten fixes go into the same merge request where the vulnerability was found.
We confirmed the effect using build logs and comparing reports before and after: we counted the share of vulnerable builds weekly, not by individual releases, to rule out a one-off result.
How does Trivy differ from registry-side scanning?
Registry-side scanning works with an already built image — layers, package database, filesystem contents.
There is one entry point and it is the last one: it is suitable as a final barrier before rollout, but the signal comes after build and tag publication, when the developer has already lost task context.
Trivy in filesystem mode scans the working directory before build: lock files and dependency manifests, Dockerfile, Kubernetes manifests, configurations, and accidentally committed secrets.
The checking scope shifts from artifact to source — you see what is only going to enter the image, and the problem is closed at branch merge, not after publication.
The cost of an error is different too. For the registry, a found defect is an existing image that must be rebuilt, republished, and redeployed to the environment.
For filesystem mode, it is a one-line edit and a failed check in the same job where tests and linters run; no rollback is needed because no bad artifact was created.
Registry-side scanning is not canceled: it sees the real build, including base layers, and covers images built outside our pipeline.
Plus it rechecks artifacts already in the registry when a new vulnerability appears in the database — the sources for an old tag may no longer exist.
Bottom line: verification points do not replace each other but divide roles. Filesystem mode provides early failure and feedback within minutes in the same run as tests; the registry remains the last barrier before release.
Common technical doubts about dependency scanning
Engineers' doubts about dependency scanning boil down to four topics: how many false positives will have to be triaged manually, whether scanning works in an air-gapped environment, how much it slows down the build, and how the vulnerability database is updated without external dependencies.
Below are short answers at the configuration level. This concerns trivy filesystem mode: it reads manifests and lock files directly from the working copy, without requiring a built image.
Will we drown in false positives?
Until filters are configured — yes. We cut off unused dependency branches, limit the rule by severity level, and maintain an exception file where each line has an author and review date. After that, only a few findings remain per release, and each requires a real decision.
Does this work in an air-gapped environment?
Yes. The vulnerability database is deployed in the internal artifact registry, updates run on a schedule, and the scan itself does not access the external network.
Where external access is allowed, the database is pulled directly; in an air-gapped environment, the mirror is checked for integrity before the run, otherwise an outdated snapshot silently misses findings.
How much does scanning slow down the build?
Noticeable only without a cache. We place the step after dependency installation, run it on changed lock files, and keep the database locally next to the build agent.
On a typical release, the added time is measured in seconds; the report is delivered as a separate artifact and does not block the build until thresholds are set.
Let's discuss the task: what to send to assess the pipeline
To come back with an estimate and work plan, we need inputs about your pipeline — without them, timeline estimates turn into guesses. Below is a list of what we usually clarify in the first conversation; some items are often already at hand.
- Link to the repository and build description file (GitLab CI, GitHub Actions, Jenkinsfile) — it shows at which stage checks currently run and where the scanning step will fit.
- Languages and dependency managers: go.mod, package-lock.json, requirements.txt, pom.xml, composer.lock — these determine which analyzers Trivy filesystem mode will enable and how long the pass will take.
- Reporting requirements: SARIF for the built-in panel, JSON for script-based parsing, or a table in build artifacts.
- Vulnerability policy: which severity levels stop the build and which are only logged; whether exceptions and a .trivyignore file exist.
- Environment constraints: air-gapped environment without external calls, your own image registry, proxy, local cache of vulnerability databases.
- Procedure for triaging findings: who is responsible for fixing, within what time critical issues are closed, how rollback is arranged for a false positive.
- Target numbers: how long the build currently takes and what time budget for the scanning step we must not exceed.
Send these inputs — we will discuss the task and come back with an estimate and work plan: stages, what we change in the pipeline, how we verify the result. If some data is missing, we will suggest where to get it.







