Skip to content
Merged
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
71 changes: 62 additions & 9 deletions docs/guides/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ short_title: Security
Supply-chain and CI security are increasingly important for scientific Python
projects; new attacks are targeting smaller packages than ever before thanks to
the ease with which exploits can be found and utilized with AI. The first six
months of 2026 had 4.5x the malitoius package volume of _all_ of 2025[^1].
months of 2026 had 4.5x the malicious package volume of _all_ of 2025[^1].

[^1]: <https://phoenix.security/accelerating-supply-chain-attacks-npm-pypi-vsx-ai-enabled-2026/>

Most of these attacks strung together smaller vulerabilties into something
Most of these attacks strung together smaller vulnerabilities into something
exploitable, often in CI like GitHub Actions. Once in, the attacks upload
malitious packages that spread the attack via PyPI or NPM.
malicious packages that spread the attack via PyPI or NPM.

This page has recommendations for keeping your repository and its automation
secure. This will never be complete, but even a few small steps can make your
Expand All @@ -22,22 +22,22 @@ code much more secure.
## GitHub Actions

{rr}`SEC001` GitHub Actions workflows are a common source of security issues,
due to how commonly it is used, and it's original design being focused on ease
due to how commonly it is used, and its original design being focused on ease
of use and convenience.

Common security problems:

* Action moving references, like `@v1`, or tags, like `@v1.0.1`, can be pushed
if an attacker comprimizes the action repository you are using. If you use
if an attacker compromises the action repository you are using. If you use
full 40 character SHA's, these cannot be modified. (Official actions are
likely okay, but important for third party actions). There's even a GitHub
setting to require this. It's conventional to include the tag as a trailing
comment.
* Action SHA references can be added by a fork. If you make a fork of
`actions/checkout`, you can reference _your_ SHA via
`actions/checkout@<SHA>`. Only accept SHAs you have verified or a tool (like
dependabot) produce. If you use Zizmor, it can also verify that an SHA matches
a tag, tags cannot be pulled from a fork.
`actions/checkout`, you can reference _your_ SHA via `actions/checkout@<SHA>`.
Only accept SHAs you have verified or a tool (like dependabot) produces. If
you use Zizmor, it can also verify that an SHA matches a tag, tags cannot be
pulled from a fork.
* Caching is dangerous. Attackers can poison an unrelated cache. Avoid caching
in your release jobs.
* `pull_request_target` is really dangerous. Attackers can use it to poison
Expand Down Expand Up @@ -108,3 +108,56 @@ GitHub Action can upload its findings to GitHub's code scanning dashboard.
You can silence individual findings with `# zizmor: ignore[rule]` comments, or
collect them in a [`zizmor.yml`](https://docs.zizmor.sh/configuration/) config
file.

## Cooldowns

Often malicious code is found and removed from indexes in a day or two. Adding
a cooldown can protect you from some of these large scale attacks that are
caught quickly. There's a problem though; this also means you'll take longer
to get fixes for known vulnerabilities. Also, if everyone does this, it could
just delay discovering attacks. So try to strike the appropriate balance.

[Dependabot's cooldown][dependabot-cooldown] can do that for you (GitHub
Actions, pre-commit, lock files for most languages):

```yaml
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 3
```

The `cooldown` block goes inside each `updates:` entry; repeat it for every
ecosystem you configure.

For uv, you can add hard-coded config with [`exclude-newer`][uv-exclude-newer]:

```toml
[tool.uv]
exclude-newer = "3 days"
```

You can also [override or skip specific packages][uv-exclude-newer-package].

For uv or pip, you can set environment variables:

```bash
PIP_UPLOADED_PRIOR_TO="P3D"
UV_EXCLUDE_NEWER="3 days" # P3D works too
```

There are also matching command line arguments for both
([`--uploaded-prior-to`][pip-uploaded-prior-to]/[`--exclude-newer`][uv-exclude-newer]).
You can also use dates instead of durations, which works much like lockfiles
without the churn. Use a full date, including time, like
`"2026-01-01T00:00:00Z"`, to get matching behavior in pip and uv, otherwise they
will be one day off each other.

[dependabot-cooldown]: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#cooldown
[uv-exclude-newer]: https://docs.astral.sh/uv/reference/settings/#exclude-newer
[uv-exclude-newer-package]: https://docs.astral.sh/uv/reference/settings/#exclude-newer-package
[pip-uploaded-prior-to]: https://pip.pypa.io/en/latest/user_guide/#filtering-by-upload-time
Loading