tools

package
v0.125.0 Latest Latest
Warning

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

Go to latest
Published: Sep 15, 2026 License: Apache-2.0 Imports: 26 Imported by: 0

Documentation

Overview

Package tools describes the external command-line scanners Draugr orchestrates and detects whether they are installed. It backs `draugr doctor`, an explicit preflight so a missing tool is reported up front with an install hint, instead of surfacing as a buried "executable file not found" error mid-scan.

Detection only ever reads the environment (looks on PATH, runs a version probe). It never downloads or installs anything. Provisioning is a separate, opt-in step.

Index

Constants

View Source
const (
	CategoryScanner = "scanner"
	CategoryUtility = "utility"
)

Tool categories.

Variables

View Source
var ErrRuntimeMissing = errors.New("this host does not have the runtime the tool is built with")

ErrRuntimeMissing marks an install that failed because this host lacks the runtime a tool is built with, rather than because of the tool, the network or the pin.

The distinction is what lets `draugr tools install` with no arguments mean "everything this host can have". Asking for a named tool and being told it worked has to stay a guarantee, so a named install still fails; asking for what is available should not refuse nine installs to report that a tenth needs a toolchain nobody asked for.

Three runtimes reach this: the Go toolchain, npm and Python. Each is absent or too old in the same way and for the same kind of reason.

Functions

func BinDir added in v0.8.0

func BinDir() (string, error)

BinDir is Draugr's managed tool directory, ~/.draugr/bin.

func Catalog

func Catalog() map[string]Tool

Catalog returns the external tools Draugr's built-in scanners use, keyed by binary name. Several scanners share one binary (trivy backs images, sca, and iac), so the catalog is keyed by the binary rather than the scanner.

func DataDirFor added in v0.55.0

func DataDirFor(name string) string

DataDirFor returns where a tool's supporting files live, or "" when it has none.

func DataRoot added in v0.55.0

func DataRoot() (string, error)

DataRoot is where tools that need more than a binary keep it, one directory per tool.

Beside bin/ rather than inside it: a directory of YAML on PATH is confusing, and a tool's data has a different lifetime from its binary. Reinstalling one should not silently orphan the other somewhere a reader has to guess at.

func DescribeFor added in v0.77.0

func DescribeFor(l Level, tool string) string

DescribeFor renders a level for one named tool.

"Draugr did not install it" is true of everything external and misleading for some of it: a tool Draugr cannot provision was never a candidate, and reporting an omission invites somebody to go and fix it with a command that will not work. So the line differs by whether Draugr could have installed this one, which now includes the tools it obtains as Python packages.

func GoVersion added in v0.114.0

func GoVersion(name string) string

GoVersion is the pinned version of a tool built with the Go toolchain.

func GovulncheckVersion added in v0.114.0

func GovulncheckVersion(out []byte) string

GovulncheckVersion reads govulncheck's own version from its `-version` output.

That output names two versions, the toolchain's first:

Go: go1.26.7
Scanner: govulncheck@v1.5.0

Taking the first semver-looking token reports the Go toolchain as the scanner's version. A number that is real, plausible, and about something else, which then travels into every report the scanner produces. The Scanner line is read instead, and an output without one yields nothing rather than the wrong thing.

func GrypeDBOK added in v0.91.0

func GrypeDBOK(out []byte) (bool, string)

GrypeDBOK reports whether Grype has a usable vulnerability database, and when it was built.

`valid` is Grype's own verdict and the only one worth reporting: a database file can exist, be readable, and still be one Grype will refuse to scan with because it is too old or its schema belongs to a version of the tool that is no longer served.

func Installable added in v0.8.0

func Installable() []string

Installable returns the names of the tools `tools install` can provision, sorted.

func Latest added in v0.123.0

func Latest(ctx context.Context, client *http.Client, name string) (string, error)

Latest is the newest version an upstream publishes for a tool.

func ManagedVersion added in v0.114.0

func ManagedVersion(name string) string

ManagedVersion is the pinned version of a tool provisioned through one of the language-package paths, or "" for anything obtained as a release archive or not managed at all.

One place to ask, because the callers that need it, the install plan, `tools list`, the up-to-date check, each named the paths individually, and a path added later joined none of them.

func NodeVersion added in v0.94.0

func NodeVersion(name string) string

NodeVersion is the pinned version of a tool obtained as an npm package.

func NucleiTemplatesOK added in v0.52.0

func NucleiTemplatesOK(out []byte) (bool, string)

NucleiTemplatesOK reports whether Nuclei has a template set, and which. Exported because the scanner checks the same thing after asking Nuclei to download one, the tool exits 0 either way, so the answer has to come from the same place `doctor` gets it.

func PinnedVersion added in v0.123.0

func PinnedVersion(name string) string

PinnedVersion is the version Draugr installs for a tool, whichever way it is packaged.

func PlatformKey added in v0.63.0

func PlatformKey() string

PlatformKey is the key into InstallSpec.Assets for the machine Draugr is running on.

func Provisionable added in v0.89.0

func Provisionable(tool string) bool

Provisionable reports whether `draugr tools install` can obtain this tool, by either method.

func PythonVersion added in v0.89.0

func PythonVersion(name string) string

PythonVersion is the pinned version of a tool obtained as a Python package.

func RuntimeMissing added in v0.123.0

func RuntimeMissing(err error) error

RuntimeMissing marks an error as one of those without changing what it says.

Wrapping with %w would append the sentinel's own sentence to a message that has already said the same thing more precisely, and the reader gets the specific one. This keeps the text theirs and makes the classification something the caller can test for.

func SemgrepVersion added in v0.8.0

func SemgrepVersion() string

SemgrepVersion is the pinned Semgrep version.

Types

type Asset added in v0.8.0

type Asset struct {
	URL string
	// URLTemplate renders the download URL for a version other than the pinned one, with
	// {version} substituted (no leading "v"). Empty means Draugr cannot fetch another version of
	// this platform's build, and will say so rather than guessing at a URL shape.
	URLTemplate     string
	SHA256          string
	BinaryInArchive string // name of the binary within the .tar.gz; "" = the download is the binary
	// DataInArchive is a directory prefix inside the archive to extract alongside the binary,
	// e.g. "cfg/" for kube-bench's benchmark definitions.
	//
	// Some tools are not one file. kube-bench without its cfg/ tree exits complaining about a
	// missing "target_mapping" section, which names an internal structure rather than the 276 files
	// nobody copied, so installing the binary alone is a half-install that looks whole.
	DataInArchive string
}

Asset is one platform's download for an installable tool. When BinaryInArchive is empty the downloaded file is the binary itself (a bare release binary, e.g. cosign), not an archive.

type Attestation added in v0.63.0

type Attestation struct {
	// Tool is the executable's name, e.g. "trivy".
	Tool string
	// Path is where it was found. Empty when it was not.
	Path string
	// Version is what the tool reports, when Draugr can read it.
	Version string
	// Level is how strongly Draugr can vouch for this binary.
	Level Level
	// Reason says why the level is what it is, when it is not the strongest.
	Reason string
}

Attestation is what Draugr can say about the build of a tool a scan used.

A scan runs whatever is on PATH, which is right: an operator may have an experimental build, a fork, or a distribution package with a vendor suffix, and each is a case where they know something Draugr does not. But a report that cannot say *which* build produced its findings cannot be reproduced, so the answer is to record it rather than to refuse it.

func Attest added in v0.63.0

func Attest(tool, path, version, binDir string) Attestation

Attest reports what Draugr knows about the build of a tool.

Attested means all three: the binary sits in Draugr's own directory, the install manifest has a record for it, and the file's hash still matches what was recorded. Anything else is used and labeled, a binary somewhere else on PATH, one Draugr never installed, or one that has changed since it did.

The hash check is the one that earns its place. Without it "attested" would mean "in the right directory", which is a claim about a path rather than about a file.

func AttestFound added in v0.63.0

func AttestFound(tool, version string) Attestation

AttestFound looks a tool up on PATH and attests to what it finds.

type CosignSpec added in v0.13.0

type CosignSpec struct {
	// ChecksumsURL is the upstream's signed checksums file, listing each asset's SHA-256.
	ChecksumsURL string
	// ChecksumsURLTemplate and BundleURLTemplate render those URLs for another version.
	ChecksumsURLTemplate string
	BundleURLTemplate    string
	// BundleURL is the Sigstore bundle (.sigstore.json) signing ChecksumsURL.
	BundleURL string
	// IdentityRegexp is the required signing certificate identity (--certificate-identity-regexp).
	IdentityRegexp string
	// OIDCIssuer is the required OIDC issuer (--certificate-oidc-issuer).
	OIDCIssuer string
}

CosignSpec describes how to verify a tool release's provenance with cosign, for upstreams that publish a keyless signature over their checksums file. It is optional and additive: the SHA-256 pin remains the mandatory integrity floor; cosign proves the checksums file was signed by the upstream's expected release identity. Verification uses the cosign CLI (no Go sigstore dependency) and the new Sigstore bundle format.

type Drift added in v0.123.0

type Drift struct {
	// Tool is the tool's name as Draugr and its own command line spell it.
	Tool string
	// Pinned is the version this build installs.
	Pinned string
	// Latest is what the upstream publishes now, empty where the question could not be answered.
	Latest string
	// Err is why Latest is empty. A network that refused is a different answer from "current",
	// and reporting the second for the first is how a checker comes to report everything current
	// while reaching nothing.
	Err error
}

Drift is one tool, what Draugr pins, and what its upstream publishes.

func Outdated added in v0.123.0

func Outdated(ctx context.Context, client *http.Client) []Drift

Outdated compares every pinned tool against its upstream, in name order.

One entry per tool whatever happened, including the ones that could not be reached. A list that silently omits a tool nobody could ask about is a list that says everything is fine.

func (Drift) Behind added in v0.123.0

func (d Drift) Behind() bool

Behind reports whether the upstream has moved past the pin.

Only a difference, never an ordering. Version schemes here are not comparable to each other and a pin ahead of its upstream is a real state, a release withdrawn after Draugr pinned it, which somebody needs to see rather than have reported as current.

type GoSpec added in v0.114.0

type GoSpec struct {
	// Command is the package path of the command to build.
	Command string
}

GoSpec describes a tool built from its module with the Go toolchain.

Some tools publish no release binary at all. govulncheck is distributed only as a package path, `go install golang.org/x/vuln/cmd/govulncheck`, with no archives on any release page, which is the same position Semgrep is in on PyPI and retire.js is in on npm. A tool Draugr asks a control to run and then cannot obtain is a control that needs a separate installation story, and most people will simply not have the control.

func GoTool added in v0.114.0

func GoTool(name string) (GoSpec, bool)

GoTool reports the spec for a tool built with the Go toolchain.

type InstallSpec added in v0.8.0

type InstallSpec struct {
	Binary  string
	Version string
	// ChecksumsURLTemplate is an *unsigned* checksums file for an arbitrary version, for an
	// upstream that publishes one but signs nothing. Weaker than Cosign and much better than
	// nothing: it catches a corrupted or truncated download.
	ChecksumsURLTemplate string
	Assets               map[string]Asset
	// Cosign, when set, verifies the release's provenance in addition to the SHA-256 pin. Nil for
	// upstreams that publish no signature (e.g. gitleaks). Those stay SHA-256-only.
	Cosign *CosignSpec
	// DataDir is where Asset.DataInArchive is written, relative to Draugr's own directory.
	// Namespaced by tool so a second tool with data files does not collide with the first.
	DataDir string
}

InstallSpec pins an installable tool to a version and its per-platform assets, keyed by "GOOS/GOARCH" (e.g. "linux/amd64").

func Spec added in v0.8.0

func Spec(name string) (InstallSpec, bool)

Spec returns the pinned install spec for a tool.

func SpecFor added in v0.63.0

func SpecFor(name, version string) (InstallSpec, error)

SpecFor returns the install spec for a requested version.

An empty version, or the one Draugr ships, returns the pinned spec unchanged, recorded SHAs, no network needed to know what to expect. Any other version returns a spec with URLs rendered from the templates and **no recorded SHA**, which is what tells Install to verify differently.

Draugr does not refuse a version it cannot vouch for. Refusing would be blocking somebody who knows something Draugr does not, an experimental build, a fork, a version newer than this release. It installs what was asked for and records how well it could check it, and that record travels into every report the tool goes on to produce.

type Installed added in v0.8.0

type Installed struct {
	Name    string
	Version string
	Path    string
	// SignatureVerified is true when an upstream cosign signature was verified (in addition
	// to the always-checked SHA-256 pin).
	SignatureVerified bool
	// ProvenanceNote summarizes the signature outcome for reporting (e.g. why it was skipped);
	// empty when the tool has no cosign provenance configured.
	ProvenanceNote string
	// AlreadyPresent is true when the pinned build was already installed and left untouched.
	AlreadyPresent bool
}

Installed describes a successfully provisioned tool.

func Install added in v0.8.0

func Install(ctx context.Context, name, destDir string, client *http.Client, force bool) (Installed, error)

Install downloads the pinned build of name, verifies its SHA-256, extracts the binary, and installs it into destDir with an executable bit. client may be nil (a default is used). The download is verified before anything is written, and the binary is placed atomically. Install provisions a pinned tool into destDir. A tool already present at exactly the pinned build is left alone unless force is set. See the install manifest below.

func InstallVersion added in v0.63.0

func InstallVersion(ctx context.Context, name, version, destDir string, client *http.Client, force bool) (Installed, error)

InstallVersion installs a specific version, or the pinned one when version is empty.

would hide the order, and the order is the security property.

type Kind added in v0.123.0

type Kind string

Kind is a way of finding the newest published version.

const (
	// KindGitHubRelease reads the repository's latest *release*, which is what most projects
	// publish and what their users are told to install.
	KindGitHubRelease Kind = "github-release"
	// KindGitHubTag reads the newest semver *tag* instead.
	//
	// For a repository holding several modules the release marked latest is whichever was
	// published most recently, which is not the newest version of the command Draugr installs:
	// golang/vuln answers v1.1.4 there while its newest tag is v1.8.0. A resolver that trusted
	// the release would hold govulncheck seven minor versions back and report itself current.
	KindGitHubTag Kind = "github-tag"
	// KindPyPI reads the version PyPI serves as current.
	KindPyPI Kind = "pypi"
	// KindNPM reads the version tagged `latest` in the npm registry.
	KindNPM Kind = "npm"
)

type Level added in v0.63.0

type Level string

Level is how strongly Draugr can vouch for a binary, strongest first.

Not a boolean, because the claims are genuinely different. An unsigned checksums file proves a download was not corrupted or truncated and that the host served consistent content; it does not prove the upstream published it. That is weaker than a signature and much stronger than nothing, and collapsing the two throws away a difference a reader may care about.

const (
	// LevelPinned: installed at the version Draugr ships, matching a SHA-256 recorded in this
	// binary. The only level that needs no network to verify.
	LevelPinned Level = "pinned"
	// LevelSigned: installed at another version, matching checksums signed by the upstream's
	// Sigstore identity.
	LevelSigned Level = "signed"
	// LevelChecksum: installed, matching an unsigned checksums file fetched from the upstream.
	LevelChecksum Level = "checksum"
	// LevelUnverified: installed with nothing published to check it against.
	LevelUnverified Level = "unverified"
	// LevelExternal: not installed by Draugr. Found on PATH, and used, the operator brought it, and
	// they know something Draugr does not.
	LevelExternal Level = "external"
)

The levels, strongest first.

func (Level) Describe added in v0.63.0

func (l Level) Describe() string

Describe renders a level for a reader who has not read this file.

func (Level) Vouched added in v0.63.0

func (l Level) Vouched() bool

Vouched reports whether Draugr installed this binary at all, at any level.

type LookPathFunc

type LookPathFunc func(string) (string, error)

LookPathFunc resolves a binary name to a path (defaults to exec.LookPath).

type NodeSpec added in v0.94.0

type NodeSpec struct {
	// Package is the name on npm.
	Package string
	// Pins names the generated pair under nodepins/, without the suffixes.
	Pins string
	// Command is the executable the package provides under node_modules/.bin.
	Command string
}

NodeSpec describes a tool obtained as an npm package.

func NodeTool added in v0.94.0

func NodeTool(name string) (NodeSpec, bool)

NodeTool reports the spec for a tool obtained as an npm package.

type PythonSpec added in v0.89.0

type PythonSpec struct {
	// Package is the distribution name on PyPI.
	Package string
	// Pins names the generated requirements file under pythonpins/, without the extension.
	Pins string
	// MinPythonMinor is the oldest Python 3 minor this package supports.
	MinPythonMinor int
}

PythonSpec describes a tool that ships as a Python package rather than a release binary.

Some scanners publish no binary at all: Semgrep's GitHub releases carry zero assets, and it exists as PyPI wheels, a Docker image and a Homebrew formula. Rather than leaving those tools outside `tools install`. Which makes one control's provisioning everybody's special case, Draugr builds a virtual environment it owns and installs the pinned set into it.

The verification floor is the same one a release archive gets, and there is more of it: every artifact in the resolved tree carries the SHA-256 PyPI publishes, so the transitive dependencies are pinned too, which a single checksum over one archive does not do.

func PythonTool added in v0.89.0

func PythonTool(name string) (PythonSpec, bool)

PythonTool reports the spec for a tool obtained as a Python package.

type RunFunc

type RunFunc func(ctx context.Context, argv []string, env ...string) ([]byte, error)

RunFunc executes argv and returns its output (defaults to running the command).

type Status

type Status struct {
	Tool    Tool
	Found   bool
	Path    string
	Version string
	// Err is set when the tool was found but the version probe failed (non-fatal).
	Err error
	// DataFound reports whether the tool's supporting data is present. Meaningless unless the
	// tool declares DataArgs; DataChecked says whether it was asked.
	DataChecked bool
	DataFound   bool
	// DataDetail describes what was found, e.g. a template-set version.
	DataDetail string
}

Status is the outcome of detecting a Tool.

func Detect

func Detect(ctx context.Context, t Tool, lookPath LookPathFunc, run RunFunc) Status

Detect reports whether a tool is on PATH and, if so, its version. lookPath and run are injectable for testing; nil uses the real environment.

type Tool

type Tool struct {
	// Binary is the executable name looked up on PATH, e.g. "trivy".
	Binary string
	// VersionArgs prints the tool's version, e.g. ["--version"]. Empty skips the probe.
	VersionArgs []string
	// InstallHint tells the user how or where to install the tool when it's missing.
	InstallHint string
	// Category groups the tool: "scanner" (backs a control) or "utility" (supporting tool
	// like git or cosign). Shown in `tools list`.
	Category string
	// Optional marks a tool whose absence should not fail `doctor`. A nice-to-have that enhances
	// behavior (e.g. cosign for signature verification) rather than a requirement.
	Optional bool
	// VersionFrom reads the version out of the probe's output, for a tool that names more than
	// one. Nil takes the first semver-looking token, which is right for almost everything.
	VersionFrom func([]byte) string
	// ProbeEnv is layered over the environment for the probes below, each "K=V".
	//
	// For a tool that treats "what version are you" as an opportunity to call home. Semgrep does:
	// `semgrep --version` contacts semgrep.dev to see whether a newer release exists, and Draugr
	// asks on every scan, so the question would reach the network on a machine that has said it
	// has none. Disabling that returns the same answer.
	ProbeEnv []string
	// DataArgs probes for data the tool needs beyond its own binary, Nuclei's template set, for
	// instance. Empty means the binary is all there is.
	//
	// Being on PATH is not the same as being able to run. A tool whose data is missing fails at
	// scan time with a message about a symptom, and `doctor` exists to answer "is this going to
	// fail" before the scan rather than after it.
	DataArgs []string
	// DataFiles are paths that must exist for the tool to work, tried in order. Used where the
	// tool cannot be asked cheaply: kube-bench only reveals a missing `cfg/` by attempting a
	// benchmark, and doctor must not run a scan to find out whether a scan would work.
	DataFiles []string
	// DataOK reads DataArgs' output and reports whether the data is there, plus a short
	// description for the report. Required when DataArgs is set.
	DataOK func(out []byte) (ok bool, detail string)
	// DataHint tells the user how to obtain the data when it is missing.
	DataHint string
}

Tool describes an external executable a scanner shells out to.

func All

func All() []Tool

All returns the catalog's tools sorted by binary name, for a full environment check when no Saga narrows the set.

type Upstream added in v0.123.0

type Upstream struct {
	// Kind is how the newest version is read. Each is a different answer to the same question and
	// the difference is not cosmetic: see KindGitHubTag.
	Kind Kind
	// Ref names the thing to ask about: "owner/repo" on GitHub, a package name on PyPI or npm.
	Ref string
}

Upstream says where a tool's releases are published and how to read the newest one.

In Draugr rather than in a workflow, so one implementation answers both a person asking what is out of date and a pipeline proposing a bump. The alternative is release-API handling written twice, in Go and in YAML, where only the half somebody runs by hand stays true.

func UpstreamFor added in v0.123.0

func UpstreamFor(name string) (Upstream, bool)

UpstreamFor reports where a tool is published.

Directories

Path Synopsis
cmd
pinbump command
Command pinbump moves one pinned scanner to a new version and records what that version hashes to.
Command pinbump moves one pinned scanner to a new version and records what that version hashes to.

Jump to

Keyboard shortcuts

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