Veridex is trust infrastructure, so it holds itself to a clear, testable security model. This document states what Veridex guarantees, what it does not, and how signing keys are handled.
- Deterministic verdicts. The same dataset bytes and the same Veridex version always produce the same verdict and the same certificate content hash. Parallelism never changes results.
- Content binding. A certificate is bound to the exact CDM content hash of the dataset it was issued for. Presenting it against a different dataset fails verification (transplant rejection). The content hash covers the dataset's actual content (episodes, streams, frames, stored/observed stats, provenance) and every source-declared value a check reads — including the per-episode declared frame count, whose disagreement with the frames ingested is itself a finding. Anything a check can fail on has to be bound, or two datasets that disagree on the verdict could share a hash and one's certificate would attest the other. The hash is independent of the order those collections happen to be listed in, and that ordering is total: episodes with a duplicate index and streams with a duplicate name — both faults Veridex reports — are ordered by their full content, so the hash never depends on the order they arrived in.
- Offline verification. Certificates are Ed25519-signed and verify against the issuer public key
with no network dependency. Any change to a signed certificate's content fails signature
verification (tamper rejection): the bytes signed are the certificate's canonical JSON, re-derived
from the parsed document at verification time, so altering any field — the verdict, the score, the
content hash, the scope — breaks the signature. What that does not pin is presentation. Two
byte-distinct files that parse to the same certificate both verify: reordered keys, different
whitespace, or a re-added field whose default is omitted. Treat the signature as attesting the
parsed document, not the file. One consequence worth knowing: serde rejects a duplicated struct
field, but a duplicated key inside a map-valued field (such as
findings_summary.by_category) resolves last-wins, so a hand-read of the raw JSON can disagree with what verification used — read the output ofveridex verify, not the file. The hex and algorithm fields must still be in the canonical spelling this crate writes. Verification also rejects a certificate signed by an untrusted issuer key, and one that declares a signature algorithm this build cannot verify (rather than assuming Ed25519). - Attestation binding and domain separation. A producer attestation (
veridex attest) is Ed25519-signed by the producer's key — a different key from the certificate issuer's — and is bound to the dataset's CDM content hash, so an attestation cannot be moved to other data. It signs under its own domain tag (veridex.attestation.sig.v1\0, distinct from the certificate's), so a signature over one document can never verify as the other even if their bytes coincided. Applying an attestation raises provenance coverage only: attested elements never enter the CDM, so the content hash still describes the data and nothing else, and the run discloses the producer key and every element it supplied (PROVENANCE.ATTESTED) — which a certificate records, signed, andveridex verifyprints. An attested value that contradicts what the dataset records is reported (PROVENANCE.ATTESTATION_CONFLICT), never allowed to overwrite it. - Non-mutation. Veridex only reads datasets and writes its own outputs to caller-specified paths. It never modifies, repairs, or deletes a user's dataset.
- No wall-clock in core. Issuance timestamps are caller-supplied, so signing is reproducible and testable; the core reads no ambient clock.
- Memory safety.
veridex-coreis compiled with#![forbid(unsafe_code)]. - Bounded ingestion. Reading a dataset is reading untrusted input, so ingestion is bounded three
ways before it allocates: a frame budget (20M frames by default,
--max-frames) on what a file can materialize, a decompression budget (100x the file's own size, with a 64 MiB floor so a small file still gets a workable allowance;--max-decompression-ratio) on how far a compressed container may expand, and a source-size ceiling (4 GiB,--max-source-bytes) on one file read whole into memory. All are charged against what the file declares — the size ceiling onstat, before the read — and the decompression budget again against what actually arrives, so a header that understates its expansion buys nothing. A file past any of the three is refused with an error naming the limit and what to do about it. The size ceiling exists because MCAP, MF4 and a rosbag2.db3are random-access containers and are read whole by design: past a certain size the run does not fail with a verdict, it fails with the process, since a failed allocation aborts and the OOM killer does not wait for that.
- It is not a malware or code scanner. Veridex checks dataset quality, timing, and provenance, not executable safety. A clean verdict does not mean a file is safe to execute.
- A grade is not an endorsement. A certificate is a nutrition label — a statement of facts (checks run, findings, score, provenance coverage), not a blessing that a dataset is "good."
- Provenance is best-effort and attested, never proof. Extracted provenance reflects what the
source encoded; asserted provenance reflects what a producer signed. Veridex never fabricates or
infers provenance, and an
assertedvalue is only as trustworthy as the key that signed it. - Trust scores compare only within a rubric version. Scores under different
rubric_versions are not comparable.
- The issuer secret key grants the ability to issue certificates in that issuer's name. Keep it
private; never commit it. The repository
.gitignoreexcludes*.keyand*.pem, and generated certificates (*.veridex.json). veridex keygen <path>writes the secret to<path>and the public key to<path>.pub. Share only the public key; verifiers use it to check certificates.keygenrefuses to overwrite an existing key file unless--forceis given, so an accidental re-run cannot destroy a signing key. On Unix the secret key file is created with0600(owner-only) permissions so another local user cannot read it.- A producer key is a separate key with a separate purpose: it signs attestations about
provenance, not certificates about verdicts. The same
keygenproduces one, and the same care applies. A verifier decides independently whether it trusts the issuer and whether it trusts the producer — a certificate can be from an issuer you trust while carrying provenance attested by a key you do not, which is exactly why the certificate records the producer key rather than folding its claims into the data. - Rotating an issuer key invalidates trust in future certificates signed by the old key; already issued certificates remain verifiable against the public key they embed.
- In memory, secret key material is scrubbed rather than left in freed allocations: the
SigningKeyitself throughed25519-dalek'szeroizefeature, and the wrappers around it — the decoded seed, the hex encoding, and the key file's contents as read by the CLI — throughZeroizing. This is defense in depth against a core dump or a swapped page, not a defense against an attacker who can already read the process's memory as it runs. One copy is deliberately outside it:veridex.keygen()returns the secret to Python, where strings are immutable and cannot be scrubbed. A caller who needs that guarantee should use the CLI, which writes the key to a0600file and scrubs its own copies.
Please report suspected vulnerabilities privately to the maintainers rather than opening a public issue, and allow reasonable time for a fix before disclosure.