fix(tor-shield,security-threats): Preset persistence and firewall stats

Tor Shield:
- Store current_preset in UCI when enabling with preset
- Return current_preset in status response
- Initialize currentPreset from stored UCI value on page load

Security Threats:
- Fix get_security_stats() firewall packet counting
- Use correct nftables chain names (input_wan, handle_reject)
- Fix grep -c exit code issue (returns 1 when no matches)
- Improve numeric validation (use tr -cd to strip non-digits)
- Add fallbacks for HAProxy socket paths

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-01-28 15:29:24 +01:00
parent 025a1085e9
commit cf49c7d80b
5 changed files with 91 additions and 27 deletions

View File

@ -5,7 +5,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-secubox-security-threats
PKG_VERSION:=1.0.0
PKG_RELEASE:=3
PKG_RELEASE:=4
PKG_ARCH:=all
PKG_LICENSE:=Apache-2.0
PKG_MAINTAINER:=CyberMind <contact@cybermind.fr>

View File

@ -279,58 +279,116 @@ get_security_stats() {
local wan_iface=$(uci -q get network.wan.device || uci -q get network.wan.ifname)
[ -z "$wan_iface" ] && wan_iface="eth0"
# WAN dropped packets from nftables (actual firewall drops on input chain)
# Count packets dropped/rejected on wan zone input
# WAN dropped packets from nftables (use counter on drop rules)
if command -v nft >/dev/null 2>&1; then
# Get drop counters from firewall input chain for wan
wan_drops=$(nft list chain inet fw4 input 2>/dev/null | grep -E "iifname.*$wan_iface.*drop|iifname.*$wan_iface.*reject" | grep -oE 'packets [0-9]+' | awk '{sum+=$2} END {print sum+0}')
# Also count from forward chain drops (wan to lan blocked)
local wan_fwd_drops=$(nft list chain inet fw4 forward 2>/dev/null | grep -E "iifname.*$wan_iface.*drop|iifname.*$wan_iface.*reject" | grep -oE 'packets [0-9]+' | awk '{sum+=$2} END {print sum+0}')
wan_drops=$((${wan_drops:-0} + ${wan_fwd_drops:-0}))
# Get all drop/reject counters from input_wan zone
# nft format: "counter packets X bytes Y"
wan_drops=$(nft list chain inet fw4 input_wan 2>/dev/null | \
grep -E "counter packets" | \
grep -oE 'packets [0-9]+' | \
awk '{sum+=$2} END {print sum+0}')
[ -z "$wan_drops" ] && wan_drops=0
# Also try reject chain
local reject_drops=$(nft list chain inet fw4 reject_from_wan 2>/dev/null | \
grep -E "counter packets" | \
grep -oE 'packets [0-9]+' | \
awk '{sum+=$2} END {print sum+0}')
[ -n "$reject_drops" ] && wan_drops=$((wan_drops + reject_drops))
fi
wan_drops=${wan_drops:-0}
# Firewall rejects - count from nftables counters (more accurate than logs)
# Firewall rejects - count from reject-specific chains
if command -v nft >/dev/null 2>&1; then
fw_rejects=$(nft list ruleset 2>/dev/null | grep -E "reject|drop" | grep -oE 'packets [0-9]+' | awk '{sum+=$2} END {print sum+0}')
# Count from handle_reject chain which has actual reject rules
fw_rejects=$(nft list chain inet fw4 handle_reject 2>/dev/null | \
grep -E "counter packets" | \
grep -oE 'packets [0-9]+' | \
awk '{sum+=$2} END {print sum+0}')
[ -z "$fw_rejects" ] && fw_rejects=0
# If no handle_reject, try counting reject rules in all chains
if [ "$fw_rejects" = "0" ]; then
fw_rejects=$(nft -a list ruleset 2>/dev/null | \
grep -E "reject|drop" | grep "counter" | \
grep -oE 'packets [0-9]+' | \
awk '{sum+=$2} END {print sum+0}')
fi
else
# Fallback to log parsing
fw_rejects=$(logread 2>/dev/null | grep -c "reject\|DROP\|REJECT" || echo 0)
fi
fw_rejects=$(echo "$fw_rejects" | tr -d '\n')
fw_rejects=${fw_rejects:-0}
# CrowdSec active bans
if [ -x "$CSCLI" ]; then
cs_bans=$($CSCLI decisions list -o json 2>/dev/null | grep -c '"id":' || echo 0)
cs_bans=$(echo "$cs_bans" | tr -d '\n')
# Use jq for proper JSON parsing if available, fallback to grep
if command -v jq >/dev/null 2>&1; then
cs_bans=$($CSCLI decisions list -o json 2>/dev/null | jq 'length' 2>/dev/null)
[ -z "$cs_bans" ] && cs_bans=0
else
# Count JSON array items
local cs_json=$($CSCLI decisions list -o json 2>/dev/null)
if [ -n "$cs_json" ] && [ "$cs_json" != "null" ] && [ "$cs_json" != "[]" ]; then
cs_bans=$(echo "$cs_json" | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
fi
fi
cs_bans=${cs_bans:-0}
# CrowdSec alerts in last 24h
cs_alerts_24h=$($CSCLI alerts list -o json --since 24h 2>/dev/null | grep -c '"id":' || echo 0)
cs_alerts_24h=$(echo "$cs_alerts_24h" | tr -d '\n')
if command -v jq >/dev/null 2>&1; then
cs_alerts_24h=$($CSCLI alerts list -o json --since 24h 2>/dev/null | jq 'length' 2>/dev/null)
[ -z "$cs_alerts_24h" ] && cs_alerts_24h=0
else
local alerts_json=$($CSCLI alerts list -o json --since 24h 2>/dev/null)
if [ -n "$alerts_json" ] && [ "$alerts_json" != "null" ] && [ "$alerts_json" != "[]" ]; then
cs_alerts_24h=$(echo "$alerts_json" | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
fi
fi
cs_alerts_24h=${cs_alerts_24h:-0}
fi
# Invalid connections (conntrack)
# Invalid connections (conntrack) - only count INVALID, not UNREPLIED
if [ -f /proc/net/nf_conntrack ]; then
invalid_conns=$(grep -c "INVALID\|UNREPLIED" /proc/net/nf_conntrack 2>/dev/null || echo 0)
# grep -c returns 1 exit code when no matches, so we can't use || echo 0
invalid_conns=$(grep -c "\[INVALID\]" /proc/net/nf_conntrack 2>/dev/null)
[ -z "$invalid_conns" ] && invalid_conns=0
fi
invalid_conns=$(echo "$invalid_conns" | tr -d '\n')
invalid_conns=${invalid_conns:-0}
# HAProxy connections (if running in LXC)
if lxc-info -n haproxy -s 2>/dev/null | grep -q "RUNNING"; then
# Try multiple socket paths (chroot-relative and absolute)
# HAProxy connections
# Try local haproxy first, then LXC
if [ -S /var/run/haproxy/admin.sock ]; then
haproxy_conns=$(echo "show stat" | socat stdio /var/run/haproxy/admin.sock 2>/dev/null | \
tail -n+2 | awk -F, '{sum+=$5} END {print sum+0}')
elif [ -S /var/lib/haproxy/stats ]; then
haproxy_conns=$(echo "show stat" | socat stdio /var/lib/haproxy/stats 2>/dev/null | \
tail -n+2 | awk -F, '{sum+=$5} END {print sum+0}')
elif command -v lxc-info >/dev/null 2>&1 && lxc-info -n haproxy -s 2>/dev/null | grep -q "RUNNING"; then
# HAProxy in LXC container
haproxy_conns=$(lxc-attach -n haproxy -- sh -c '
for sock in /stats /run/haproxy.sock /var/lib/haproxy/stats /var/run/haproxy/admin.sock; do
[ -S "$sock" ] && echo "show stat" | socat stdio "$sock" 2>/dev/null && break
done | tail -n+2 | awk -F, "{sum+=\$5} END {print sum+0}"
' 2>/dev/null || echo 0)
[ -S "$sock" ] && { echo "show stat" | socat stdio "$sock" 2>/dev/null; break; }
done' 2>/dev/null | tail -n+2 | awk -F, '{sum+=$5} END {print sum+0}')
fi
haproxy_conns=$(echo "$haproxy_conns" | tr -d '\n')
haproxy_conns=${haproxy_conns:-0}
# Clean up and ensure numeric values (remove all non-digits)
wan_drops=$(printf '%s' "$wan_drops" | tr -cd '0-9')
fw_rejects=$(printf '%s' "$fw_rejects" | tr -cd '0-9')
cs_bans=$(printf '%s' "$cs_bans" | tr -cd '0-9')
cs_alerts_24h=$(printf '%s' "$cs_alerts_24h" | tr -cd '0-9')
invalid_conns=$(printf '%s' "$invalid_conns" | tr -cd '0-9')
haproxy_conns=$(printf '%s' "$haproxy_conns" | tr -cd '0-9')
# Default to 0 if empty
: ${wan_drops:=0}
: ${fw_rejects:=0}
: ${cs_bans:=0}
: ${cs_alerts_24h:=0}
: ${invalid_conns:=0}
: ${haproxy_conns:=0}
# Output JSON
cat << EOF
{

View File

@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-tor-shield
PKG_VERSION:=1.0.0
PKG_RELEASE:=9
PKG_RELEASE:=10
PKG_ARCH:=all
PKG_LICENSE:=MIT

View File

@ -285,6 +285,9 @@ return view.extend({
var presets = data.presets || [];
var bandwidth = data.bandwidth || {};
// Initialize currentPreset from stored UCI value
this.currentPreset = status.current_preset || 'anonymous';
var isActive = status.enabled && status.running;
var isProtected = isActive && status.is_tor;
var isConnecting = isActive && status.bootstrap < 100;

View File

@ -54,17 +54,19 @@ get_bootstrap() {
get_status() {
json_init
local enabled mode dns_over_tor kill_switch
local enabled mode dns_over_tor kill_switch current_preset
config_load "$CONFIG"
config_get enabled main enabled '0'
config_get mode main mode 'transparent'
config_get dns_over_tor main dns_over_tor '1'
config_get kill_switch main kill_switch '1'
config_get current_preset main current_preset 'anonymous'
json_add_boolean "enabled" "$enabled"
json_add_string "mode" "$mode"
json_add_boolean "dns_over_tor" "$dns_over_tor"
json_add_boolean "kill_switch" "$kill_switch"
json_add_string "current_preset" "$current_preset"
# Running state
if is_running; then
@ -164,6 +166,7 @@ do_enable() {
uci set tor-shield.main.mode="$preset_mode"
uci set tor-shield.main.dns_over_tor="$preset_dns"
uci set tor-shield.main.kill_switch="$preset_kill"
uci set tor-shield.main.current_preset="$preset"
if [ "$preset_bridges" = "1" ]; then
uci set tor-shield.bridges.enabled='1'