The dependency question Guard answers
Vulnerability data is one part of dependency hygiene. The audit answers a wider operational question: is the repository's current dependency surface controlled well enough for safe ongoing change, and which verified risks or gaps need attention first?
The surface extends beyond packages declared in package.json, go.mod, or pyproject.toml. It includes base container images, third-party release actions, remote install scripts, and commands such as npx that can fetch code during a build. Any path through which the project downloads or executes external code belongs in the inventory.
Guard scores the revision that is checked in. Dependabot, Renovate, scheduled scans, and a named owner provide context, but none adds or removes points by itself. The result follows the dependency state Guard can prove now, including the gaps that prevent a stronger conclusion.
Dependency risk is accumulating across build and runtime acquisition paths. Pin the runtime image and remove unbounded build-time acquisition first.
What Guard collects
Inventory comes before interpretation. In a monorepo or polyglot project, Guard keeps ecosystems and deployable surfaces distinct so one well-controlled package cannot conceal an unbounded path elsewhere.
- Manifests and lockfiles: declared dependencies, resolved versions, package managers, workspaces, and dependency scope.
- Install and acquisition paths: commands in CI, Makefiles, Dockerfiles, release scripts, deploy scripts, bootstrap tooling, and tracked generated clients that bring external code into the project.
- Runtime boundaries: which dependencies belong to production, build, development, test, tooling, containers, or CI.
- Container sources: base image tags, immutable digests, and operating system packages that do not appear in a language lockfile.
- Registry and install configuration: private mirrors, registry settings, lifecycle scripts, package-age gates, and related controls.
- Supporting evidence: existing SBOM files, accepted-risk records, and dependency maintenance configuration. A root
GUARD.md, when present, is read as repository context rather than as authority over the audit. Each stage locates that root file independently and matches its name case-insensitively.
Guard also records the repository provider, host, complete repository path, default branch, exact audited revision, commit link when one can be built, and available history depth. This keeps the target precise for GitHub, GitLab, and repositories with nested group paths. Without that identity, a report becomes ambiguous as soon as a lockfile, image, or workflow changes.
Repository guidance is handled cautiously. Guard applies only relevant facts that agree with the repository evidence and the audit's safety rules. A missing guidance file is neutral, as is guidance that cannot be used safely. Neither state creates a finding or a penalty. A file cannot grant extra permissions, force a score, suppress a finding, or turn a suggested command into something the audit must execute. When guidance materially affects the audit, the report discloses what was applied in ordinary product language.
Generated and machine-local directories such as node_modules, virtual environments, build output, caches, coverage, and vendored scanner databases are excluded by default. They create duplicates and describe the audit machine more often than the tracked repository. A file from one of those locations is included only when it proves a problem that is itself checked in.
What the audit deliberately does not do
The dependency hygiene audit is analysis-only. It does not install or update project dependencies, execute project lifecycle scripts, run project builds or tests, rewrite manifests or lockfiles, create a branch, or open an issue or pull request. Scanner tools may be prepared outside the audited repository, but project dependencies stay untouched. If a conclusion requires project code to run, that check remains explicitly unverified.
Workflow files are read only where they show dependency acquisition, for examplenpm ci, npx, a container image, or a third-party action. Branch protection, release gates, and the general quality of the pipeline belong to a separate CI/CD audit.
The same boundary applies to instructions found inside the repository. They may explain a path, constraint, or intended dependency setup, but they cannot change the requested language, broaden repository access, weaken verification, hide evidence, or authorize a write.
How the audit works
1. Fix the revision, inventory the surface, and check tool readiness
Guard fixes the target revision, detects package managers, and locates manifests, lockfiles, images, install commands, registry settings, and existing SBOM data. Each source is recorded with its ecosystem, path, scope, and resolved source when one is available. Guard also reads root repository guidance as contextual evidence and records whether any part of it materially changed the interpretation.
Tool selection happens after inventory. An npm repository does not need a Rust scanner, and a JVM repository should not be forced to build only to create one more output file. If the repository already produces a trustworthy SBOM, it may offer better evidence than installing a broad toolchain during the audit.
2. Run bounded checks, normalize evidence, and calculate the current score
Matching tools read tracked manifests, lockfiles, SBOM documents, and supported images. Guard avoids commands that change the dependency graph or execute repository code. A tool that is missing, unsupported, or unable to reach a private registry is recorded as a coverage limitation. The rest of the audit continues. This stage independently locates and reads any root guidance again instead of inheriting an earlier interpretation.
Raw rows are normalized to ecosystem, package and version, source path, scope, advisory aliases, fixed versions, direct or transitive status, contributing tools, confidence, and limitations. Repeated output is merged only when the advisory aliases or root cause, package coordinates, scope, and path describe the same problem. Every contributing tool remains attached to the resulting finding.
3. Draft the current result, then make any previous-run comparison
Guard scores the nine criteria, applies deductions and hard caps, then drafts the current summary and report from current-revision evidence before it reads an earlier audit. If a previous report or summary can then be validated and read, Guard reconciles the two runs and explains material movement. If no usable earlier result exists, the current run becomes the comparison baseline. Guard does not invent a prior score or trend. Before publication, this stage checks the root guidance independently once more. The final Limitations disclose once when that file was absent or could not be used, without turning either neutral state into a finding.
Reports
33 / 100July 8, 2026Current
Executive Summary
- Dependency risk is accumulating across build and runtime acquisition paths.
- Score: 33/100. Mutable runtime and build acquisition paths drive the result.
- The full fictional run has three deduplicated High findings and one Medium control gap; two rows appear in this excerpt.
Audit Target
- Repository: github.com/brightyard/checkout-service
- Default branch: main
- Checked commit: 83f4d1c28b7a6e05c2d7f910a4e936bc58d120fa
- Revision confidence: Full history and remote metadata available
What Was Checked
- Tracked manifests and lockfiles, package-manager settings, install paths in CI and Dockerfiles, container references, declared exceptions, and scanner evidence available for this revision.
Findings
| Priority | What was found | Why it matters | Where to look | Next action |
|---|---|---|---|---|
| High | Runtime image uses a mutable tag instead of a digest. | A later build can receive different runtime code from the same revision. | Dockerfile:28 | Pin and verify the resolved image digest. |
| High | Build step resolves an unpinned package with npx. | The build can execute a package version that was never reviewed or locked. | Dockerfile:14 | Declare and lock the build dependency. |
How scanner output earns finding status
Scanner output starts the investigation. It affects the audit only when it matches the audited revision and helps establish the package, version, source path, scope, and available fix. Adding more scanners does not raise the score.
- npm audit provides the package manager's view of the npm dependency tree, advisory data, and possible remediation.
- OSV-Scanner matches supported manifests and lockfiles to OSV advisories and helps reconcile aliases for the same vulnerability.
- Trivy adds language-package, filesystem, SBOM, and container-image evidence.
- Syft can build a software bill of materials from a filesystem or image.
- Grype matches components from directories, images, or SBOM documents against vulnerability data.
Other ecosystems may use their own read-only sources when they fit the repository and are available. Examples include govulncheck, pip-audit, uv audit, cargo audit, cargo deny, bundler-audit, and composer audit.
How a row becomes a finding
Guard first links the row to a dependency source and an installed version. It records whether the package is direct or transitive, where it is used, which fixed versions are known, which tools confirmed it, its confidence, and what the evidence cannot prove. Only a deduplicated Medium- or High-confidence finding categorized as risk or control can reduce the score. Information-only observations, low-confidence notes, and coverage limitations remain visible without a deduction.
Deduplication prevents scanner volume from becoming fake severity. Rows merge only when their advisory aliases or root cause, package coordinates, dependency scope, and source path identify the same problem. If npm audit, OSV-Scanner, Trivy, and Grype all satisfy that test for one esbuild version, the report contains one finding with four attached evidence sources. The score is reduced once. A tool with no confirmed row is still named with the reason its output was absent, irrelevant, or inconclusive.
Why scanner severity is not copied blindly
Critical, High, Medium, Low, and Info are dependency-hygiene priority labels, not claims that an issue is exploitable. Guard considers source path, deploy exposure, fixability, control gaps, and confidence in the evidence. A confirmed runtime or release problem can be High. The same advisory in isolated tooling may be Medium or Low. A mutable runtime image or privileged install path can also be High even when no CVE describes the underlying problem.
The nine scoring criteria
Each criterion receives 0-5 points. A 4 or 5 needs strong evidence of a controlled current state. Scores of 2-3 mark partial or uneven control; 0-1 marks an absent control, an unknown primary surface, or active risk.
- 1
Dependency source inventory
Guard checks whether the main manifests, lockfiles, images, and acquisition paths have been found for every deployable surface. A strong score requires a complete map tied to repository evidence. Missing primary sources score poorly and can cap the final result.
- 2
Reproducible install
The audit asks whether CI, build, and release will resolve the same dependency graph from the audited revision. Frozen installs, lock integrity, and consistent package-manager use improve this criterion. A primary path that can silently resolve new versions weakens it.
- 3
Known vulnerability posture
Affected versions, advisory aliases, fixed versions, and the actual dependency path must be understood. Scanner output alone is not enough. Guard looks for a confirmed package and version in the current repository state, plus a bounded next action when a fix exists.
- 4
Runtime, development, and test separation
A production dependency and a test-only tool do not carry the same exposure. Guard records scope so the report can distinguish runtime, deploy, build, development, test, tooling, container, and CI paths instead of assigning one severity to everything.
- 5
Safe install and acquisition paths
This criterion covers the places where build or deploy can fetch and execute third-party code. Floating container tags, unpinned CI actions, remote scripts, and build-time package resolution matter even when no package-manager advisory describes them.
- 6
Lifecycle script controls
Package installation can execute arbitrary code through lifecycle hooks. Guard checks whether that behavior is visible, necessary, and controlled. Unrestricted install scripts without a documented reason reduce the score even when no malicious package has been confirmed.
- 7
Dependency footprint control
The resolved graph should be bounded and inspectable. Guard considers lock integrity, duplicate footprint, stale components, available SBOM data, container pinning, and whether the dependency set is current enough for its role.
- 8
Managed exceptions
An accepted risk is useful only when it is explicit, narrow, owned, and still valid for the audited revision. Broad suppressions or undocumented exceptions can hide current findings and therefore lower this criterion.
- 9
Evidence quality and deduplication
The conclusion must be supported by sufficient coverage and clean evidence. Guard records skipped tools and limitations, reconciles scanner disagreement, and counts each root cause once instead of treating repeated rows as separate problems.
How the final score is calculated
The nine equally scaled criteria set the baseline. Their 45 available points are converted to a 0-100 score before findings and caps are applied.
criteria_score = round((criteria_sum / 45) * 100)Only deduplicated findings with Medium or High confidence can reduce that baseline. Risk findings describe a confirmed vulnerable dependency or unsafe acquisition path with concrete runtime, deploy, release, or privileged-build exposure.
risk_reduction = min(
critical * 20 + high * 8 + medium * 3 + low * 1,
35
)Control findings describe a current gap without the same confirmed deployable impact, for example lifecycle-script exposure, incomplete package-age controls, or a tooling-only issue.
control_reduction = min(
high * 3 + medium * 1 + low * 0,
8
)
risk_adjusted_score = clamp(
criteria_score - risk_reduction - control_reduction,
0,
100
)
final_score = min(risk_adjusted_score, every_applicable_cap)
final_score = clamp(final_score, 0, 100)Hard caps
Caps are applied after deductions. They prevent one serious stop signal from disappearing inside an otherwise healthy average.
- Any Critical finding caps the score at 39.
- Two or more High findings in runtime, deploy, or privileged acquisition paths cap it at 69.
- A non-reproducible primary CI, build, or release install caps it at 69.
- Unidentified primary dependency sources for the main deployable surface cap it at 59.
- Materially incomplete scanner coverage, when repository evidence is otherwise available, caps it at 79.
When a score exists
A successful audit publishes an integer score from 0 to 100. Missing scanners or private registry access do not automatically erase it: Guard uses static repository evidence, states the limitation, and applies the coverage cap when that evidence is still usable. A repository with no dependency sources can be a legitimate no-action result for docs or configuration, but the same absence in an application or service is an inventory gap and can trigger the source-coverage cap.
Guard does not invent an N/A score. Invalid repository access, a failed clone, an unresolved revision, or missing required inputs produce a diagnostic failure without a score or report. An absent previous audit does not cancel a successful current score.
A worked example: 33 out of 100
The following example is fictional. Its purpose is to make the arithmetic explicit without exposing a customer repository.
26 out of 45 points, scaled and rounded
58Three deduplicated High findings at 8 points each
-24One Medium control finding
-158 - 24 - 1
33In this example, the criteria scores are 5, 3, 2, 3, 2, 2, 3, 2, and 4. They total 26. Three High-risk findings remove 24 points, and one Medium control gap removes 1. The High finding cap does not change the result because 33 is already below 69.
Dependency hygiene
Check whether repository dependencies are accumulating risk or disorder that makes safe development harder, using scanner-backed evidence and key verification gaps.
How to read the result in Guard
The project matrix places Dependency hygiene beside the other audit areas so a team can see which repository has moved out of the green zone. The repository card compresses that result into a status, score, and the Problems, Watch, Healthy, and Skipped counts.
Opening the audit reveals the full evidence: the audited target, current-state answer, nine criteria, deduplicated findings, tool coverage, limitations, score inputs, and improvement checklist. When a readable previous result exists, the report can also state the two revisions or comparison range, what is unchanged, new, or resolved, how confident that comparison is, and why the score moved. Movement must come from repository changes, corrected evidence, changed scope or guidance, or a material methodology change—not cosmetic report churn. Without a readable prior result, the report contains no invented delta or relation. The compact dashboard and detailed report do not use different formulas. They are two levels of detail for the same result.
Scores of 70 and above are in the good band. Scores from 40 through 69 need attention. Scores below 40 indicate a materially unhealthy dependency state. The score is a summary, not a substitute for the evidence that produced it.
Where the audit stops and autofix begins
The audit records the root cause, affected path, confidence, impact, supporting evidence, known fixed versions, and a safe next action. It leaves the project unchanged. Execution begins only in a separate, revalidated improvement run.
Findings that are bounded enough can become candidates for a separate autofix run. Current candidate types include:
- a vulnerable dependency upgrade;
- a lockfile reproducibility fix;
- removal or pinning of an unsafe install path;
- a lifecycle-script control;
- a package-age gate;
- a stale dependency review.
A person chooses which candidate runs and reviews the resulting change. If the evidence is weak, the change is too broad, or the audit cannot define a bounded candidate, Guard keeps the item in the report instead of manufacturing an autofix.
These six types describe the audit handoff, not six currently published executors. Today, the paired Dependency update action handles one dependency root cause at a time and limits its edits to manifests, lockfiles, package-manager configuration, or directly related dependency metadata. Image pinning, workflow-action pinning, unsafe acquisition-path changes, and the other candidate types remain report recommendations unless a separately published action supports them.
Candidates are ordered by operational risk: confirmed fixable Critical or High runtime dependencies first; unsafe privileged acquisition paths second; lockfile and frozen-install gaps on delivery paths third; then stale-dependency reviews. Managed-exception cleanup and unknown-severity checks stay in the report as manual follow-up when the current improvement cannot accept them as a validated handoff. A handoff is still not write permission. The improvement revalidates the current revision before it creates a branch, pull request, or code change, and a person chooses and reviews the work. Dependency update can also discover a bounded current candidate on its own; it does not require an earlier audit or saved handoff to run.
Choose which autofixes to run right now.
Move the confirmed package to its bounded patched release and refresh the lockfile.
Ready to fix.Change the declared version and the directly related package-manager metadata.
Pull request createdUse the smallest supported version change and keep unrelated packages untouched.
Ready to fix.Limits, reruns, and current dependency health
The score carries the boundary of the evidence that produced it:
- No install, build, or test command is executed, so claims that require project code to run remain unverified.
- Private registries and unsupported ecosystems can reduce coverage. The report states what was checked and which missing check could change the decision.
- Scanner severity does not prove reachability. Guard claims runtime impact only when repository evidence or an authorized tool supports it.
- A clean result applies to the audited revision. A new dependency, image tag, workflow, or lockfile can change the score.
- An earlier score is used only when its source can be validated and read. Without one, the current audit is the baseline rather than evidence of improvement or decline.
- Repository guidance can add useful context, but its absence does not reduce the score and its instructions cannot override the audit's evidence or safety boundary.
Keeping the dependency surface in the green zone
Once an approved fix lands, Guard audits the new revision. Later changes can move the project out of the green zone again, so the report remains one checkpoint inside the recurring audit and autofix cycle.
Coding agents inherit the dependency decisions already present in the project. Keeping this surface healthy gives new feature work a clearer, more reproducible base instead of adding another layer over mutable, stale, vulnerable, or poorly understood external code.
Enji Guard