Join our Newsletter — 33% off our NHI Course
Home› Guides› Gitea and Forgejo Security: Hardening Tokens, Runners, SSH…
Guide Software & AI Supply Chain

Gitea and Forgejo Security: Hardening Tokens, Runners, SSH and Exposure

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 8 November 2026 14 min read
On this page

A self-hosted Git server holds source code, deployment keys, CI secrets and the power to change what ships, yet many Forgejo and Gitea instances run with open registration, password logins over HTTPS, broadly scoped tokens and Actions runners that execute untrusted workflows on the host. Forgejo and Gitea are lightweight Git forges with the same roots: Forgejo began as a fork of Gitea, and both share most configuration keys in app.ini, the same token scope families and a GitHub Actions compatible CI system with separate runners. Platforms are listed alphabetically on this page; order is not a ranking. This guide covers authentication and two-factor enforcement, site administrators and Git hooks, personal access tokens, OAuth2 applications and runner registration tokens, SSH, logging and a hardening baseline with real setting names and defaults. It is for platform engineers and security teams running either forge, using Forgejo's documentation and Gitea's v1.27.3 configuration reference and runner documentation as checked in November 2026.

This is an independent guide from NHI Mgmt Group, based on the Forgejo and Gitea projects' public documentation as of November 2026. It is not sponsored or reviewed by the Forgejo or Gitea projects. Product features change, so confirm settings against the Forgejo and Gitea projects' current documentation.

Key takeaways

  • Close the front door first. In both forges DISABLE_REGISTRATION defaults to false and REQUIRE_SIGNIN_VIEW to false: an internet-facing instance lets anyone create an account and browse public content. Turn registration off or route it through SSO.
  • Enforce two-factor authentication. Forgejo uses GLOBAL_TWO_FACTOR_REQUIREMENT (none, admin or all); Gitea uses TWO_FACTOR_AUTH = enforced. Both default to no requirement.
  • Git hooks are remote code execution. Custom server-side hooks run as the forge's operating system user and can be used to become site administrator. Keep DISABLE_GIT_HOOKS = true and patch promptly.
  • Runners are the biggest blast radius. A host label runs jobs on the machine with no isolation, and jobs there can read the runner's credentials. Use container labels, ephemeral registration and the narrowest registration scope.
  • Tokens need scopes and a place to live. Every token needs at least one scope; create read-only, narrowly scoped tokens for automation, and set DISABLE_QUERY_AUTH_TOKEN = true so tokens never appear in URLs and logs.

How identity and access work in Forgejo and Gitea

Both forges keep local user accounts and can add external authentication sources: LDAP, SMTP, PAM, OAuth2 and OpenID Connect providers, and reverse proxy authentication. Access to repositories comes from ownership, organisation teams and collaborators. Git operations authenticate over SSH with user keys or over HTTPS with a password or token.

SettingSectionDefaultHardened value
DISABLE_REGISTRATION[service]falsetrue, or ALLOW_ONLY_EXTERNAL_REGISTRATION through SSO
REQUIRE_SIGNIN_VIEW[service]falsetrue for internal instances
ENABLE_BASIC_AUTHENTICATION[service]truefalse once users rely on tokens and SSH; it disables password basic auth but not token basic auth
ENABLE_CAPTCHA[service]falsetrue if registration must stay open
MIN_PASSWORD_LENGTH[security]812 or more
PASSWORD_CHECK_PWN[security]falsetrue, if outbound calls to the breached password service are acceptable
PASSWORD_HASH_ALGO[security]pbkdf2argon2 or a tuned pbkdf2, applied as users change passwords
REVERSE_PROXY_TRUSTED_PROXIES[security]127.0.0.0/8,::1/128Only your proxy addresses; never *
  • Two-factor enforcement: Forgejo's GLOBAL_TWO_FACTOR_REQUIREMENT accepts none (default), admin or all. Gitea's TWO_FACTOR_AUTH = enforced forces enrolment, and users without it lose web and API access to repositories. Gitea also exposes ENABLE_PASSKEY_AUTHENTICATION and can hide the password form with ENABLE_PASSWORD_SIGNIN_FORM = false. See the Passkey Rollout Guide.
  • Reverse proxy authentication: when ENABLE_REVERSE_PROXY_AUTHENTICATION is on, the forge trusts the X-WEBAUTH-USER header. Gitea's reference warns the forge must only be reachable through the proxy, which must set or strip the header on every request. A forge reachable directly on its port with this enabled lets anyone claim any identity.
  • HTTPS passwords are a separate door: the PHP Git Server Compromise 2021 came through password logins over HTTPS that bypassed the key-based SSH access layer. Disable basic password authentication and require tokens or SSH.

Admin roles and privileged access

Site administrators can change any repository, user and authentication source, and reach the configuration and monitoring screens. Organisation owners and repository administrators hold more limited but still powerful rights, including adding collaborators, managing deploy keys and secrets, and changing visibility.

  • Git hooks: DISABLE_GIT_HOOKS defaults to true in both forges. Gitea's configuration reference explains why: custom hooks allow arbitrary code execution on the host as the forge user, access to app.ini and the database, and from there site administrator rights. Setting it to true does not remove hooks already created, so audit repositories if it was ever false. Never grant the "may create Git hooks" permission to ordinary users.
  • Patch the forge itself: apply security updates promptly and treat repository write access as a privileged right on any instance that is behind on updates. Forgejo publishes advisories in the forgejo/security-announcements repository on Codeberg; Gitea publishes on its blog and in its GitHub security advisories. Subscribe to whichever you run.
  • Local path imports: keep IMPORT_LOCAL_PATHS = false so users cannot import directories from the server's file system as repositories.
  • Limit creation: on shared instances, set MAX_CREATION_LIMIT (default -1, unlimited) and DISABLE_REGULAR_ORG_CREATION to stop spam and resource abuse. Forgejo's runner security guidance also recommends ALLOW_FORK_WITHOUT_MAXIMUM_LIMIT = false, since forking otherwise bypasses the repository limit.
  • Few, named administrators: keep the site administrator group to two or three named people with two-factor or passkeys, separate from their daily accounts. Default or shared admin logins are a known route in: the Nissan Source Code Leak 2021 followed an internet-facing Git server left on its factory administrator login.
  • Installer lock: make sure INSTALL_LOCK = true after setup so the web installer cannot be rerun.

Integrations, API tokens and service accounts

Machine identities on a forge include personal access tokens used by scripts and integrations, OAuth2 applications, deploy keys, webhooks with shared secrets, Actions secrets and runner registrations. Each one can read or change code. See the Secrets Management Guide for storage and rotation.

Machine identityWhat it can doControls
Personal access tokenAct as its user within its scopes: activitypub, admin, issue, misc, notification, organization, package, repository, user, each read: or write:At least one scope is required. Use read scopes where possible; never give automation write:admin. Create tokens under dedicated bot accounts, not personal ones
Deploy keySSH read, or read and write, on one repositoryRead-only unless the job pushes; one key per consumer; remove when the consumer is retired
OAuth2 applicationThe forge is an OAuth2 provider ([oauth2] ENABLED = true, RS256 signing by default)Review registered applications and granted authorisations; disable the provider if unused
WebhookSends events to other systems[webhook] ALLOWED_HOST_LIST defaults to external, which blocks private network targets; keep it, and use a secret on every hook
Runner registration tokenRegisters runners that receive jobs and their secretsNarrowest scope; one token can register any number of runners until reset, so reset it after use
Actions secretsAvailable to workflows at runtimeFork pull_request workflows get none; pull_request_target workflows get them and need careful review

Availability: Forgejo's token page adds resource restrictions to scopes: All, Public only, or Specific repositories, where only repository and issue scopes apply and repository administration is blocked. Gitea's token scopes are defined in its access token model with the same families listed above; check the token creation screen of your Gitea release for its current repository and organisation access options.

  • Keep tokens out of URLs: both forges default DISABLE_QUERY_AUTH_TOKEN to false, which still accepts tokens in query strings. Set it to true so tokens arrive only in the Authorization header and never land in proxy logs or browser history. API tokens are stored as pbkdf2 hashes.
  • Protect the instance secrets: SECRET_KEY encrypts data such as two-factor secrets (losing it makes them undecryptable) and INTERNAL_TOKEN authenticates internal calls. Load them from files with SECRET_KEY_URI and INTERNAL_TOKEN_URI so app.ini can be managed in configuration tooling without holding secrets.

Actions runners

Runners pull jobs from the forge and execute them. Their isolation decides whether a malicious workflow, or a workflow fed malicious input, stays inside a throwaway container or reaches the host, the network and other jobs' secrets.

Runner choiceForgejoGiteaRecommendation
Execution labelsdocker, lxc, host; host runs without isolation and lxc without resource limitsdocker://, kubernetes://, host; host jobs run as the runner user without isolation, for trusted repositories onlyContainer or pod labels for anything that builds untrusted code
Registration scopeRepository, organisation or instance (global runners take jobs from any repository)Instance, organisation, user or repositoryNarrowest scope that works; separate runners for sensitive repositories
Ephemeral mode--ephemeral, enforced by Forgejo, used with forgejo-runner one-job--ephemeral or GITEA_RUNNER_EPHEMERAL=1; credentials revoked once the job is assigned (daemon --once leaves them valid)Ephemeral runners for untrusted or public repositories
Runner credential file.runner readable by jobs in host mode; theft lets an attacker impersonate the runner and receive secrets.runner (or runner.file) must be treated as a secret; one per runner processRestrict permissions; re-register if exposed
Container settingscontainer.privileged = false, container.valid_volumes empty, container.network = "" for a per-job network, limits via container.options and runner.timeoutRegistration token passed with --token-file or GITEA_RUNNER_REGISTRATION_TOKEN to keep it out of shell historyApply both columns' practices whichever forge you run
  • Pin actions: Gitea's DEFAULT_ACTIONS_URL defaults to github, so uses: references resolve to github.com. Forgejo recommends pinning remote actions to a commit SHA and reviewing changes. Consider mirroring approved actions locally. See the CI/CD Security Guide for pipeline controls that carry over.
  • Fork pull requests: in Forgejo the first pull request from a fork needs approval before workflows run, but later ones from the same fork do not. Anyone with push access to a new branch can trigger on: push workflows.
  • Network placement: isolate runner subnets from internal systems with firewall rules or VLANs, and do not log runner hosts into private registries unless workflows are meant to pull from them.

SSH and network exposure

  • Built-in or system SSH: START_SSH_SERVER defaults to false, meaning the forge relies on the host's OpenSSH with forced commands. If you enable the built-in server, set SSH_SERVER_CIPHERS, SSH_SERVER_KEY_EXCHANGES and SSH_SERVER_MACS to modern values and keep MINIMUM_KEY_SIZE_CHECK = true so weak user keys are rejected. See the OpenSSH Server Hardening Guide.
  • Do not expose it if you do not need to: an internal forge belongs behind a VPN or identity-aware proxy. If it must be public, publish only HTTPS through a reverse proxy and SSH, and keep the application port bound to localhost.
  • TLS and cookies: set ROOT_URL to an HTTPS address so session cookies are marked secure (Gitea's COOKIE_SECURE defaults to true when ROOT_URL is HTTPS).
  • Outbound calls: webhooks, migrations and mirrors make the server fetch URLs. Keep the webhook allowlist at external and restrict migration hosts so the forge cannot be used for server-side request forgery against internal services.

Logging and detection

Both forges log to the console by default ([log] MODE = console, LEVEL = Info). Enable the access logger (logger.access.MODE) and, for SSH, ENABLE_SSH_LOG (default false), then ship logs to central storage.

SignalWhy it mattersATT&CK
New site administrator or new authentication sourcePersistence and identity takeoverT1098 Account Manipulation
Bursts of failed logins or token use from new IPsPassword spraying, stolen tokensT1110 Brute Force, T1078 Valid Accounts
Mass clone or archive downloads by one accountSource code theftT1213.003 Code Repositories
New deploy keys or SSH keys on privileged accountsDurable access outside SSOT1098
New runner registrations, especially host labelsA rogue runner receives jobs and secretsT1528 Steal Application Access Token
Hook files appearing in repository directories on diskCode execution on the serverT1190 Exploit Public-Facing Application

Measures that show progress: share of accounts with two-factor or passkeys (target 100% for administrators and organisation owners), number of tokens with write or admin scopes and their age, share of runners that are ephemeral and container-based, days from a forge advisory to patch (target under 3 for critical issues), and the number of instances reachable from the internet without SSO in front.

Hardening baseline

  1. Inventory: list every Forgejo and Gitea instance, version, exposure, authentication sources, administrators and runners. Retire unmaintained instances.
  2. Patch: move to the current supported release and subscribe to the project's security announcements.
  3. Lock registration and sign-in: DISABLE_REGISTRATION = true, REQUIRE_SIGNIN_VIEW = true for internal forges, SSO through OIDC, two-factor enforced for all users.
  4. Secrets out of app.ini: use SECRET_KEY_URI and INTERNAL_TOKEN_URI; back up the secret key with the database, since losing it breaks two-factor.
  5. Tokens: set DISABLE_QUERY_AUTH_TOKEN = true, disable password basic authentication once integrations move to tokens, and review all tokens with write or admin scopes.
  6. Runners: replace host runners with container runners, register them ephemeral at repository or organisation scope, and reset registration tokens.
  7. Network: reverse proxy with TLS, application port on localhost, VPN or identity-aware proxy for internal use, firewall rules on runner subnets.
  8. Test and roll back: apply changes on a staging copy restored from backup, keep the previous app.ini and binary or image, and announce the date password Git access stops so users can switch to tokens or SSH first. See the Git Repository Security Guide.

Common misconfigurations

  • Open registration on a public instance: spam accounts, abuse of storage and Actions, and a source of write access you did not intend to grant.
  • A host runner shared by many repositories: any workflow can read the runner token and .runner file and reach the host network.
  • Reverse proxy authentication with a reachable backend: anyone who can reach the application port can set X-WEBAUTH-USER.
  • Trusting all proxies: REVERSE_PROXY_TRUSTED_PROXIES = * lets clients forge their source address.
  • Admin-scoped tokens in CI: a leaked pipeline variable becomes full control of the forge.
  • Webhook allowlist widened to *: turns the forge into a request forgery tool against internal services and cloud metadata.

Framework mapping

AreaNIST SP 800-53 Rev. 5MITRE ATT&CK
Registration, sign-in and two-factorIA-2, IA-5T1078 Valid Accounts, T1110 Brute Force
Administrators and Git hooksAC-6, CM-7T1098 Account Manipulation
Tokens, deploy keys and runner credentialsIA-5, SC-12T1528 Steal Application Access Token, T1552.004 Private Keys
Patching the forgeSI-2, RA-5T1190 Exploit Public-Facing Application
Runners and actionsSR-3, CM-7T1195.002 Compromise Software Supply Chain
Logging and repository access monitoringAU-2, AU-6T1213.003 Code Repositories

Practitioner checklist

  • Patch to the current release and subscribe to Forgejo or Gitea security announcements.
  • Set DISABLE_REGISTRATION = true and INSTALL_LOCK = true, and REQUIRE_SIGNIN_VIEW = true on internal instances.
  • Enforce two-factor for all users with GLOBAL_TWO_FACTOR_REQUIREMENT = all (Forgejo) or TWO_FACTOR_AUTH = enforced (Gitea).
  • Confirm DISABLE_GIT_HOOKS = true and audit repositories for existing custom hooks.
  • Set DISABLE_QUERY_AUTH_TOKEN = true and review every token with write or admin scope.
  • Move SECRET_KEY and INTERNAL_TOKEN to files referenced by URI and back them up securely.
  • Replace host runners with ephemeral container runners at the narrowest registration scope.
  • Keep the webhook allowlist at external and pin actions to commit SHAs.
  • Enable access and SSH logging and alert on new admins, keys, runners and mass clones.

Standards and references

Related NHI Mgmt Group resources: Git Repository Security Guide · CI/CD Security Guide · OpenSSH Server Hardening Guide · Secrets Management Guide

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 8 November 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org