SECURITY.md
Rama main · réplica de lectura del repositorio original.
# Security
## Reporting a vulnerability
**Please do not open a public issue.** Use GitHub's
[private vulnerability reporting](https://github.com/roleALPHA/good-workshop/security/advisories/new)
— it is enabled on this repository, and it gives us a place to talk before anything is public.
Tell us what you found, how to reach it, and what an attacker gets out of it. A proof of concept
helps; a sentence that explains the consequence helps more.
You will hear back within a week. If a report turns out to be valid we will agree a disclosure
date with you and credit you in the advisory unless you would rather we did not.
## What this product is, for the purposes of a report
GoodWorkshop is **self-hosted**. There is no service to compromise — every installation belongs
to whoever runs it, holds their data in their own Postgres, and is reachable at their own
hostname. A finding here is a finding in software other people deploy, which is the reason a
quiet report matters more than it would for a hosted product: the fix has to reach operators
before the details do.
## The boundaries worth attacking
These are the lines the architecture is built around. If you can cross one, that is a finding
even if nothing else looks broken:
- **The tenant boundary.** Row-level security, forced on every tenant-scoped table, is what keeps
one organisation's data out of another's. `docs/architecture.md` explains why the boundary is
in the database rather than in the application.
- **The workshop boundary.** Authorisation is in the application layer on purpose; a colleague
who was never shared with must get "not found", from the same code path everybody else uses.
- **Folder grants.** They reach a whole subtree and workshops other people own, bounded by two
rules: the nearest folder decides, and nobody hands on more than they hold.
- **Share links for guests.** A link grants exactly one workshop, creates no identity and no
member, and its session cannot become a login — `share_session` has no `identity_id` column at
all. Anything that turns a guest into an account is serious.
- **MCP tokens and OAuth.** A token acts as the person who made it and can never do more than
that person can. There is deliberately no scope for user administration. An OAuth access token
carries the audience it was issued for and must not work anywhere else.
- **The first-run setup screen.** It is gated by a key printed to the server log and disappears
once an administrator exists. A way to reach it afterwards, or to claim an installation without
the key, is a takeover.
## Not vulnerabilities
- A tenant administrator being able to see the data of their own tenant. That is the role.
- `GW_MAIL_TRANSPORT=console` printing working sign-in links into the log. It is documented as
such, warned about at startup, and is the intended route for an installation with no relay.
- Missing rate limits on an endpoint behind a session, unless you can show what it costs.
## Supported versions
The latest release. This project is young and moves quickly; there is no back-porting yet.
## How a report is handled
So that the process is visible rather than promised:
1. **Acknowledged within a week**, through the private advisory thread.
2. **Assessed** — what an attacker gets, which versions are affected, whether it crosses one of
the boundaries above.
3. **Fixed on a private fork** where the change is not public before the advisory is.
4. **Released** as a normal tagged release, with the advisory published at the same time and a
CVE requested through GitHub where one is warranted.
5. **Credited** to you in the advisory unless you would rather we did not.
Because every installation is somebody else's, there is no "we patched production and you are
already safe". Operators have to pull an image, so the advisory says which version fixes it and
the release notes repeat it.
## Finding out what is in a release
You do not have to take our word for what a version contains:
- **`sbom.cdx.json`** is attached to every release — a CycloneDX document with the exact version
of every dependency that went into it. Feed it to Dependency-Track, Grype or anything else
that speaks CycloneDX.
- **The published image carries an SPDX attestation** of its own filesystem, readable without
pulling it:
```bash
docker buildx imagetools inspect ghcr.io/rolealpha/good-workshop:0.5.3 --format '{{ json .SBOM }}'
```
- **`THIRD-PARTY-LICENSES.txt`** ships inside the image and is attached to every release.
## Checking that an image is ours
Every published image is signed with a keyless [Sigstore](https://www.sigstore.dev/) signature
and carries a GitHub build attestation. Both name the workflow and the commit that built it, so
an image pushed by anybody else — with a stolen registry token, say — fails the check:
```bash
cosign verify ghcr.io/rolealpha/good-workshop:0.5.3 \
--certificate-identity-regexp '^https://github\.com/roleALPHA/good-workshop/\.github/workflows/publish\.yml@' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
gh attestation verify oci://ghcr.io/rolealpha/good-workshop:0.5.3 --repo roleALPHA/good-workshop
```
`cosign` needs to be version 3 or later: the signatures are stored in the Sigstore bundle
format, which version 2 does not look for and reports as "no signatures found". Releases before
0.5.3 carry neither a signature nor an attestation.
Dependabot, CodeQL (`security-extended`), dependency review and secret scanning with push
protection run on this repository. So do gitleaks over the whole git history, zizmor over the
workflows, osv-scanner over the lockfile, Grype over the built image, and the OpenSSF Scorecard.
CI runs the full suite nightly. That is how we find the
boring half; the interesting half comes from people like you.