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
61 changes: 46 additions & 15 deletions .github/SECURITY.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,54 @@
# Reporting Vulnerabilities
# Security policy

**⚠️ Please do not file public GitHub issues for security vulnerabilities as they are open for everyone to see! ⚠️**
pytest-env is a pytest plugin that sets environment variables for a test session from pytest configuration and `.env`
files. This policy says how to report a security problem and how we decide whether a report is one.

We encourage responsible disclosure practices for security vulnerabilities.
## 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/pytest-dev/pytest-env/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).

If you believe you've found a security-related bug, fill out a new vulnerability report via GitHub directly. To do so,
follow these instructions:
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.

1. Click on the `Security` tab in the project repository.
1. Click the green `Report a vulnerability` button at the top right corner.
1. Fill in the form as accurately as you can, including as many details as possible.
1. Click the green `Submit report` button at the bottom.
## What to expect

## Don't have a GitHub account?
Volunteers maintain pytest-env, 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.

Alternatively, to report a security vulnerability, please use the
[Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
## Supported versions

It is currently set up to forward every incoming report to Bernát Gábor. We will try to assess the problem in timely
manner and disclose it in a responsible way.
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 pytest-env makes. A report that first needs control of something the threat model
trusts, such as the pytest configuration or the `.env` files it names, 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**

- pytest-env loading a `.env` file that neither the configuration nor `--envfile` names.
- A variable value printed when `--pytest-env-verbose` is off.
- TOML, INI and command-line precedence, or the skip-if-set baseline, differing from the documentation so a value the
user protected is overwritten.
- A configured value creating or removing a variable other than the one its entry names.

**Out of scope**

- Values the configuration sets: the project's pytest configuration is trusted, like the `conftest.py` and tests it
runs.
- `{VAR}` expansion reaching other variables: it is documented, off with `R:` in INI and opt-in with `transform` in
TOML.
- `.env` paths the configuration or `--envfile` chooses, including ones outside the rootdir.
- Values printed with `--pytest-env-verbose`, which exists to print them.
- Parsing behavior of `.env` files, which belongs to python-dotenv.

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

This model says what pytest-env 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 pytest-env works

Before tests are collected, pytest-env reads its entries from the `[pytest_env]` table of `pytest.toml`, `.pytest.toml`
or `pyproject.toml`, found in the pytest configuration's directory or the nearest parent with such a table, or else from
the `env` option of the INI configuration. It loads the `.env` files that `env_files` or `--envfile` name, resolved
against the rootdir and parsed with python-dotenv, then sets, unsets or keeps each variable, expanding `{VAR}`
references in INI entries not marked `R:` and in TOML entries that set `transform`. With `--pytest-env-verbose` it
prints each assignment, values included, in the report header.

```mermaid
flowchart LR
cfg[("pytest config<br/>(trusted)")] --> pe["pytest-env"]
dot[(".env files named by config<br/>(trusted)")] --> pe
pe -->|"set, unset, expand"| env["os.environ for the session"]
pe -->|"--pytest-env-verbose only"| log["Report header"]
classDef trusted fill:#cfe4ff,stroke:#1f6feb,color:#0b1f3a;
classDef boundary fill:#ffe3a3,stroke:#d29200,color:#3a2c00;
class cfg,dot,pe trusted;
class env,log 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 |
| -------------------------------------------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [direnv](https://direnv.net/man/direnv.1.html) | Go | Treats `.envrc` as untrusted until `direnv allow`, because it executes it; pytest-env's `.env` files are data named by trusted configuration and are never executed. |
| [dotenv](https://github.com/motdotla/dotenv/security/policy) | JavaScript | Publishes only a reporting contact. |
| [Vitest](https://github.com/vitest-dev/vitest/security/policy) | JavaScript | Trusts the developer, the config file, plugins and every project file; only reports that need no trusted element compromised count. |
| [Jest](https://github.com/jestjs/jest/security/policy) | JavaScript | Test runner; private reports with an escalation path, and no separate threat model. |

## Trusted

An attacker who controls any of these already has what pytest-env could give them:

- The pytest configuration and command line, including `env`, `env_files` and `--envfile`.
- The `.env` files they name, and the process environment used for `{VAR}` expansion.
- The tests, which run with the variables set.

## Untrusted inputs

| Input | Comes from | What pytest-env guarantees |
| ------------------------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Variable values in output | Secrets placed in `.env` files or the configuration | Not printed unless `--pytest-env-verbose` is given. |
| `.env` names | Configuration and `--envfile` | Resolved against the rootdir and never searched for elsewhere; a missing `--envfile` raises, a missing configured file is skipped. |

## Threats and controls

| STRIDE | Threat | Control |
| ---------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Information disclosure | A secret from `.env` appears in a public CI log. | Values are printed only with `--pytest-env-verbose`. |
| Tampering | A `.env` file nobody named is loaded. | Only named files are loaded. |
| Tampering | A protected variable is overwritten. | `env_files_skip_if_set` takes its baseline before any `.env` file is applied; `D:` and `skip_if_set` entries check the environment at their place in the list. |

## 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 |
| --------------------------------------------------------------------- | ------------------- | ----------------------------- |
| A secret shows up in the report header without `--pytest-env-verbose` | Vulnerability | Output guarantee broken. |
| The INI entry `FOO={HOME}` expands to the home directory | Not a vulnerability | Documented expansion. |
| A malicious `pytest.ini` sets `LD_PRELOAD` | Not a vulnerability | The configuration is trusted. |

## Severity

- **High:** a secret value printed without `--pytest-env-verbose`.
- **Medium:** an unnamed `.env` file loaded, or a protected variable overwritten against the documented precedence.
- **Low:** a crash or confusing error on malformed configuration.

## Accepted risks

- `--pytest-env-verbose` prints values, secrets included; do not enable it with secrets in public logs.
- The TOML search walks up parent directories without checking who owns them; do not run pytest below a directory other
users can write.

## Keeping this current

Revisit this model when pytest-env 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.