feat(interceptor): Add InterceptoR transparent traffic interception

The Gandalf Proxy - unified traffic interception with 5 pillars:

New packages:
- secubox-cookie-tracker: HTTP cookie classification with mitmproxy addon
  - SQLite database for cookie tracking
  - 100+ known tracker domains (Google Analytics, Facebook, etc.)
  - CLI: cookie-trackerctl status/list/block/report

- luci-app-interceptor: Unified dashboard aggregating all pillars
  - Health score (0-100%) based on active pillars
  - Status cards: WPAD, mitmproxy, CDN Cache, Cookie Tracker, API Failover

Enhanced modules:
- luci-app-network-tweaks: WPAD enforcement via iptables redirect
  - setWpadEnforce/getWpadEnforce RPCD methods
  - Catches clients ignoring WPAD auto-discovery

- luci-app-cdn-cache: API failover and offline mode
  - stale-if-error patterns for /api/ and .json endpoints
  - WAN hotplug script (99-cdn-offline) toggles offline mode
  - collapsed_forwarding for duplicate request handling

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-02-11 10:58:53 +01:00
parent 8ef0c70d0f
commit 8055bca368
21 changed files with 2162 additions and 6 deletions

View File

@ -19,7 +19,8 @@ LUCI_PKGARCH:=all
# - Helper scripts: 755 (if executable)
# - Config files: 644 (readable by all, writable by root)
# - CSS/JS files: 644 (set automatically by luci.mk)
PKG_FILE_MODES:=/usr/libexec/rpcd/luci.cdn-cache:root:root:755
PKG_FILE_MODES:=/usr/libexec/rpcd/luci.cdn-cache:root:root:755 \
/etc/hotplug.d/iface/99-cdn-offline:root:root:755
include $(TOPDIR)/feeds/luci/luci.mk

View File

@ -87,3 +87,19 @@ config exclusion 'bypass_streaming'
config statistics 'stats'
option retention_days '30'
option sample_interval '60'
# API Failover configuration - serve stale content on backend errors
config api_failover 'api_failover'
option enabled '1'
# Serve stale content for this many seconds after backend failure
option stale_if_error '86400'
# Offline mode - serve stale content for all requests (set by hotplug)
option offline_mode '0'
# Enable collapsed forwarding (combine duplicate requests)
option collapsed_forwarding '1'
# API URL patterns to cache aggressively
list api_patterns '/api/'
list api_patterns '.json'
# Connection timeouts (seconds)
option connect_timeout '5'
option read_timeout '30'

View File

@ -0,0 +1,39 @@
#!/bin/sh
# CDN Cache Offline Mode - Auto-detect WAN connectivity
# Enables offline mode (serve stale) when WAN goes down
# Disables offline mode when WAN comes back up
# Only handle WAN interface events
[ "$INTERFACE" = "wan" ] || [ "$INTERFACE" = "wan6" ] || exit 0
# Check if API failover is enabled
api_enabled=$(uci -q get cdn-cache.api_failover.enabled)
[ "$api_enabled" = "1" ] || exit 0
case "$ACTION" in
ifdown)
# WAN went down - enable offline mode
logger -t cdn-cache "WAN down ($INTERFACE) - enabling offline mode"
uci set cdn-cache.api_failover.offline_mode='1'
uci commit cdn-cache
# Reload Squid to apply offline mode
if [ -x /etc/init.d/cdn-cache ]; then
/etc/init.d/cdn-cache reload 2>/dev/null &
fi
;;
ifup)
# WAN came back up - disable offline mode
logger -t cdn-cache "WAN up ($INTERFACE) - disabling offline mode"
uci set cdn-cache.api_failover.offline_mode='0'
uci commit cdn-cache
# Reload Squid to disable offline mode
if [ -x /etc/init.d/cdn-cache ]; then
/etc/init.d/cdn-cache reload 2>/dev/null &
fi
;;
esac
exit 0

View File

@ -251,10 +251,48 @@ cache_log $LOG_DIR/cache.log
cache_store_log none
debug_options ALL,$log_level
# === API FAILOVER ===
EOF
# Add API failover configuration if enabled
local api_enabled stale_if_error offline_mode collapsed_forwarding connect_timeout read_timeout
api_enabled=$(uci -q get cdn-cache.api_failover.enabled || echo "1")
stale_if_error=$(uci -q get cdn-cache.api_failover.stale_if_error || echo "86400")
offline_mode=$(uci -q get cdn-cache.api_failover.offline_mode || echo "0")
collapsed_forwarding=$(uci -q get cdn-cache.api_failover.collapsed_forwarding || echo "1")
connect_timeout=$(uci -q get cdn-cache.api_failover.connect_timeout || echo "5")
read_timeout=$(uci -q get cdn-cache.api_failover.read_timeout || echo "30")
if [ "$api_enabled" = "1" ]; then
cat >> "$SQUID_CONF" << EOF
# API Failover - serve stale content on backend errors
refresh_pattern -i /api/ 1 20% 4320 stale-if-error=$stale_if_error ignore-no-store
refresh_pattern -i \.json\$ 1 20% 4320 stale-if-error=$stale_if_error
EOF
if [ "$collapsed_forwarding" = "1" ]; then
cat >> "$SQUID_CONF" << EOF
# Collapsed forwarding - combine duplicate requests during backend fetch
collapsed_forwarding on
EOF
fi
if [ "$offline_mode" = "1" ]; then
cat >> "$SQUID_CONF" << EOF
# Offline mode - serve stale content for all requests
offline_mode on
EOF
fi
fi
cat >> "$SQUID_CONF" << EOF
# === PERFORMANCE ===
dns_nameservers 8.8.8.8 8.8.4.4
connect_timeout 30 seconds
read_timeout 90 seconds
connect_timeout $connect_timeout seconds
read_timeout $read_timeout seconds
request_timeout 60 seconds
client_lifetime 1 day
half_closed_clients off
@ -262,7 +300,7 @@ pconn_timeout 60 seconds
quick_abort_min 0 KB
quick_abort_max 0 KB
quick_abort_pct 95
negative_ttl 5 minutes
negative_ttl 30 seconds
# === CACHE MANAGER ===
cache_mgr secubox@local

View File

@ -0,0 +1,17 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-interceptor
PKG_VERSION:=1.0.0
PKG_RELEASE:=1
PKG_ARCH:=all
PKG_LICENSE:=GPL-3.0
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>
LUCI_TITLE:=SecuBox InterceptoR - Unified Traffic Interception Dashboard
LUCI_DESCRIPTION:=Unified dashboard for WPAD, mitmproxy, CDN Cache, Cookie Tracker, and API Failover
LUCI_DEPENDS:=+luci-base +rpcd
LUCI_PKGARCH:=all
PKG_FILE_MODES:=/usr/libexec/rpcd/luci.interceptor:root:root:755
include $(TOPDIR)/feeds/luci/luci.mk

View File

@ -0,0 +1,84 @@
# LuCI InterceptoR Dashboard
Unified dashboard for SecuBox InterceptoR - "The Gandalf Proxy" transparent traffic interception system.
## Features
- **Health Score** - Overall interception coverage (0-100%)
- **5 Pillar Status Cards**:
- WPAD Redirector - Auto-proxy discovery status
- MITM Proxy - Threat detection and connection stats
- CDN Cache - Hit ratio and bandwidth savings
- Cookie Tracker - Tracking cookie detection
- API Failover - Stale content serving status
- **Quick Links** - Direct access to individual module dashboards
## Installation
```bash
opkg install luci-app-interceptor
```
## Menu Location
SecuBox > InterceptoR > Overview
## Architecture
InterceptoR aggregates status from 5 interception pillars:
```
+-------------------+
| InterceptoR |
| Dashboard |
+-------------------+
|
+------+------+--------+--------+------+
| | | | | |
WPAD MITM CDN Cookie API
Proxy Proxy Cache Tracker Failover
```
### Pillar Modules
| Pillar | Package | Function |
|--------|---------|----------|
| WPAD | luci-app-network-tweaks | Auto-proxy via DHCP/DNS |
| MITM | secubox-app-mitmproxy | HTTPS inspection, threat detection |
| CDN Cache | luci-app-cdn-cache | Content caching, bandwidth savings |
| Cookie Tracker | secubox-cookie-tracker | Cookie classification, tracking |
| API Failover | luci-app-cdn-cache | Stale-if-error, offline mode |
## RPCD Methods
| Method | Description |
|--------|-------------|
| `status` | Aggregated status from all pillars |
| `getPillarStatus` | Status for specific pillar |
## Health Score Calculation
- 20 points: WPAD enabled or enforcement active
- 20 points: mitmproxy running
- 20 points: CDN Cache (Squid) running
- 20 points: Cookie Tracker enabled
- 20 points: API Failover enabled
## Public Access
The `status` method is available to unauthenticated users for monitoring dashboards.
## Dependencies
- luci-base
- rpcd
Optional (for full functionality):
- luci-app-network-tweaks
- secubox-app-mitmproxy
- luci-app-cdn-cache
- secubox-cookie-tracker
## License
GPL-3.0

View File

@ -0,0 +1,183 @@
'use strict';
'require view';
'require rpc';
'require poll';
var callGetStatus = rpc.declare({
object: 'luci.interceptor',
method: 'status',
expect: {}
});
var PILLAR_ICONS = {
wpad: '&#x1F310;', // Globe for WPAD
mitm: '&#x1F6E1;', // Shield for mitmproxy
cdn_cache: '&#x1F4BE;', // Disk for CDN Cache
cookie_tracker: '&#x1F36A;', // Cookie for Cookie Tracker
api_failover: '&#x26A1;' // Lightning for API Failover
};
var PILLAR_NAMES = {
wpad: 'WPAD Redirector',
mitm: 'MITM Proxy',
cdn_cache: 'CDN Cache',
cookie_tracker: 'Cookie Tracker',
api_failover: 'API Failover'
};
return view.extend({
load: function() {
return callGetStatus();
},
renderHealthScore: function(data) {
var summary = data.summary || {};
var score = summary.health_score || 0;
var pillars_active = summary.pillars_active || 0;
var pillars_total = summary.pillars_total || 5;
var scoreColor = score >= 80 ? '#4caf50' : score >= 50 ? '#ff9800' : '#f44336';
return E('div', { 'class': 'cbi-section', 'style': 'text-align: center; padding: 30px;' }, [
E('div', { 'style': 'font-size: 64px; margin-bottom: 10px;' }, [
E('span', { 'style': 'color: ' + scoreColor + '; font-weight: bold;' }, score + '%')
]),
E('div', { 'style': 'font-size: 18px; color: #888;' },
'InterceptoR Health Score'),
E('div', { 'style': 'font-size: 14px; color: #666; margin-top: 10px;' },
pillars_active + ' of ' + pillars_total + ' pillars active')
]);
},
renderPillarCard: function(id, data, name, icon) {
var pillarData = data[id] || {};
var enabled = pillarData.enabled || false;
var running = pillarData.running !== undefined ? pillarData.running : enabled;
var statusColor = running ? '#4caf50' : '#f44336';
var statusText = running ? 'Active' : 'Inactive';
var statsHtml = [];
// Build stats based on pillar type
switch(id) {
case 'wpad':
if (pillarData.dhcp_configured) {
statsHtml.push(E('div', { 'style': 'font-size: 12px; color: #888;' },
'DHCP: Configured'));
}
if (pillarData.enforce_enabled) {
statsHtml.push(E('div', { 'style': 'font-size: 12px; color: #4caf50;' },
'Enforcement: ON'));
}
break;
case 'mitm':
statsHtml.push(E('div', { 'style': 'font-size: 12px; color: #888;' },
'Threats Today: ' + (pillarData.threats_today || 0)));
statsHtml.push(E('div', { 'style': 'font-size: 12px; color: #888;' },
'Active: ' + (pillarData.active_connections || 0)));
break;
case 'cdn_cache':
statsHtml.push(E('div', { 'style': 'font-size: 12px; color: #888;' },
'Hit Ratio: ' + (pillarData.hit_ratio || 0) + '%'));
statsHtml.push(E('div', { 'style': 'font-size: 12px; color: #888;' },
'Saved: ' + (pillarData.saved_mb || 0) + ' MB'));
if (pillarData.offline_mode) {
statsHtml.push(E('div', { 'style': 'font-size: 12px; color: #ff9800;' },
'OFFLINE MODE'));
}
break;
case 'cookie_tracker':
statsHtml.push(E('div', { 'style': 'font-size: 12px; color: #888;' },
'Cookies: ' + (pillarData.total_cookies || 0)));
statsHtml.push(E('div', { 'style': 'font-size: 12px; color: #f44336;' },
'Trackers: ' + (pillarData.trackers_detected || 0)));
statsHtml.push(E('div', { 'style': 'font-size: 12px; color: #888;' },
'Blocked: ' + (pillarData.blocked || 0)));
break;
case 'api_failover':
statsHtml.push(E('div', { 'style': 'font-size: 12px; color: #888;' },
'Stale Serves: ' + (pillarData.stale_serves || 0)));
break;
}
return E('div', {
'style': 'background: #222; border-radius: 8px; padding: 20px; margin: 10px; ' +
'min-width: 200px; flex: 1; text-align: center; ' +
'border-left: 4px solid ' + statusColor + ';'
}, [
E('div', { 'style': 'font-size: 32px; margin-bottom: 10px;' }, icon),
E('div', { 'style': 'font-size: 16px; font-weight: bold; margin-bottom: 5px;' }, name),
E('div', { 'style': 'font-size: 12px; color: ' + statusColor + '; margin-bottom: 10px;' },
statusText),
E('div', {}, statsHtml)
]);
},
render: function(data) {
if (!data || !data.success) {
return E('div', { 'class': 'alert-message warning' },
'Failed to load InterceptoR status');
}
var pillars = [
{ id: 'wpad', name: PILLAR_NAMES.wpad, icon: PILLAR_ICONS.wpad },
{ id: 'mitm', name: PILLAR_NAMES.mitm, icon: PILLAR_ICONS.mitm },
{ id: 'cdn_cache', name: PILLAR_NAMES.cdn_cache, icon: PILLAR_ICONS.cdn_cache },
{ id: 'cookie_tracker', name: PILLAR_NAMES.cookie_tracker, icon: PILLAR_ICONS.cookie_tracker },
{ id: 'api_failover', name: PILLAR_NAMES.api_failover, icon: PILLAR_ICONS.api_failover }
];
var cards = pillars.map(function(p) {
return this.renderPillarCard(p.id, data, p.name, p.icon);
}, this);
return E('div', { 'class': 'cbi-map' }, [
E('h2', { 'style': 'margin-bottom: 5px;' }, 'SecuBox InterceptoR'),
E('p', { 'style': 'color: #888; margin-bottom: 20px;' },
'The Gandalf Proxy - Transparent traffic interception and protection'),
// Health Score
this.renderHealthScore(data),
// Pillars Grid
E('h3', { 'style': 'margin-top: 30px;' }, 'Interception Pillars'),
E('div', {
'style': 'display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin-top: 15px;'
}, cards),
// Quick Links
E('h3', { 'style': 'margin-top: 30px;' }, 'Quick Links'),
E('div', { 'style': 'display: flex; flex-wrap: wrap; gap: 10px; margin-top: 15px;' }, [
E('a', {
'href': '/cgi-bin/luci/admin/secubox/network-tweaks',
'class': 'cbi-button',
'style': 'text-decoration: none;'
}, 'Network Tweaks (WPAD)'),
E('a', {
'href': '/cgi-bin/luci/admin/secubox/mitmproxy/overview',
'class': 'cbi-button',
'style': 'text-decoration: none;'
}, 'mitmproxy'),
E('a', {
'href': '/cgi-bin/luci/admin/secubox/cdn-cache/overview',
'class': 'cbi-button',
'style': 'text-decoration: none;'
}, 'CDN Cache'),
E('a', {
'href': '/cgi-bin/luci/admin/secubox/crowdsec/overview',
'class': 'cbi-button',
'style': 'text-decoration: none;'
}, 'CrowdSec')
])
]);
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});

View File

@ -0,0 +1,253 @@
#!/bin/sh
# RPCD backend for SecuBox InterceptoR Dashboard
# Aggregates status from all interception pillars
. /lib/functions.sh
. /usr/share/libubox/jshn.sh
# Helper to safely get JSON from ubus
get_ubus_json() {
local result
result=$(ubus call "$1" "$2" 2>/dev/null)
[ -z "$result" ] && result='{}'
echo "$result"
}
# Get WPAD pillar status
get_wpad_status() {
local enabled=0 dhcp_configured=0 enforce_enabled=0 pac_url=""
# Check PAC file
[ -f /www/wpad.dat ] && enabled=1
# Check DHCP option 252
local dhcp_opt=$(uci -q get dhcp.lan.dhcp_option_force)
if echo "$dhcp_opt" | grep -q "252"; then
dhcp_configured=1
pac_url=$(echo "$dhcp_opt" | sed 's/252,//')
fi
# Check WPAD enforcement
enforce_enabled=$(uci -q get network_tweaks.global.wpad_enforce || echo "0")
json_add_object "wpad"
json_add_boolean "enabled" "$enabled"
json_add_boolean "dhcp_configured" "$dhcp_configured"
json_add_boolean "enforce_enabled" "$enforce_enabled"
json_add_string "pac_url" "$pac_url"
json_close_object
}
# Get mitmproxy pillar status
get_mitm_status() {
local enabled=0 running=0 threats_today=0 connections=0
# Check UCI config
enabled=$(uci -q get mitmproxy.main.enabled || echo "0")
# Check if LXC container is running
if command -v lxc-ls >/dev/null 2>&1; then
lxc-ls --running 2>/dev/null | grep -q "secbx-mitmproxy" && running=1
fi
# Count today's threats from log
local today=$(date +%Y-%m-%d)
if [ -f /srv/mitmproxy/threats.log ]; then
threats_today=$(grep -c "$today" /srv/mitmproxy/threats.log 2>/dev/null || echo "0")
fi
# Get connection count from mitmproxy stats if available
if [ "$running" = "1" ]; then
# Try to get stats via mitmproxyctl if available
if [ -x /usr/sbin/mitmproxyctl ]; then
connections=$(/usr/sbin/mitmproxyctl stats 2>/dev/null | jsonfilter -e '@.active_connections' 2>/dev/null || echo "0")
fi
fi
json_add_object "mitm"
json_add_boolean "enabled" "$enabled"
json_add_boolean "running" "$running"
json_add_int "threats_today" "${threats_today:-0}"
json_add_int "active_connections" "${connections:-0}"
json_close_object
}
# Get CDN Cache pillar status
get_cdn_status() {
local enabled=0 running=0 hit_ratio=0 saved_mb=0 offline_mode=0
# Check UCI config
enabled=$(uci -q get cdn-cache.main.enabled || echo "0")
offline_mode=$(uci -q get cdn-cache.api_failover.offline_mode || echo "0")
# Check if Squid is running
if pgrep squid >/dev/null 2>&1; then
running=1
fi
# Get cache stats from Squid if running
if [ "$running" = "1" ] && command -v squidclient >/dev/null 2>&1; then
local stats=$(squidclient -h 127.0.0.1 mgr:info 2>/dev/null)
if [ -n "$stats" ]; then
# Parse hit ratio
hit_ratio=$(echo "$stats" | grep -o 'Request Hit Ratios:.*' | grep -oP '\d+' | head -1)
[ -z "$hit_ratio" ] && hit_ratio=0
# Estimate bandwidth saved (simplified)
local cache_hits=$(echo "$stats" | grep -o 'Cache Hits:.*' | grep -oP '\d+' | head -1)
[ -n "$cache_hits" ] && saved_mb=$((cache_hits / 1024))
fi
fi
json_add_object "cdn_cache"
json_add_boolean "enabled" "$enabled"
json_add_boolean "running" "$running"
json_add_boolean "offline_mode" "$offline_mode"
json_add_int "hit_ratio" "${hit_ratio:-0}"
json_add_int "saved_mb" "${saved_mb:-0}"
json_close_object
}
# Get Cookie Tracker pillar status
get_cookie_status() {
local enabled=0 total_cookies=0 trackers=0 blocked=0
# Check UCI config
enabled=$(uci -q get cookie-tracker.main.enabled || echo "0")
# Get stats from database
local db="/var/lib/cookie-tracker/cookies.db"
if [ -f "$db" ]; then
total_cookies=$(sqlite3 "$db" "SELECT COUNT(*) FROM cookies;" 2>/dev/null || echo "0")
trackers=$(sqlite3 "$db" "SELECT COUNT(*) FROM cookies WHERE category IN ('tracking', 'advertising');" 2>/dev/null || echo "0")
blocked=$(sqlite3 "$db" "SELECT COUNT(*) FROM cookies WHERE blocked=1;" 2>/dev/null || echo "0")
fi
json_add_object "cookie_tracker"
json_add_boolean "enabled" "$enabled"
json_add_int "total_cookies" "${total_cookies:-0}"
json_add_int "trackers_detected" "${trackers:-0}"
json_add_int "blocked" "${blocked:-0}"
json_close_object
}
# Get API Failover pillar status
get_failover_status() {
local enabled=0 stale_serves=0 failed_origins=0
# Check UCI config
enabled=$(uci -q get cdn-cache.api_failover.enabled || echo "0")
# Count stale serves from Squid log (last 24h)
local yesterday=$(date -d "yesterday" +%Y/%m/%d 2>/dev/null || date -d "-1 day" +%Y/%m/%d 2>/dev/null)
if [ -f /var/log/cdn-cache/access.log ] && [ -n "$yesterday" ]; then
stale_serves=$(grep -c "TCP_STALE_HIT" /var/log/cdn-cache/access.log 2>/dev/null || echo "0")
fi
json_add_object "api_failover"
json_add_boolean "enabled" "$enabled"
json_add_int "stale_serves" "${stale_serves:-0}"
json_add_int "failed_origins" "${failed_origins:-0}"
json_close_object
}
case "$1" in
list)
json_init
json_add_object "status"
json_close_object
json_add_object "getPillarStatus"
json_add_string "pillar" "string"
json_close_object
json_dump
;;
call)
case "$2" in
status)
# Aggregate all pillar statuses
json_init
json_add_boolean "success" 1
# Get all pillar statuses
get_wpad_status
get_mitm_status
get_cdn_status
get_cookie_status
get_failover_status
# Overall health score (0-100)
local score=0
local pillars_active=0
# WPAD
[ "$(uci -q get network_tweaks.global.wpad_enforce)" = "1" ] || [ -f /www/wpad.dat ] && {
score=$((score + 20))
pillars_active=$((pillars_active + 1))
}
# mitmproxy running
pgrep -x mitmproxy >/dev/null 2>&1 || lxc-ls --running 2>/dev/null | grep -q "secbx-mitmproxy" && {
score=$((score + 20))
pillars_active=$((pillars_active + 1))
}
# CDN Cache running
pgrep squid >/dev/null 2>&1 && {
score=$((score + 20))
pillars_active=$((pillars_active + 1))
}
# Cookie Tracker enabled
[ "$(uci -q get cookie-tracker.main.enabled)" = "1" ] && {
score=$((score + 20))
pillars_active=$((pillars_active + 1))
}
# API Failover enabled
[ "$(uci -q get cdn-cache.api_failover.enabled)" = "1" ] && {
score=$((score + 20))
pillars_active=$((pillars_active + 1))
}
json_add_object "summary"
json_add_int "health_score" "$score"
json_add_int "pillars_active" "$pillars_active"
json_add_int "pillars_total" "5"
json_close_object
json_dump
;;
getPillarStatus)
read input
json_load "$input"
json_get_var pillar pillar
json_init
json_add_boolean "success" 1
case "$pillar" in
wpad) get_wpad_status ;;
mitm) get_mitm_status ;;
cdn) get_cdn_status ;;
cookie) get_cookie_status ;;
failover) get_failover_status ;;
*)
json_add_boolean "success" 0
json_add_string "error" "Unknown pillar: $pillar"
;;
esac
json_dump
;;
*)
json_init
json_add_boolean "success" 0
json_add_string "error" "Unknown method"
json_dump
;;
esac
;;
esac

View File

@ -0,0 +1,21 @@
{
"admin/secubox/interceptor": {
"title": "InterceptoR",
"order": 35,
"action": {
"type": "firstchild"
},
"depends": {
"acl": ["luci-app-interceptor"],
"uci": {}
}
},
"admin/secubox/interceptor/overview": {
"title": "Overview",
"order": 1,
"action": {
"type": "view",
"path": "interceptor/overview"
}
}
}

View File

@ -0,0 +1,24 @@
{
"luci-app-interceptor": {
"description": "Grant access to InterceptoR Dashboard",
"read": {
"ubus": {
"luci.interceptor": ["status", "getPillarStatus"]
},
"uci": ["network_tweaks", "mitmproxy", "cdn-cache", "cookie-tracker"]
},
"write": {
"ubus": {
"luci.interceptor": []
}
}
},
"unauthenticated": {
"description": "Public access to InterceptoR status",
"read": {
"ubus": {
"luci.interceptor": ["status"]
}
}
}
}

View File

@ -5,3 +5,5 @@ config global 'global'
option sync_dnsmasq '1'
option lan_interface 'lan'
option default_ip ''
# WPAD enforcement - redirect HTTP/HTTPS from non-WPAD-compliant clients to proxy
option wpad_enforce '0'

View File

@ -345,6 +345,11 @@ case "$1" in
json_add_string "url" "string"
json_add_string "timeout" "integer"
json_close_object
json_add_object "getWpadEnforce"
json_close_object
json_add_object "setWpadEnforce"
json_add_string "enabled" "boolean"
json_close_object
json_dump
;;
@ -843,6 +848,86 @@ OPKG_EOF
json_dump
;;
getWpadEnforce)
# Get WPAD enforcement (iptables redirect) status
json_init
json_add_boolean "success" 1
# Check if iptables rules exist for WPAD enforcement
enforce_http=0
enforce_https=0
if iptables -t nat -L PREROUTING -n 2>/dev/null | grep -q "SECUBOX_WPAD_HTTP"; then
enforce_http=1
fi
if iptables -t nat -L PREROUTING -n 2>/dev/null | grep -q "SECUBOX_WPAD_HTTPS"; then
enforce_https=1
fi
# Check UCI config
config_load "$CONFIG"
config_get wpad_enforce global wpad_enforce "0"
json_add_object "wpad_enforce"
json_add_boolean "enabled" "$wpad_enforce"
json_add_boolean "http_active" "$enforce_http"
json_add_boolean "https_active" "$enforce_https"
json_close_object
json_dump
;;
setWpadEnforce)
# Enable/disable WPAD enforcement (iptables redirect for non-WPAD clients)
read input
json_load "$input"
json_get_var enabled enabled
# Get router IP and proxy port
lan_ip=$(uci get network.lan.ipaddr 2>/dev/null | cut -d'/' -f1)
[ -z "$lan_ip" ] && lan_ip="192.168.255.1"
proxy_port=$(uci -q get cdn-cache.main.listen_port || echo "3128")
lan_iface="br-lan"
if [ "$enabled" = "1" ] || [ "$enabled" = "true" ]; then
# Store setting in UCI
uci set ${CONFIG}.global.wpad_enforce='1'
uci commit "$CONFIG"
# Remove existing WPAD enforce rules if any
iptables -t nat -D PREROUTING -i "$lan_iface" -p tcp --dport 80 -m comment --comment "SECUBOX_WPAD_HTTP" -j REDIRECT --to-port "$proxy_port" 2>/dev/null
iptables -t nat -D PREROUTING -i "$lan_iface" -p tcp --dport 443 -m comment --comment "SECUBOX_WPAD_HTTPS" -j REDIRECT --to-port "$proxy_port" 2>/dev/null
# Add iptables rules to redirect HTTP/HTTPS to proxy
# Skip traffic destined to the router itself
iptables -t nat -A PREROUTING -i "$lan_iface" -p tcp --dport 80 ! -d "$lan_ip" -m comment --comment "SECUBOX_WPAD_HTTP" -j REDIRECT --to-port "$proxy_port"
iptables -t nat -A PREROUTING -i "$lan_iface" -p tcp --dport 443 ! -d "$lan_ip" -m comment --comment "SECUBOX_WPAD_HTTPS" -j REDIRECT --to-port "$proxy_port"
logger -t network-tweaks "WPAD enforcement enabled: redirecting HTTP/HTTPS to port $proxy_port"
json_init
json_add_boolean "success" 1
json_add_string "message" "WPAD enforcement enabled - traffic redirected to proxy port $proxy_port"
else
# Store setting in UCI
uci set ${CONFIG}.global.wpad_enforce='0'
uci commit "$CONFIG"
# Remove iptables rules
iptables -t nat -D PREROUTING -i "$lan_iface" -p tcp --dport 80 -m comment --comment "SECUBOX_WPAD_HTTP" -j REDIRECT --to-port "$proxy_port" 2>/dev/null
iptables -t nat -D PREROUTING -i "$lan_iface" -p tcp --dport 443 -m comment --comment "SECUBOX_WPAD_HTTPS" -j REDIRECT --to-port "$proxy_port" 2>/dev/null
logger -t network-tweaks "WPAD enforcement disabled"
json_init
json_add_boolean "success" 1
json_add_string "message" "WPAD enforcement disabled"
fi
json_dump
;;
*)
json_init
json_add_boolean "success" 0

View File

@ -149,6 +149,28 @@ reload_services() {
fi
}
setup_wpad_enforce() {
local enforce
enforce=$(uci -q get ${CONFIG_TWEAKS}.global.wpad_enforce)
[ "$enforce" != "1" ] && return 0
local lan_ip proxy_port lan_iface
lan_ip=$(uci -q get network.lan.ipaddr | cut -d'/' -f1)
[ -z "$lan_ip" ] && lan_ip="192.168.255.1"
proxy_port=$(uci -q get cdn-cache.main.listen_port || echo "3128")
lan_iface="br-lan"
# Remove existing rules if any
iptables -t nat -D PREROUTING -i "$lan_iface" -p tcp --dport 80 -m comment --comment "SECUBOX_WPAD_HTTP" -j REDIRECT --to-port "$proxy_port" 2>/dev/null
iptables -t nat -D PREROUTING -i "$lan_iface" -p tcp --dport 443 -m comment --comment "SECUBOX_WPAD_HTTPS" -j REDIRECT --to-port "$proxy_port" 2>/dev/null
# Add iptables rules
iptables -t nat -A PREROUTING -i "$lan_iface" -p tcp --dport 80 ! -d "$lan_ip" -m comment --comment "SECUBOX_WPAD_HTTP" -j REDIRECT --to-port "$proxy_port"
iptables -t nat -A PREROUTING -i "$lan_iface" -p tcp --dport 443 ! -d "$lan_ip" -m comment --comment "SECUBOX_WPAD_HTTPS" -j REDIRECT --to-port "$proxy_port"
log_msg "WPAD enforcement enabled: redirecting HTTP/HTTPS to port $proxy_port"
}
cleanup_entries() {
log_msg "Cleaning up managed entries..."
@ -199,6 +221,9 @@ sync_all() {
reload_services "$reload_needed"
# Setup WPAD enforcement iptables rules if enabled
setup_wpad_enforce
log_msg "Synchronization complete"
}

View File

@ -3,14 +3,14 @@
"description": "Grant access to Network Tweaks",
"read": {
"ubus": {
"luci.network-tweaks": ["getStatus", "getConfig", "getNetworkComponents", "getCumulativeImpact", "getWpadStatus", "getProxyStatus"],
"luci.network-tweaks": ["getStatus", "getConfig", "getNetworkComponents", "getCumulativeImpact", "getWpadStatus", "getProxyStatus", "getOpkgSettings", "testUrl", "getWpadEnforce"],
"luci.cdn-cache": ["status", "stats"]
},
"uci": ["network_tweaks", "vhosts", "cdn-cache", "dhcp"]
},
"write": {
"ubus": {
"luci.network-tweaks": ["syncNow", "setConfig", "setComponentEnabled", "setWpadEnabled", "setAdGuardEnabled"],
"luci.network-tweaks": ["syncNow", "setConfig", "setComponentEnabled", "setWpadEnabled", "setAdGuardEnabled", "setOpkgIpv4", "setWpadEnforce"],
"luci.cdn-cache": ["set_enabled", "restart"]
},
"uci": ["network_tweaks", "dhcp", "adguardhome"]

View File

@ -0,0 +1,81 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=secubox-cookie-tracker
PKG_RELEASE:=1
PKG_VERSION:=1.0.0
PKG_ARCH:=all
PKG_MAINTAINER:=CyberMind Studio <contact@cybermind.fr>
PKG_LICENSE:=GPL-3.0
include $(INCLUDE_DIR)/package.mk
define Package/secubox-cookie-tracker
SECTION:=utils
CATEGORY:=Utilities
PKGARCH:=all
SUBMENU:=SecuBox Apps
TITLE:=SecuBox Cookie Tracker - HTTP cookie classification and tracking
DEPENDS:=+sqlite3-cli +jsonfilter
endef
define Package/secubox-cookie-tracker/description
Cookie Tracker for SecuBox InterceptoR.
Features:
- Track and classify HTTP cookies by category
- Categories: essential, functional, analytics, advertising, tracking
- SQLite database for persistent storage
- mitmproxy addon for real-time cookie extraction
- Known tracker domain database
- CLI for cookie management and reporting
- Integration with Vortex Firewall for blocking
Works with secubox-app-mitmproxy for transparent interception.
endef
define Package/secubox-cookie-tracker/conffiles
/etc/config/cookie-tracker
endef
define Build/Compile
endef
define Package/secubox-cookie-tracker/install
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_CONF) ./files/config/cookie-tracker $(1)/etc/config/cookie-tracker
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./root/etc/init.d/cookie-tracker $(1)/etc/init.d/cookie-tracker
$(INSTALL_DIR) $(1)/usr/sbin
$(INSTALL_BIN) ./root/usr/sbin/cookie-trackerctl $(1)/usr/sbin/cookie-trackerctl
$(INSTALL_DIR) $(1)/usr/lib/secubox/cookie-tracker
$(INSTALL_DATA) ./root/usr/lib/secubox/cookie-tracker/mitmproxy-addon.py $(1)/usr/lib/secubox/cookie-tracker/
$(INSTALL_DATA) ./root/usr/lib/secubox/cookie-tracker/known-trackers.tsv $(1)/usr/lib/secubox/cookie-tracker/
$(INSTALL_DIR) $(1)/var/lib/cookie-tracker
endef
define Package/secubox-cookie-tracker/postinst
#!/bin/sh
[ -n "$${IPKG_INSTROOT}" ] || {
echo ""
echo "Cookie Tracker installed."
echo ""
echo "Initialize database:"
echo " cookie-trackerctl init"
echo ""
echo "Enable mitmproxy addon (add to /etc/config/mitmproxy):"
echo " option addon_script '/usr/lib/secubox/cookie-tracker/mitmproxy-addon.py'"
echo ""
echo "Commands:"
echo " cookie-trackerctl status # Statistics"
echo " cookie-trackerctl list # List cookies"
echo " cookie-trackerctl report # Generate report"
echo ""
}
exit 0
endef
$(eval $(call BuildPackage,secubox-cookie-tracker))

View File

@ -0,0 +1,150 @@
# SecuBox Cookie Tracker
HTTP cookie classification and tracking for SecuBox InterceptoR.
## Features
- **Cookie Extraction** - Capture cookies from HTTP traffic via mitmproxy
- **Auto-Classification** - Categorize cookies as essential, functional, analytics, advertising, or tracking
- **SQLite Database** - Persistent storage with search and filtering
- **Known Tracker Database** - 100+ pre-configured tracker domains
- **Vortex Integration** - Feed blocked domains to Vortex Firewall
- **CLI Management** - Full command-line interface for cookie management
## Installation
```bash
opkg install secubox-cookie-tracker
```
Requires `secubox-app-mitmproxy` for traffic interception.
## Quick Start
```bash
# Initialize database
cookie-trackerctl init
# View status
cookie-trackerctl status
# List cookies
cookie-trackerctl list
# Block a tracking domain
cookie-trackerctl block doubleclick.net
```
## CLI Commands
| Command | Description |
|---------|-------------|
| `status [--json]` | Show statistics summary |
| `init [force]` | Initialize/reset database |
| `reload` | Reload tracker rules from UCI |
| `list [options]` | List cookies with filters |
| `show <domain>` | Show cookies for domain |
| `classify <domain> <name> <cat>` | Manually classify cookie |
| `block <domain>` | Block all cookies from domain |
| `unblock <domain>` | Unblock domain |
| `report [--json]` | Generate cookie report |
| `export [file]` | Export database to CSV |
| `import <file>` | Import tracker rules from TSV |
| `feed-vortex` | Feed blocked domains to Vortex |
| `stats` | Detailed statistics |
## Cookie Categories
| Category | Description | Default Action |
|----------|-------------|----------------|
| `essential` | Required for site functionality | Allow |
| `functional` | User preferences, settings | Allow |
| `analytics` | Usage tracking for site improvement | Alert |
| `advertising` | Ad targeting and retargeting | Block |
| `tracking` | Cross-site tracking, fingerprinting | Block |
| `unknown` | Not yet classified | Allow |
## mitmproxy Integration
Add the addon to your mitmproxy configuration:
```bash
# /etc/config/mitmproxy
config filtering 'filtering'
option addon_script '/usr/lib/secubox/cookie-tracker/mitmproxy-addon.py'
```
Or load alongside the main analytics addon:
```bash
mitmdump -s /usr/lib/secubox/cookie-tracker/mitmproxy-addon.py \
-s /srv/mitmproxy/addons/secubox_analytics.py
```
## UCI Configuration
```
# /etc/config/cookie-tracker
config cookie_tracker 'main'
option enabled '1'
option auto_classify '1'
option block_tracking '0'
option block_advertising '0'
config tracker_rule 'custom'
option pattern '_my_tracker'
option category 'tracking'
```
## Database Schema
```sql
CREATE TABLE cookies (
id INTEGER PRIMARY KEY,
domain TEXT NOT NULL,
name TEXT NOT NULL,
category TEXT DEFAULT 'unknown',
first_seen INTEGER,
last_seen INTEGER,
count INTEGER DEFAULT 1,
client_mac TEXT,
blocked INTEGER DEFAULT 0,
UNIQUE(domain, name)
);
CREATE TABLE tracker_domains (
domain TEXT PRIMARY KEY,
category TEXT,
source TEXT
);
```
## Examples
```bash
# List all tracking cookies
cookie-trackerctl list --category tracking
# List cookies from a specific domain
cookie-trackerctl list --domain google.com
# Generate JSON report for dashboard
cookie-trackerctl report --json
# Export all data
cookie-trackerctl export /tmp/cookies.csv
# Block and sync to Vortex
cookie-trackerctl block ads.example.com
cookie-trackerctl feed-vortex
```
## Dependencies
- secubox-app-mitmproxy (for traffic interception)
- sqlite3-cli
- jsonfilter
## License
GPL-3.0

View File

@ -0,0 +1,84 @@
config cookie_tracker 'main'
option enabled '1'
# Database path
option db_path '/var/lib/cookie-tracker/cookies.db'
# Auto-classify cookies using known tracker database
option auto_classify '1'
# Block tracking cookies (requires Vortex integration)
option block_tracking '0'
# Block advertising cookies
option block_advertising '0'
# Log level: debug, info, warn, error
option log_level 'info'
# Cookie categories and their defaults
config category 'essential'
option description 'Required for basic site functionality'
option block '0'
option alert '0'
config category 'functional'
option description 'Enhance user experience (preferences, languages)'
option block '0'
option alert '0'
config category 'analytics'
option description 'Track user behavior for site improvement'
option block '0'
option alert '1'
config category 'advertising'
option description 'Used for targeted advertising'
option block '1'
option alert '1'
config category 'tracking'
option description 'Cross-site tracking and fingerprinting'
option block '1'
option alert '1'
# Known tracker patterns (extend with custom rules)
config tracker_rule 'google_analytics'
option pattern '_ga|_gid|_gat|__utm'
option category 'analytics'
option source 'builtin'
config tracker_rule 'facebook'
option pattern '_fbp|_fbc|fr|datr'
option category 'advertising'
option source 'builtin'
config tracker_rule 'doubleclick'
option domain_pattern 'doubleclick\.net|googlesyndication\.com'
option category 'advertising'
option source 'builtin'
config tracker_rule 'segment'
option pattern 'ajs_user_id|ajs_anonymous_id'
option category 'analytics'
option source 'builtin'
config tracker_rule 'mixpanel'
option pattern 'mp_.*_mixpanel'
option category 'analytics'
option source 'builtin'
config tracker_rule 'hubspot'
option pattern '__hs.*|hubspotutk'
option category 'analytics'
option source 'builtin'
# Whitelist - never block these cookies
config whitelist 'trusted'
list domain ''
list cookie_name ''
# Integration settings
config integration 'mitmproxy'
option enabled '1'
option addon_path '/usr/lib/secubox/cookie-tracker/mitmproxy-addon.py'
config integration 'vortex'
option enabled '0'
# Feed blocked tracker domains to Vortex Firewall
option feed_blocklist '0'

View File

@ -0,0 +1,33 @@
#!/bin/sh /etc/rc.common
START=95
STOP=10
USE_PROCD=1
DB_PATH="/var/lib/cookie-tracker/cookies.db"
start_service() {
local enabled
config_load cookie-tracker
config_get enabled main enabled '0'
[ "$enabled" = "1" ] || return 0
# Initialize database if needed
/usr/sbin/cookie-trackerctl init 2>/dev/null
logger -t cookie-tracker "Cookie Tracker service started"
}
stop_service() {
logger -t cookie-tracker "Cookie Tracker service stopped"
}
reload_service() {
# Reload tracker rules from UCI
/usr/sbin/cookie-trackerctl reload
}
service_triggers() {
procd_add_reload_trigger "cookie-tracker"
}

View File

@ -0,0 +1,140 @@
# SecuBox Cookie Tracker - Known Tracker Domains
# Format: domain<TAB>category<TAB>source
# Categories: analytics, advertising, tracking
#
# Google Analytics
google-analytics.com analytics builtin
analytics.google.com analytics builtin
googletagmanager.com analytics builtin
#
# Google Ads / DoubleClick
doubleclick.net advertising builtin
googlesyndication.com advertising builtin
googleadservices.com advertising builtin
pagead2.googlesyndication.com advertising builtin
#
# Facebook
pixel.facebook.com tracking builtin
connect.facebook.net tracking builtin
facebook.com advertising builtin
#
# Microsoft / Bing
bat.bing.com advertising builtin
clarity.ms analytics builtin
#
# LinkedIn
ads.linkedin.com advertising builtin
px.ads.linkedin.com tracking builtin
snap.licdn.com tracking builtin
#
# Twitter / X
ads.twitter.com advertising builtin
analytics.twitter.com analytics builtin
t.co tracking builtin
#
# Pinterest
ct.pinterest.com tracking builtin
ads.pinterest.com advertising builtin
#
# TikTok
analytics.tiktok.com analytics builtin
ads.tiktok.com advertising builtin
#
# Hotjar
hotjar.com analytics builtin
static.hotjar.com analytics builtin
#
# Segment
segment.io analytics builtin
segment.com analytics builtin
cdn.segment.com analytics builtin
#
# Mixpanel
mixpanel.com analytics builtin
api.mixpanel.com analytics builtin
#
# HubSpot
hubspot.com analytics builtin
hs-analytics.net analytics builtin
hs-scripts.com analytics builtin
#
# Amplitude
amplitude.com analytics builtin
api.amplitude.com analytics builtin
#
# Heap
heap.io analytics builtin
heapanalytics.com analytics builtin
#
# FullStory
fullstory.com analytics builtin
rs.fullstory.com analytics builtin
#
# Criteo
criteo.com advertising builtin
criteo.net advertising builtin
#
# Taboola
taboola.com advertising builtin
trc.taboola.com advertising builtin
#
# Outbrain
outbrain.com advertising builtin
widgets.outbrain.com advertising builtin
#
# Ad Networks
adnxs.com advertising builtin
adsrvr.org advertising builtin
rubiconproject.com advertising builtin
pubmatic.com advertising builtin
openx.net advertising builtin
casalemedia.com advertising builtin
quantserve.com advertising builtin
advertising.com advertising builtin
#
# Matomo / Piwik
matomo.cloud analytics builtin
#
# Adobe Analytics
omtrdc.net analytics builtin
demdex.net tracking builtin
#
# Optimizely
optimizely.com analytics builtin
#
# VWO
visualwebsiteoptimizer.com analytics builtin
#
# Inspectlet
inspectlet.com analytics builtin
#
# Reddit
redditstatic.com tracking builtin
#
# Snap
sc-static.net tracking builtin
#
# Yahoo / Verizon Media
ads.yahoo.com advertising builtin
#
# Amazon
amazon-adsystem.com advertising builtin
#
# Quantcast
quantcast.com tracking builtin
#
# Scorecard Research
scorecardresearch.com tracking builtin
#
# Comscore
comscore.com analytics builtin
#
# New Relic
newrelic.com analytics builtin
nr-data.net analytics builtin
#
# Datadog
datadoghq.com analytics builtin
#
# Sentry
sentry.io analytics builtin
Can't render this file because it has a wrong number of fields in line 6.

View File

@ -0,0 +1,293 @@
#!/usr/bin/env python3
"""
SecuBox Cookie Tracker Addon for mitmproxy
Extracts, classifies, and tracks HTTP cookies passing through the proxy.
Integrates with the cookie-tracker SQLite database.
"""
import sqlite3
import os
import re
import time
from datetime import datetime
from mitmproxy import http, ctx
# Database path
DB_PATH = "/var/lib/cookie-tracker/cookies.db"
# Known tracking cookie patterns
TRACKING_PATTERNS = {
'analytics': [
r'^_ga$', r'^_gid$', r'^_gat', r'^__utm', # Google Analytics
r'^_hjid', r'^_hjSession', # Hotjar
r'^ajs_user_id', r'^ajs_anonymous_id', # Segment
r'^mp_.*_mixpanel', # Mixpanel
r'^__hssc', r'^__hssrc', r'^__hstc', r'^hubspotutk', # HubSpot
r'^_pk_id', r'^_pk_ses', # Matomo/Piwik
r'^amplitude_id', # Amplitude
r'^_clck', r'^_clsk', # Microsoft Clarity
r'^__insp_', # Inspectlet
r'^_vwo_', # VWO
r'^optimizelyEndUserId', # Optimizely
],
'advertising': [
r'^_fbp$', r'^_fbc$', r'^fr$', r'^datr$', # Facebook
r'^IDE$', r'^NID$', r'^ANID$', # Google Ads/DoubleClick
r'^_gcl_', # Google Conversion Linker
r'^_uetsid', r'^_uetvid', # Microsoft Ads/Bing
r'^_pin_unauth', # Pinterest
r'^__pdst', # Pardot
r'^li_sugr', r'^bcookie', r'^bscookie', # LinkedIn
r'^_tt_enable_cookie', r'^_ttp', # TikTok
r'^cto_bundle', # Criteo
r'^taboola_', # Taboola
r'^outbrain_', # Outbrain
],
'tracking': [
r'^__cfduid', # Cloudflare (legacy)
r'^_dc_gtm_', # Google Tag Manager
r'^_gac_', # Google Ads conversion
r'^uuid$', r'^visitor_id', # Generic tracking
r'^_parsely', # Parse.ly
r'^__gads', # Google Ads
r'^_rdt_uuid', # Reddit
r'^_scid', # Snap
r'^_twclid', # Twitter
r'^_derived_epik', # Pinterest
],
'functional': [
r'^lang$', r'^locale$', r'^language$',
r'^timezone$', r'^tz$',
r'^theme$', r'^dark_mode$',
r'^remember_token$', r'^user_pref',
r'^cookie_consent', r'^gdpr',
],
'essential': [
r'^session', r'^sess_', r'^PHPSESSID$', r'^JSESSIONID$',
r'^csrf', r'^_csrf', r'^XSRF-TOKEN',
r'^auth', r'^token$', r'^jwt$',
r'^__Secure-', r'^__Host-',
],
}
# Known tracker domains
TRACKER_DOMAINS = {
'analytics': [
'google-analytics.com', 'analytics.google.com',
'hotjar.com', 'segment.io', 'segment.com',
'mixpanel.com', 'hubspot.com', 'hs-analytics.net',
'matomo.cloud', 'amplitude.com', 'clarity.ms',
'inspectlet.com', 'visualwebsiteoptimizer.com',
'optimizely.com', 'fullstory.com', 'heap.io',
],
'advertising': [
'doubleclick.net', 'googlesyndication.com', 'googleadservices.com',
'facebook.com', 'facebook.net', 'fbcdn.net',
'ads.linkedin.com', 'ads.twitter.com', 'ads.pinterest.com',
'criteo.com', 'criteo.net', 'taboola.com', 'outbrain.com',
'adsrvr.org', 'adnxs.com', 'rubiconproject.com',
'pubmatic.com', 'openx.net', 'casalemedia.com',
'advertising.com', 'quantserve.com',
],
'tracking': [
'pixel.facebook.com', 'bat.bing.com', 'px.ads.linkedin.com',
't.co', 'analytics.tiktok.com', 'sc-static.net',
'ct.pinterest.com', 'snap.licdn.com',
],
}
class CookieTracker:
def __init__(self):
self.db_initialized = False
self._init_db()
ctx.log.info("Cookie Tracker addon loaded")
def _init_db(self):
"""Initialize database connection and create tables if needed."""
try:
# Ensure directory exists
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = sqlite3.connect(DB_PATH)
conn.execute("""
CREATE TABLE IF NOT EXISTS cookies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
domain TEXT NOT NULL,
name TEXT NOT NULL,
category TEXT DEFAULT 'unknown',
first_seen INTEGER DEFAULT (strftime('%s', 'now')),
last_seen INTEGER DEFAULT (strftime('%s', 'now')),
count INTEGER DEFAULT 1,
client_mac TEXT,
blocked INTEGER DEFAULT 0,
UNIQUE(domain, name)
)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS tracker_domains (
domain TEXT PRIMARY KEY,
category TEXT NOT NULL,
source TEXT DEFAULT 'manual',
added INTEGER DEFAULT (strftime('%s', 'now'))
)
""")
conn.execute("CREATE INDEX IF NOT EXISTS idx_cookies_domain ON cookies(domain)")
conn.execute("CREATE INDEX IF NOT EXISTS idx_cookies_category ON cookies(category)")
conn.commit()
conn.close()
self.db_initialized = True
ctx.log.info(f"Cookie Tracker database ready: {DB_PATH}")
except Exception as e:
ctx.log.error(f"Failed to initialize database: {e}")
def _classify_cookie(self, domain: str, name: str) -> str:
"""Classify a cookie based on name patterns and domain."""
name_lower = name.lower()
domain_lower = domain.lower()
# Check name patterns first (most specific)
for category, patterns in TRACKING_PATTERNS.items():
for pattern in patterns:
if re.match(pattern, name, re.IGNORECASE):
return category
# Check domain against known trackers
for category, domains in TRACKER_DOMAINS.items():
for tracker_domain in domains:
if tracker_domain in domain_lower:
return category
# Check database for custom tracker domains
try:
conn = sqlite3.connect(DB_PATH)
cursor = conn.execute(
"SELECT category FROM tracker_domains WHERE ? LIKE '%' || domain || '%'",
(domain_lower,)
)
row = cursor.fetchone()
conn.close()
if row:
return row[0]
except Exception:
pass
return 'unknown'
def _parse_set_cookie(self, header: str) -> dict:
"""Parse Set-Cookie header into components."""
parts = header.split(';')
if not parts:
return None
# First part is name=value
name_value = parts[0].strip()
if '=' not in name_value:
return None
name, value = name_value.split('=', 1)
name = name.strip()
value = value.strip()
cookie = {
'name': name,
'value': value[:100], # Truncate value
'attributes': {}
}
# Parse attributes
for part in parts[1:]:
part = part.strip()
if '=' in part:
key, val = part.split('=', 1)
cookie['attributes'][key.lower().strip()] = val.strip()
else:
cookie['attributes'][part.lower()] = True
return cookie
def _record_cookie(self, domain: str, cookie: dict, client_ip: str = None):
"""Record a cookie in the database."""
if not self.db_initialized:
return
name = cookie['name']
category = self._classify_cookie(domain, name)
try:
conn = sqlite3.connect(DB_PATH)
cursor = conn.execute("""
INSERT INTO cookies (domain, name, category, client_mac)
VALUES (?, ?, ?, ?)
ON CONFLICT(domain, name) DO UPDATE SET
last_seen = strftime('%s', 'now'),
count = count + 1,
category = CASE
WHEN excluded.category != 'unknown' THEN excluded.category
ELSE cookies.category
END
""", (domain, name, category, client_ip))
conn.commit()
conn.close()
# Log tracking cookies
if category in ('tracking', 'advertising'):
ctx.log.warn(f"TRACKING COOKIE: {domain} - {name} [{category}]")
else:
ctx.log.debug(f"Cookie recorded: {domain}/{name} [{category}]")
except Exception as e:
ctx.log.error(f"Failed to record cookie: {e}")
def response(self, flow: http.HTTPFlow):
"""Process response and extract Set-Cookie headers."""
if not flow.response:
return
# Get domain from request
domain = flow.request.host
# Get client IP for tracking
client_ip = None
if flow.client_conn and flow.client_conn.peername:
client_ip = flow.client_conn.peername[0]
# Check for Set-Cookie headers
cookies = flow.response.headers.get_all('set-cookie')
if not cookies:
return
for cookie_header in cookies:
cookie = self._parse_set_cookie(cookie_header)
if cookie:
self._record_cookie(domain, cookie, client_ip)
def request(self, flow: http.HTTPFlow):
"""Process request and extract Cookie header."""
if not flow.request:
return
domain = flow.request.host
client_ip = None
if flow.client_conn and flow.client_conn.peername:
client_ip = flow.client_conn.peername[0]
# Get Cookie header
cookie_header = flow.request.headers.get('cookie')
if not cookie_header:
return
# Parse cookies from request
for cookie_str in cookie_header.split(';'):
cookie_str = cookie_str.strip()
if '=' in cookie_str:
name, value = cookie_str.split('=', 1)
cookie = {
'name': name.strip(),
'value': value.strip()[:100],
'attributes': {}
}
self._record_cookie(domain, cookie, client_ip)
addons = [CookieTracker()]

View File

@ -0,0 +1,587 @@
#!/bin/sh
#
# cookie-trackerctl - CLI controller for SecuBox Cookie Tracker
#
# Usage: cookie-trackerctl <command> [options]
#
. /lib/functions.sh
DB_PATH="/var/lib/cookie-tracker/cookies.db"
TRACKER_TSV="/usr/lib/secubox/cookie-tracker/known-trackers.tsv"
VORTEX_DB="/var/lib/vortex-firewall/blocklist.db"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
usage() {
cat <<EOF
SecuBox Cookie Tracker - HTTP cookie classification and tracking
Usage: cookie-trackerctl <command> [options]
Commands:
status Show statistics summary
init Initialize/reset database
reload Reload tracker rules from UCI
list [options] List cookies
--domain <d> Filter by domain
--category <c> Filter by category
--limit <n> Limit results (default: 100)
show <domain> Show cookies for domain
classify <domain> <name> <category>
Manually classify a cookie
block <domain> Block all cookies from domain
unblock <domain> Unblock domain
report [--json] Generate cookie report
export [file] Export database to CSV
import <file> Import tracker rules from TSV
feed-vortex Feed blocked domains to Vortex Firewall
stats Detailed statistics
Categories:
essential Required for site functionality
functional User preferences/settings
analytics Usage tracking
advertising Ad targeting
tracking Cross-site tracking
Examples:
cookie-trackerctl list --category tracking
cookie-trackerctl classify google.com _ga analytics
cookie-trackerctl block doubleclick.net
cookie-trackerctl report --json
EOF
exit 1
}
log_info() {
logger -t cookie-tracker -p info "$1"
}
log_warn() {
logger -t cookie-tracker -p warn "$1"
}
log_error() {
logger -t cookie-tracker -p err "$1"
}
# Initialize database
init_db() {
local force="$1"
mkdir -p "$(dirname "$DB_PATH")"
if [ -f "$DB_PATH" ] && [ "$force" != "force" ]; then
echo "Database exists at $DB_PATH"
echo "Use 'init force' to reset"
return 0
fi
rm -f "$DB_PATH"
sqlite3 "$DB_PATH" <<-EOF
CREATE TABLE IF NOT EXISTS cookies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
domain TEXT NOT NULL,
name TEXT NOT NULL,
category TEXT DEFAULT 'unknown',
first_seen INTEGER DEFAULT (strftime('%s', 'now')),
last_seen INTEGER DEFAULT (strftime('%s', 'now')),
count INTEGER DEFAULT 1,
client_mac TEXT,
blocked INTEGER DEFAULT 0,
UNIQUE(domain, name)
);
CREATE TABLE IF NOT EXISTS tracker_domains (
domain TEXT PRIMARY KEY,
category TEXT NOT NULL,
source TEXT DEFAULT 'manual',
added INTEGER DEFAULT (strftime('%s', 'now'))
);
CREATE TABLE IF NOT EXISTS blocked_domains (
domain TEXT PRIMARY KEY,
reason TEXT,
blocked_at INTEGER DEFAULT (strftime('%s', 'now'))
);
CREATE INDEX IF NOT EXISTS idx_cookies_domain ON cookies(domain);
CREATE INDEX IF NOT EXISTS idx_cookies_category ON cookies(category);
CREATE INDEX IF NOT EXISTS idx_cookies_last_seen ON cookies(last_seen);
EOF
echo "Database initialized at $DB_PATH"
# Import known trackers
if [ -f "$TRACKER_TSV" ]; then
import_trackers "$TRACKER_TSV"
fi
log_info "Database initialized"
}
# Import tracker rules from TSV
import_trackers() {
local file="${1:-$TRACKER_TSV}"
if [ ! -f "$file" ]; then
echo "File not found: $file"
return 1
fi
local count=0
while IFS=$'\t' read -r domain category source; do
# Skip comments and empty lines
case "$domain" in
"#"*|"") continue ;;
esac
sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO tracker_domains (domain, category, source) VALUES ('$domain', '$category', '$source');"
count=$((count + 1))
done < "$file"
echo "Imported $count tracker domains"
log_info "Imported $count tracker domains from $file"
}
# Reload UCI rules into database
reload_rules() {
config_load cookie-tracker
local count=0
reload_tracker_rule() {
local section="$1"
local pattern domain_pattern category source
config_get pattern "$section" pattern
config_get domain_pattern "$section" domain_pattern
config_get category "$section" category "tracking"
config_get source "$section" source "uci"
# For domain patterns, extract and add domains
if [ -n "$domain_pattern" ]; then
# Convert regex pattern to domain list (simplified)
local domains=$(echo "$domain_pattern" | sed 's/\\\././' | tr '|' '\n')
for d in $domains; do
sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO tracker_domains (domain, category, source) VALUES ('$d', '$category', '$source');"
count=$((count + 1))
done
fi
}
config_foreach reload_tracker_rule tracker_rule
echo "Reloaded $count tracker rules from UCI"
log_info "Reloaded $count tracker rules"
}
# Show status/statistics
show_status() {
local json="$1"
if [ ! -f "$DB_PATH" ]; then
echo "Database not initialized. Run: cookie-trackerctl init"
return 1
fi
local total=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies;")
local domains=$(sqlite3 "$DB_PATH" "SELECT COUNT(DISTINCT domain) FROM cookies;")
local blocked=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE blocked=1;")
local trackers=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM tracker_domains;")
local blocked_domains=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM blocked_domains;")
# Category breakdown
local essential=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE category='essential';")
local functional=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE category='functional';")
local analytics=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE category='analytics';")
local advertising=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE category='advertising';")
local tracking=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE category='tracking';")
local unknown=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE category='unknown';")
# Last 24h activity
local today=$(date +%s)
local yesterday=$((today - 86400))
local new_today=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE first_seen > $yesterday;")
local seen_today=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE last_seen > $yesterday;")
if [ "$json" = "--json" ]; then
cat <<EOF
{
"total_cookies": $total,
"unique_domains": $domains,
"blocked_cookies": $blocked,
"known_trackers": $trackers,
"blocked_domains": $blocked_domains,
"categories": {
"essential": $essential,
"functional": $functional,
"analytics": $analytics,
"advertising": $advertising,
"tracking": $tracking,
"unknown": $unknown
},
"last_24h": {
"new_cookies": $new_today,
"seen_cookies": $seen_today
}
}
EOF
else
echo "============================================"
echo " SecuBox Cookie Tracker Status"
echo "============================================"
echo ""
printf "Total Cookies: %s\n" "$total"
printf "Unique Domains: %s\n" "$domains"
printf "Blocked Cookies: %s\n" "$blocked"
printf "Known Trackers: %s\n" "$trackers"
printf "Blocked Domains: %s\n" "$blocked_domains"
echo ""
echo "--- Category Breakdown ---"
printf " Essential: ${GREEN}%s${NC}\n" "$essential"
printf " Functional: ${BLUE}%s${NC}\n" "$functional"
printf " Analytics: ${YELLOW}%s${NC}\n" "$analytics"
printf " Advertising: ${RED}%s${NC}\n" "$advertising"
printf " Tracking: ${RED}%s${NC}\n" "$tracking"
printf " Unknown: %s\n" "$unknown"
echo ""
echo "--- Last 24 Hours ---"
printf " New Cookies: %s\n" "$new_today"
printf " Seen Cookies: %s\n" "$seen_today"
echo ""
fi
}
# List cookies
list_cookies() {
local domain="" category="" limit=100 json=""
while [ $# -gt 0 ]; do
case "$1" in
--domain) domain="$2"; shift 2 ;;
--category) category="$2"; shift 2 ;;
--limit) limit="$2"; shift 2 ;;
--json) json="1"; shift ;;
*) shift ;;
esac
done
local where=""
[ -n "$domain" ] && where="WHERE domain LIKE '%$domain%'"
[ -n "$category" ] && {
[ -n "$where" ] && where="$where AND" || where="WHERE"
where="$where category='$category'"
}
if [ "$json" = "1" ]; then
echo "["
sqlite3 -json "$DB_PATH" "SELECT domain, name, category, count, blocked, datetime(last_seen, 'unixepoch') as last_seen FROM cookies $where ORDER BY last_seen DESC LIMIT $limit;" 2>/dev/null || \
sqlite3 "$DB_PATH" "SELECT domain, name, category, count, blocked, datetime(last_seen, 'unixepoch') as last_seen FROM cookies $where ORDER BY last_seen DESC LIMIT $limit;" | \
awk -F'|' 'BEGIN{first=1} {
if(!first) print ","
first=0
printf "{\"domain\":\"%s\",\"name\":\"%s\",\"category\":\"%s\",\"count\":%s,\"blocked\":%s,\"last_seen\":\"%s\"}", $1, $2, $3, $4, $5, $6
}'
echo "]"
else
printf "%-30s %-25s %-12s %6s %s\n" "DOMAIN" "COOKIE" "CATEGORY" "COUNT" "BLOCKED"
echo "--------------------------------------------------------------------------------"
sqlite3 "$DB_PATH" "SELECT domain, name, category, count, CASE WHEN blocked=1 THEN 'YES' ELSE '' END FROM cookies $where ORDER BY last_seen DESC LIMIT $limit;" | \
while IFS='|' read -r d n c cnt b; do
printf "%-30s %-25s %-12s %6s %s\n" "${d:0:30}" "${n:0:25}" "$c" "$cnt" "$b"
done
fi
}
# Show cookies for specific domain
show_domain() {
local domain="$1"
if [ -z "$domain" ]; then
echo "Usage: cookie-trackerctl show <domain>"
return 1
fi
echo "Cookies for domain: $domain"
echo "========================================"
sqlite3 "$DB_PATH" "SELECT name, category, count, blocked, datetime(first_seen, 'unixepoch'), datetime(last_seen, 'unixepoch') FROM cookies WHERE domain LIKE '%$domain%' ORDER BY count DESC;" | \
while IFS='|' read -r name cat cnt blocked first last; do
printf "\n Name: %s\n" "$name"
printf " Category: %s\n" "$cat"
printf " Count: %s\n" "$cnt"
printf " Blocked: %s\n" "$([ "$blocked" = "1" ] && echo "Yes" || echo "No")"
printf " First seen: %s\n" "$first"
printf " Last seen: %s\n" "$last"
done
}
# Manually classify a cookie
classify_cookie() {
local domain="$1"
local name="$2"
local category="$3"
if [ -z "$domain" ] || [ -z "$name" ] || [ -z "$category" ]; then
echo "Usage: cookie-trackerctl classify <domain> <name> <category>"
return 1
fi
# Validate category
case "$category" in
essential|functional|analytics|advertising|tracking|unknown) ;;
*) echo "Invalid category: $category"; return 1 ;;
esac
sqlite3 "$DB_PATH" "UPDATE cookies SET category='$category' WHERE domain='$domain' AND name='$name';"
if [ $? -eq 0 ]; then
echo "Cookie classified: $domain/$name -> $category"
log_info "Cookie classified: $domain/$name -> $category"
else
echo "Failed to classify cookie"
return 1
fi
}
# Block domain
block_domain() {
local domain="$1"
local reason="${2:-manual}"
if [ -z "$domain" ]; then
echo "Usage: cookie-trackerctl block <domain>"
return 1
fi
sqlite3 "$DB_PATH" "INSERT OR REPLACE INTO blocked_domains (domain, reason) VALUES ('$domain', '$reason');"
sqlite3 "$DB_PATH" "UPDATE cookies SET blocked=1 WHERE domain LIKE '%$domain%';"
echo "Domain blocked: $domain"
log_info "Domain blocked: $domain"
}
# Unblock domain
unblock_domain() {
local domain="$1"
if [ -z "$domain" ]; then
echo "Usage: cookie-trackerctl unblock <domain>"
return 1
fi
sqlite3 "$DB_PATH" "DELETE FROM blocked_domains WHERE domain='$domain';"
sqlite3 "$DB_PATH" "UPDATE cookies SET blocked=0 WHERE domain LIKE '%$domain%';"
echo "Domain unblocked: $domain"
log_info "Domain unblocked: $domain"
}
# Generate report
generate_report() {
local json="$1"
# Top 10 domains by cookie count
local top_domains=$(sqlite3 "$DB_PATH" "SELECT domain, COUNT(*) as cnt FROM cookies GROUP BY domain ORDER BY cnt DESC LIMIT 10;")
# Top tracking domains
local top_trackers=$(sqlite3 "$DB_PATH" "SELECT domain, COUNT(*) as cnt FROM cookies WHERE category IN ('tracking', 'advertising') GROUP BY domain ORDER BY cnt DESC LIMIT 10;")
# Recent cookies
local recent=$(sqlite3 "$DB_PATH" "SELECT domain, name, category FROM cookies ORDER BY last_seen DESC LIMIT 10;")
if [ "$json" = "--json" ]; then
# Build JSON report
echo "{"
echo " \"generated\": \"$(date -Iseconds)\","
# Top domains
echo " \"top_domains\": ["
echo "$top_domains" | awk -F'|' 'BEGIN{first=1} {
if(!first) print ","
first=0
printf " {\"domain\":\"%s\",\"count\":%s}", $1, $2
}'
echo ""
echo " ],"
# Top trackers
echo " \"top_trackers\": ["
echo "$top_trackers" | awk -F'|' 'BEGIN{first=1} {
if(!first) print ","
first=0
printf " {\"domain\":\"%s\",\"count\":%s}", $1, $2
}'
echo ""
echo " ],"
# Recent
echo " \"recent\": ["
echo "$recent" | awk -F'|' 'BEGIN{first=1} {
if(!first) print ","
first=0
printf " {\"domain\":\"%s\",\"name\":\"%s\",\"category\":\"%s\"}", $1, $2, $3
}'
echo ""
echo " ]"
echo "}"
else
echo "============================================"
echo " Cookie Tracker Report"
echo " Generated: $(date)"
echo "============================================"
echo ""
echo "--- Top 10 Domains by Cookie Count ---"
echo "$top_domains" | while IFS='|' read -r d c; do
printf " %-40s %s cookies\n" "$d" "$c"
done
echo ""
echo "--- Top Tracking/Advertising Domains ---"
echo "$top_trackers" | while IFS='|' read -r d c; do
printf " ${RED}%-40s${NC} %s cookies\n" "$d" "$c"
done
echo ""
echo "--- Recently Seen Cookies ---"
echo "$recent" | while IFS='|' read -r d n c; do
printf " %-30s %-20s [%s]\n" "$d" "$n" "$c"
done
echo ""
fi
}
# Export to CSV
export_csv() {
local file="${1:-/tmp/cookies-export.csv}"
echo "domain,name,category,count,blocked,first_seen,last_seen" > "$file"
sqlite3 -csv "$DB_PATH" "SELECT domain, name, category, count, blocked, datetime(first_seen, 'unixepoch'), datetime(last_seen, 'unixepoch') FROM cookies ORDER BY domain, name;" >> "$file"
echo "Exported to $file"
}
# Feed blocked domains to Vortex Firewall
feed_vortex() {
if [ ! -f "$VORTEX_DB" ]; then
echo "Vortex Firewall database not found: $VORTEX_DB"
echo "Make sure secubox-vortex-firewall is installed"
return 1
fi
local count=0
# Get blocked domains
sqlite3 "$DB_PATH" "SELECT domain FROM blocked_domains;" | while read -r domain; do
sqlite3 "$VORTEX_DB" "INSERT OR IGNORE INTO domains (domain, category, source) VALUES ('$domain', 'cookie_tracker', 'cookie-tracker');"
count=$((count + 1))
done
# Get tracking/advertising domains
sqlite3 "$DB_PATH" "SELECT DISTINCT domain FROM cookies WHERE category IN ('tracking', 'advertising') AND blocked=1;" | while read -r domain; do
sqlite3 "$VORTEX_DB" "INSERT OR IGNORE INTO domains (domain, category, source) VALUES ('$domain', 'cookie_tracker', 'cookie-tracker');"
count=$((count + 1))
done
echo "Fed $count domains to Vortex Firewall"
log_info "Fed blocked domains to Vortex Firewall"
# Reload Vortex if available
[ -x /etc/init.d/vortex-firewall ] && /etc/init.d/vortex-firewall reload
}
# Detailed statistics
detailed_stats() {
echo "============================================"
echo " Cookie Tracker Detailed Statistics"
echo "============================================"
echo ""
echo "--- Cookies by Category ---"
sqlite3 "$DB_PATH" "SELECT category, COUNT(*) as cnt, SUM(count) as total FROM cookies GROUP BY category ORDER BY cnt DESC;" | \
while IFS='|' read -r cat cnt total; do
printf " %-15s %5s cookies, %6s total hits\n" "$cat" "$cnt" "$total"
done
echo ""
echo "--- Top 20 Most Frequent Cookies ---"
sqlite3 "$DB_PATH" "SELECT domain, name, count, category FROM cookies ORDER BY count DESC LIMIT 20;" | \
while IFS='|' read -r d n c cat; do
printf " %-30s %-20s %6s [%s]\n" "${d:0:30}" "${n:0:20}" "$c" "$cat"
done
echo ""
echo "--- Cookie Age Distribution ---"
local now=$(date +%s)
local hour=$((now - 3600))
local day=$((now - 86400))
local week=$((now - 604800))
local last_hour=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE last_seen > $hour;")
local last_day=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE last_seen > $day;")
local last_week=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE last_seen > $week;")
local older=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM cookies WHERE last_seen <= $week;")
printf " Last hour: %5s\n" "$last_hour"
printf " Last day: %5s\n" "$last_day"
printf " Last week: %5s\n" "$last_week"
printf " Older: %5s\n" "$older"
}
# Main command dispatcher
case "${1:-status}" in
status)
show_status "$2"
;;
init)
init_db "$2"
;;
reload)
reload_rules
;;
list)
shift
list_cookies "$@"
;;
show)
show_domain "$2"
;;
classify)
classify_cookie "$2" "$3" "$4"
;;
block)
block_domain "$2" "$3"
;;
unblock)
unblock_domain "$2"
;;
report)
generate_report "$2"
;;
export)
export_csv "$2"
;;
import)
import_trackers "$2"
;;
feed-vortex)
feed_vortex
;;
stats)
detailed_stats
;;
-h|--help|help)
usage
;;
*)
echo "Unknown command: $1"
usage
;;
esac