@@ -21,7 +21,7 @@ pub struct ResourceLimits {
2121}
2222
2323/// Configuration for an agent
24- #[ derive( Debug , Clone ) ]
24+ #[ derive( Clone ) ]
2525pub struct AgentConfig {
2626 /// Agent identifier
2727 pub agent_id : String ,
@@ -44,6 +44,43 @@ pub struct AgentConfig {
4444 pub supports_stdin : bool ,
4545}
4646
47+ /// Returns `true` if an env-var key name indicates a sensitive credential.
48+ fn is_sensitive_key ( key : & str ) -> bool {
49+ let upper = key. to_uppercase ( ) ;
50+ upper. contains ( "TOKEN" )
51+ || upper. contains ( "KEY" )
52+ || upper. contains ( "SECRET" )
53+ || upper. contains ( "PASSWORD" )
54+ }
55+
56+ impl std:: fmt:: Debug for AgentConfig {
57+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
58+ let redacted: std:: collections:: HashMap < & str , & str > = self
59+ . env_vars
60+ . iter ( )
61+ . map ( |( k, v) | {
62+ let val: & str = if is_sensitive_key ( k) {
63+ "***REDACTED***"
64+ } else {
65+ v. as_str ( )
66+ } ;
67+ ( k. as_str ( ) , val)
68+ } )
69+ . collect ( ) ;
70+ f. debug_struct ( "AgentConfig" )
71+ . field ( "agent_id" , & self . agent_id )
72+ . field ( "cli_command" , & self . cli_command )
73+ . field ( "args" , & self . args )
74+ . field ( "working_dir" , & self . working_dir )
75+ . field ( "env_vars" , & redacted)
76+ . field ( "required_api_keys" , & self . required_api_keys )
77+ . field ( "resource_limits" , & self . resource_limits )
78+ . field ( "use_stdin" , & self . use_stdin )
79+ . field ( "supports_stdin" , & self . supports_stdin )
80+ . finish ( )
81+ }
82+ }
83+
4784impl AgentConfig {
4885 /// Create agent config from a provider
4986 pub fn from_provider ( provider : & Provider ) -> Result < Self , ValidationError > {
@@ -562,6 +599,78 @@ mod tests {
562599 ) ;
563600 }
564601
602+ #[ test]
603+ fn test_is_sensitive_key_detects_known_patterns ( ) {
604+ assert ! ( is_sensitive_key( "ANTHROPIC_API_KEY" ) ) ;
605+ assert ! ( is_sensitive_key( "OPENAI_API_KEY" ) ) ;
606+ assert ! ( is_sensitive_key( "GITHUB_TOKEN" ) ) ;
607+ assert ! ( is_sensitive_key( "DB_PASSWORD" ) ) ;
608+ assert ! ( is_sensitive_key( "APP_SECRET" ) ) ;
609+ assert ! ( is_sensitive_key( "api_key" ) ) ;
610+ assert ! ( is_sensitive_key( "auth_token" ) ) ;
611+ }
612+
613+ #[ test]
614+ fn test_is_sensitive_key_allows_safe_keys ( ) {
615+ assert ! ( !is_sensitive_key( "HOME" ) ) ;
616+ assert ! ( !is_sensitive_key( "PATH" ) ) ;
617+ assert ! ( !is_sensitive_key( "LOG_LEVEL" ) ) ;
618+ assert ! ( !is_sensitive_key( "RUST_LOG" ) ) ;
619+ assert ! ( !is_sensitive_key( "AGENT_ID" ) ) ;
620+ }
621+
622+ #[ test]
623+ fn test_debug_redacts_sensitive_env_vars ( ) {
624+ let mut env_vars = HashMap :: new ( ) ;
625+ env_vars. insert (
626+ "ANTHROPIC_API_KEY" . to_string ( ) ,
627+ "sk-ant-real-secret" . to_string ( ) ,
628+ ) ;
629+ env_vars. insert ( "GITHUB_TOKEN" . to_string ( ) , "ghp_real_token" . to_string ( ) ) ;
630+ env_vars. insert ( "LOG_LEVEL" . to_string ( ) , "debug" . to_string ( ) ) ;
631+ env_vars. insert ( "HOME" . to_string ( ) , "/home/agent" . to_string ( ) ) ;
632+
633+ let config = AgentConfig {
634+ agent_id : "test-agent" . to_string ( ) ,
635+ cli_command : "claude" . to_string ( ) ,
636+ args : vec ! [ ] ,
637+ working_dir : None ,
638+ env_vars,
639+ required_api_keys : vec ! [ ] ,
640+ resource_limits : ResourceLimits :: default ( ) ,
641+ use_stdin : false ,
642+ supports_stdin : true ,
643+ } ;
644+
645+ let debug_output = format ! ( "{:?}" , config) ;
646+
647+ // Sensitive values must not appear
648+ assert ! (
649+ !debug_output. contains( "sk-ant-real-secret" ) ,
650+ "API key value must be redacted"
651+ ) ;
652+ assert ! (
653+ !debug_output. contains( "ghp_real_token" ) ,
654+ "Token value must be redacted"
655+ ) ;
656+
657+ // Redaction marker must appear
658+ assert ! (
659+ debug_output. contains( "***REDACTED***" ) ,
660+ "Redaction marker must appear for sensitive keys"
661+ ) ;
662+
663+ // Non-sensitive values must be visible
664+ assert ! (
665+ debug_output. contains( "debug" ) ,
666+ "Non-sensitive env var value must be visible"
667+ ) ;
668+ assert ! (
669+ debug_output. contains( "/home/agent" ) ,
670+ "Non-sensitive env var value must be visible"
671+ ) ;
672+ }
673+
565674 #[ test]
566675 fn test_model_args_pi_rust_bare ( ) {
567676 let args = AgentConfig :: model_args ( "pi-rust" , "glm-5.1" ) ;
@@ -594,4 +703,22 @@ mod tests {
594703 let keys = AgentConfig :: infer_api_keys ( "/home/alex/.local/bin/pi-rust" ) ;
595704 assert ! ( keys. is_empty( ) ) ;
596705 }
706+
707+ #[ test]
708+ fn test_debug_empty_env_vars_shows_nothing_redacted ( ) {
709+ let config = AgentConfig {
710+ agent_id : "test" . to_string ( ) ,
711+ cli_command : "claude" . to_string ( ) ,
712+ args : vec ! [ ] ,
713+ working_dir : None ,
714+ env_vars : HashMap :: new ( ) ,
715+ required_api_keys : vec ! [ ] ,
716+ resource_limits : ResourceLimits :: default ( ) ,
717+ use_stdin : false ,
718+ supports_stdin : true ,
719+ } ;
720+ let debug_output = format ! ( "{:?}" , config) ;
721+ assert ! ( !debug_output. contains( "***REDACTED***" ) ) ;
722+ assert ! ( debug_output. contains( "AgentConfig" ) ) ;
723+ }
597724}
0 commit comments