@@ -1429,12 +1429,64 @@ pub fn sha256_hex(data: &[u8]) -> String {
14291429
14301430fn handle_verify ( file_path : & str , parent : Option < & str > ) -> Result < ( ) , String > {
14311431 let path = std:: path:: Path :: new ( file_path) ;
1432+
1433+ // 1. Rejects absolute paths
1434+ if path. is_absolute ( ) {
1435+ return Err (
1436+ "Security Policy Violation: Absolute paths are forbidden in verify command."
1437+ . to_string ( ) ,
1438+ ) ;
1439+ }
1440+
1441+ // 2. Reject directory traversal escaping the repository boundary
1442+ let current_dir = std:: env:: current_dir ( )
1443+ . map_err ( |e| format ! ( "failed to get current working directory: {e}" ) ) ?;
1444+
1445+ let canonical_current_dir = current_dir
1446+ . canonicalize ( )
1447+ . map_err ( |e| format ! ( "failed to canonicalize current directory: {e}" ) ) ?;
1448+
14321449 if !path. exists ( ) {
14331450 return Err ( format ! ( "File '{}' does not exist." , file_path) ) ;
14341451 }
14351452
1436- let content = std:: fs:: read ( path)
1437- . map_err ( |e| format ! ( "failed to read file '{}': {e}" , path. display( ) ) ) ?;
1453+ let canonical_path = path
1454+ . canonicalize ( )
1455+ . map_err ( |e| format ! ( "failed to canonicalize file path '{}': {e}" , file_path) ) ?;
1456+
1457+ if !canonical_path. starts_with ( & canonical_current_dir) {
1458+ return Err (
1459+ "Security Policy Violation: Target path escapes the repository boundary." . to_string ( ) ,
1460+ ) ;
1461+ }
1462+
1463+ // 3. Reject forbidden files and directories: .env, .env.*, *.key, *.pem, .git/, .ssh/, .aws/, id_rsa, id_ed25519
1464+ let file_name = path. file_name ( ) . and_then ( |n| n. to_str ( ) ) . unwrap_or ( "" ) ;
1465+ if file_name == ".env"
1466+ || file_name. starts_with ( ".env." )
1467+ || file_name. ends_with ( ".key" )
1468+ || file_name. ends_with ( ".pem" )
1469+ || file_name == "id_rsa"
1470+ || file_name == "id_ed25519"
1471+ {
1472+ return Err (
1473+ "Security Policy Violation: Accessing secrets or configuration files is forbidden."
1474+ . to_string ( ) ,
1475+ ) ;
1476+ }
1477+
1478+ for component in canonical_path. components ( ) {
1479+ if let std:: path:: Component :: Normal ( os_str) = component {
1480+ if let Some ( s) = os_str. to_str ( ) {
1481+ if s == ".git" || s == ".ssh" || s == ".aws" {
1482+ return Err ( "Security Policy Violation: Accessing sensitive directories (.git, .ssh, .aws) is forbidden." . to_string ( ) ) ;
1483+ }
1484+ }
1485+ }
1486+ }
1487+
1488+ let content = std:: fs:: read ( & canonical_path)
1489+ . map_err ( |e| format ! ( "failed to read file '{}': {e}" , canonical_path. display( ) ) ) ?;
14381490
14391491 let computed_hash = sha256_hex ( & content) ;
14401492
@@ -2029,36 +2081,88 @@ mod tests {
20292081 ) ) ;
20302082 }
20312083
2084+ #[ test]
2085+ fn test_sha256_standard_vectors ( ) {
2086+ use super :: sha256_hex;
2087+ assert_eq ! (
2088+ sha256_hex( b"" ) ,
2089+ "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
2090+ ) ;
2091+ assert_eq ! (
2092+ sha256_hex( b"abc" ) ,
2093+ "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
2094+ ) ;
2095+ assert_eq ! (
2096+ sha256_hex( b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" ) ,
2097+ "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"
2098+ ) ;
2099+ }
2100+
20322101 #[ test]
20332102 fn test_provenance_verification ( ) {
20342103 use super :: handle_verify;
2035- let temp_dir = std :: env :: temp_dir ( ) ;
2036- let test_file_path = temp_dir . join ( "test_provenance_artifact .txt") ;
2037- let manifest_path = temp_dir . join ( "test_provenance_artifact .txt.provenance.json") ;
2104+
2105+ let test_file_path = "test_provenance_artifact_temp .txt";
2106+ let manifest_path = "test_provenance_artifact_temp .txt.provenance.json";
20382107
20392108 // Clean up any leftovers
2040- let _ = std:: fs:: remove_file ( & test_file_path) ;
2041- let _ = std:: fs:: remove_file ( & manifest_path) ;
2109+ let _ = std:: fs:: remove_file ( test_file_path) ;
2110+ let _ = std:: fs:: remove_file ( manifest_path) ;
20422111
20432112 // 1. Write file
2044- std:: fs:: write ( & test_file_path, "provenance test contents" ) . unwrap ( ) ;
2113+ std:: fs:: write ( test_file_path, "provenance test contents" ) . unwrap ( ) ;
20452114
20462115 // 2. Generate manifest
2047- let gen_res = handle_verify ( & test_file_path. to_string_lossy ( ) , Some ( "parent_task_123" ) ) ;
2116+ let gen_res = handle_verify ( test_file_path, Some ( "parent_task_123" ) ) ;
20482117 assert ! ( gen_res. is_ok( ) ) ;
2049- assert ! ( manifest_path. exists( ) ) ;
2118+ assert ! ( std :: path :: Path :: new ( manifest_path) . exists( ) ) ;
20502119
20512120 // 3. Verify manifest
2052- let verify_res = handle_verify ( & test_file_path. to_string_lossy ( ) , None ) ;
2121+ let verify_res = handle_verify ( test_file_path, None ) ;
20532122 assert ! ( verify_res. is_ok( ) ) ;
20542123
20552124 // 4. Modify file and verify failure
2056- std:: fs:: write ( & test_file_path, "provenance test contents MUTATED" ) . unwrap ( ) ;
2057- let verify_fail_res = handle_verify ( & test_file_path. to_string_lossy ( ) , None ) ;
2125+ std:: fs:: write ( test_file_path, "provenance test contents MUTATED" ) . unwrap ( ) ;
2126+ let verify_fail_res = handle_verify ( test_file_path, None ) ;
20582127 assert ! ( verify_fail_res. is_err( ) ) ;
20592128
2129+ // 5. Test path safety constraints
2130+ // Rejects absolute path
2131+ let test_abs_path = if cfg ! ( windows) {
2132+ "C:\\ some\\ abs\\ path"
2133+ } else {
2134+ "/some/abs/path"
2135+ } ;
2136+ let abs_res = handle_verify ( test_abs_path, None ) ;
2137+ assert ! ( abs_res. is_err( ) ) ;
2138+ assert ! ( abs_res
2139+ . unwrap_err( )
2140+ . contains( "Absolute paths are forbidden" ) ) ;
2141+
2142+ // Rejects secret files (.env)
2143+ // Note: we don't write it, we just check validation rejection logic
2144+ // But since verify check requires file to exist, let's check .env.example or create .env.temp.key
2145+ std:: fs:: write ( "test_prov.key" , "dummy" ) . unwrap ( ) ;
2146+ let key_res = handle_verify ( "test_prov.key" , None ) ;
2147+ assert ! ( key_res. is_err( ) ) ;
2148+ assert ! ( key_res
2149+ . unwrap_err( )
2150+ . contains( "Accessing secrets or configuration files is forbidden" ) ) ;
2151+ let _ = std:: fs:: remove_file ( "test_prov.key" ) ;
2152+
2153+ // Rejects sensitive directory (.git/config)
2154+ let git_res = handle_verify ( ".git/config" , None ) ;
2155+ assert ! ( git_res. is_err( ) ) ;
2156+ assert ! ( git_res
2157+ . unwrap_err( )
2158+ . contains( "Accessing sensitive directories" ) ) ;
2159+
2160+ // Rejects directory traversal
2161+ let traverse_res = handle_verify ( "../outside.txt" , None ) ;
2162+ assert ! ( traverse_res. is_err( ) ) ;
2163+
20602164 // Clean up
2061- let _ = std:: fs:: remove_file ( & test_file_path) ;
2062- let _ = std:: fs:: remove_file ( & manifest_path) ;
2165+ let _ = std:: fs:: remove_file ( test_file_path) ;
2166+ let _ = std:: fs:: remove_file ( manifest_path) ;
20632167 }
20642168}
0 commit comments