jsonvetdocsAssurance case

playgroundthis page on GitHub

Assurance case

This page argues why jsonvet meets its security requirements, and names the code, test or workflow behind each claim. SECURITY.md holds the requirements, the known limits and how to report a problem; the design itself is in design.md.

The requirements, from SECURITY.md:

  1. A crafted file must not crash or hang jsonvet, or make it use far more time or memory than its size explains.
  2. No output line may carry text that drives a terminal or passes for a GitHub workflow command.
  3. A schema path a project picks must not read or show a file outside the project.
  4. The GitHub Action must not read or write outside the files it was given.

The two open cases in SECURITY.md's "Known limits" are exceptions to the first requirement, and this page does not claim them.

Threat model

jsonvet often runs in CI on pull requests from strangers, so we assume an attacker who controls:

We also defend the release against a malicious dependency, a compromised action, or someone who controls the package index the Action installs from.

We do not defend against someone who can already run code on the machine, change jsonvet's own binary, or edit the workflow that calls it. A vulnerability in jsonschema, a runner or GitHub Actions is reported upstream (SECURITY.md, "Scope").

Trust boundaries

Boundary What crosses it Controls
Files to check Bytes from the checkout or stdin One hand-written parser in safe Rust (unsafe_code = "forbid", 0002); a nesting cap (Config::DEFAULT_MAX_DEPTH, crates/jsonvet/src/rule.rs); duplicate keys hashed with SipHash under OS keys, so crafted keys cannot collide (0003, crates/jsonvet/src/parse.rs); only regular files are read, so a FIFO cannot hang a run and /dev/zero cannot fill memory (read in crates/jsonvet-cli/src/lib.rs)
Config and ignore files Settings that change what is checked and how A size cap (MAX_CONFIG_BYTES) and the same regular-file rule; the config search stops at the repository root or the working directory (0009); a symlinked config is followed, but an error in it shows none of its values (crates/jsonvet-cli/src/settings.rs)
Schemas A schema chosen by --schema, a config file or $schema Fixed caps on schema size and depth, file depth, errors and message length, and on regexes (2 MB compiled each, 1,000 per schema and 1,000 per run; SchemaStore's largest is 715 KB, and its most in one schema 457), not a timeout (0010, crates/jsonvet-cli/src/schema/limits.rs); external schema references stay disabled; only --schema-catalog enables bounded HTTPS downloads and a local cache (0016); a path from a project must stay inside it, and its errors quote nothing from it (confined in settings.rs, 0012)
Output File names, paths and messages printed to a terminal, a CI log, JSON or SARIF One escaping rule for every format: control, bidi and zero-width characters are printed as \u escapes, line breaks as \r and \n, and workflow-command values are percent-encoded (printable and escape in crates/jsonvet-cli/src/output.rs, 0007); SARIF stops at 25,000 results (MAX_RESULTS)
The GitHub Action Its inputs, and the wheel it downloads from PyPI Inputs reach the script through environment variables, never expanded into the shell (action.yml); the wheel is checked with gh attestation verify against this repository's release workflow before it is installed (verify_wheel in scripts/action.sh)
The playground Text pasted into a static page The library compiled to WebAssembly with no file or network access; a Content-Security-Policy that allows only the page's own script and wasm, and no forms or other origins (playground/index.html)
Release and CI Code, dependencies, the built files Pull requests run under pull_request with a read-only token and no secrets, except that CodSpeed's job also gets an OpenID Connect token to upload its results (a pull request from a fork gets none); every checkout drops its credentials; actions are pinned by commit; zizmor checks the workflows; cargo deny checks licences, sources and RustSec advisories; the jobs that publish run on v* tags only, run no project code, and wait for the maintainer's approval in the pypi and crates-io environments; every release file gets a build provenance attestation (.github/workflows/release.yml, GOVERNANCE.md)

Secure design principles

Countering common weaknesses