Configuring requirements
Write a policy that enforces the criteria your project actually needs.
This guide walks through writing the [requirement] block of an
openvet.toml: how to start small, how to tighten, and how to handle
the dependencies that don’t immediately pass. For the field-by-field
reference of the requirement language, see
Requirement syntax. For the full
list of claims you can use, see
Claims vocabulary.
Where requirements live #
Requirements live in the [requirement] block of openvet.toml. Each
entry is a named boolean expression over claim names.
The shortest form is bare:
[requirement]
benign = "is-benign"
A bare entry applies to every subject by default. To make a
requirement opt-in instead, use the full form and set default = false:
[requirement]
benign = "is-benign"
sandbox = { condition = "uses-filesystem and uses-network", default = false }
An opt-in requirement only applies to subjects that explicitly include it via an override.
From minimum to useful to maximum #
You can configure OpenVet anywhere on a spectrum from “barely cares” to “requires everything.” Where you sit depends on what your project does and what untrusted code it handles.
The minimum. Just require that nothing in your tree is actively malicious:
[requirement]
benign = "is-benign"
This says: “I don’t care what the package does, but it should not be malicious.” Loose, but legitimate. A toy project, a research script, or a tool that processes only trusted local data may not need more.
The useful minimum. For most projects, the next step up is to
require safety for each behaviour the dependency actually exercises.
The pattern is <discovery-claim> implies <safety-claim>:
[requirement]
benign = "is-benign"
unsafe-reviewed = "uses-unsafe implies unsafe-safe"
network-reviewed = "uses-network implies network-safe"
filesystem-reviewed = "uses-filesystem implies filesystem-safe"
crypto-reviewed = "uses-crypto implies crypto-safe"
crypto-impl-reviewed = "impl-crypto implies crypto-impl-safe"
build-exec-reviewed = "has-build-exec implies build-exec-safe"
This is a reasonable default starting point for a real project: every dependency must be benign, and any dependency that does something sensitive must have had that aspect reviewed. Dependencies that don’t touch a given surface are silent on the corresponding requirement (unknown is fine when the condition isn’t met).
The maximum. For a tighter posture, require not just safety but also correctness and testing for everything applicable:
[requirement]
benign = "is-benign"
unsafe-reviewed = "uses-unsafe implies unsafe-safe and unsafe-tested"
crypto-impl-strict = "impl-crypto implies crypto-impl-safe and crypto-impl-correct and crypto-impl-tested"
parser-impl-strict = "impl-parser implies parser-impl-safe and parser-impl-correct and parser-impl-tested"
network-strict = "uses-network implies network-safe and network-secure"
build-exec-strict = "has-build-exec implies build-exec-safe and build-exec-no-network and build-exec-minimal"
# ... and similarly for every other category
This is the strictest posture you can express with the canonical vocabulary. In practice few real dependency trees will pass cleanly, so you should expect to combine it with overrides or contribute audits where they’re missing.
Exploring your dependency tree #
openvet query takes an expression in the same syntax as a
requirement and lists every package in your lockfile that satisfies
it. Two main uses:
- Surveying what’s actually in your tree. How many packages use
unsafe? How many implement crypto? How many touch the network without being markednetwork-safe? Useful when picking up an existing OpenVet-enabled project, or when starting to write requirements from scratch. - Prototyping requirements interactively. Try a candidate
requirement on the CLI, iterate on it until it expresses what you
mean, and only then encode it in
openvet.toml. Much faster than the edit →openvet check→ edit loop.
# Every package that uses memory-unsafe code
openvet query uses-unsafe
# Every package that uses unsafe and hasn't had it reviewed
openvet query uses-unsafe and not unsafe-safe
Two flags shape the output. --status filters by tri-state result
(match, contradicted, unknown, or all); --explain prints
the claim values that drove the verdict for each listed package.
Prototyping a stricter crypto-implementation requirement might look
like:
openvet query impl-crypto implies crypto-impl-safe and crypto-impl-correct and crypto-impl-tested --status contradicted --explain
This shows you exactly which packages would fail the proposed
requirement and why; from there you can decide to encode it as-is,
weaken it, or add specific overrides for the failing packages
before committing it to openvet.toml.
When a dependency fails #
Once you’ve configured requirements, openvet check will tell you
which dependencies don’t pass. There are two ways to respond.
The shortcoming affects you. Replace the dependency, contribute the missing audits, or open an issue with the auditor. The failure is real; OpenVet surfaced a property of the package you needed to know about.
The shortcoming does not affect you. Add an override that exempts the specific package from the failing requirement. This is the right call when the package’s behaviour is genuinely a problem in general, but not in your usage of it.
A worked example. Imagine a HashMap implementation that is not marked
crypto-safe because an attacker who can choose input keys can
trigger algorithmic complexity attacks. That’s a real property of the
package — and a real problem if your project ever exposes the
HashMap to untrusted input. But if your project only ever uses it for
trusted, internal data, the shortcoming doesn’t apply. The right
response is to override it for that package and move on:
[[override]]
package = "fasthash"
requirements = { remove = ["crypto-reviewed"] }
This is OpenVet’s intended loop: it surfaces properties of your dependencies that you would not otherwise be aware of, and forces you to make informed decisions about which ones matter for your project.
Overrides come in two forms:
- Replace swaps the effective requirement set for matching
subjects with an exact list:
[[override]] package = "internal-tool" requirements = ["benign"] - Patch adds and/or removes requirements from the inherited set:
[[override]] registry = "cargo" package = "libc" requirements = { add = ["sandbox"], remove = ["unsafe-reviewed"] }
Matchers can constrain by registry, package, version, variant,
or hash — omit a field to match any.
Defining custom claims #
When the canonical vocabulary doesn’t cover what you need, you can introduce a custom claim and require it in your policy. Worked example: requiring that any package implementing cryptography is also FIPS-180 certified.
[requirement]
fips-crypto = "impl-crypto implies certified-fips-180"
certified-fips-180 is not in the canonical vocabulary; it’s a
custom claim. For the requirement to ever pass, the audits in your
subscribed logs need to actually assert this claim on the relevant
packages. Custom claims are most useful when you have an audit feed
(yours or your organisation’s) that asserts them, or when you want to
flag a class of dependency for follow-up review before allowing it.
The naming rule for any claim, canonical or custom, is kebab-case
ASCII ([a-z][a-z0-9-]*).
Handling logs that disagree on naming #
Canonical claim names mean the same thing across all logs by definition. Custom claims are not standardised: two logs that both assert FIPS-180 compliance may encode it under different names.
If you require a custom claim and your subscribed logs encode it
differently, use the [alias] table to normalise the names to a
single name you reference in [requirement]. For example, alice’s
log calls FIPS-180 compliance certified-fips-180, and bob’s log
calls it compliant-fips-180:
[alias]
fips-180 = ["alice:certified-fips-180", "bob:compliant-fips-180"]
The left-hand side is the name you reference in your requirements.
The right-hand side lists per-log overrides: when looking up
fips-180 in an audit from alice’s log, OpenVet looks up
certified-fips-180 instead; in an audit from bob’s log, it looks
up compliant-fips-180. Audits from logs not listed in any mapping
fall back to the bare name.
A complete worked example #
A realistic openvet.toml for a small Rust web service:
# Logs we trust
[log.mozilla]
url = "https://openvet.org/mozilla"
[log.google]
url = "https://openvet.org/google"
[log.our-org]
url = "https://openvet.org/our-org"
# Project lockfile to gate
[[lockfile]]
kind = "cargo"
path = "Cargo.lock"
# Policy: useful-minimum, with FIPS for any in-tree crypto
[requirement]
benign = "is-benign"
unsafe-reviewed = "uses-unsafe implies unsafe-safe"
network-reviewed = "uses-network implies network-safe"
crypto-reviewed = "uses-crypto implies crypto-safe"
crypto-impl-strict = "impl-crypto implies crypto-impl-safe and fips-180"
build-exec-reviewed = "has-build-exec implies build-exec-safe"
# `libc` uses unsafe pervasively for FFI; reviewed elsewhere
[[override]]
package = "libc"
requirements = { remove = ["unsafe-reviewed"] }
# Mozilla and Google encode our custom FIPS-180 claim under different names
[alias]
fips-180 = ["mozilla:certified-fips-180", "google:compliant-fips-180"]
This gates the project against a useful baseline, expresses the
FIPS-180 requirement that the team’s threat model needs, exempts one
package whose shortcoming was reviewed out of band, and normalises
claim names across two subscribed logs. It would be a reasonable
starting point for a team that needs to refine further once
openvet check surfaces specific failures.