Skip to content
Merged
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 46 additions & 9 deletions .github/SECURITY.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,50 @@
# Security Policy
# Security policy

## Supported Versions
pre-commit-uv is a pre-commit add-on that builds Python hook environments with uv in place of virtualenv and pip. This
policy says how to report a security problem and how we decide whether a report is one.

| Version | Supported |
| ------- | ------------------ |
| 4+ | :white_check_mark: |
| <4 | :x: |
## Reporting a vulnerability

## Reporting a Vulnerability
**Do not open a public issue for a security problem.** Use
[GitHub's private vulnerability reporting](https://github.com/tox-dev/pre-commit-uv/security/advisories/new). If you do
not have a GitHub account, or you are a Tidelift subscriber, report through the
[Tidelift security contact](https://tidelift.com/security) instead; Tidelift coordinates the fix and the disclosure with
us. If neither works for you, email [gaborjbernat@gmail.com](mailto:gaborjbernat@gmail.com).

To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift
will coordinate the fix and disclosure.
Include what the attacker controls and what they gain, the steps or a proof of concept, and the versions and platform
you tested. Write the report yourself; unreviewed tool or model output takes longer to triage.

## What to expect

Volunteers maintain pre-commit-uv, with no on-call rotation. We aim to acknowledge a report within **5 working days**
and assess it within **15 working days**. Fixes are developed in a private fork attached to the draft advisory, ship in
a new release, and the advisory, with a CVE requested through GitHub, goes out with that release. The default embargo is
**90 days** from the report; we agree any change with the reporter. We credit reporters in the advisory under the name
they choose, or not at all. There is no bug bounty.

## Supported versions

Only the latest release receives fixes. Confirm the problem reproduces on it before reporting.

## Vulnerability or bug

A report is a vulnerability when an attacker who controls only an input that the [threat model](THREAT_MODEL.md) lists
as untrusted breaks a guarantee pre-commit-uv makes. A report that first needs control of something the threat model
trusts, such as the `.pre-commit-config.yaml`, the hook repositories it pins, or the bundled uv, is a bug: we fix it in
the open and publish no advisory. Classify by who supplies the dangerous input, not by how bad the outcome sounds.

**In scope**

- pre-commit-uv installing something other than what pre-commit asked for: a different hook repository, revision or
`additional_dependencies`.
- pre-commit-uv running a `uv` other than the bundled `uv` package.
- The patch activating when `DISABLE_PRE_COMMIT_UV_PATCH` is set.

**Out of scope**

- Hooks and their dependencies: the config chooses them and pre-commit runs them; a malicious hook repository is trusted
by the config that pins it.
- pre-commit's own behavior; report it to [pre-commit](https://github.com/pre-commit/pre-commit).
- uv's resolution and downloads; report those to [uv](https://github.com/astral-sh/uv/security/policy).

If you cannot tell which side a finding falls on, report it privately and we will decide.
94 changes: 94 additions & 0 deletions .github/THREAT_MODEL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Threat model

This model says what pre-commit-uv trusts, where untrusted input enters, and which guarantees hold at each boundary, so
a report can be classified as a vulnerability or a bug. [SECURITY.md](SECURITY.md) covers reporting; if the two
disagree, SECURITY.md wins and we fix this file.

## How pre-commit-uv works

Installed next to pre-commit, pre-commit-uv ships a `.pth` file that runs at every interpreter start in that environment
and patches pre-commit's Python language only when the running program is pre-commit (or `FORCE_PRE_COMMIT_UV_PATCH` is
set) and `DISABLE_PRE_COMMIT_UV_PATCH` is not. When pre-commit installs a Python hook, the patch creates the hook's
environment with `uv venv` and installs the hook package and `additional_dependencies` with `uv pip install`, using the
bundled `uv` with `--project` pointing at the repository root. Everything else, from cloning hook repositories to
running hooks, stays with pre-commit.

```mermaid
flowchart LR
cfg[(".pre-commit-config.yaml<br/>(trusted)")] --> pc["pre-commit"]
pc -->|"install Python hook"| pcu["pre-commit-uv patch"]
pcu -->|"bundled uv: venv, pip install"| env["Hook environment"]
idx[("Indexes, hook repos")] --> env
pc -->|"run hook on files"| env
classDef trusted fill:#cfe4ff,stroke:#1f6feb,color:#0b1f3a;
classDef boundary fill:#ffe3a3,stroke:#d29200,color:#3a2c00;
class cfg,pc,pcu trusted;
class env,idx boundary;
```

## Compared with similar tools

We drew the boundary where comparable projects in other ecosystems draw theirs. The shared rule, stated by the
[Node.js threat model](https://github.com/nodejs/node/blob/main/SECURITY.md#the-nodejs-threat-model), is that a report
which first needs a trusted element compromised is out of scope; the first triage question, as
[DOMPurify](https://github.com/cure53/DOMPurify/blob/main/SECURITY.md#what-we-consider-a-vulnerability) puts it, is what
the attacker controls.

| Project | Language | Where it draws the line |
| -------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------- |
| [pre-commit](https://github.com/pre-commit/pre-commit) | Python | The host tool runs whatever hooks the config pins; reports go through Tidelift. |
| [Lefthook](https://github.com/evilmartians/lefthook/security/policy) | Go | Runs the commands its configuration names by design; that is not a vulnerability. |
| [uv](https://github.com/astral-sh/uv/security/policy) | Rust | The installer pre-commit-uv delegates to. |

## Trusted

An attacker who controls any of these already has what pre-commit-uv could give them:

- The user, `.pre-commit-config.yaml`, and the hook repositories and revisions it pins.
- pre-commit and the bundled `uv`.
- The configured package indexes, `UV_*` settings, and the repository's own uv configuration, which uv reads through
`--project`.

## Untrusted inputs

| Input | Comes from | What pre-commit-uv guarantees |
| ------------------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| The program being started | Any Python process in the environment, since the `.pth` file runs at startup | The patch applies only when pre-commit itself is running or `FORCE_PRE_COMMIT_UV_PATCH` is set, and never with `DISABLE_PRE_COMMIT_UV_PATCH`. |

## Threats and controls

| STRIDE | Threat | Control |
| ---------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| Tampering | The patch installs a hook package other than the one pre-commit requested. | It passes pre-commit's own package list to `uv pip install` unchanged. |
| Elevation of privilege | The patch loads into an unrelated program and changes how it installs. | Startup check that the caller is pre-commit; `DISABLE_PRE_COMMIT_UV_PATCH` turns it off. |

## Release chain

Releases run on the shared [tox-dev/github-workflows](https://github.com/tox-dev/github-workflows) pipeline. A release
App, not a person, pushes the release commit and tag; the build installs its tools from a hash-checked `pylock.toml`
resolved with a seven-day upload cooldown by default; PyPI receives the files through
[trusted publishing](https://docs.pypi.org/trusted-publishers/) with attestations, and the GitHub Release is immutable.
Every workflow pins third-party actions to a commit SHA.

## Vulnerability or bug

| Report | Verdict | Why |
| ---------------------------------------------------------------------------------------- | ------------------- | -------------------------------------------- |
| `pre-commit-uv` installs an `additional_dependencies` entry that the config did not list | Vulnerability | Install set must match pre-commit's request. |
| A pinned hook repository ships malware | Not a vulnerability | The config trusts its hooks. |
| uv picks a different Python than virtualenv would | Bug | Behavior difference, no boundary crossed. |

## Severity

- **High:** pre-commit-uv installs a package pre-commit did not request, or runs a `uv` other than the bundled one.
- **Medium:** the patch changes how an unrelated program installs, or activates despite `DISABLE_PRE_COMMIT_UV_PATCH`.
- **Low:** a crash or confusing error while setting up a hook environment.

## Accepted risks

- pre-commit-uv inherits pre-commit's trust in hook repositories.

## Keeping this current

Revisit this model when pre-commit-uv gains a new input source, network access, a new way to run code, or a new place it
writes to, and whenever an advisory is published.