hive-priv: redact password-bearing lines before logging forgejo admin output

This commit is contained in:
damocles 2026-07-26 15:02:29 +02:00 committed by mara
commit 7a3614552b

View file

@ -1366,6 +1366,21 @@ fn validate_forge_admin_arg(arg: &str) -> Result<()> {
Ok(())
}
/// Redact a line before it hits the (root-readable, but still
/// unnecessarily exposed) host journal. `forgejo admin user create
/// --random-password` prints the generated password straight to stdout —
/// case-insensitive substring match on "password" is deliberately broad
/// (not pinned to forgejo's exact wording, which can change across
/// versions) so any password-bearing line gets caught rather than relying
/// on a phrase that could silently drift out of sync.
fn redact_password_line(line: &str) -> std::borrow::Cow<'_, str> {
if line.to_ascii_lowercase().contains("password") {
std::borrow::Cow::Borrowed("[redacted: line mentions a password]")
} else {
std::borrow::Cow::Borrowed(line)
}
}
/// Run `forgejo admin <args>` inside the `hive-forge` container as the
/// `forgejo` unix user. Requires root (for nsenter into the container's
/// namespaces). Returns `(stdout, stderr)`.
@ -1394,10 +1409,10 @@ async fn run_forge_admin(args: &[String]) -> Result<(String, String)> {
let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
let stderr = String::from_utf8_lossy(&out.stderr).into_owned();
for line in stdout.lines() {
tracing::info!(target: "forgejo-admin", "{line}");
tracing::info!(target: "forgejo-admin", "{}", redact_password_line(line));
}
for line in stderr.lines() {
tracing::warn!(target: "forgejo-admin", "{line}");
tracing::warn!(target: "forgejo-admin", "{}", redact_password_line(line));
}
if !out.status.success() {
bail!(
@ -1986,10 +2001,26 @@ async fn sync_agent_tmpfiles(agents: &[String]) -> Result<(String, String)> {
#[cfg(test)]
mod tests {
use super::write_state_file_nofollow;
use super::{redact_password_line, write_state_file_nofollow};
use std::path::PathBuf;
use std::sync::atomic::{AtomicU32, Ordering};
#[test]
fn redacts_lines_mentioning_password_case_insensitively() {
assert_eq!(
redact_password_line("New password: hunter2"),
"[redacted: line mentions a password]"
);
assert_eq!(
redact_password_line("PASSWORD=hunter2"),
"[redacted: line mentions a password]"
);
assert_eq!(
redact_password_line("User \"foo\" was successfully created."),
"User \"foo\" was successfully created."
);
}
/// Unique scratch dir per test, no external tempfile dep.
fn scratch() -> PathBuf {
static CTR: AtomicU32 = AtomicU32::new(0);