From bfb3edbd4a5ffec453ad328df01155b4d1b23b0d Mon Sep 17 00:00:00 2001 From: anti Date: Fri, 17 Apr 2026 22:36:06 -0400 Subject: [PATCH] fix(ssh-capture): add ss-only attribution fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fuser and /proc fd walks race scp/wget/sftp — by close_write the writer has already closed the fd, so pid-chain attribution always resolved to unknown for non-interactive drops. Fall back to the ss snapshot: one established session → ss-only, multiple → ss-ambiguous (still record src_ip from the first, analysts cross-check concurrent_sessions). --- templates/ssh/capture.sh | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/templates/ssh/capture.sh b/templates/ssh/capture.sh index cf92d49..adb5dae 100755 --- a/templates/ssh/capture.sh +++ b/templates/ssh/capture.sh @@ -146,6 +146,31 @@ _capture_one() { attribution="pid-chain" fi fi + # Fallback 1: ss-only. scp/wget/sftp close their fd before close_write + # fires, so fuser/proc-fd walks miss them. If there's exactly one live + # sshd session, attribute to it. With multiple, attribute to the first + # but tag ambiguous so analysts know to cross-check concurrent_sessions. + if [ "$attribution" = "unknown" ]; then + local ss_len + ss_len="$(echo "$ss_json" | jq 'length')" + if [ "$ss_len" -ge 1 ]; then + src_ip="$(echo "$ss_json" | jq -r '.[0].src_ip')" + src_port="$(echo "$ss_json" | jq -r '.[0].src_port')" + ssh_pid="$(echo "$ss_json" | jq -r '.[0].pid // empty')" + if [ -n "${ssh_pid:-}" ] && [ -d "/proc/$ssh_pid" ]; then + local ssh_cmd + ssh_cmd="$(tr '\0' ' ' < "/proc/$ssh_pid/cmdline" 2>/dev/null)" + ssh_user="$(echo "$ssh_cmd" | sed -nE 's/^sshd: ([^@]+)@.*/\1/p')" + fi + if [ "$ss_len" -eq 1 ]; then + attribution="ss-only" + else + attribution="ss-ambiguous" + fi + fi + fi + + # Fallback 2: utmp. Weakest signal; often empty in containers. if [ "$attribution" = "unknown" ] && [ "$(echo "$who_json" | jq 'length')" -gt 0 ]; then src_ip="$(echo "$who_json" | jq -r '.[0].src_ip')" attribution="utmp-only"