Skip to content

fix(api): pin the container toolchain, drop root, and close the CORS origin wildcard - #36

Merged
PunGrumpy merged 5 commits into
mainfrom
advisor/003-docker-cors-hardening
Aug 30, 2026
Merged

PunGrumpy merged 5 commits into
mainfrom
advisor/003-docker-cors-hardening

Conversation

@PunGrumpy

Copy link
Copy Markdown
Owner

Why

  • The Docker builder used `golang:tip-alpine`, Go's unreleased development branch, while the rest of the repo pins 1.26.4. The same commit could produce different binaries on different days.
  • The runtime stage ran the process as root on `distroless/base-debian12` even though the binary is a static `CGO_ENABLED=0` build. The image decoders parse attacker-controlled bytes, so the process should hold the smallest identity and surface available.
  • CORS allowed any origin matching the prefix `https://pigo-web\` plus the suffix `.vercel.app`. Vercel project names are first come, first served, so anyone could register `pigo-web-anything.vercel.app` and gain a permanently allowed origin.
  • `PORT` and `CORS_ALLOWED_ORIGINS` were documented nowhere.

What changed

  • Dockerfile: builder pinned to `golang:1.26.4-alpine`, `go.work.sum` copied for reproducible module resolution, `-trimpath` added, runtime moved to `gcr.io/distroless/static-debian12:nonroot` with an explicit `USER nonroot:nonroot`.
  • `cors.go`: the hardcoded prefix/suffix branch is gone. Origins now match exactly against the allowlist, and an allowlist entry of the form `https://*.example.com` matches one subdomain level. `Vary: Origin` is set on every response so shared caches never serve a header-less response to an allowed origin.
  • New `cors_test.go`: table tests covering the exact match, the `pigo-web-evil.vercel.app` spoof regression, wildcard edge cases (two labels, lookalike suffix, non-https scheme), and an httptest pass through the middleware for both allowed and disallowed origins.
  • New `apps/api/.env.example` and a README Configuration section documenting both variables. The API has no dotenv loader; the docs say to set the variables in the process environment.

Breaking change for previews

Vercel preview deployments are no longer allowed by default. Set `CORS_ALLOWED_ORIGINS` with your account-scoped wildcard, for example `https://*.your-team-projects.vercel.app`, which outsiders cannot register under.

Verification

  • `go test ./...` and `go vet ./...` pass in `apps/api` (9 new CORS subtests).
  • Docker was unavailable in the sandbox, so the image build and `/health` smoke test still need one local run: `docker build -f apps/api/Dockerfile -t pigo-api .` then `docker run --rm -p 3001:3001 pigo-api` and `curl localhost:3001/health`.

Replace golang:tip-alpine with the pinned 1.26.4 release used
elsewhere in the repo, copy go.work.sum for reproducible builds, and
switch the runtime stage to distroless/static-debian12:nonroot so the
container no longer runs as root.
The old prefix+suffix check (https://pigo-web* + *.vercel.app) allows
anyone who registers a Vercel project like pigo-web-anything to get a
permanently allowed origin. Replace it with exact-match against the
allowlist plus an explicit https://*.example.com wildcard syntax that
matches a single subdomain label. Also set Vary: Origin on every
response instead of only allowed-origin responses.
Cover exact-match, the prefix-spoof regression (pigo-web-evil.vercel.app),
wildcard subdomain matching edge cases, and the Vary header behavior of
the CORS middleware end to end.
Add apps/api/.env.example and a Configuration subsection in the
README so the API's environment variables are no longer undocumented.
The API reads os.Getenv directly; nothing loads a .env file, so
telling users to copy apps/api/.env.example to apps/api/.env had no
effect. State that the API reads its settings from the process
environment instead. Also tightens the CORS table cell to active
voice.
@vercel

vercel Bot commented Aug 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pigo Ready Ready Preview Aug 30, 2026 2:00am
pigo-api Ready Ready Preview Aug 30, 2026 2:00am

@changeset-bot

changeset-bot Bot commented Aug 30, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 7c277d5

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@PunGrumpy
PunGrumpy merged commit 22c7622 into main Aug 30, 2026
10 checks passed
@PunGrumpy
PunGrumpy deleted the advisor/003-docker-cors-hardening branch August 30, 2026 02:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant