# Security Policy

`mgf-uitest-web` is a federation sibling of
[`mgf-common`](https://codeberg.org/magogi-admin/mgf-common). It is a
**browser-level UI-testing harness** — a `pytest11` plugin + small Python library
that drives each Magogi web console (ProofWarden / `mgf-test-web`, `mgf-fed-web`,
`mgf-cloud-web`) in a **real Chromium via Playwright** and asserts layout
integrity, accessibility (axe-core), and runtime health that an in-process
`TestClient` can't see.

Its security surface is deliberately small. The harness handles **no secrets, no
credentials, no cryptography, and no authentication**; it has no vault, no token
handling, and no TLS client of its own. It **never imports** any console's
FastAPI / Jinja stack (mechanically pinned by the import-linter `PR-01` contract
in `pyproject.toml`) — it drives each console as a **black box over loopback
HTTP**. The whole attack surface is therefore: how it launches a console
subprocess, the loopback traffic it makes, and the screenshots it writes.

## Threat surface

- **Subprocess launch of a caller-supplied console argv.** The harness spawns
  each console under test as a black-box server in `live_server(...)`
  (`src/mgf/uitest_web/server.py:111`): `subprocess.Popen(list(command), …)` — an
  **argv list**, never a shell string (`shell` stays at its `False` default), so
  there is no shell-injection seam (**SC-02**). `command` is the console's own
  launch argv, supplied by the consumer's `console_launch` fixture — it carries
  the same trust as any test code the consumer runs.
- **Loopback-only outbound traffic.** The only network egress the harness itself
  makes is a readiness probe — `urllib.request.urlopen` against
  `http://127.0.0.1:<port>` (`_probe`, `src/mgf/uitest_web/server.py:80`), the
  loopback console under test — with a finite 2-second timeout (**SC-07**).
  Playwright's Chromium then drives that same loopback URL. Nothing is sent
  off-box.
- **Screenshot writes into the consumer's own test tree.** `capture(...)`
  (`src/mgf/uitest_web/checks.py:243`) writes PNGs beneath the consumer's own
  `tests/_artifacts/gallery/`. The filename is built from caller-supplied test
  labels; no external/network input reaches the filesystem.
- **No secret, crypto, or auth handling.** The harness stores, reads, and
  transmits no credentials, and performs no cryptography or authentication.
  A console's *own* auth is exercised only through the console process itself,
  never re-implemented here.

Static analysis backs this posture: ruff's bandit-`S` rules, `gitleaks`, and
`pip-audit` run in CI on every change.

## Supported versions

In the 0.x window, **only the latest MINOR receives fixes** (per
[SemVer 0.x](https://semver.org/#spec-item-4) and rule **AP-03** — each MINOR MAY
break the public API, so we do not branch-and-backport across minors). Fixes ship
as the next PATCH on the latest MINOR.

| Version | Supported |
|---------|-----------|
| `0.1.x` | ✅ — latest MINOR (Pre-Alpha) |
| `< 0.1` | ❌ — pre-publish development |

## Remediating a vulnerable release — there is no auto-yank

`mgf-uitest-web` is distributed from the self-hosted **devpi** index
(`magogi/prod`), **not** pypi.org, so there is **no PyPI-style "yank"** an advisory
can trigger automatically. A vulnerable release is remediated by **pin +
republish**: **ship the fixed version first**, then — only if the affected version
must be withdrawn — an index admin toggles the non-volatile stage volatile and runs
`devpi remove <name>==<version>` on the devpi host (a manual host-admin action,
recorded in [`CHANGELOG.md`](CHANGELOG.md)). See
[`SECURITY_STANDARD.md`](https://codeberg.org/magogi-admin/mgf-standard/src/branch/main/docs/standards/SECURITY_STANDARD.md)
§11.3 (**SC-11.3**) and
[`PACKAGE_DISTRIBUTION.md`](https://codeberg.org/magogi-admin/mgf-standard/src/branch/main/docs/standards/PACKAGE_DISTRIBUTION.md)
**PKG-07** for the immutability + delete/re-upload contract.

## Reporting a vulnerability

**Please do not open a public issue or pull request** until we have had a chance
to triage and patch. Report privately via either:

- a **Codeberg private security advisory** on
  [the repository's Security tab](https://codeberg.org/magogi-admin/mgf-uitest-web/security), or
- the maintainer (see the `magogi-admin` Codeberg profile).

**Include:** a description of the issue and its impact; steps to reproduce (a
minimal failing spec, or an HTTP transcript against the loopback console, is
ideal); the affected version(s); and your Python version + OS.

**What to expect:** acknowledgement within 3 business days; an initial severity
assessment within 7 business days; a patch + release targeted within 30 days for
high-severity issues, 90 days for medium. We follow a **90-day default embargo**,
extendable by mutual agreement. Once a patch ships (or the embargo expires,
whichever is first), the issue is disclosed in the [`CHANGELOG.md`](CHANGELOG.md)
entry for the fixing release, with credit to the reporter unless declined.

**Out of scope:** vulnerabilities in `playwright`, `axe-playwright-python`,
`pytest`, or any other dependency (report upstream); and vulnerabilities in a
**console under test** itself (report to that console's own repository — the
harness only drives it).

## A note on the standards

`mgf-uitest-web` inherits the cross-project engineering security standard
([`SECURITY_STANDARD.md`](https://codeberg.org/magogi-admin/mgf-standard/src/branch/main/docs/standards/SECURITY_STANDARD.md),
the `SC-*` rules) from mgf-standard via the `mgf-common[standards]` extra; it is
not copied here. See [`docs/standards/README.md`](docs/standards/README.md) for
the pointer.
