From eda5c35bbae1e09b065de75d879b7de04bdd014a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Tue, 18 Aug 2026 17:01:26 -0300 Subject: [PATCH] fix(xcatd): redact secrets in the commands.log response xcatd redacts the request in commands.log but appends the command response verbatim. A command whose output holds a secret writes it in clear text. Examples are tabdump passwd, gettab of a passwd column, and getcredentials. Collect the response into a per-command buffer. Set a sensitive flag when the command is getcredentials, an argument names a password, or the request was redacted. When the command finishes, replace the whole buffer if the flag is set or the buffer still holds password content, then append the buffer. A connection can carry more than one command, so the buffer is finalized at the next command's start and at the end of the connection. The buffer holds the full response, so a secret split across several callbacks is also redacted. A per-callback check cannot do this. The word-content check is a fallback. The request classification is the main signal. A secret with no password marker, such as the output of an xdsh cat of a shadow file, is a pre-existing leak of the root-only log. It is out of scope. Recovered from the lenovobuild branch. Reimplemented against master. --- xCAT-server/sbin/xcatd | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/xCAT-server/sbin/xcatd b/xCAT-server/sbin/xcatd index d1e27025d..0596d49a3 100755 --- a/xCAT-server/sbin/xcatd +++ b/xCAT-server/sbin/xcatd @@ -224,6 +224,8 @@ if ($tmp) { } my $cmdlog_alllog = "====================================================\n"; my $cmdlog_starttime=undef; +my $cmdlog_response_buffer = ""; +my $cmdlog_response_sensitive = 0; # ----used for command log end--------- my $enable_perf = $sitetab->getAttribs({'key'=>'enableperf'},'value'); @@ -2848,6 +2850,7 @@ sub service_connection { } # ----used for command log start---------- + cmdlog_finalize_response(); $cmdlog_starttime = time(); my ($sec, $min, $hour, $mday, $mon, $year) = localtime($cmdlog_starttime); $year += 1900; @@ -2878,8 +2881,11 @@ sub service_connection { } } # Replace passwords with 'x' + my $cmdlog_before_redact = $cmdlog_alllog; $cmdlog_alllog = xCAT::xcatd->redact_password($cmdlog_alllog); + my $cmdlog_req_redacted = ($cmdlog_alllog ne $cmdlog_before_redact) ? 1 : 0; $cmdlog_alllog .= "\n[Response]\n"; + $cmdlog_response_sensitive = cmdlog_response_is_sensitive($req, $cmdlog_req_redacted); # ----used for command log end---------- @@ -3064,6 +3070,7 @@ sub service_connection { } # ----used for command log start------- + cmdlog_finalize_response(); $cmdlog_alllog .= "[NumberNodes] $numofnodes \n"; my $reqhandletime = sprintf("%.3f", time()-$cmdlog_starttime); $cmdlog_alllog .= "[ElapsedTime] $reqhandletime s\n"; @@ -3136,6 +3143,7 @@ sub relay_fds { # Relays file descriptors from pipes to children to the SSL sock xCAT::MsgUtils->message("S", "Client abort requested"); # ----used for command log start------- + cmdlog_finalize_response(); $cmdlog_alllog .= "Client abort requested\n"; my $reqhandletime = sprintf("%.3f", time()-$cmdlog_starttime); $cmdlog_alllog .= "[ElapsedTime] $reqhandletime s\n"; @@ -3606,10 +3614,27 @@ sub cmdlog_collectlog() { } } } - $cmdlog_alllog .= $rsp_log; + $cmdlog_response_buffer .= $rsp_log; return 0; } +sub cmdlog_response_is_sensitive { + my ($req, $req_redacted) = @_; + return 1 if defined $req->{command}->[0] and $req->{command}->[0] eq 'getcredentials'; + return 1 if join(' ', @{ $req->{arg} || [] }) =~ /passw/i; + return 1 if $req_redacted; + return 0; +} + +sub cmdlog_finalize_response { + if ($cmdlog_response_sensitive or $cmdlog_response_buffer =~ /passw/i) { + $cmdlog_response_buffer = "*REDACTED*\n"; + } + $cmdlog_alllog .= $cmdlog_response_buffer; + $cmdlog_response_buffer = ""; + $cmdlog_response_sensitive = 0; +} + # -------------------------------------------------------------------------------- =head3 cmdlog_submitlog