Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
22 changes: 17 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,25 @@ jobs:
# default build, so lint it in a dedicated pass.
- run: cargo clippy -p exav-core --all-targets --features unstable-internals -- -D warnings
- run: cargo clippy -p exav-render --all-targets --features dwg -- -D warnings
- run: cargo clippy -p exav-render --all-targets --no-default-features --features ifc,stl -- -D warnings
- run: cargo clippy -p exav --all-targets --no-default-features --features yara,zip -- -D warnings
- run: cargo clippy -p exav-render --all-targets --no-default-features --features ifc,stl,obj,ply,gltf -- -D warnings
- run: cargo clippy -p exav-render --all-targets --no-default-features --features markdown -- -D warnings
- run: cargo clippy -p exav-render --all-targets --no-default-features --features text-pdf -- -D warnings
- run: cargo clippy -p exav-render --all-targets --no-default-features --features office-sheets -- -D warnings
- run: cargo clippy -p exav-render --all-targets --no-default-features --features office-slides -- -D warnings
- run: cargo clippy -p exav-render --all-targets --no-default-features --features office-docs -- -D warnings
- run: cargo clippy -p exav-render --all-targets --no-default-features --features office,office-test-util -- -D warnings
- run: cargo clippy -p exav-render --all-targets --no-default-features -- -D warnings
- run: cargo clippy -p exav-render --all-targets --no-default-features --features text-office,office-test-util -- -D warnings
- run: cargo clippy -p exav-core --all-targets --features visible-text-office -- -D warnings
- run: cargo clippy -p exav-grep --all-targets --features text-office -- -D warnings
- run: cargo clippy -p exav-unpack --all-targets --features text-pdf -- -D warnings
# The full native test matrix, every feature pass. Defined once in the
# Makefile so this and a local `make test` can't drift.
- run: make test-native

# The promised floor is `rust-version` in Cargo.toml (1.92, imposed by the
# hayro image decoders). `make msrv` reads it there and installs that toolchain on
# The promised floor is `rust-version` in Cargo.toml (1.95, imposed by the
# delharc LHA reader). `make msrv` reads it there and installs that toolchain on
# demand, so a bump flows through with no edit here, and a contributor on
# stable can't land a newer feature without this going red.
msrv:
Expand Down Expand Up @@ -127,7 +139,7 @@ jobs:
- uses: actions/checkout@v7
- uses: actions/setup-node@v4
with:
node-version: 22
node-version: 24
cache: npm
cache-dependency-path: www/package-lock.json
# exav.org is the authoritative answer on format coverage and CLI flags,
Expand Down Expand Up @@ -194,7 +206,7 @@ jobs:
# the masked fixtures are unmasked.
seeds="$RUNNER_TEMP/fuzz-seeds"
scripts/fuzz-seeds.sh "$seeds"
for t in filetype sigs cvd pe unpack analyze bytecode full_pipeline seekable yara pcre clamd icap volumes authenticode database ndb_compile rar3_ppmd pe_emulator x86_decode parser_recursion imagehash drawing cad_dxf cad_dwg ifc; do
for t in filetype sigs cvd pe unpack analyze bytecode full_pipeline seekable yara pcre clamd icap volumes authenticode database ndb_compile rar3_ppmd pe_emulator x86_decode parser_recursion imagehash drawing cad_dxf cad_dwg ifc mesh; do
mkdir -p "fuzz/corpus/$t"
extra=""
[ -d "$seeds/$t" ] && extra="$seeds/$t"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ jobs:
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest
type=raw,value=latest,enable=${{ !contains(github.ref_name, '-') }}
- uses: docker/build-push-action@v6
with:
context: .
Expand Down
16 changes: 16 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,22 @@ jobs:
- uses: actions/checkout@v7
with:
persist-credentials: false
# Everything a release publishes is labelled with a version of its own:
# the binaries with the workspace's, the npm packages with theirs. One
# that was not bumped ships next to the others under another number, and
# nothing else fails. Checked before anything is built.
- name: Check every version matches the tag
run: |
TAG_VERSION="${GITHUB_REF_NAME#v}"
WORKSPACE=$(sed -n '/^\[workspace.package\]/,/^\[/{s/^version *= *"\(.*\)"/\1/p;}' Cargo.toml | head -1)
VIEWER=$(node -p "require('./crates/exav-viewer/package.json').version")
UNPACK=$(node -p "require('./crates/exav-unpack-wasm/package.json').version")
status=0
for pair in "Cargo.toml:$WORKSPACE" "crates/exav-viewer/package.json:$VIEWER" "crates/exav-unpack-wasm/package.json:$UNPACK"; do
file="${pair%%:*}"; version="${pair#*:}"
[ "$version" = "$TAG_VERSION" ] || { echo "$file is $version, the tag is $TAG_VERSION"; status=1; }
done
exit $status
- uses: taiki-e/create-gh-release-action@v1
with:
# The release notes are this tag's section of the changelog
Expand Down
12 changes: 11 additions & 1 deletion .github/workflows/www.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ jobs:
--site "${{ steps.pages.outputs.origin }}" \
--base "${{ steps.pages.outputs.base_path }}"
working-directory: ${{ env.BUILD_PATH }}
# Every internal link and #anchor of the built pages must resolve (offline,
# a second or two). The demo's own pages are copied in below.
- name: Check internal links
run: node scripts/check-links.mjs ${{ env.BUILD_PATH }}/dist
# The @exav/viewer demo, served at /viewer/demo/: the package and its wasm
# modules built from this commit, the archive engine's pkg/ too.
- uses: dtolnay/rust-toolchain@stable
Expand All @@ -71,8 +75,14 @@ jobs:
npm ci
npm run build:wasm
npm run build
# The sidebar's files: github.com/sylvinus/exav-samples, HEAD.
# The sidebar's files, from the network: github.com/sylvinus/exav-samples, HEAD.
npm run demo:showcase
# `demo:build` runs `npm run demo:examples` first: the example files
# committed under demo/examples/files/ are copied, and the others (tens
# of MB, from Zenodo, figshare, Wikimedia Commons and the like) are
# downloaded at build time, each checked against its size and SHA-256 in
# demo/examples/examples.json. The build fails when one cannot be fetched
# or has changed.
# DEMO_ORIGIN goes into the sandboxed frame's policy, for Safari.
DEMO_BASE="${{ steps.pages.outputs.base_path }}/viewer/demo/" DEMO_ORIGIN="${{ steps.pages.outputs.origin }}" npm run demo:build
# dist/viewer/ holds the File viewer docs; the demo must not land on them.
Expand Down
2 changes: 1 addition & 1 deletion .oss-scanner/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#
# Build it the way the scanner does, from the repository root:
# docker build -f .oss-scanner/Dockerfile -t exav-oss-scanner .
FROM rust:1.92-bookworm
FROM rust:1.95-bookworm

# Frame pointers and line tables for a debugger, without the full debug info
# that makes the build several times larger.
Expand Down
108 changes: 108 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,114 @@ follow [semantic versioning](https://semver.org/).

## [Unreleased]

## [0.0.3] - 2026-10-29

Many more formats read, in place and in bounded memory; a visible-text layer;
a much larger viewer; and a review round that closed the cases where exav read
a file differently from the program that opens it.

### Highlights

- **More formats, read where they lie.** Virtual disks (VHD, VHDX, QCOW2,
VMDK), filesystems (exFAT, SquashFS, cramfs, romfs, EROFS, HFS+, APFS, with
ext, FAT and NTFS) and 7z, WIM and xar are read from a seekable source
without holding them. A file past `--max-object-bytes` inside one is
scanned in full, not skipped. The registry lists 126 formats.
- **A visible-text layer** (`visible-text`, on by default, off under
`--clamav-compat`): Markdown, HTML, Word, PowerPoint, Excel, OpenDocument and
AutoCAD files are also scanned as the text a reader sees, for `Target:0` and
`Target:7` signatures and `--alert-phishing`. PDF text and the text of legacy
and flat office files (DOC, XLS, PPT, RTF, FODT and others) are opt-in
(`visible-text-pdf`, `visible-text-office`). `exav-grep --text` searches it.
- **Office documents converted in safe Rust, in `exav-render`** (features
`office-sheets`, `office-slides`, `office-docs`, `office`; none is a
default): XLS, ODS, FODS, Numbers, Excel 2003 XML, HTML tables and SYLK to
XLSX; PowerPoint 95 to 2003, ODP, FODP and Keynote to PPTX; DOC, ODT, FODT,
RTF, Word 2003 XML and Pages to DOCX; ODG, FODG and Visio (VSDX) to PPTX.
The viewer shows them through `xlsx()`, `pptx()` and `docx()`. Each file is
bounded in input, memory and decompressed size, and an encrypted, oversized
or damaged file is reported with its reason.
- **One reader per format.** The scanner and `exav-render` share the readers
for OLE2 (`exav_unpack::cfb`), Excel decryption, RTF, BIFF and code pages.
- **Bounded memory and time.** Deterministic sweeps (`saturate`, `nesting`,
`office_memory`) and new limits for the block cache, bytecode memory and
archive entries; `exav-grep` searches a file of any size in bounded memory.
- **The viewer** (`@exav/viewer`) opens many more files (RAW cameras, JPEG XL,
SVG, OBJ, PLY, glTF, more IFC, older Office, more archives), draws pages of
documents, 3D models and drawings, can be driven by the host's own UI, and
speaks German, Spanish and Dutch.
- **Daemon**: `--listen-health ADDR`, a listener that answers while the
workers are busy, and a `STATS` that reports the whole pool.

### Breaking changes

- The minimum Rust version is 1.95 (it was 1.92) and every crate is edition
2024.
- A `.cvd` whose header md5 does not match, or that is cut short, is refused.
- A source with no end (`exav /dev/zero`) is an error, not `OK`.
- A YARA rule nested more than 10 parentheses deep is refused (12 before): the
parser's time doubles with each level.
- A one-shot scan that `--max-scan-secs` ends keeps the exit status of what it
found (`1`, then `2`) and otherwise follows `--partial-as`; it was always `3`.
- The `Content-Range` of an HTTP answer is checked; a server that answers
with another range or object is an error.
- `--object-cache-block-bytes` defaults to 1 MiB (it was 64 KiB, fixed).
- The viewer's CSS prefix is `exav-viewer-` (it was `exv-`), the sandboxed
frame's protocol is version 5 (it was 3), and `controllers.viewport` is
computed only while used.
- exav-render's office features depend on `exav-unpack`. `exav-unpack`'s `ole`
feature no longer pulls the `cfb` crate, now a dev-dependency.
- `exav-unpack` ext, FAT, HFS+ and APFS readers are in-tree: member names no
longer begin with `/`, and a damaged volume ends with an error after the
files before the damage.
- Objects already scanned clean in the same place are scanned once per scan.

### Added

- Settings: `--max-extracted-bytes` (back), `--max-bytecode-memory-bytes`,
`--object-cache-bytes`, `--object-cache-block-bytes`, `--http-request-bytes`,
`maxArchiveEntries` and `maxEntities` in the viewer.
- Formats: LZFSE, LZVN and InstallShield Z in-tree, camera RAW, DDS, ICO and cursors, PLY, OBJ, glTF, TIFF variants (fax,
CMYK, Zstandard, tiled, planar and more), and an external converter hook for
the viewer.
- `scripts/bench-speed.sh`, a speed benchmark against `clamscan`.
- 243 freely licensed example files for the viewer demo.

### Changed

- Dependencies are at their latest versions, except the image decoders pinned
to libclamav 1.5.4. Fewer dependencies and less `unsafe`: 8 crates and
1,155 `unsafe` uses fewer in a default build.
- `fuzzy_img` JPEGs are decoded by zune-jpeg 0.5.15, not libclamav's 0.5.8: the
hashes are the same except for baseline JPEGs with a scan for each component,
which 0.5.8 drew flat (`8000000000000000`), and damaged files it panicked on.
- A damaged CAB or archive keeps more of what it decoded.

### Fixed

The ones that could change a verdict:

- **Files that a reader opens differently from exav are read as that reader
reads them:** ZIP, PDF, mail, ISO and UDF, `ar`, `cpio`, bzip2, xz, CAB,
NSIS and PowerPoint objects, and files holding a second archive. A message
attached to an email is now scanned.
- **Base64 in scripts and pages:** more archive types are decoded, and a cap
on the number of decoded files is reported (`LIMITS-EXCEEDED`).
- A damaged executable carved from a file no longer turns a detection behind
it into `PARTIAL` or `OK`.
- Large files over HTTP no longer cost a request per executable candidate.
- `exav-unpack` no longer makes symbolic links that lead out of the extraction
directory, and no longer panics on a closed pipe. `VERSIONCOMMANDS` no longer
lists `SHUTDOWN` and `QUIT` on a daemon that refuses them.
- Resource bounds: nested documents, YARA rules, Visio formulas, PPMd, zstd,
JPEG XL, JBIG2, bytecode, 7z headers, RAR key derivation, and the memory of
XML trees, converters and drawings are bounded. Several recursive readers
no longer overflow the stack.
- Rendering and decoding fixes across TIFF, BMP, PNM, ICO, DWG, DXF, IFC, STL,
glTF, JPEG 2000, DDS, Word, Excel and PowerPoint files.
- PDF strings `\b` and `\f`, which made encrypted PDFs that open with an empty
password report password-protected.

## [0.0.2] - 2026-10-11

The scanner, rebuilt around one streaming scan; three new projects (a browser
Expand Down
Loading
Loading