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
1012set -euo pipefail
1113
1214HORNET_HOME=" ${HORNET_HOME:-/ home/ hornet_agent} "
1315DEEP=0
16+ FIX=0
1417for 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
1822done
1923
2024# Counters
2125critical=0
2226warn=0
2327info=0
2428pass=0
29+ fixed=0
30+ skipped=0
31+ fix_errors=0
2532
2633finding () {
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+
4480echo " "
4581echo " π Hornet Security Audit"
4682echo " ========================"
83+ if [ " $FIX " -eq 1 ]; then
84+ echo " Mode: auto-fix enabled"
85+ fi
4786echo " "
4887
4988# ββ Docker group βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
@@ -52,6 +91,7 @@ echo "Docker Access"
5291if 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"
5595else
5696 ok " hornet_agent not in docker group"
5797fi
@@ -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"
102146if [ -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
113162if [ -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
121175fi
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
122192echo " "
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
220301else
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"
223305fi
224306echo " "
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
259342fi
260343
@@ -269,6 +352,87 @@ else
269352fi
270353echo " "
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
274438echo " Services"
@@ -428,8 +592,22 @@ echo " β
Pass: $pass"
428592echo " β Critical: $critical "
429593echo " β οΈ Warn: $warn "
430594echo " βΉοΈ 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
431602echo " "
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+
433611if [ " $critical " -gt 0 ]; then
434612 echo " π¨ $critical critical finding(s) β fix immediately!"
435613 exit 2
0 commit comments