Skip to content

Delete personal Gitea repositories on creation again, behind API checks - #113

Closed
IanCou wants to merge 1 commit into
gitea-1.27-upgradefrom
gitea-personal-repo-cleanup
Closed

IanCou wants to merge 1 commit into
gitea-1.27-upgradefrom
gitea-personal-repo-cleanup

Conversation

@IanCou

@IanCou IanCou commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

Brings back the deletion of personal Gitea repositories on creation, with guards so a forged or mismatched webhook can't delete anything.

Stacked on #112 (base branch gitea-1.27-upgrade), because both touch the webhook handler. Merge #112 first; this PR then retargets to master.

Why it stopped

Until 0c1c3bb ("authenticate Gitea webhooks and close two IDOR holes", Sept 5), handleRepoCreation deleted any repository whose provisioning failed. A personal repo always fails (no Authentik team with that name), so personal repos were removed automatically. That commit replaced the delete with "leave untouched" because the endpoint was unauthenticated and the delete used the owner/name from the request body: a forged event could delete any repository. Since then, personal repositories stay, without branch protection.

What this does

The webhooks now require the bearer secret, and the handlers re-fetch from the API. On a created event:

  1. Ask the API whether the owner is an organization (GET /orgs/{owner}; only a 404 means "user", any other error leaves the repo alone). The body can't be used: Gitea fills organization with the owner even for personal repos.
  2. Organizations: unchanged provisioning. A failed provisioning still never deletes.
  3. Users: delete only if the API's copy of the repository has the event's id and owner, and was created within 10 minutes (PERSONAL_REPO_DELETE_WINDOW_MS). A replayed or forged event can't delete an older or different repository. Every refusal is logged.

Also adds GiteaClient.isOrganization and URL-encodes deleteRepository's path.

Behaviour to be aware of

  • Applies to every user, including site admins, as the pre-September behaviour did. Mirrors and migrations into a personal namespace emit created too, so they are deleted as well.
  • Only new creations are affected. Existing personal repositories are not touched.

Test plan

  • gitea-personal-repos.test.js (faked clients): fresh personal repo deleted; org repo provisioned and never deleted; no delete on id mismatch, owner mismatch, repo older than the window, unparseable created_at, or when the org lookup errors; non-created actions never delete
  • Full server suite 143/143; prettier@3.9.10 --check; nx build
  • End to end on Gitea 1.27.3 (local dev stack + server): new personal repo devadmin/scratch-personal deleted within seconds (Deleted personal repository ... logged); new TechOps/hook-test-4 provisioned as before (public, default branch main)

Team code belongs in team organizations. Until 0c1c3bb, any repository whose
provisioning failed was deleted, which is how personal repositories were
kept out. That commit stopped deleting because the webhook was
unauthenticated and the delete used the owner and name from the body, so a
forged event could delete any repository. Since then people could create
personal repositories, and those get no branch protection.

Webhooks are now authenticated, and this brings the delete back for personal
repositories only. The owner's kind comes from the API, since Gitea fills
the body's `organization` with the owner even for personal repositories. The
repository is deleted only when the API's copy has the event's id and owner,
belongs to a user, and was created within 10 minutes; a replayed or forged
event can't delete an older or different repository. Any failure, including
not being able to tell whether the owner is an organization, logs and leaves
the repository alone. Organization repositories go through provisioning
exactly as before, and a failed provisioning still never deletes.

Tests cover each guard with faked clients. End to end on Gitea 1.27.3: a new
personal repository was deleted within seconds of creation, and a new team
repository in the same run was provisioned as before.
Copilot AI balanced review requested due to automatic review settings October 28, 2026 04:17

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, so this stacked PR is replaced by a standalone one against master that only restores personal-repo deletion.

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