Skip to content

Prepare People Portal and the dev stack for Gitea 1.27.3 - #112

Closed
IanCou wants to merge 3 commits into
masterfrom
gitea-1.27-upgrade
Closed

IanCou wants to merge 3 commits into
masterfrom
gitea-1.27-upgrade

Conversation

@IanCou

@IanCou IanCou commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

Prepares People Portal and the dev stack for upgrading git.appdevclub.com from Gitea 1.24.3 to 1.27.3, which fixes the Actions fork-PR approval-gate bypasses (CVE-2026-58424, CVE-2026-71184). Found by running the upgrade end to end on the local dev stack with a hardened act_runner VM.

This must ship before (or with) the prod Gitea upgrade. On 1.27.3, every Gitea webhook delivery to People Portal returned 422: Gitea adds fields to the webhook repository object (branch_count, has_code, allow_merge_update, ...) and tsoa runs with noImplicitAdditionalProperties: throw-on-extras. New team repos would never be provisioned or branch-protected.

Commits

  1. Accept new Gitea webhook fields so repos keep provisioning after upgrades. Webhook models now declare only the fields the handlers read and accept the rest (the handlers already re-fetch the repository from the API before acting). Provisioning takes the API's copy of the repository plus the sender. routes.ts regenerated.
  2. Run the dev stack on Gitea 1.27.3 with production's planned hardening. DISABLE_HTTP_GIT=false (Actions checkout needs it), ENABLE_BASIC_AUTHENTICATION=false, DISABLE_QUERY_AUTH_TOKEN=true. With Basic passwords refused, bootstrap-gitea.sh's token recovery broke (403); it now uses a short-lived CLI-minted token as the Basic password, sweeps stale and orphaned rotation tokens, saves the replacement before cleanup, revokes the short-lived token via an EXIT trap on failure, and keeps tokens out of argv.
  3. Gitea 1.24 → 1.27 upgrade runbook in the wiki (deployment section): config, backup/rollback, release-note checks, People Portal prerequisite, per-org Restricted job tokens, verification checklist. Also bumps the legacy manual Gitea example off 1.21.0.

Test plan

  • prettier@3.9.10 --check on webui/server src, nx run-many -t build -p pplserver pplui, nx run pplserver:test-unit (CI's PR checks); full server suite 135/135
  • New gitea-webhook-payloads.test.js: 1.27-shaped payloads accepted (both were 422 on the old code); missing required field still rejected; created / first-push-to-main act on the API's repository and do nothing on an id/owner mismatch (faked clients)
  • Dev stack upgraded in place from a 1.24.3 volume: migrations 336-342 in one boot, scripts/verify.sh 16/16
  • End to end on 1.27.3 with the server running: new org repo private → public with default branch main; first push created the main rule (2 approvals, PeoplePortalTeamOwners allowlists, no force push); no validation errors
  • Bootstrap: normal run, recovery with stale token present / absent / orphaned rotation token, and a mid-recovery failure (lookup container stopped) that exits non-zero and leaves no extra token; no token in any script process's argv
  • Actions on a hardened runner VM: checkout passes on 1.27.3 (403 on the 1.24.3 baseline); org on Restricted → default token write 403, contents: write 201
  • Auth: password over git/API refused, token allowed, ?token= 401
  • corpwiki npm run build succeeds
  • Prod upgrade itself, following the new runbook (Postgres there; local stack is SQLite)

…ades

tsoa runs with noImplicitAdditionalProperties "throw-on-extras", and the
webhook bodies were modelled as the full repository object. Gitea 1.27 adds
fields to it (branch_count, has_code, allow_merge_update, ...), so every
repository and push delivery came back 422 and new team repos were never
made public, handed to their team, or branch-protected.

The webhook models now declare only the fields the handlers read and accept
the rest. Nothing else in the body was trusted anyway: both handlers re-fetch
the repository from the Gitea API before acting. Provisioning takes the
API's copy of the repository plus the sender instead of the raw event.

Tests post 1.27-shaped payloads through the generated routes (both were 422
before this change), check that a payload missing a field the handlers read
is still rejected, and drive the privileged paths with faked clients: repo
creation and the first push to main act on the API's copy of the repository,
and do nothing when it doesn't match the body. Verified end to end against
Gitea 1.27.3: a new org repo went private to public with default branch
main, and its first push created the main protection rule.
Bumps the local Gitea from 1.24.3 to 1.27.3, the release that fixes the
Actions fork-PR approval bypasses, and applies the settings planned for
git.appdevclub.com so local development sees the same behaviour:
DISABLE_HTTP_GIT=false (Actions checkout needs git over HTTP),
ENABLE_BASIC_AUTHENTICATION=false (no passwords over HTTP Basic) and
DISABLE_QUERY_AUTH_TOKEN=true (no ?token=).

With Basic passwords refused, bootstrap-gitea.sh could no longer recover a
lost .gitea-token: it listed and deleted the old token with the admin
password (HTTP 403). The CLI can mint tokens but not list or delete them, so
recovery now mints a short-lived token and sends it as the Basic password to
remove the stale people-portal-dev token (and any rotation tokens an earlier
failed run left), saves the replacement, then deletes the short-lived token
by name. An EXIT trap revokes the short-lived token if recovery stops
partway. Tokens reach curl through --config on stdin and the Python helper
through its environment, never argv.

Tested: normal run; recovery with the stale token present, absent, and with
an orphaned rotation token; and a recovery with the lookup's container
stopped mid-run, which exits non-zero and leaves no extra token. Each
success ends with exactly one valid people-portal-dev token and
.gitea-token mode 600; no token appeared in any script process's arguments.

Upgrade checked on an existing 1.24.3 volume: migrations 336-342 ran in one
boot and scripts/verify.sh passes all 16 checks.
Covers why (fork-PR approval-gate CVEs fixed in 1.26.3 and 1.27.3, with
instance-wide club runners), the target config (HTTP git on so Actions
checkout works, passwords off over HTTP Basic, no ?token=), backup and
rollback, release-note items, People Portal's webhook fix as a prerequisite,
per-org Restricted job tokens (UI only in 1.27.3), and a verification
checklist. Results quoted in the page come from the dev-stack upgrade and a
hardened act_runner VM.

Also moves the legacy manual Gitea compose example off 1.21.0.
Copilot AI balanced review requested due to automatic review settings October 28, 2026 04:09

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@IanCou

IanCou commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator Author

Closing: we're staying on Gitea 1.24.3 instead of upgrading. Checkout was fixed by config alone (DISABLE_HTTP_GIT=false, ENABLE_BASIC_AUTHENTICATION=false, DISABLE_QUERY_AUTH_TOKEN=true), deployed through TechOperationsCDK. The 1.27-specific webhook model change and dev-stack bump aren't needed on 1.24.3.

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.

2 participants