lock

package
v0.6.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 10, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package lock parses dependency lockfiles into a common representation.

Index

Constants

View Source
const SniffBudget = 20

SniffBudget is the default cap PathFilter puts on content-sniff candidates per remote fetch.

Variables

View Source
var CIInstanceHost string

CIInstanceHost is the GitLab instance host the files being parsed run their pipelines on, when the caller knows it. The `mr`/`compare`/`queue` modes set it from the MR or compare URL itself (via ghpr.Result.CIHost) around parsing, and reset it to "" afterwards. When set, component pins written as `$CI_SERVER_FQDN/ns/proj/name@1.2.3` resolve against this host and get the full tag-verification treatment; when empty (local files, other forges) they stay claim-free — a directory on disk does not record which instance runs its pipelines.

Functions

func HostOf added in v0.4.6

func HostOf(raw string) string

HostOf extracts the lowercase host from a URL string; "" when it can't.

func ImageHost added in v0.5.12

func ImageHost(name string) (host, path string)

ImageHost returns the registry host an image name resolves from and the repository path on that registry, following the daemon's rules: no host part means Docker Hub, and single-component Hub names live under library/.

func KnownBasenames

func KnownBasenames() []string

KnownBasenames lists every lockfile filename lockvet understands.

func PathFilter added in v0.5.19

func PathFilter(sniffBudget int) func(string) bool

PathFilter returns a fresh predicate for remote diff fetches: every known lockfile path always passes, and up to sniffBudget other YAML files (SniffableYAML) pass for content sniffing — the cap keeps a big refactor PR from ballooning the number of file downloads. The closure is stateful; make one per fetch and do not share across goroutines.

func Sanitize

func Sanitize(s string) string

Sanitize makes untrusted strings (lockfile- or registry-derived) safe to render: it enforces valid UTF-8 and strips control characters (so hostile input cannot smuggle ANSI escape sequences into terminal output).

func SniffableYAML added in v0.5.19

func SniffableYAML(p string) bool

SniffableYAML reports whether a changed file that no basename or path convention claims is still worth a content sniff as a Kubernetes manifest. GitOps repos keep workload manifests under arbitrary layouts (default/nzbget/nzbget.yaml); the strict top-level apiVersion: + kind: gate inside the parser makes over-matching free, so in diff modes — where the changed-file list is small — every other YAML file qualifies. Helm chart templates stay excluded: their image references are {{ interpolated }}. Directory walks (audit) keep convention-based discovery instead: sniffing every YAML in a large tree would read far more than it finds.

Types

type Ecosystem

type Ecosystem string

Ecosystem is an OSV.dev ecosystem identifier.

const (
	NPM       Ecosystem = "npm"
	CratesIO  Ecosystem = "crates.io"
	PyPI      Ecosystem = "PyPI"
	Go        Ecosystem = "Go"
	Packagist Ecosystem = "Packagist"
	RubyGems  Ecosystem = "RubyGems"
	Hex       Ecosystem = "Hex"
	Pub       Ecosystem = "Pub"
	Maven     Ecosystem = "Maven"
	NuGet     Ecosystem = "NuGet"
	SwiftURL  Ecosystem = "SwiftURL"
	CocoaPods Ecosystem = "CocoaPods"

	// CRAN and Bioconductor cover R packages from renv.lock. A single
	// renv.lock mixes both; Bioconductor packages are marked per-package
	// via File.PkgEco.
	CRAN         Ecosystem = "CRAN"
	Bioconductor Ecosystem = "Bioconductor"

	// Nix has no OSV.dev ecosystem and no semver: flake inputs pin git
	// revisions. lockvet still explains what moved and by how much time.
	Nix Ecosystem = "Nix"

	// Conda covers pixi.lock and conda-lock.yml. Conda channels have no
	// OSV.dev ecosystem or deps.dev coverage, so conda packages get diff,
	// graph and version-jump treatment only; pip/pypi packages inside the
	// same lockfile are marked PyPI per-package (File.PkgEco) and get
	// full vulnerability, age and deprecation data.
	Conda Ecosystem = "conda"

	// Terraform providers (.terraform.lock.hcl, Terraform & OpenTofu)
	// and Helm charts (Chart.lock) have no OSV.dev ecosystem or deps.dev
	// coverage: changes are diffed, classified and explained without
	// vulnerability or age claims.
	Terraform Ecosystem = "Terraform"
	Helm      Ecosystem = "Helm"

	// Julia covers Manifest.toml (General registry packages and stdlibs;
	// OSV ecosystem "Julia"). Hackage covers stack.yaml.lock and
	// cabal.project.freeze/cabal.config (OSV ecosystem "Hackage").
	Julia   Ecosystem = "Julia"
	Hackage Ecosystem = "Hackage"

	// GitHubActions covers pkg:github purls in SBOMs (OSV ecosystem
	// "GitHub Actions").
	GitHubActions Ecosystem = "GitHub Actions"

	// Conan covers conan.lock (C/C++). "ConanCenter" is a valid OSV
	// ecosystem (currently near-empty — advisories will surface here
	// automatically if it fills in); deps.dev has no Conan system, so
	// internal/conanreg is the metadata layer for these lockfiles.
	Conan Ecosystem = "ConanCenter"

	// Bazel covers MODULE.bazel.lock (bzlmod). There is no OSV.dev
	// ecosystem and no deps.dev system for Bazel modules; internal/bzlreg
	// reads the Bazel Central Registry directly (yanked versions with
	// reasons, version lists, source repositories).
	Bazel Ecosystem = "Bazel"

	// Zig covers build.zig.zon: Zig has no lockfile beyond it, no central
	// registry, and no OSV.dev ecosystem. Dependencies pin source archive
	// URLs plus content hashes, so lockvet explains bumps and checks the
	// pins themselves (integrity, resolution source) without registry or
	// vulnerability claims.
	Zig Ecosystem = "Zig"

	// Docker covers container base-image pins: Dockerfile / Containerfile
	// FROM lines and Compose file image: values. There is no OSV.dev
	// ecosystem for whole images; internal/ocireg verifies tags and
	// digest pins against the image registries themselves (Docker Hub
	// ages included).
	Docker Ecosystem = "Docker"

	// PreCommit covers .pre-commit-config.yaml `rev:` pins: each entry
	// names a hook repository (on any git forge — names keep their host)
	// at an exact tag or commit that pre-commit clones and runs. No
	// OSV.dev ecosystem; internal/actreg verifies revs against the hook
	// repositories' real tags.
	PreCommit Ecosystem = "pre-commit"

	// Ansible covers Galaxy requirements.yml files: collections
	// (namespace.name) and classic roles (owner.name), which OSV.dev and
	// deps.dev both lack entirely. internal/ansreg reads galaxy.ansible.com
	// itself (the v3 collection index and v1 role index): release ages,
	// collection deprecation, registry-verified unlisted versions, source
	// repositories. Roles are marked per-package via File.PkgEco.
	Ansible     Ecosystem = "Ansible Galaxy"
	AnsibleRole Ecosystem = "Ansible Galaxy role"

	// GitLabCI covers .gitlab-ci.yml `include: component:` pins (CI/CD
	// Catalog components) and `include: project:` refs. Component pins
	// are verified against the component project's real tags
	// (internal/actreg); project includes stay claim-free — the file
	// never records which GitLab instance hosts them. Image pulls in the
	// same file are marked Docker per-package (File.PkgEco).
	GitLabCI Ecosystem = "GitLab CI"

	// CircleCI covers .circleci/config.yml `orbs:` pins: reusable CI
	// packages published to the CircleCI orb registry at exact semver
	// versions (Renovate's circleci manager bumps them like lockfile
	// entries, and the orb's code runs in every pipeline). No OSV.dev
	// ecosystem and no deps.dev system; internal/orbreg asks the orb
	// registry itself for ages, full version lists (unlisted detection)
	// and source repositories. Docker executor image pulls in the same
	// file are marked Docker per-package (File.PkgEco).
	CircleCI Ecosystem = "CircleCI"

	// SBOMEco is the file-level ecosystem of an SBOM: a single CycloneDX
	// or SPDX document mixes ecosystems, so each package carries its own
	// (File.PkgEco) and this value is only a label / fallback.
	SBOMEco Ecosystem = "SBOM"

	// GradleDist is the Gradle distribution itself, pinned by
	// gradle-wrapper.properties: no OSV ecosystem and no deps.dev
	// system — internal/gradlereg verifies pins against
	// services.gradle.org (ages, broken releases, unlisted versions,
	// official checksums). Maven's wrapper pins, by contrast, are
	// ordinary Maven coordinates and use the Maven ecosystem.
	GradleDist Ecosystem = "Gradle"

	// MiseTool covers asdf/mise toolchain pins (.tool-versions,
	// mise.toml): each entry pins the exact tool release a project runs.
	// No OSV ecosystem and no registry — internal/actreg verifies pins
	// against the tool's own repository tags via a curated tool→repo map
	// (per-tool tag conventions: go1.23.4, ruby v3_3_4, OTP-27.1).
	// Backend-prefixed entries (npm:, cargo:, pipx:, gem:, dotnet:, go:)
	// are real registry packages and are marked per-package via
	// File.PkgEco; gradle/maven/sbt map onto their existing registries.
	MiseTool Ecosystem = "mise/asdf"

	// Vcpkg covers vcpkg.json / vcpkg-configuration.json (C/C++). There
	// is no OSV ecosystem and no deps.dev system — internal/vcpkgreg is
	// the metadata layer: baseline commits are verified against the
	// registry repository itself (existence + commit date via the GitHub
	// API) and override pins against the registry's append-only versions
	// database.
	Vcpkg Ecosystem = "vcpkg"
)

func ToolEntryEco added in v0.6.0

func ToolEntryEco(tool string) (name string, eco Ecosystem, nonRegistry, ok bool)

ToolEntryEco maps an asdf/mise tool name to the package name and ecosystem lockvet records it as: backend-prefixed names onto their real registry ecosystems (npm:prettier → npm), gradle/maven/sbt onto the registries that already verify them, everything else onto the "mise/asdf" ecosystem. ok=false means the entry names nothing usable. nonRegistry marks plugin-sourced tools whose pins claim nothing.

func (Ecosystem) HasOSV

func (e Ecosystem) HasOSV() bool

HasOSV reports whether the ecosystem can be queried on OSV.dev. This is a whitelist: SBOMs introduce open-ended ecosystem strings (Linux distro packages, unknown purl types) and OSV rejects a whole batch when one query names an invalid ecosystem.

func (Ecosystem) HasSemver

func (e Ecosystem) HasSemver() bool

HasSemver reports whether version-jump levels (major/minor/patch) are meaningful for the ecosystem. Nix pins git revisions; Debian/Ubuntu/RPM version strings carry epochs and distro revisions where semver labels would be noise.

func (Ecosystem) PublicRegistryHost added in v0.4.6

func (e Ecosystem) PublicRegistryHost(h string) bool

PublicRegistryHost reports whether the host is the ecosystem's canonical public registry (or its official CDN). Used to tell "resolution moved to the public registry" — the dependency-confusion direction — apart from ordinary mirror hops.

type File

type File struct {
	Path      string
	Kind      string // e.g. "package-lock.json"
	Ecosystem Ecosystem
	Packages  map[string][]string

	// Dependency-graph info, filled in only when the lockfile format
	// records it. Deps maps package name -> names it depends on.
	// Roots are the project's *direct* dependencies when the lockfile
	// itself says so (npm root entry, pnpm importers, Gemfile.lock
	// DEPENDENCIES, go.mod without "// indirect", ...).
	// RootsKnown distinguishes "no roots recorded" from "empty roots".
	Deps       map[string][]string
	Roots      []string
	RootsKnown bool

	// PkgEco overrides the file-level Ecosystem per package. Only SBOMs
	// use it: one CycloneDX/SPDX document mixes npm, PyPI, distro
	// packages and more. nil for ordinary lockfiles.
	PkgEco map[string]Ecosystem

	// NonRegistry marks packages the lockfile itself says do NOT come
	// from the format's public registry: workspace members, path and
	// git dependencies, alternate indexes. Registry-metadata checks
	// (like the unlisted-version flag) skip them.
	NonRegistry map[string]bool

	// PkgChannel records the registry "channel" a package resolves
	// from, when the lockfile names one: the conda channel
	// (conda.anaconda.org/<channel>/… artifact URLs) for the conda
	// formats, or the chart repository URL for Helm charts. The
	// condareg/helmreg layers key their lookups on it. nil elsewhere.
	PkgChannel map[string]string

	// PkgRepo records the source repository URL the lockfile itself
	// resolves a package from, when the format pins repositories rather
	// than registry releases (Nix flake inputs). diffx copies it onto
	// Change.SourceRepo so compare links work without any registry
	// lookup. nil elsewhere.
	PkgRepo map[string]string

	// PkgYanked records versions the lockfile ITSELF admits are yanked
	// from their registry, keyed "name@version" → reason. Only Bazel's
	// MODULE.bazel.lock fills it (selectedYankedVersions, written when
	// the build passes --allow_yanked_versions): a fully-offline
	// deprecation signal. nil elsewhere.
	PkgYanked map[string]string

	// PinsOnly marks files whose whole job is recording pins for a
	// sibling manifest (go.sum next to go.mod): version churn in them
	// duplicates the manifest's rows, so diffing surfaces ONLY
	// same-version repins — the tampered-hash shape — and nothing else.
	PinsOnly bool

	// Pins records what the lockfile itself pins for a (package, version)
	// beyond the version string: the content hash it expects and the
	// registry host it resolves from. Filled only by formats that write
	// integrity hashes or resolution URLs (npm, pnpm, yarn, Cargo, uv,
	// poetry, Pipfile, requirements --hash, Gemfile.lock remotes).
	// name → version → meta.
	Pins map[string]map[string]PinMeta
}

File is a parsed lockfile: package name -> set of pinned versions. A lockfile may legitimately contain multiple versions of one package (npm nesting, cargo duplicate majors), hence the set.

func (*File) Pin added in v0.4.6

func (f *File) Pin(name, version string) PinMeta

Pin returns the recorded integrity/host metadata for (name, version), or a zero PinMeta when the format records none.

type Parser

type Parser struct {
	Kind      string
	Ecosystem Ecosystem
	Parse     func(p string, data []byte) (*File, error)
}

Parser turns lockfile bytes into a File.

func ByBasename

func ByBasename(p string) *Parser

ByBasename returns the parser responsible for a given file path, or nil. Both forward and backward slashes separate: forge and git paths always use "/", but file mode (`lockvet diff`, MCP vet_files) gets OS paths, and no supported lockfile basename contains a backslash.

func FallbackParser added in v0.5.0

func FallbackParser(name string) *Parser

FallbackParser guesses a parser for a file whose name is not a known lockfile basename (explicit two-file diffs, playground drops): YAML files are tried as CI workflows — strict: at least one `uses:` pin — then as Kubernetes manifests (top-level apiVersion: and kind: required), so a mis-named real lockfile still gets a helpful error — and everything else goes to SBOM content sniffing.

func SBOMParser added in v0.1.16

func SBOMParser() *Parser

SBOMParser returns the format-sniffing SBOM parser. `lockvet diff` uses it for files whose names aren't recognizable lockfile names.

func SniffParser added in v0.5.19

func SniffParser() *Parser

SniffParser parses files admitted by SniffableYAML: Kubernetes YAML gets the manifest treatment, anything else parses to an empty file.

type PinMeta added in v0.4.6

type PinMeta struct {
	// Integrity is a space-joined set of content hashes in whatever
	// notation the format uses ("sha512-…", "sha256:…", bare hex,
	// yarn-berry "10c0/…"). A set because Python lockfiles record one
	// hash per artifact (sdist + wheels) for the same version.
	Integrity string
	// Host is the registry/CDN host the package resolves from, e.g.
	// "registry.npmjs.org". Empty when the format doesn't record it.
	Host string
}

PinMeta is the integrity/resolution metadata a lockfile records for one pinned (package, version).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL