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.
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.
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.gitrepository. - 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.
Once confirmed, I removed the spam-owned repositories and then removed their user accounts through Gitea. Afterward, I verified that both 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 compose config
docker inspect gitea \
--format '{{range .Config.Env}}{{println .}}{{end}}'
The registration page should no longer allow arbitrary users to create accounts.
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.
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.