openclaw_oauth_sync/scripts/webhook-security/ntfy-blocked-pickup.sh
sol 2db7d7d90a feat: merge Gitea webhook security into setup wizard (issue #2)
Integrates the 5-layer Gitea webhook security system from
sol/clawgravity-hook-security (v2.0) into the setup wizard.

## What's added

### New files (from clawgravity-hook-security v2.0)
- scripts/webhook-security/gitea-hmac-verify.js    -- njs HMAC-SHA256 module
- scripts/webhook-security/gitea-approve-repo       -- allowlist helper
- scripts/webhook-security/rotate-webhook-secret.sh -- monthly secret rotation (templated)
- scripts/webhook-security/webhook-audit-alert.sh   -- daily audit summaries (templated)
- scripts/webhook-security/ntfy-blocked-pickup.sh   -- blocked webhook alerts (templated)
- templates/webhook-security/nginx-site.conf.example
- templates/webhook-security/nginx.conf.example
- templates/webhook-security/gitea-repo-allowlist.json.example
- docs/WEBHOOK-SECURITY.md   -- full documentation
- docs/SECURITY-AUDIT.md     -- 35-case test matrix
- tests/test-webhook-security.sh  -- 48 offline tests

### Modified files
- setup.sh: Step 11 (webhook security wizard with 6 sub-sections)
- scripts/uninstall.sh: webhook security cleanup section
- README.md: Webhook Security section after Quick Start
- Makefile: test target now runs test-webhook-security.sh
- .secret-scan-allowlist: allowlist docs/SECURITY-AUDIT.md (test fixture)

## Security layers
1. IP allowlisting (nginx)
2. Rate limiting 10 req/s burst 20 (nginx)
3. Payload size 1MB (nginx)
4. HMAC-SHA256 signature verification (njs)
5. Per-repository allowlist (njs)

## make check
- prettier: PASS
- secret-scan: PASS
- tests: 48/48 PASS

Closes #2
2026-03-01 08:43:02 +00:00

78 lines
2.3 KiB
Bash
Executable File

#!/bin/bash
# ntfy-blocked-pickup.sh - Scans nginx error log for blocked webhook attempts
# and sends ntfy.sh notifications for each new occurrence.
#
# Designed to run as a cron job every 60 seconds:
# * * * * * /opt/webhook-security/scripts/ntfy-blocked-pickup.sh
#
# State file tracks the last-seen log position to avoid duplicate alerts.
# ------------------------------------------------------------------
set -euo pipefail
# ========================= CONFIGURATION =========================
# Customize these values for your environment
ERROR_LOG="/var/log/nginx/error.log"
STATE_FILE="/var/lib/webhook-security/ntfy-pickup-state"
NTFY_TOPIC="@@NTFY_TOPIC@@"
PATTERN="gitea-hmac: BLOCKED webhook from unauthorized repo:"
# =================================================================
# Ensure state directory exists
mkdir -p "$(dirname "$STATE_FILE")"
# Read last processed byte offset (0 if first run)
if [ -f "$STATE_FILE" ]; then
LAST_OFFSET=$(cat "$STATE_FILE")
else
LAST_OFFSET=0
fi
# Get current file size
if [ ! -f "$ERROR_LOG" ]; then
echo "No error log found at $ERROR_LOG"
exit 0
fi
CURRENT_SIZE=$(stat -c%s "$ERROR_LOG" 2>/dev/null || stat -f%z "$ERROR_LOG" 2>/dev/null)
# Handle log rotation (file shrank)
if [ "$CURRENT_SIZE" -lt "$LAST_OFFSET" ]; then
LAST_OFFSET=0
fi
# No new data
if [ "$CURRENT_SIZE" -eq "$LAST_OFFSET" ]; then
exit 0
fi
# Extract new lines and filter for blocked webhook entries
NEW_BLOCKS=$(tail -c +"$((LAST_OFFSET + 1))" "$ERROR_LOG" | grep -F "$PATTERN" || true)
if [ -n "$NEW_BLOCKS" ]; then
# Count blocked attempts
COUNT=$(echo "$NEW_BLOCKS" | wc -l)
# Extract unique repo names
REPOS=$(echo "$NEW_BLOCKS" | grep -oP 'unauthorized repo: \K\S+' | sort -u | tr '\n' ', ' | sed 's/,$//')
# Build notification message
MSG="BLOCKED: ${COUNT} webhook(s) from unauthorized repo(s): ${REPOS}"
# Send ntfy notification
curl -sf \
-H "Title: Gitea Webhook Blocked" \
-H "Priority: urgent" \
-H "Content-Type: text/plain" \
-d "$MSG" \
"$NTFY_TOPIC" > /dev/null 2>&1 || true
# Also log to syslog
logger -t webhook-security "ntfy-blocked-pickup: $MSG"
fi
# Update state file with current position
echo "$CURRENT_SIZE" > "$STATE_FILE"