CodeVetter · Repo Unpack · Risk map
astral-sh/ruff
The repository's own threat model treats project source and config as attacker-controlled and forbids analysis from executing that code. The sharp edges that do cross a process or network boundary are ty's uv subprocess, fix/format writes, cache files, stdio LSP, and the unauthenticated playground share API. Internally, the rule table in `codes.rs`, Salsa cancellation, and the unstable linter API are the maintenance blast radii.
- The CLI threat model states analysis must not execute user-supplied code; `--isolated` must ignore config files; reading files reached via imports, config extensions, or symlinks is expected; fixes, format, ignore insertion, and cache cleanup may change only the operation's selected targets; `ruff format --check` must not write source.
- ty's uv integration is off unless `TY_UV` is `1`, `true` (`UseUv::On`), or `scripts` (`UseUv::Scripts`). When on, `Uv::execute` runs the `UV` env var or `PATH`'s `uv`. Script metadata uses `workspace metadata --sync --script`, which the threat model says may install dependencies.
- `--fix` and format `Write` mode replace file contents via `File::create` and a full write of the transformed source. A failed fix falls back to `lint_only` on the original. `--add-noqa` also rewrites files. These are the intentional mutation paths.
- Cache files are written outside the source tree only in the sense that `.ruff_cache` (or `cache-dir`) sits in the project and is excluded from linting by default. Persistence is atomic rename, but a mismatched package root silently clears `package.files`, and unreadable cache files are replaced with an empty cache after a warning.
- The playground share Worker stores the entire POST body in KV with a random UUID and no authentication, size check, or content-type check in `fetch`. GET returns the stored text to any caller who has the id. Production CORS is an allowlist, not an auth scheme. Dev mode sets `Access-Control-Allow-Origin: *`.
- Both language servers read editor documents over stdio and trust the client connection. Ruff publishes diagnostics from in-memory snapshots (`snapshot.query()`), so a hostile document is linted with the server's filesystem rights. ty's server additionally requires a Unicode cwd and keeps an `OsSystem` fallback for files not in the index.
- Python launcher risk is local binary lookup: `find_ruff_bin` returns the first `ruff` executable on a fixed list of script directories and `__main__` `execvp`s it. A planted binary earlier in that search list would run with the user's privileges. On Windows the launcher uses `subprocess.run` and maps `KeyboardInterrupt` to exit 2.
- Lint panics are isolated per file with `catch_unwind` and reported as diagnostics, but `Cache::open` and the Windows color setup still use `unwrap`/`assert` on paths that are not panic-isolated (`SystemTime::UNIX_EPOCH.elapsed().unwrap()`, `set_virtual_terminal` assert). ty's check thread uses `salsa::Cancelled::catch` and drops stale revisions.
- `codes.rs` `code_to_rule` is a single macro-expanded match from every rule code to a rule type. A new rule that is not added there is unreachable from `--select` and noqa codes. `Rule::from_code` searches `Linter::all_rules` by noqa suffix.
- ty one-shot checks `freeze` the database and then `std::mem::forget(db)` so destructors do not run. `system_mut` documents that it cancels other handles and can deadlock if the refcount is not 1. Watch mode, fix modes, and memory reports must not freeze.
- The linter library is explicitly unstable. WASM, the CLI, and the language server all call into `check_path` / settings types, so a signature change in `ruff_linter` fans out across `crates/ruff`, `ruff_server`, and `ruff_wasm`.
- Docker build fetches rustup with `curl | sh` and compiles a musl binary. That is a release-supply-chain path, not a runtime path for users of the scratch image. cargo-dist comments that CI customizes cargo-dist install specifically to avoid `curl | sh` for the dist tool itself.
- Debug Ruff builds warn when the cache is left on, because `--no-cache` is what developers expect while iterating. Shipping a debug binary to users would both warn and serve potentially stale-looking cache behavior relative to source edits.