| Version | Supported |
|---|---|
| Latest minor (0.x) | ✅ |
| Older minor releases |
Security fixes are released as patch versions and back-ported to the current minor series. Older minor releases receive fixes on a best-effort basis.
Please do not open a public GitHub issue for security vulnerabilities.
Report security issues privately via GitHub Security Advisories.
You will receive a response within 48 hours acknowledging your report. We aim to release a fix within 7 days for critical issues and 30 days for others. We will credit reporters in the release notes unless you prefer to remain anonymous.
vastlint targets SLSA Build Level 2 for all release artifacts.
Every binary, library, and .vsix attached to a
GitHub Release has a signed
SLSA provenance attestation stored in GitHub's attestation store. To verify
any artifact:
gh attestation verify <file> --repo aleksUIX/vastlintThe vastlint npm package is published with provenance. Verify the published
package is linked to its CI run:
npm audit signatures vastlint- Release builds are performed exclusively by GitHub Actions from tagged source.
- The release workflow requires all smoke tests to pass before any artifact is published or uploaded.
- All release artifacts are built from a clean checkout of the tagged commit - no local developer machines are involved in producing release binaries.
We are working toward alignment with the following:
| Standard | Status |
|---|---|
| SLSA Build L2 | ✅ Implemented |
| SLSA Build L3 | 🔄 In progress |
| OpenSSF Best Practices (Passing) | 🔄 In progress |
| OpenSSF Scorecard | 🔄 In progress |
This section explains why we believe vastlint is secure for its intended use: validating untrusted VAST XML.
The attacker-controlled input is the VAST XML document itself. Ad tags arrive from third-party ad servers and cannot be trusted. The threats we defend against:
- Denial of service: crafted XML that causes excessive CPU or memory use, hangs, or crashes the host process.
- Panic / crash: malformed, truncated, or hostile input that triggers a Rust panic and takes down the embedding application.
- Incorrect output: input that causes vastlint to mis-report, e.g. declare a malicious or broken tag valid.
Out of scope: vastlint does not execute, fetch, or render any resource referenced by the tag (media files, VPAID/SIMID JavaScript, tracking URLs), so threats inside those resources are not part of its attack surface. It flags suspicious constructs; it never runs them.
- Input is a single untrusted XML string handed to
vastlint-core. - Output is a structured validation report (rule IDs, severities, locations). No part of the input is executed or interpreted beyond XML parsing.
vastlint-coreperforms no network calls, no filesystem access, and no environment access. Components that do I/O (the CLI reading files or URLs, the hosted API, the MCP server) sit outside the core and pass fetched content across the same string-in / report-out boundary. Wrapper-chain fetching happens only in those outer layers and is capped at a default depth of 5 (max_wrapper_depth, per the IAB VAST 4.x recommendation).
- Fail-safe defaults: malformed XML is rejected at the parser with a structured parse error before any rule runs. A document that cannot be parsed is never reported as valid.
- Complete mediation: every input byte passes through the XML parser; rules only ever see the parsed tree, never raw input.
- Least privilege: the core needs, and has, no I/O capability at all. An embedding process grants it nothing but CPU and memory.
- Economy of mechanism: the core has three dependencies (
quick-xml,url,phf), all pure Rust and compiled in. Rules are plain compiled Rust functions; there is no schema interpreter, regex engine, dynamic loading, or plugin system to attack.
- CWE-20 (improper input validation): all input is parsed by
quick-xmlbefore any rule runs; malformed structure and invalid UTF-8 are rejected or safely replaced rather than propagated. Field values are then validated against allowlists: enum values, RFC 3986 URI syntax, IANA MIME types, ISO 4217 currency codes. - CWE-400 (uncontrolled resource consumption): parsing is a single
streaming pass with memory proportional to input size, and the caller
controls input size at the boundary.
<!DOCTYPE>declarations are skipped and custom entities are never expanded (only the five XML predefined entities and numeric character references are resolved), so entity-expansion amplification ("billion laughs") does not apply. Wrapper-chain fetching outside the core is depth-limited as described above. - CWE-835 (infinite loop): the parser is a single forward pass that terminates on end-of-input or on the first structural error. There is no backtracking and no loop whose bound is controlled by input content, so termination holds by construction.
- CWE-119 / CWE-120 / CWE-125 (memory corruption):
vastlint-corecontains nounsafeblocks; Rust's memory safety guarantees rule out buffer overflows and out-of-bounds reads in the validator itself. The FFI (vastlint-ffi) and Erlang NIF (vastlint-nif) binding crates necessarily useunsafeat the C ABI boundary; that code is confined to argument marshalling and is not reachable from the parsing or rule logic.
These claims are exercised continuously: three libFuzzer targets fuzz the
parser, validator, and auto-fix engine on every CI push, and
cargo clippy -D warnings plus cargo audit run on every push and PR.