Skip to content
Open
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
73 changes: 64 additions & 9 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# How to contribute

This repository is maintained by the website committee of [Tartarus](https://tartarus.science.ru.nl/), reachable at [tartaruswebsite@science.ru.nl](mailto:tartaruswebsite@science.ru.nl). The project is open-source — issues and pull requests are welcome.
This repository is maintained by the website committee of [Tartarus](https://tartarus.science.ru.nl/), reachable at <www-tosti@science.ru.nl>. The project is open source; issues and pull requests are welcome.

If you're an AI coding agent (Claude Code, Cursor, Aider, …), read [`AGENTS.md`](AGENTS.md) instead — it carries the same conventions but is condensed to what an agent needs to make safe edits. Humans should keep reading here.

> [!IMPORTANT]
> Before you build something, read the next section. The scope check is the single load-bearing rule in this project.

---

## Before you build something: does it belong in TOSTI?

Expand All @@ -14,9 +21,23 @@ If a proposed feature fails any of these tests, it does not belong in TOSTI. The

**When in doubt, open an issue first** so the website committee can weigh in before code is written.

## Getting set up
---

## Quick start

The README's "🛠️ Development Setup" section has the canonical instructions. Briefly: clone, `uv sync`, `uv run python website/manage.py migrate`, `uv run python website/manage.py runserver`. SAML is disabled in development; use `/admin-login` to sign in as a Django superuser.
1. **Set up locally.** Follow the [Local development](README.md#-local-development) section in the README — clone, `uv sync`, `migrate`, `runserver`. SAML is disabled in development; use `/admin-login` to sign in as a Django superuser.
2. **Branch off `master`.** Pick a short, descriptive branch name (e.g. `fix-spotify-auto-start`, `add-tampon-form`).
3. **Make your change.** Keep the diff focused — one feature or fix per PR.
4. **Run the checks locally** before opening the PR:
```bash
cd website
uv run python manage.py test # tests
uv run black website # format
uv run flake8 --exclude="migrations,website/tosti/settings/*.py" \
--max-line-length=119 website # lint
uv run python manage.py spectacular --validate --api-version v1 > /dev/null
```
5. **Open the PR** against `master`. CI must pass (test + lint + image build). Merging to `master` triggers an automatic deploy to <https://tosti.science.ru.nl> via the self-hosted runner. See [`deploy/README.md`](deploy/README.md).

## Project conventions

Expand Down Expand Up @@ -79,6 +100,8 @@ The site runs under a strict Content Security Policy (`tosti/settings/base.py`).

The `yivi/` and `age/` apps implement legally-loaded age verification for the beer fridge. A bug here can result in underage students opening the fridge. Treat these apps as critical: changes need close review, no shortcuts, and any modification of the verification path should come with a corresponding test demonstrating the legal-compliance behaviour still holds. Don't refactor opportunistically; if a change isn't strictly necessary, don't make it.

If you spot a security-sensitive bug in these (or anywhere else), see [`SECURITY.md`](SECURITY.md) — don't open a public issue for it.

### Privacy and PII

TOSTI handles personal data: names, emails, age verifications, transactions, song requests. New features that touch user data must:
Expand All @@ -92,11 +115,12 @@ TOSTI handles personal data: names, emails, age verifications, transactions, son
- Each app keeps its own tests under `<app>/tests/test_*.py`. We use a `tests/` package per app — single-file `tests.py` is being phased out.
- Cross-cutting tests live in `tosti/tests/`. For example, the MCP transport / auth-gate / discovery tests are there, while individual MCP tool behaviour is tested in the owning app's test module.
- Use existing fixtures (e.g. `venues.json`) when your test needs venues; don't reinvent setup logic in every test class.
- Run the full suite: `uv run python website/manage.py test website/`.
- Don't delete or `@skip` failing tests to make CI pass — fix the underlying bug (or, if the test was wrong, restate the expected behaviour explicitly).
- Run the full suite locally before you push: `uv run python website/manage.py test website/`.

### Code style

- `black` for formatting, `flake8` for linting (line length 119). CI runs both.
- `black` for formatting, `flake8` for linting (line length 119), `pydocstyle` for docstring style. CI runs them all.
- The site is in **English**. Don't introduce Dutch strings into UI text — the canteens are used by international students. The handful of existing Dutch fragments are scoped to legacy or external integrations; don't grow that footprint.
- Heading hierarchy: pages should have one `<h1>` and use `<h2>`/`<h3>` for sections. Use `<p class="lead tagline">` for branding subtitles, not `<h6>`.
- For dropping shared reading-width on text-heavy pages, use the project's `.prose` utility class.
Expand All @@ -105,8 +129,39 @@ TOSTI handles personal data: names, emails, age verifications, transactions, son

Vendored under `website/<app>/static/<app>/` or `website/tosti/static/tosti/`. See the README's "Vendored frontend libraries" section for the upgrade procedure. Don't pull in new CDN dependencies — keep CSP tight.

## Workflow
## Opening a pull request

A good PR is short, focused, and self-explanatory:

- **One feature or fix per PR.** Bundling unrelated changes makes review slow and rollbacks risky.
- **PR description.** Say what changed and *why*. Link the issue if there is one. If the change touches an external system (Sentry, Yivi, Spotify, Silvasoft, SURFconext), call that out — it's the kind of thing reviewers can miss.
- **Tests.** If you fix a bug, add a regression test that fails on master and passes on your branch. If you add a feature, cover the happy path plus at least one edge case.
- **Migrations.** If you touched a model, run `makemigrations` and commit the file in the same PR. Flag destructive migrations in the description.
- **Don't squash other people's commits.** Rebases are fine for your own branch; rewriting shared history is not.
- **Don't `--force-push` master or `--no-verify` past hooks.** Ever.

CI runs tests + flake8 + black + image build. All must pass before merge.

## Reviewer checklist

If you're reviewing a PR (whether you're a fellow committee member or a maintainer):

- [ ] Does the change satisfy the scope check at the top of this document?
- [ ] Is it isolated to one app where possible? Cross-app imports justified?
- [ ] If a model changed, is there a migration?
- [ ] If a sensitive app (`age`, `yivi`, `fridges`, `silvasoft`) changed, is there a test that proves the contract still holds?
- [ ] Is there new PII being logged or stored? If stored, is the privacy policy updated?
- [ ] Are new frontend assets vendored, not CDN-loaded?
- [ ] CI green?

Once merged, the change ships to <https://tosti.science.ru.nl> automatically. Treat every merge as a deploy.

## Getting help

If you're stuck:

1. Branch off `master`.
2. Open a PR. CI must pass (test + lint + image build).
3. Merging to `master` triggers a deploy to `tosti.science.ru.nl` via the self-hosted runner. See `deploy/README.md`.
- Open a draft PR and tag the website committee — early feedback beats a long-running branch.
- Email <www-tosti@science.ru.nl>.
- For security-sensitive questions, see [`SECURITY.md`](SECURITY.md).
</content>
</invoke>
59 changes: 54 additions & 5 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,58 @@
# Security Policy
# Security policy

## Supported Versions
TOSTI handles personal data for Radboud students, processes payments and refunds, and includes legally-loaded surfaces like Yivi-backed age verification for the beer fridge. If you find a security issue, we want to hear about it before anyone else does.

Only the most recent version of this product is supported.
## Supported versions

## Reporting a Vulnerability
Only the most recent version of TOSTI &mdash; whatever is currently deployed at <https://tosti.science.ru.nl> &mdash; is supported. We do not back-port fixes to older deployments.

In case you find a vulnerability in this product that cannot responsibly be published by creating an issue, please contact the maintainers via [www-tosti@science.ru.nl](mailto:www-tosti@science.ru.nl)
The same applies to the [TOSTI-fridge-client](https://github.com/KiOui/TOSTI-fridge-client) firmware running on the Raspberry Pi inside each beer fridge: only the most recent release is supported.

## Reporting a vulnerability

**Do not open a public GitHub issue or pull request for security-sensitive bugs.** Email <www-tosti@science.ru.nl> instead.

Please include:

- A description of the issue and what an attacker could do with it.
- Steps to reproduce. A minimal proof-of-concept &mdash; a `curl` invocation, a script, a request payload &mdash; helps us confirm the issue quickly.
- The TOSTI version or commit you tested against (if you know it).
- Whether you've already shared the details with anyone else.

You should hear back from the website committee within **7 days** with an acknowledgement. We'll work with you to confirm the issue, agree on a fix timeline, and credit you in the release notes if you'd like.

If you do not get a response within 14 days, the report has likely been missed — please re-send the email and consider also reaching out to the Tartarus board directly.

## What counts as a security issue

When in doubt, report it &mdash; we'd rather field a false alarm than miss something. As a rough guide, these are squarely in scope:

- **Authentication / authorisation bypasses** &mdash; anything that lets a user access another user's account, place orders or transactions on someone else's behalf, or read data without the right scope.
- **Age-verification bypass** for the beer fridge. This is the most legally-loaded surface in the project; treat anything that lets an underage user open a fridge as a high-severity issue.
- **OAuth / MCP issues** &mdash; flaws in the authorize / token / consent flow at `/oauth/authorize/`, `/oauth/token/`, `/oauth/register/`, or the MCP endpoints. Scope-confusion, PKCE bypass, token leakage, replayable codes, etc.
- **SAML / SURFconext integration** issues &mdash; signature bypass, assertion replay, IdP impersonation.
- **Privacy leaks** &mdash; endpoints that return PII (names, emails, age verifications, transaction history, song requests) to viewers who shouldn't see it.
- **Stored XSS / CSRF / SQL injection / SSRF** in any user-reachable surface.
- **Secret leakage** &mdash; credentials, tokens, or keys exposed in the repo, in logs, in Sentry events, or in client-side responses.
- **Supply chain** &mdash; vulnerabilities in our vendored frontend assets (Vue, Bootstrap, Swagger UI, etc.) or in dependencies that meaningfully affect TOSTI.

Generally out of scope (open a regular GitHub issue for these):

- Bugs in feature behaviour that don't have a security impact.
- Cosmetic issues, broken links, typos.
- Reports against domains we don't operate (e.g. `radboudnet.nl`, `surf.nl`).
- Findings from automated scanners without a working proof-of-concept &mdash; we appreciate the heads-up, but please verify the finding manually first.

## What we ask of reporters

- **Don't pivot.** Once you've demonstrated the issue exists, stop. Don't download other people's data, modify production state, or escalate to other surfaces.
- **Don't test in production unless you have to.** A local dev instance reproduces almost everything.
- **Coordinate disclosure.** Don't publish details &mdash; talks, blog posts, social media &mdash; until we've shipped a fix and agreed on a publication date with you.

## Maintainer contact

- Website committee &mdash; <www-tosti@science.ru.nl>

This is the same address that handles general project correspondence; flag your message clearly as a security issue in the subject line so it gets routed appropriately.
</content>
</invoke>