Skip to content

Commit 6456bf0

Browse files
committed
security: audit --fix, workspace confinement, audit logging, network logging
Phase 1: Add --fix flag to security-audit.sh - Auto-remediates permission findings (chmod 600/700) - Fixes leaky session logs and control sockets - Fixes wrong file ownership in hornet repo - Runs log redaction for leaked secrets - Skips fixes requiring root (firewall, hooks, hidepid) - Reports fixed/skipped/error counts in summary - 8 new tests (39 total audit tests) Phase 2: Workspace confinement in tool-guard.ts - Flip write/edit from deny-list to allow-list - Only /home/hornet_agent/ is writable - Blocks /tmp, /var/tmp, /opt, /usr/local, etc. - Protected hornet paths still blocked within allowed prefix - 18 new workspace confinement tests (70 total tool-guard tests) Phase 3: Append-only command audit log - tool-guard.ts logs every tool call (bash/write/edit) as JSON lines - Logs both allowed and blocked calls with rule IDs - Primary: /var/log/hornet/commands.log (root-owned, chattr +a) - Fallback: ~/logs/commands.log (functional without root setup) - security-audit.sh checks for log existence and append-only attr - --fix creates fallback log if missing Phase 4: Outbound network logging - Add iptables LOG rules for SYN packets and DNS queries - Low-volume: only new connections, not every packet - Queryable via: journalctl -k --grep=hornet-out - security-audit.sh checks for LOG rules in HORNET_OUTPUT chain All 230 tests pass across 6 test files.
1 parent 41dfa09 commit 6456bf0

5 files changed

Lines changed: 551 additions & 51 deletions

File tree

β€Žbin/security-audit.shβ€Ž

Lines changed: 189 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,33 @@
22
# Security audit for Hornet agent infrastructure
33
# Run as hornet_agent or admin user to check security posture
44
#
5-
# Usage: ~/hornet/bin/security-audit.sh [--deep]
5+
# Usage: ~/hornet/bin/security-audit.sh [--deep] [--fix]
66
# sudo -u hornet_agent ~/hornet/bin/security-audit.sh --deep
7+
# sudo -u hornet_agent ~/hornet/bin/security-audit.sh --fix
78
#
89
# --deep: Run the Node.js extension scanner for cross-pattern analysis
10+
# --fix: Auto-remediate findings where possible
911

1012
set -euo pipefail
1113

1214
HORNET_HOME="${HORNET_HOME:-/home/hornet_agent}"
1315
DEEP=0
16+
FIX=0
1417
for arg in "$@"; do
15-
if [ "$arg" = "--deep" ]; then
16-
DEEP=1
17-
fi
18+
case "$arg" in
19+
--deep) DEEP=1 ;;
20+
--fix) FIX=1 ;;
21+
esac
1822
done
1923

2024
# Counters
2125
critical=0
2226
warn=0
2327
info=0
2428
pass=0
29+
fixed=0
30+
skipped=0
31+
fix_errors=0
2532

2633
finding() {
2734
local severity="$1"
@@ -41,9 +48,41 @@ ok() {
4148
pass=$((pass + 1))
4249
}
4350

51+
# fix_action: attempt a remediation and report result
52+
# Usage: fix_action "description" command [args...]
53+
fix_action() {
54+
local desc="$1"
55+
shift
56+
if [ "$FIX" -ne 1 ]; then
57+
return 1 # signal: not fixed (caller should emit finding)
58+
fi
59+
if "$@" 2>/dev/null; then
60+
echo " πŸ”§ FIXED: $desc"
61+
fixed=$((fixed + 1))
62+
return 0
63+
else
64+
echo " ❌ FIX-ERR: $desc (command failed)"
65+
fix_errors=$((fix_errors + 1))
66+
return 1
67+
fi
68+
}
69+
70+
# fix_skip: report that fix was skipped (needs root, manual intervention, etc.)
71+
fix_skip() {
72+
local desc="$1"
73+
local reason="$2"
74+
if [ "$FIX" -eq 1 ]; then
75+
echo " ⏭️ SKIPPED: $desc β€” $reason"
76+
skipped=$((skipped + 1))
77+
fi
78+
}
79+
4480
echo ""
4581
echo "πŸ”’ Hornet Security Audit"
4682
echo "========================"
83+
if [ "$FIX" -eq 1 ]; then
84+
echo " Mode: auto-fix enabled"
85+
fi
4786
echo ""
4887

4988
# ── Docker group ─────────────────────────────────────────────────────────────
@@ -52,6 +91,7 @@ echo "Docker Access"
5291
if id hornet_agent 2>/dev/null | grep -q '(docker)'; then
5392
finding "CRITICAL" "hornet_agent is in docker group" \
5493
"Can bypass hornet-docker wrapper via /usr/bin/docker directly"
94+
fix_skip "Remove from docker group" "Requires root: sudo gpasswd -d hornet_agent docker"
5595
else
5696
ok "hornet_agent not in docker group"
5797
fi
@@ -87,7 +127,11 @@ check_perms() {
87127
sev="CRITICAL"
88128
fi
89129
fi
90-
finding "$sev" "$desc is $actual (expected $expected)" "$path"
130+
if fix_action "chmod $expected $path" chmod "$expected" "$path"; then
131+
ok "$desc ($expected) [fixed]"
132+
else
133+
finding "$sev" "$desc is $actual (expected $expected)" "$path"
134+
fi
91135
fi
92136
}
93137

@@ -102,8 +146,13 @@ check_perms "$HORNET_HOME/.pi/agent/settings.json" "600" "Pi settings"
102146
if [ -d "$HORNET_HOME/.pi/agent/sessions" ]; then
103147
leaky_logs=$(find "$HORNET_HOME/.pi/agent/sessions" -name '*.jsonl' -perm /044 2>/dev/null | wc -l)
104148
if [ "$leaky_logs" -gt 0 ]; then
105-
finding "WARN" "$leaky_logs session log(s) are group/world-readable" \
106-
"Run: ~/hornet/bin/harden-permissions.sh"
149+
if fix_action "Fix $leaky_logs session log permissions" \
150+
find "$HORNET_HOME/.pi/agent/sessions" -name '*.jsonl' -perm /044 -exec chmod 600 {} +; then
151+
ok "Session logs fixed to owner-only"
152+
else
153+
finding "WARN" "$leaky_logs session log(s) are group/world-readable" \
154+
"Run: ~/hornet/bin/harden-permissions.sh"
155+
fi
107156
else
108157
ok "Session logs are owner-only"
109158
fi
@@ -113,12 +162,33 @@ fi
113162
if [ -d "$HORNET_HOME/.pi/session-control" ]; then
114163
leaky_socks=$(find "$HORNET_HOME/.pi/session-control" -name '*.sock' -perm /044 2>/dev/null | wc -l)
115164
if [ "$leaky_socks" -gt 0 ]; then
116-
finding "WARN" "$leaky_socks control socket(s) are group/world-accessible" \
117-
"Other users could send commands to running agent sessions"
165+
if fix_action "Fix $leaky_socks control socket permissions" \
166+
find "$HORNET_HOME/.pi/session-control" -name '*.sock' -perm /044 -exec chmod 600 {} +; then
167+
ok "Control sockets fixed to owner-only"
168+
else
169+
finding "WARN" "$leaky_socks control socket(s) are group/world-accessible" \
170+
"Other users could send commands to running agent sessions"
171+
fi
118172
else
119173
ok "Control sockets are owner-only"
120174
fi
121175
fi
176+
177+
# Check for files owned by wrong user in hornet repo
178+
if [ -d "$HORNET_HOME/hornet" ]; then
179+
wrong_owner=$(find "$HORNET_HOME/hornet" -not -user hornet_agent -not -path '*/.git/objects/*' 2>/dev/null | wc -l)
180+
if [ "$wrong_owner" -gt 0 ]; then
181+
if fix_action "Fix $wrong_owner file(s) with wrong ownership" \
182+
find "$HORNET_HOME/hornet" -not -user hornet_agent -not -path '*/.git/objects/*' -exec chown hornet_agent:hornet_agent {} +; then
183+
ok "File ownership fixed in hornet repo"
184+
else
185+
finding "WARN" "$wrong_owner file(s) in hornet repo not owned by hornet_agent" \
186+
"Run: find ~/hornet -not -user hornet_agent -exec chown hornet_agent:hornet_agent {} +"
187+
fi
188+
else
189+
ok "All files in hornet repo owned by hornet_agent"
190+
fi
191+
fi
122192
echo ""
123193

124194
# ── Secrets in readable files ────────────────────────────────────────────────
@@ -203,8 +273,19 @@ if [ -d "$HORNET_HOME/.pi/agent/sessions" ]; then
203273
-exec grep -lE '(sk-[a-zA-Z0-9]{20,}|xoxb-[0-9]{10,}|xapp-[0-9]{10,})' {} \; 2>/dev/null | wc -l || true)
204274
log_secrets="${log_secrets:-0}"
205275
if [ "$log_secrets" -gt 0 ]; then
206-
finding "CRITICAL" "$log_secrets session log(s) contain possible API keys/tokens" \
207-
"Review and redact: find ~/.pi/agent/sessions -name '*.jsonl' -exec grep -l 'sk-\|xoxb-\|xapp-' {} +"
276+
if [ "$FIX" -eq 1 ] && [ -x "$HORNET_HOME/hornet/bin/redact-logs.sh" ]; then
277+
echo " πŸ”§ Running log redaction..."
278+
"$HORNET_HOME/hornet/bin/redact-logs.sh" 2>/dev/null && {
279+
echo " πŸ”§ FIXED: Redacted secrets from session logs"
280+
fixed=$((fixed + 1))
281+
} || {
282+
echo " ❌ FIX-ERR: Log redaction failed"
283+
fix_errors=$((fix_errors + 1))
284+
}
285+
else
286+
finding "CRITICAL" "$log_secrets session log(s) contain possible API keys/tokens" \
287+
"Review and redact: find ~/.pi/agent/sessions -name '*.jsonl' -exec grep -l 'sk-\|xoxb-\|xapp-' {} +"
288+
fi
208289
else
209290
ok "No secrets detected in session logs"
210291
fi
@@ -220,6 +301,7 @@ if echo "$proc_mount" | grep -q 'hidepid=2'; then
220301
else
221302
finding "WARN" "/proc not mounted with hidepid=2" \
222303
"hornet_agent can see all system processes β€” run setup.sh or: sudo mount -o remount,hidepid=2,gid=<procview_gid> /proc"
304+
fix_skip "Remount /proc with hidepid=2" "Requires root"
223305
fi
224306
echo ""
225307

@@ -255,6 +337,7 @@ if command -v iptables &>/dev/null; then
255337
else
256338
finding "WARN" "No firewall rules for hornet_agent" \
257339
"Run: sudo ~/hornet/bin/setup-firewall.sh"
340+
fix_skip "Install firewall rules" "Requires root: sudo ~/hornet/bin/setup-firewall.sh"
258341
fi
259342
fi
260343

@@ -269,6 +352,87 @@ else
269352
fi
270353
echo ""
271354

355+
# Check for network logging rules
356+
if command -v iptables &>/dev/null; then
357+
if iptables -L HORNET_OUTPUT -n 2>/dev/null | grep -qE "LOG.*hornet-(out|dns):"; then
358+
ok "Network logging active (SYN + DNS)"
359+
else
360+
finding "WARN" "No network logging in firewall rules" \
361+
"Run: sudo ~/hornet/bin/setup-firewall.sh to add LOG rules"
362+
fi
363+
fi
364+
echo ""
365+
366+
# ── Audit Log ────────────────────────────────────────────────────────────────
367+
368+
echo "Audit Log"
369+
370+
AUDIT_LOG_PRIMARY="/var/log/hornet/commands.log"
371+
AUDIT_LOG_FALLBACK="$HORNET_HOME/logs/commands.log"
372+
373+
if [ -f "$AUDIT_LOG_PRIMARY" ]; then
374+
ok "Audit log exists ($AUDIT_LOG_PRIMARY)"
375+
# Check append-only attribute
376+
if command -v lsattr &>/dev/null; then
377+
log_attrs=$(lsattr "$AUDIT_LOG_PRIMARY" 2>/dev/null | awk '{print $1}' || true)
378+
if echo "$log_attrs" | grep -q 'a'; then
379+
ok "Audit log has append-only attribute (tamper-proof)"
380+
else
381+
finding "WARN" "Audit log missing append-only attribute" \
382+
"Run: sudo chattr +a $AUDIT_LOG_PRIMARY"
383+
fix_skip "Set append-only attribute" "Requires root: sudo chattr +a $AUDIT_LOG_PRIMARY"
384+
fi
385+
fi
386+
# Check permissions
387+
log_perms=$(stat -c '%a' "$AUDIT_LOG_PRIMARY" 2>/dev/null || echo "???")
388+
if [ $((0$log_perms & 004)) -eq 0 ]; then
389+
ok "Audit log is not world-readable ($log_perms)"
390+
else
391+
finding "WARN" "Audit log is world-readable ($log_perms)" \
392+
"Run: sudo chmod 660 $AUDIT_LOG_PRIMARY"
393+
fi
394+
elif [ -f "$AUDIT_LOG_FALLBACK" ]; then
395+
finding "WARN" "Audit log using fallback location ($AUDIT_LOG_FALLBACK)" \
396+
"For tamper-proof logging, set up /var/log/hornet/ as root with chattr +a"
397+
else
398+
finding "WARN" "No audit log file found" \
399+
"Create: mkdir -p $HORNET_HOME/logs && touch $HORNET_HOME/logs/commands.log"
400+
if [ "$FIX" -eq 1 ]; then
401+
if mkdir -p "$HORNET_HOME/logs" && touch "$HORNET_HOME/logs/commands.log" && chmod 600 "$HORNET_HOME/logs/commands.log" 2>/dev/null; then
402+
echo " πŸ”§ FIXED: Created fallback audit log at $AUDIT_LOG_FALLBACK"
403+
fixed=$((fixed + 1))
404+
else
405+
echo " ❌ FIX-ERR: Could not create audit log"
406+
fix_errors=$((fix_errors + 1))
407+
fi
408+
fi
409+
fi
410+
echo ""
411+
412+
# ── Pre-commit hook ──────────────────────────────────────────────────────────
413+
414+
echo "Pre-commit Hook"
415+
416+
if [ -d "$HORNET_HOME/hornet/.git" ]; then
417+
hook_path="$HORNET_HOME/hornet/.git/hooks/pre-commit"
418+
if [ -f "$hook_path" ]; then
419+
ok "Pre-commit hook installed"
420+
hook_owner=$(stat -c '%U' "$hook_path" 2>/dev/null || echo "unknown")
421+
if [ "$hook_owner" = "root" ]; then
422+
ok "Pre-commit hook is root-owned (tamper-proof)"
423+
else
424+
finding "WARN" "Pre-commit hook owned by $hook_owner (should be root)" \
425+
"Run: sudo chown root:root $hook_path"
426+
fix_skip "Fix hook ownership" "Requires root: sudo chown root:root $hook_path"
427+
fi
428+
else
429+
finding "WARN" "Pre-commit hook not installed" \
430+
"Run: sudo cp ~/hornet/hooks/pre-commit $hook_path && sudo chown root:root $hook_path"
431+
fix_skip "Install pre-commit hook" "Requires root"
432+
fi
433+
fi
434+
echo ""
435+
272436
# ── Services ─────────────────────────────────────────────────────────────────
273437

274438
echo "Services"
@@ -428,8 +592,22 @@ echo " βœ… Pass: $pass"
428592
echo " ❌ Critical: $critical"
429593
echo " ⚠️ Warn: $warn"
430594
echo " ℹ️ Info: $info"
595+
596+
if [ "$FIX" -eq 1 ]; then
597+
echo ""
598+
echo " πŸ”§ Fixed: $fixed"
599+
echo " ⏭️ Skipped: $skipped"
600+
echo " ❌ Errors: $fix_errors"
601+
fi
431602
echo ""
432603

604+
if [ "$FIX" -eq 1 ] && [ "$fixed" -gt 0 ]; then
605+
echo "πŸ”§ $fixed fix(es) applied."
606+
[ "$skipped" -gt 0 ] && echo "⏭️ $skipped fix(es) skipped (require root or manual intervention)."
607+
[ "$fix_errors" -gt 0 ] && echo "❌ $fix_errors fix(es) failed."
608+
echo ""
609+
fi
610+
433611
if [ "$critical" -gt 0 ]; then
434612
echo "🚨 $critical critical finding(s) β€” fix immediately!"
435613
exit 2

0 commit comments

Comments
Β (0)