You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Minor Changes
4b68dd5: aval stats --registry <repo> [--test <id>] [--since <duration|date>] [--by test|repository] [--output text|json] reads the run records in the registry and aggregates them on demand: runs, hit rate, misses by cause, verification failures, shadow false hits, and total and saved test time, per test (or per source repository with --by repository) and in total. It writes nothing. Records that are unreadable, oversized, malformed or inconsistent are skipped and counted; reading stops after 1000 records or 64 pages per listing, with a warning. New format, provisional: aval.trianalab.io/stats-report/v1 (pkg/evidence adds StatsReport, TestStats, RepositoryStats, RunStats, MissCauses, StatsReportVersion and StatsUnknownRepository). Run records gain the optional verifiedResult and repository (--repository on aval run and aval record; records without it count as unknown), and evidence.ParseRunRecord now rejects inconsistent records (reused: true requires outcome verified and testDurationMs 0; verifiedResult requires outcome verified), so aval record refuses a hand-edited record that claims a reuse that never happened. Docs explain pruning old records with ORAS.
6351105: Registry I/O is bounded. run, verify and push take --registry-timeout (default 1m). A registry that stalls past the timeout gives outcome error, and aval run still runs the test. Lookup follows at most 64 referrers pages. A listed attestation whose manifest was deleted (404) counts as absent: the remaining candidates can still be reused, and the outcome is a miss if every candidate is gone. One credential store and one auth cache are shared by every registry call. The keyless trusted root (a TUF fetch when trustedRoot is not pinned) is loaded only when a candidate's bundle carries a certificate. After pushing, push and run --push confirm that the new digest is listed among the referrers, re-pushing up to three times, and fail if it never appears.
7cd688f: Aval detects mutable references in a test's inputs and refuses to reuse them. A static, offline scanner reads the files in the contract scope and runner.command, one detector per ecosystem: container images without a digest (Compose and Kubernetes image:, Dockerfile FROM and COPY --from, docker run and podman run, compose files the command uses but the scope does not hold), Go (go.mod without go.sum, go install pkg@latest), npm, yarn, pnpm and bun (package.json without a lockfile, unpinned npm i and npx), Python (unpinned requirements*.txt entries, missing hashes, Poetry and Pipenv without a lockfile, unpinned pip install), Rust (Cargo.toml without Cargo.lock), Java (Maven and Gradle SNAPSHOT, LATEST, RELEASE, dynamic versions and ranges), Ruby (Gemfile without Gemfile.lock), PHP (composer.json without composer.lock), OS packages (apt-get install and apk add without versions), remote scripts (curl … | sh, remote ADD without --checksum) and git (git clone without a commit, submodules that track a branch). For hermetic and controlled-environment contracts any unacknowledged finding makes the lookup a miss (exit 4 for verify) whose reason lists each reference, the test runs, and run --push and push do not publish (exit 1). external-dependencies contracts get the findings as information; non-deterministic is unchanged. aval run prints each finding as a mutable: line (--fingerprint-only prints them on stderr). Contract format change, additive within aval.trianalab.io/v1: the optional scope.allowMutable list (ref and reason) acknowledges a reference by the exact text Aval reports. It is part of the contract hash; contracts without it keep their fingerprint, and the manifest stays aval-fingerprint/v1. Older aval binaries reject contracts that use the new field. A contract whose scope holds a mutable reference that earlier versions reused now misses until the reference is pinned or acknowledged. Guide: "Pin your dependencies" (docs/pinning.md).
0e259b7: Aval resolves declared artifacts to immutable ids at fingerprint time. The optional scope.resolve list takes kind: reference entries for container images (image, tag to manifest or index digest), npm, PyPI, Go modules (through the module proxy, h1: hash), Maven, crates.io, RubyGems, git (ref to commit with git ls-remote) and plain URLs (sha256 of the download, up to 256 MiB). Each resolves a tag, a version range or a branch to its digest, checksum or commit, which goes into the manifest as a resolve <kind> <reference> <version> <id> line: a repushed tag, a new release inside a range or a moved branch changes the fingerprint. Resolution is opt-in and stateless, bounded by a 2-minute deadline; a reference that cannot be resolved fails the fingerprint, so nothing is looked up, the test runs and nothing is published, and the reason names the reference. aval run --explain prints the time each lookup took on stderr. scope.resolve never clears a finding of the mutable-reference scanner: it makes a move change the fingerprint, and the reference still blocks reuse until it is pinned in place or accepted with scope.allowMutable. scope.resolve binds the named artifact, not its transitive dependencies. Registries come from $npm_config_registry, $GOPROXY, $AVAL_PYPI_URL, $AVAL_MAVEN_URL, $AVAL_CRATES_INDEX and $AVAL_RUBYGEMS_URL; credentials from the Docker config, ~/.npmrc tokens, ~/.netrc machine entries and git, sent only over https or to a loopback host. Format changes, additive: the contract field within aval.trianalab.io/v1 (older binaries reject contracts that use it), and the resolve manifest lines within aval-fingerprint/v1, which only contracts with the field have; every other manifest is unchanged. Guide: "Resolve references" (docs/resolve/).
8d4b8d8: Run statistics in the registry. aval run --record publishes one run record per run, for every outcome, with the test id, fingerprint, outcome, whether the result was reused, the result, the test duration, the saved duration, the aval version, a timestamp and an optional --ci-run-url. Records hang off a per-test stats anchor tagged stats-<test id>, never off a fingerprint anchor. run and verify never read them, so a record cannot cause reuse or count toward the candidate cap. Recording is best-effort: a record that cannot be published, for example without write credentials, is a warning and never changes the exit code. --record-file writes the record to a file and aval record publishes it later. New format identifiers, provisional: aval.trianalab.io/run-record/v1, application/vnd.trianalab.aval.run.v1 (+json for the layer) and application/vnd.trianalab.aval.stats.v1. pkg/evidence adds RunRecord, ParseRunRecord and these constants.
ff2a2c8: A contract can depend on other contracts in the same repository. The optional scope.contracts list takes paths to other aval.yaml files, relative to the contract (../backend/aval.yaml). Each referenced contract is fingerprinted from its own directory and scope, and its fingerprint goes into the manifest as a contract <path> <name> <fingerprint> line, transitively: a change inside a referenced contract's scope makes the dependent miss and run, a change outside it does not. References must stay inside the git working tree of the contract (the nearest directory with a .git entry); a missing, invalid, symlinked or cyclic reference, or more than 8 levels or 64 contracts, fails the fingerprint, so the test runs. A referenced contract's mutable references block the dependent's reuse unless the referenced contract's own scope.allowMutable accepts them. aval run --explain prints the dependency tree after the manifest. Format changes, additive: the contract field within aval.trianalab.io/v1 (older binaries reject contracts that use it), and the contract <path> manifest lines within aval-fingerprint/v1, which only contracts with the field have; every other manifest is unchanged. Guide: "Depend on another part of the repo".
6b94c21: One-line installers: scripts/get-aval.sh (Linux and macOS, curl | bash) and scripts/install.ps1 (Windows, irm | iex). Both refuse a download whose sha256 does not match the release's checksums.txt, and verify the release signature (cosign) and SLSA provenance (gh attestation verify) when those tools are installed. See docs/install.md.
Patch Changes
0553f1e: scope.allowMutable entries take an optional file: (the path a finding prints, relative to the contract, or runner.command): the entry then accepts the reference in that file only. A ref-only entry still accepts a reference that one file reports; when several files report it (generic refs such as resources[0] or patches[0] repeat across kustomizations), nothing is accepted and the finding asks for file:. With scope.contracts, each contract's entries apply to its own findings. The scanner also reports kustomize helmCharts entries other than a local chart in the scope, and patches given by a path outside the scope; checks instructions behind ONBUILD; uses the # escape= character when it splits heredoc words; and no longer reports a range loop's key variable used as a key directly under labels: or annotations: in a Helm template. Since the previous release, image refs in JSON files ("image": "postgres:16",) no longer end in ",: update an acknowledgement written with the trailing characters to the bare reference (postgres:16).
2df8ee2: A signer the trust policy lists but below minTrustLevel is now reported as miss (exit 4), and with outcome miss in the verify report's rejected list, instead of verification-failed (exit 5). Such an attestation is still never reused, and a false claim from that signer stays verification-failed. The report stays aval.trianalab.io/verify-report/v1; rejected[].outcome may now also be miss. Rejected verification failures are printed with the aval: SECURITY: prefix even when another candidate verifies. evidence.Verify is unchanged.
e5497e1: --registry-timeout help no longer claims "push has its own" deadline on aval push, aval record and aval stats; only aval run says lookup and fetch share one. Shorter aval run, aval record and aval stats help. The docs site is shorter and single-sourced: the README links into the site instead of copying the quickstart, and the resolver pages and vision move under Reference and Project.
7fae6b0: The fingerprint no longer hashes Aval's own outputs: .aval directories at any depth, and the --result and --report paths of the command when they fall inside the contract root. A broad scope such as scope.source: ["**"] now keeps its fingerprint across aval runs, and aval push accepts the result aval run recorded. The manifest format stays aval-fingerprint/v1: trees with no Aval output in scope keep byte-identical manifests and digests (the committed v1 golden and Aval's own contracts are unchanged). Only manifests that listed a file under .aval/ or a --result/--report file change, to the manifest of the same tree without those files. Such a manifest held a result from an earlier run, so it changed on every run and was not reusable in practice; results published from a clean checkout (no Aval output yet) keep their fingerprint and stay reusable. .aval/ is now reserved for Aval: a file the test reads must not live there.
91a57b8: run --push and push check their preconditions before anything runs and exit 2 (usage) instead of failing with exit 1 after the test: a --key that cannot be read or is not an unencrypted ECDSA P-256 PEM key (the error names the requirement and the openssl command to create one; cosign keys are encrypted and not supported), or no --key and no $SIGSTORE_ID_TOKEN. run, verify and push reject a --registry that is not an OCI repository (a scheme, no repository, a tag or a digest) with exit 2 and an example. A --result or --report path that the contract scope includes and that holds a real input (anything other than an Aval result or report) exits 2 before the test runs, because skipping it would drop that input from the fingerprint; run also refuses to overwrite any existing file at those paths that is not exactly an Aval result or report. Exit codes keep their values; only these cases move from exit 1 to the existing exit 2.
c7c5888: Docs site: live run statistics. The home metric strip and a new Stats page (Project > Stats) read stats.json, which the docs workflow rebuilds twice an hour and on every push from aval stats --by repository --since 30d over the public stats registry ghcr.io/trianalab/aval-stats, without rebuilding any page; a failed refresh keeps the previous file, and a missing or stale file shows a neutral state. Aval's own CI now publishes its run records there. The Stats page explains how to keep statistics in your own registry and how a repository joins the public aggregate.
f14b7b4: Docs site: the mobile menu (hamburger) now slides in the site navigation on every page. On inner pages the panel opened under the dark overlay; on the home page there was no panel at all. make docs-nav-smoke, part of make ci-docs, opens the menu at a phone width on the home page and an inner page and checks the panel is on screen with links.
133adf0: The README now has an Install section and a local quickstart (registry:2, an openssl P-256 key, aval run --push --key, aval verify) that reaches verified; an acceptance test runs it as written. A missing aval.yaml and a pre-freeze aval.dev/apiVersion (contract or policy) now print a next step. aval verify --help lists exit codes 1 and 2, and docs/cli-reference.md has one exit-code table for every command. New docs: registry credentials, the network and HOME needs of keyless verification and how to pin trustedRoot, and the 64-candidate cap with its cleanup.
ed37d7e: New docs Quickstart page: copy-paste steps with the expected output from an empty folder to a verified reuse, a changed input and a rejected attestation (exit 5), with terminal recordings of real runs and a "what just happened" diagram; an acceptance test runs the page and checks every output it shows. make docs-demos re-records the GIFs from VHS tapes. The Install page now shows a manual release download with cosign and checksum checks.
5871b37: Readable errors and reasons. Output escapes only control and non-printable characters (quotes and backslashes print as they are), and each cause of a joined error gets its own aval: line (an invalid contract) or is separated by ; in the report reason (an invalid policy). A reason is printed once, in the report: stderr no longer repeats it, and a verification failure adds a single aval: SECURITY: ... line that links to docs/trust-model.md. A signer missing from the policy gets the reason signer key id "<id>" is not in the trust policy (trust policy <file>) instead of the Sigstore error. Skipped lookups (no policy, no registry, --no-reuse, a cacheability class that is never or not allowed to be reused) say only why they did not look; they stay misses. No attest.Sign: or registry.Lookup prefixes reach users. Text-mode verify prints signer: (with the trust level name) and reference: lines for a verified result. non-deterministic is called "never reusable" everywhere. The JSON verify report is unchanged except for the wording of reason, where joined causes are now separated by ; .
cb81760: scope.resolve detects registry overrides as the tools read them. pip variables match in any spelling pip reads (PIP_ then any letter case, - equal to _, one optional leading --: PIP_index-url, PIP___INDEX_URL). ~/.npmrc is read as ini, like npm: quotes stripped from keys and values, ; and # comments, keys under a [section] ignored, ${VAR} expanded in keys and values as npm does (an undefined variable stays literal); a line Aval cannot read (including one with U+2028 or U+2029, or a quoted key or value that is JSON other than a string) fails the fingerprint and sends no token. A floating Maven version fails when <latest> or <release> is a SNAPSHOT or not in <versions>. Errors show only the host of a URL, so a token in a registry path (https://npm-proxy.fury.io/<token>/user/) is masked too.
9082a6c: pkg/evidence: ErrNotImplemented, Verification.TrustLevel (use Verification.Signer.TrustLevel) and Runner.Issuer/Runner.TrustLevel are deprecated; nothing is removed. Pushing an attestation without a Sigstore bundle is now refused, since it could never verify. Keyless signing always uses the Sigstore public-good instance, as the CLI already did.
9556f78: The mutable-reference scanner adds parser-based detection to its line-by-line pass, which still runs on every file it ran on before, so it finds everything it found before. Dockerfiles named as such (Dockerfile, Containerfile, Dockerfile.*, Dockerfile-dev, Dockerfile_windows, *.dockerfile) are also split the way BuildKit does: a comment line ending in \ no longer swallows the next instruction, continued lines join with nothing (FROM db:1\ then 6 is db:16), comment and blank lines inside a continuation are dropped, a leading # escape= sets the continuation character, and heredoc bodies are kept apart. YAML is also decoded: image: keys are matched by their decoded value ("ima\x67e":, ? image, !!binary, tagged, anchored or alias keys, nested sequences), after an anchor or an empty flow value, or after a lone CR, NEL or LS line break. The scanner also reports kustomize images: and imageTags: entries with newName, newTag or tagSuffix and no digest, kustomize patches, replacements, transformers, generators and helmCharts that set an image (by ref, such as patches[0]), kind: ImageTagTransformer, images in Helm *.tpl helpers, RUN --mount=...,from= images, JSON manifests under templates/ or listed in kustomize resources, Compose build.dockerfile_inline and docker-image://additional_contexts, and every name of a multi-name or lowercase Dockerfile ARG. What Aval reports because it cannot tell (a templated key in a Helm template, a YAML key with an unknown tag or an undecodable escape, an undecodable heredoc name, a FROM image with a variable that is not a global ARG with a value, a kustomize resource that is remote or outside the scope (resources[2]), a tagged key in a template the parser rejects, a Dockerfile BuildKit refuses) is in the container ecosystem, so scope.allowMutable can acknowledge it; only an unreadable or oversized file stays in scan. A shell continuation at the very end of a file is no longer dropped. docs/pinning.md now says the scanner is a best-effort safety net and lists its known limits.
59adaf5: The installers are served from the docs site: curl -fsSL https://aval.run/install.sh | bash and irm https://aval.run/install.ps1 | iex. The site root redirects to the latest release (to main before the first one), and the outdated-version banner shows only on releases older than latest, never on main.
6a2a118: aval stats finds run records on registries that do not sort tags, such as GHCR: it lists every tag and filters by the stats- prefix instead of starting the listing after stats. Up to 64 pages of tags are read (6400 tags at a registry page size of 100); past that the report carries a truncation warning.
843f7a3: A symlink in the contract directory under which a source or dependencies glob could match files (for example pkg/sub -> /elsewhere with **/*.go), now fails the fingerprint with an error naming the path. Before, it was skipped, so edits behind it kept the same fingerprint and a stale result could be reused. A symlink no glob can reach is still ignored, and a contract directory that is itself a symlink fingerprints like its target.
27d7391: Try Aval in the browser: click the terminal on the home page of the docs site (or its "Try it live" button) and it runs the real Aval compiled to WebAssembly, with a simulated test, an in-memory registry and a throwaway key generated in the browser. It supports aval run (a miss, then a verified reuse), aval run --explain, aval verify -o json, edit <file>, tamper (the next verify fails with exit 5), reset and help. Nothing is fetched until the visitor clicks the terminal or presses a key in it, and without WebAssembly the page keeps the recorded transcript. Keyless signing is not available in the browser.