ferrolint
Extensible Python linter in Rust that runs custom rules, loaded as WASM plugins, next to Ruff.
Table of contents
- About
- Key features
- Requirements
- Usage
- Getting started
- How a run works
- Writing a plugin
- Type inference with ty
- Security
- Contributing
- License
About
ferrolint lints Python code with Ruff's own parser crates
(ruff_python_parser, ruff_python_ast, ruff_python_semantic) and runs
checks that ship as WASM plugins. Instead of a plugin host interpreting
Python, each check is a compiled WebAssembly component: the contract feeds it
AST visitor events and lets it ask scope and binding questions, and the plugin
answers with diagnostics carrying a code, message, range, severity and an
optional fix.
Ruff stays in the loop and keeps every built-in rule, including the Pylint
PL* checks. ferrolint is the layer for checks Ruff does not have: a team's
custom rules, ported Pylint checkers, and Pylint message-ID/symbol mappings,
all running in the same pass.
Key features
- Plugins as WebAssembly components. Each plugin runs in an empty sandbox with per-file fuel and time caps, and loads only with its pinned SHA-256.
- Pylint compatibility. Rules keep Pylint's message IDs and symbols, so
selecting, ignoring and
# pylint: disable=comments keep working. - Speed on large codebases. On a million lines with the ten benchmark
plugins, a cold run takes about 6.5 s on four cores and a warm run 0.25 s;
Pylint running the same ten checks takes 167 s.
make benchmeasures it. - Incremental runs. A content-keyed cache in
.ferrolint-cache/and--diff-baseto lint only what changed. - Deterministic output in text, JSON, SARIF or JUnit.
- One static binary for Linux x86_64 and aarch64 and for macOS aarch64.
Requirements
- Linux (x86_64 or aarch64) or macOS (aarch64). There is no Windows build.
- Python 3.9 or later and
piporuv, to install the wheel. The linter itself is a native binary and lints any Python code. - To write a plugin: Rust and the
wasm32-wasip2target (rustup target add wasm32-wasip2).
Usage
Lint the current directory:
ferrolint
Lint chosen paths with an explicit configuration file, in SARIF:
ferrolint --config ci.toml --format sarif src tests
| Option | Default | Effect |
|---|---|---|
[PATHS]... |
. |
Files or directories to lint. |
-c, --config |
none | A ferrolint.toml or pyproject.toml to read. |
--threads <N> |
0 |
Worker threads; 0 uses every core. |
--format <FORMAT> |
text |
text, json, sarif or junit. |
--diff-base <REF> |
none | Lint only files changed since the merge base of REF. |
--no-cache |
off | Neither read nor write .ferrolint-cache/. |
--stats |
off | Print files, cache hits, parses and plugin runs. |
| Exit code | Meaning |
|---|---|
0 |
No findings. |
1 |
At least one finding. |
2 |
Usage, configuration or plugin error. |
Configuration lives in ferrolint.toml or in the [tool.ferrolint] table of
pyproject.toml (all keys):
select = ["FL002"]
ignore = ["FL003"]
exclude = ["vendor/"]
[[plugins]]
path = "plugins/my_checker.wasm"
sha256 = "…"
[per-file-ignores]
"tests/*.py" = ["FL002"]
Getting started
Install the wheel from PyPI:
pip install ferrolint # or: uv tool install ferrolint
ferrolint --version
To try an unreleased commit, install from Git. The build needs the pinned
Rust toolchain and the wasm32-wasip2 target, and the first build compiles
Wasmtime, which takes minutes:
pip install "git+https://github.com/gajaguar/ferrolint"
How a run works
A run discovers the Python files under its targets, honouring .gitignore and
the configured excludes, parses them in parallel with Ruff's parser, and runs
the host's own rules plus every configured WASM plugin against the
plugin.wit contract. The rules and their Pylint counterparts are
listed in Native rules. The requirements and the design
decisions live in the docs/ bundle: requirements and
decisions.
Writing a plugin
A plugin is a Rust cdylib built to wasm32-wasip2 against
ferrolint-plugin-sdk:
[dependencies]
ferrolint-plugin-sdk = "1"
The SDK's version is the version of the plugin contract: a plugin built
against any 1.x loads in a ferrolint 1.x host. The sample
unused-import ports Pylint's W0611 and matches its findings on
the fixtures. Writing a plugin builds it step by step, and
A plugin outside this repository covers a plugin kept in its
own crate.
Type inference with ty
ty is an optional extension that adds ten
Pylint rules needing the type of a value. The published wheels are built
without it. To use it, build with cargo build --release --features ty and set
VIRTUAL_ENV to the project's environment. See Rules answered by
ty.
Security
Report a vulnerability by emailing dev@gajaguar.com rather than opening a public issue. SECURITY.md has the supported versions.
Contributing
See CONTRIBUTING.md.
License
ferrolint is released under the MIT License; see LICENSE.
Metadata
Release files for ferrolint 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| ferrolint-1.0.0.tar.gz | 599.4 kB | With provenance | Details for ferrolint-1.0.0.tar.gz |
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| ferrolint-1.0.0-py3-none-manylinux_2_17_x86_64.musllinux_1_2_x86_64.whl | Python 3 | none | Linux musl 1.2+ x86-64, Linux glibc 2.17+ x86-64 | Details for ferrolint-1.0.0-py3-none-manylinux_2_17_x86_64.musllinux_1_2_x86_64.whl |
| ferrolint-1.0.0-py3-none-manylinux_2_17_aarch64.musllinux_1_2_aarch64.whl | Python 3 | none | Linux glibc 2.17+ ARM64, Linux musl 1.2+ ARM64 | Details for ferrolint-1.0.0-py3-none-manylinux_2_17_aarch64.musllinux_1_2_aarch64.whl |
| ferrolint-1.0.0-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details for ferrolint-1.0.0-py3-none-macosx_11_0_arm64.whl |
Total release size: 27.9 MB
Release files / ferrolint-1.0.0.tar.gz
| Download URL | ferrolint-1.0.0.tar.gz |
|---|---|
| Size | 599.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d1e03c610778c26392fdf5780a9d589c1a1e7e07da6a815bb136aa0cd889a744
|
|
BLAKE2b-256 checksum How to use checksums |
08d7d99a582261e863e0b26b637168d40515c066797ca678d9c4767e3522d13c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logRelease files / ferrolint-1.0.0-py3-none-manylinux_2_17_x86_64.musllinux_1_2_x86_64.whl
| Download URL | ferrolint-1.0.0-py3-none-manylinux_2_17_x86_64.musllinux_1_2_x86_64.whl |
|---|---|
| Size | 9.9 MB |
| Tags | Linux glibc 2.17+ x86-64 Linux musl 1.2+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
d8631fa468534ba8ecf63c65115c2573b54ae7d5a2c134ddb742dd9f0ddefc5d
|
|
BLAKE2b-256 checksum How to use checksums |
71aa9aea83654bf52c20216c0ecec521d840787324c8f0ce2265f26f10e7f3da
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logRelease files / ferrolint-1.0.0-py3-none-manylinux_2_17_aarch64.musllinux_1_2_aarch64.whl
| Download URL | ferrolint-1.0.0-py3-none-manylinux_2_17_aarch64.musllinux_1_2_aarch64.whl |
|---|---|
| Size | 8.5 MB |
| Tags | Linux glibc 2.17+ ARM64 Linux musl 1.2+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
bf16afa535616129404c9c3f10fce00fddf5ca3f15e4a88246b2cc29ec02ad2b
|
|
BLAKE2b-256 checksum How to use checksums |
b266d77955602f1b9a9e9e5dff19192b255db08b7fd9c8925f9c28314ce9e9c3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logRelease files / ferrolint-1.0.0-py3-none-macosx_11_0_arm64.whl
| Download URL | ferrolint-1.0.0-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 8.9 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
67451c5dbccd04320722708fefef206cfe61b3b45700287be64af9dcd9d836c9
|
|
BLAKE2b-256 checksum How to use checksums |
54c05268e1fc0ab186551f27cd884b71f76bb6355137bf0df701853e3c8883e9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logSecurity for ferrolint 1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 4 files in this release have been uploaded with provenance metadata.
Attestation for ferrolint-1.0.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.