Discovering the abuse

While investigating storage growth, I found repository owners I did not recognize. Their names and repositories followed patterns common to search-engine spam: product keywords, numeric suffixes, empty projects, and matching wiki repositories.

The legitimate repositories belonged to my own user. The unknown accounts had accumulated over time because the Gitea instance was publicly reachable and registration had remained enabled.

This changed the task from simple disk cleanup into incident response. Removing large files was not enough; I needed to remove the application records, close the registration path, and review credentials.

The storage investigation is documented separately in Recovering a Docker host from a full filesystem. This article begins after the unknown accounts were identified and focuses on preserving Gitea's application integrity.

Gitea stores more than Git directories

A repository in Gitea is represented in at least two important places:

  • A bare Git repository on disk.
  • Metadata and ownership records in Gitea's database.

There may also be wiki repositories, issues, releases, attachments, webhooks, access records, and user relationships.

Deleting a bare repository directory directly is therefore the wrong first move. It can leave a database record pointing to missing files. Deleting database rows manually has the opposite risk: orphaned repositories and incomplete related-data cleanup.

I used supported Gitea administration paths for users and repositories whenever possible. Before any deletion, I backed up:

  • MariaDB.
  • Gitea's app.ini.
  • Git repositories.
  • Attachments and other application data.

The database dump was written to a separate mounted volume so it would not consume more space on the already constrained root filesystem.

A backup is useful only if it can be restored. I recorded the Gitea, MariaDB, and Git versions alongside it, checked that the files were non-empty, and protected the backup as sensitive data: it can contain password hashes, repository contents, tokens, configuration secrets, and private keys.

Establishing the cleanup set

I treated my known account as the allowlist and reviewed every other user. For each suspicious account, I checked:

  • Repository names and descriptions.
  • Creation and update dates.
  • Whether it owned a corresponding .wiki.git repository.
  • Whether it had organization membership or collaborator access.
  • Whether it had SSH keys, access tokens, or active sessions.

This avoided deleting a forgotten legitimate account based only on an unusual name.

The Gitea CLI can produce a read-only user inventory from inside the application container:

docker compose exec --user git server \
  gitea --config /data/gitea/conf/app.ini admin user list

I compared that inventory with the Administration pages, repository ownership, organizations, and access records. I did not publish the output because it contains account names and email addresses.

Once confirmed, I removed the spam-owned repositories and then removed their user accounts through Gitea's Administration UI. This order made ownership explicit before account deletion. Gitea also provides gitea admin user delete, but deletion syntax and behavior should be checked against the installed version with:

docker compose exec --user git server \
  gitea --config /data/gitea/conf/app.ini admin user delete --help

I deliberately do not include a ready-to-paste bulk deletion loop. An allowlist typo in such a command could destroy legitimate accounts and repositories. After each small batch, I verified that the UI records and corresponding repository directories were gone.

Closing the registration path

The durable configuration change was to disable self-registration:

environment:
  GITEA__service__DISABLE_REGISTRATION: "true"

After changing Compose configuration, I recreated the Gitea service rather than merely restarting the old container:

docker compose up -d --force-recreate server

I then checked the effective Compose configuration and container environment:

docker inspect gitea \
  --format '{{range .Config.Env}}{{println .}}{{end}}' |
grep '^GITEA__service__DISABLE_REGISTRATION='

The registration page should no longer allow arbitrary users to create accounts.

I avoid sharing the full output of docker compose config, docker inspect, or env: resolved configuration frequently contains database passwords and other secrets.

Reviewing credentials

Because unknown users had already used the service, I treated all persistent access mechanisms as worthy of review:

  • Gitea administrators.
  • User and deploy SSH keys.
  • Personal access tokens.
  • OAuth applications.
  • Webhooks.
  • Sessions.
  • Database credentials.
  • Host SSH access.

I also updated the Gitea administrator password through Gitea's own administrative tooling instead of trying to store a plaintext password in MariaDB. Application-managed password hashes should be generated by the application.

Disabling registration stops new self-service accounts; it does not invalidate existing sessions, SSH keys, access tokens, OAuth grants, or webhooks. Those require separate review or revocation.

Validation

The cleanup was not finished until I verified:

docker compose ps
docker compose logs --tail 100 server
docker compose logs --tail 100 mariadb

I also checked:

  • My legitimate repositories still opened and cloned.
  • New public registration was disabled.
  • Spam owners no longer appeared in Explore.
  • Disk usage stopped growing unexpectedly.
  • Gitea could still read every remaining repository.

Lessons learned

The important distinction was between removing files and removing application entities. Gitea owns the relationship among its users, database records, and repository storage, so Gitea should perform that cleanup.

The larger security lesson is that a private personal service should be private by configuration, not merely by obscurity. If I do not intend to offer public Git hosting, registration should be disabled from the beginning.

The Gitea command-line reference and backup and restore guide are the version-aware references for administrative operations.