@@ -1668,14 +1668,23 @@ fn is_sensitive_path(path: &std::path::Path) -> bool {
16681668 || file_name == ".git"
16691669 || file_name == ".ssh"
16701670 || file_name == ".aws"
1671+ || file_name == ".netrc"
1672+ || file_name == ".git-credentials"
1673+ || file_name == ".envrc"
16711674 {
16721675 return true ;
16731676 }
16741677 }
16751678 for component in path. components ( ) {
16761679 if let std:: path:: Component :: Normal ( os_str) = component {
16771680 if let Some ( s) = os_str. to_str ( ) {
1678- if s == ".git" || s == ".ssh" || s == ".aws" {
1681+ if s == ".git"
1682+ || s == ".ssh"
1683+ || s == ".aws"
1684+ || s == ".netrc"
1685+ || s == ".git-credentials"
1686+ || s == ".envrc"
1687+ {
16791688 return true ;
16801689 }
16811690 }
@@ -1746,6 +1755,9 @@ fn handle_state_capture(task: &str) -> Result<(), String> {
17461755 let mut evidence = Vec :: new ( ) ;
17471756 collect_files_recursive ( & current_dir, & current_dir, & mut evidence) ?;
17481757
1758+ // Sort evidence by id, then by file_path to guarantee stable/deterministic order
1759+ evidence. sort_by ( |a, b| a. id . cmp ( & b. id ) . then_with ( || a. file_path . cmp ( & b. file_path ) ) ) ;
1760+
17491761 let state = AgentState {
17501762 schema_version : "0.1" . to_string ( ) ,
17511763 task : task. to_string ( ) ,
@@ -2760,6 +2772,24 @@ mod tests {
27602772 . unwrap_err( )
27612773 . contains( "Accessing secrets or sensitive files" ) ) ;
27622774
2775+ let verify_netrc_res = handle_state_verify ( ".netrc" ) ;
2776+ assert ! ( verify_netrc_res. is_err( ) ) ;
2777+ assert ! ( verify_netrc_res
2778+ . unwrap_err( )
2779+ . contains( "Accessing secrets or sensitive files" ) ) ;
2780+
2781+ let verify_gitcreds_res = handle_state_verify ( ".git-credentials" ) ;
2782+ assert ! ( verify_gitcreds_res. is_err( ) ) ;
2783+ assert ! ( verify_gitcreds_res
2784+ . unwrap_err( )
2785+ . contains( "Accessing secrets or sensitive files" ) ) ;
2786+
2787+ let verify_envrc_res = handle_state_verify ( ".envrc" ) ;
2788+ assert ! ( verify_envrc_res. is_err( ) ) ;
2789+ assert ! ( verify_envrc_res
2790+ . unwrap_err( )
2791+ . contains( "Accessing secrets or sensitive files" ) ) ;
2792+
27632793 // 2. Test state report rejects secrets in its own path
27642794 let report_env_res = handle_state_report ( ".env" ) ;
27652795 assert ! ( report_env_res. is_err( ) ) ;
@@ -2825,7 +2855,7 @@ mod tests {
28252855
28262856 let captured_content = std:: fs:: read_to_string ( temp_state_file) . unwrap ( ) ;
28272857 let state: AgentState = serde_json:: from_str ( & captured_content) . unwrap ( ) ;
2828- for entry in state. evidence {
2858+ for entry in & state. evidence {
28292859 let path = std:: path:: Path :: new ( & entry. file_path ) ;
28302860 let name = path. file_name ( ) . and_then ( |n| n. to_str ( ) ) . unwrap_or ( "" ) ;
28312861 assert_ne ! ( name, ".env" ) ;
@@ -2835,6 +2865,32 @@ mod tests {
28352865 assert ! ( !entry. file_path. contains( ".git" ) ) ;
28362866 assert ! ( !entry. file_path. contains( ".ssh" ) ) ;
28372867 assert ! ( !entry. file_path. contains( ".aws" ) ) ;
2868+ assert_ne ! ( name, ".netrc" ) ;
2869+ assert_ne ! ( name, ".git-credentials" ) ;
2870+ assert_ne ! ( name, ".envrc" ) ;
2871+ }
2872+
2873+ // Verify evidence entries are sorted deterministically by id, then file_path
2874+ let mut prev_id = String :: new ( ) ;
2875+ let mut prev_file_path = String :: new ( ) ;
2876+ for entry in & state. evidence {
2877+ if entry. id == prev_id {
2878+ assert ! (
2879+ entry. file_path >= prev_file_path,
2880+ "Paths out of order: '{}' vs '{}'" ,
2881+ prev_file_path,
2882+ entry. file_path
2883+ ) ;
2884+ } else {
2885+ assert ! (
2886+ entry. id > prev_id,
2887+ "IDs out of order: '{}' vs '{}'" ,
2888+ prev_id,
2889+ entry. id
2890+ ) ;
2891+ }
2892+ prev_id = entry. id . clone ( ) ;
2893+ prev_file_path = entry. file_path . clone ( ) ;
28382894 }
28392895
28402896 // Clean up
0 commit comments