Continuous Monitoring
Sleep. Sub registered overnight. Discord pings. You hunt it before everyone else.
When to invoke
Trigger phrases:
- "monitor target"
- "alert on new subdomain"
- "watch this program"
- "set up continuous recon"
What we monitor
| Signal | Why it matters | Frequency |
|---|---|---|
| New subdomains | New attack surface, often less tested | Nightly |
| Subdomain takeover candidates | Free critical if found early | Nightly |
| JS file hash changes | New endpoints, removed mitigations | Daily |
| New endpoints in JS | Hidden routes shipped in deploys | Daily |
| HTTP status changes (200 → 401 etc.) | New restrictions / new exposures | Daily |
| New tech in fingerprint | New stack = new vuln classes | Weekly |
| CVE matches against fingerprint | 0-days → 1-day exploitation window | Daily |
| GitHub commits to public repos | Secrets, hints, new features | Hourly |
| Cert transparency new certs | New subdomains often pre-deployed in CT | Hourly |
| Wayback URL diff | New paths historical scanners pick up | Daily |
Two deployment models
A. Cron-based (your VPS, $5/month)
┌────────────────────────────────────────┐
│ DigitalOcean / Linode / Hetzner VPS │
│ - cron jobs │
│ - bash scripts │
│ - state in ~/ccs-state/ │
│ - notify → Discord webhook │
└────────────────────────────────────────┘
B. GitHub Actions (free, no VPS)
┌────────────────────────────────────────┐
│ GitHub Actions │
│ - scheduled workflows │
│ - state in repo artifacts │
│ - notify → Discord webhook │
└────────────────────────────────────────┘
We'll cover both.
Setup A — Cron on a VPS
Step 1: Notify configuration
Use notify — supports Discord/Slack/Telegram/Teams.
mkdir -p ~/.config/notify
cat > ~/.config/notify/provider-config.yaml <<EOF
discord:
- id: "ccs-alerts"
discord_webhook_url: "https://discord.com/api/webhooks/YOUR_WEBHOOK_HERE"
slack:
- id: "ccs-alerts"
slack_webhook_url: "https://hooks.slack.com/services/YOUR/WEBHOOK"
telegram:
- id: "ccs-alerts"
telegram_api_key: "YOUR_BOT_TOKEN"
telegram_chat_id: "YOUR_CHAT_ID"
EOF
# Test
echo "Test alert from claude-cybersecurity-skills" | notify -id ccs-alerts -bulk
Step 2: Subdomain monitoring script
# scripts/monitor-subs.sh
#!/usr/bin/env bash
set -euo pipefail
TARGETS_FILE="$HOME/ccs-state/targets.txt" # one root domain per line
STATE_DIR="$HOME/ccs-state"
mkdir -p "$STATE_DIR/subs"
while read TARGET; do
OLD="$STATE_DIR/subs/$TARGET.txt"
NEW="$STATE_DIR/subs/$TARGET.new.txt"
# Run passive recon
(subfinder -d "$TARGET" -all -silent;
assetfinder --subs-only "$TARGET";
curl -s "https://crt.sh/?q=%25.${TARGET}&output=json" | jq -r '.[].name_value' | sed 's/\*\.//g') | \
sort -u > "$NEW"
if [[ -f "$OLD" ]]; then
# Diff
DIFF=$(comm -13 <(sort "$OLD") <(sort "$NEW"))
if [[ -n "$DIFF" ]]; then
COUNT=$(echo "$DIFF" | wc -l)
echo -e "🆕 New subdomains for **$TARGET** ($COUNT)\n\`\`\`\n$DIFF\n\`\`\`" | \
notify -id ccs-alerts -bulk -silent
# Auto-probe new ones for quick wins
echo "$DIFF" | httpx -silent -tech-detect -title -status-code | \
tee -a "$STATE_DIR/subs/$TARGET.probed.log" | \
notify -id ccs-alerts -bulk -silent
# Check for takeover candidates
echo "$DIFF" | nuclei -t http/takeovers/ -silent -nc -severity high,critical | \
notify -id ccs-alerts -bulk -silent
fi
else
echo "[*] Initial baseline for $TARGET ($(wc -l < "$NEW") subs)"
fi
mv "$NEW" "$OLD"
done < "$TARGETS_FILE"
Step 3: JS file monitoring
# scripts/monitor-js.sh
#!/usr/bin/env bash
set -euo pipefail
URLS_FILE="$HOME/ccs-state/js-urls.txt" # JS file URLs to monitor
STATE_DIR="$HOME/ccs-state/js"
mkdir -p "$STATE_DIR"
while read JS_URL; do
HASH_FILE="$STATE_DIR/$(echo "$JS_URL" | md5sum | cut -d' ' -f1).hash"
CURRENT_HASH=$(curl -s "$JS_URL" | md5sum | cut -d' ' -f1)
if [[ -f "$HASH_FILE" ]]; then
OLD_HASH=$(cat "$HASH_FILE")
if [[ "$OLD_HASH" != "$CURRENT_HASH" ]]; then
# Get the diff in endpoints
OLD_ENDPOINTS_FILE="$STATE_DIR/$(echo "$JS_URL" | md5sum | cut -d' ' -f1).endpoints"
curl -s "$JS_URL" > /tmp/current.js
python3 ~/tools/LinkFinder/linkfinder.py -i /tmp/current.js -o cli 2>/dev/null | sort -u > /tmp/current.endpoints
if [[ -f "$OLD_ENDPOINTS_FILE" ]]; then
NEW_ENDPOINTS=$(comm -13 <(sort "$OLD_ENDPOINTS_FILE") /tmp/current.endpoints)
if [[ -n "$NEW_ENDPOINTS" ]]; then
echo -e "🔧 JS changed: $JS_URL\n**New endpoints:**\n\`\`\`\n$NEW_ENDPOINTS\n\`\`\`" | \
notify -id ccs-alerts -bulk -silent
fi
fi
mv /tmp/current.endpoints "$OLD_ENDPOINTS_FILE"
fi
fi
echo "$CURRENT_HASH" > "$HASH_FILE"
done < "$URLS_FILE"
Step 4: Cert transparency real-time
Subscribe to certstream for near-instant new-cert alerts:
# scripts/certstream-monitor.py
import certstream
import re
import requests
WEBHOOK = "https://discord.com/api/webhooks/YOUR_WEBHOOK_HERE"
TARGETS = ["target.com", "anothertarget.io"]
def callback(message, context):
if message['message_type'] != 'certificate_update':
return
domains = message['data']['leaf_cert']['all_domains']
for d in domains:
for t in TARGETS:
if d.endswith("." + t) or d == t:
msg = f"🆕 New cert: `{d}` (target: {t})"
requests.post(WEBHOOK, json={"content": msg})
return
certstream.listen_for_events(callback, url='wss://certstream.calidog.io/')
Run as systemd service:
# /etc/systemd/system/certstream-monitor.service
[Unit]
Description=Cert Transparency Monitor
After=network.target
[Service]
ExecStart=/usr/bin/python3 /home/user/ccs-state/scripts/certstream-monitor.py
Restart=always
User=user
[Install]
WantedBy=multi-user.target
sudo systemctl enable --now certstream-monitor
Step 5: Crontab
# Subdomain enumeration
0 3 * * * /home/user/ccs-state/scripts/monitor-subs.sh >> /var/log/ccs-monitor.log 2>&1
# JS monitoring (every 6 hours)
0 */6 * * * /home/user/ccs-state/scripts/monitor-js.sh >> /var/log/ccs-monitor.log 2>&1
# Nuclei sweep of in-scope (daily)
0 5 * * * /home/user/ccs-state/scripts/nuclei-sweep.sh >> /var/log/ccs-monitor.log 2>&1
# Weekly tech re-fingerprint
0 6 * * 1 /home/user/ccs-state/scripts/refingerprint.sh >> /var/log/ccs-monitor.log 2>&1
Setup B — GitHub Actions
.github/workflows/monitor.yml
name: BB Continuous Monitoring
on:
schedule:
- cron: '0 3 * * *' # daily 3am UTC
workflow_dispatch: # manual trigger
jobs:
monitor-subs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Install tools
run: |
go install github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest
go install github.com/projectdiscovery/httpx/cmd/httpx@latest
go install github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
go install github.com/projectdiscovery/notify/cmd/notify@latest
go install github.com/tomnomnom/anew@latest
- name: Configure notify
run: |
mkdir -p ~/.config/notify
cat > ~/.config/notify/provider-config.yaml <<EOF
discord:
- id: "ccs-alerts"
discord_webhook_url: "${{ secrets.DISCORD_WEBHOOK }}"
EOF
- name: Run monitoring
env:
CHAOS_KEY: ${{ secrets.CHAOS_KEY }}
run: |
chmod +x scripts/monitor-subs.sh
./scripts/monitor-subs.sh
- name: Commit state
run: |
git config user.name "ccs-monitor"
git config user.email "monitor@claude-cybersecurity-skills"
git add state/
git diff --staged --quiet || git commit -m "monitor: state update $(date -u +%Y-%m-%dT%H:%MZ)"
git push
Configure secrets in GitHub repo settings:
DISCORD_WEBHOOKCHAOS_KEY- (any other API keys)
Nuclei daily sweep
Hit all alive in-scope hosts with critical/high templates daily:
# scripts/nuclei-sweep.sh
#!/usr/bin/env bash
set -euo pipefail
ALIVE="$HOME/ccs-state/all-alive.txt"
nuclei -list "$ALIVE" \
-severity critical,high \
-rate-limit 50 \
-bulk-size 25 \
-concurrency 25 \
-silent \
-nc \
-json -o "$HOME/ccs-state/nuclei-daily.json"
# Diff against yesterday
NEW_FINDINGS=$(comm -13 \
<(sort "$HOME/ccs-state/nuclei-yesterday.json" 2>/dev/null) \
<(sort "$HOME/ccs-state/nuclei-daily.json"))
if [[ -n "$NEW_FINDINGS" ]]; then
echo -e "🚨 New nuclei findings:\n\`\`\`json\n$NEW_FINDINGS\n\`\`\`" | \
notify -id ccs-alerts -bulk -silent
fi
cp "$HOME/ccs-state/nuclei-daily.json" "$HOME/ccs-state/nuclei-yesterday.json"
GitHub commit monitoring (for OSS targets)
# scripts/github-watch.sh
# Watches commits on target's public GitHub orgs
GH_ORGS=("target-inc" "target-oss")
for ORG in "${GH_ORGS[@]}"; do
LAST_SHA_FILE="$HOME/ccs-state/gh-$ORG.sha"
# Get latest events
LATEST=$(gh api "orgs/$ORG/events" --jq '.[0].id')
if [[ -f "$LAST_SHA_FILE" ]]; then
OLD=$(cat "$LAST_SHA_FILE")
if [[ "$OLD" != "$LATEST" ]]; then
EVENTS=$(gh api "orgs/$ORG/events" --jq '.[] | select(.type=="PushEvent") | "\(.repo.name): \(.payload.commits[].message)"' | head -5)
echo -e "📦 Activity in **$ORG**:\n$EVENTS" | notify -id ccs-alerts -bulk -silent
fi
fi
echo "$LATEST" > "$LAST_SHA_FILE"
done
State structure
~/ccs-state/
├── targets.txt ← one root domain per line
├── subs/
│ ├── target.com.txt ← yesterday's list
│ └── target.com.probed.log
├── js/
│ ├── <hash>.hash ← last-seen hash
│ └── <hash>.endpoints ← last-seen endpoint list
├── js-urls.txt ← JS files to monitor
├── all-alive.txt
├── nuclei-daily.json
├── nuclei-yesterday.json
├── gh-target-inc.sha
└── logs/
Tuning alerts (avoid notification fatigue)
# rules.yaml — what to alert on
alert_rules:
subdomain_new:
threshold: 1 # alert on any new sub
quiet_hours: "22-08" # don't ping at night
takeover_candidate:
threshold: 1
severity: high # always alert
nuclei_finding:
severity: [critical, high] # skip medium/low to reduce noise
js_change:
only_with_new_endpoints: true # don't alert on cosmetic changes
Cross-references
[[subdomain-enum]]— the engine behind monitoring[[js-analysis]]— for endpoint diffs[[subdomain-takeover]]— auto-check on new subs[[scope-analysis]]— only alert on in-scope assets
Common pitfalls
- Monitoring out-of-scope assets. Alerts you can't act on. Filter first.
- Notification fatigue. Tune
severityandquiet_hoursaggressively. - State loss. If
~/ccs-state/is wiped, you re-baseline = many false "new" alerts. - VPS IP blocked. Some targets ban scanner IPs. Rotate or use Cloud Run / Lambda.
- API rate limits. Don't run subfinder every hour — daily is enough for most.
Pro tips
- Separate webhooks per priority (one for "new subs", one for "critical findings").
- Use Telegram for mobile alerts — instant push.
- Tag alerts with target name in the message for fast triage.
- Auto-create a private issue in your private bug-bounty repo for each finding.
- Track your "first to find" rate — measures how quickly you act on monitoring alerts.
Real-world example
[Day 1, 3am UTC]
🆕 New subdomain for target.com (1):
admin-staging.target.com [200] Tech: Spring Boot 2.5.4, Actuator
[Day 1, 9am — you wake up]
You check: /actuator/env → exposed. CRITICAL.
Report submitted by 10am. Triaged within 2h. $5,000 bounty by EOD.
This is the difference between manual recon and automated monitoring.