rskit-process 0.1.0-alpha.1

Process and subprocess execution with timeout and signal handling
Documentation
use std::ffi::OsString;
use std::fmt;

use crate::ArgRedaction;

pub(super) struct RedactedArgs<'a> {
    args: &'a [OsString],
    redaction: &'a ArgRedaction,
}

impl<'a> RedactedArgs<'a> {
    pub(super) fn new(args: &'a [OsString], redaction: &'a ArgRedaction) -> Self {
        Self { args, redaction }
    }
}

impl fmt::Debug for RedactedArgs<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut list = f.debug_list();
        let mut redact_next = false;
        for arg in self.args {
            let arg = arg.to_string_lossy();
            if redact_next {
                if let Some((key, _value)) = arg.split_once('=')
                    && self.redaction.is_sensitive_arg_name(key)
                {
                    let redacted = format!("{key}=<redacted>");
                    list.entry(&redacted);
                    redact_next = false;
                    continue;
                }

                if self.redaction.is_sensitive_arg_name(&arg) {
                    list.entry(&arg);
                    continue;
                }

                list.entry(&"<redacted>");
                redact_next = false;
                continue;
            }

            if let Some((key, _value)) = arg.split_once('=')
                && self.redaction.is_sensitive_arg_name(key)
            {
                let redacted = format!("{key}=<redacted>");
                list.entry(&redacted);
                continue;
            }

            if self.redaction.is_sensitive_arg_name(&arg) {
                list.entry(&arg);
                redact_next = true;
            } else {
                list.entry(&arg);
            }
        }
        list.finish()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn redacted_args_masks_secret_values() {
        let args = vec![
            OsString::from("--token"),
            OsString::from("abc123"),
            OsString::from("--password=hunter2"),
            OsString::from("--auth-token=super-secret"),
            OsString::from("--author"),
            OsString::from("octocat"),
            OsString::from("--name"),
            OsString::from("visible"),
        ];

        let rendered = format!("{:?}", RedactedArgs::new(&args, &ArgRedaction::default()));

        assert!(rendered.contains("\"--token\""));
        assert!(rendered.contains("\"<redacted>\""));
        assert!(rendered.contains("\"--password=<redacted>\""));
        assert!(rendered.contains("\"--auth-token=<redacted>\""));
        assert!(rendered.contains("\"--author\""));
        assert!(rendered.contains("\"octocat\""));
        assert!(rendered.contains("\"visible\""));
        assert!(!rendered.contains("abc123"));
        assert!(!rendered.contains("hunter2"));
        assert!(!rendered.contains("super-secret"));
    }

    #[test]
    fn redacted_args_uses_custom_secret_names() {
        let args = vec![
            OsString::from("--license-key"),
            OsString::from("licensed"),
            OsString::from("--public-key"),
            OsString::from("visible"),
        ];
        let redaction = ArgRedaction::default().with_name("license-key");

        let rendered = format!("{:?}", RedactedArgs::new(&args, &redaction));

        assert!(rendered.contains("\"--license-key\""));
        assert!(rendered.contains("\"<redacted>\""));
        assert!(rendered.contains("\"--public-key\""));
        assert!(rendered.contains("\"visible\""));
        assert!(!rendered.contains("licensed"));
    }

    #[test]
    fn redacted_args_handles_adjacent_sensitive_flags() {
        let args = vec![
            OsString::from("--token"),
            OsString::from("--password"),
            OsString::from("hunter2"),
        ];

        let rendered = format!("{:?}", RedactedArgs::new(&args, &ArgRedaction::default()));

        assert!(rendered.contains("\"--token\""));
        assert!(rendered.contains("\"--password\""));
        assert!(rendered.contains("\"<redacted>\""));
        assert!(!rendered.contains("hunter2"));
    }

    #[test]
    fn redacted_args_preserves_sensitive_flag_when_next_arg_is_sensitive_assignment() {
        let args = vec![
            OsString::from("--token"),
            OsString::from("--password=hunter2"),
            OsString::from("visible"),
        ];

        let rendered = format!("{:?}", RedactedArgs::new(&args, &ArgRedaction::default()));

        assert!(rendered.contains("\"--token\""));
        assert!(rendered.contains("\"--password=<redacted>\""));
        assert!(rendered.contains("\"visible\""));
        assert!(!rendered.contains("hunter2"));
    }
}