1+ //! Dependency pinning checks for security-alert remediations.
2+
3+ use std:: fs;
4+
5+ fn cargo_lock ( ) -> String {
6+ fs:: read_to_string ( format ! ( "{}/Cargo.lock" , env!( "CARGO_MANIFEST_DIR" ) ) )
7+ . expect ( "Cargo.lock should be readable" )
8+ }
9+
10+ fn package_versions < ' a > ( lockfile : & ' a str , package_name : & str ) -> Vec < & ' a str > {
11+ let versions: Vec < _ > = lockfile
12+ . split ( "[[package]]" )
13+ . filter_map ( |package| {
14+ let mut name = None ;
15+ let mut version = None ;
16+
17+ for line in package. lines ( ) {
18+ if let Some ( value) = line. strip_prefix ( "name = \" " ) {
19+ name = value. strip_suffix ( '"' ) ;
20+ }
21+
22+ if let Some ( value) = line. strip_prefix ( "version = \" " ) {
23+ version = value. strip_suffix ( '"' ) ;
24+ }
25+ }
26+
27+ ( name == Some ( package_name) ) . then_some ( version) . flatten ( )
28+ } )
29+ . collect ( ) ;
30+
31+ assert ! (
32+ !versions. is_empty( ) ,
33+ "{package_name} should be present in Cargo.lock"
34+ ) ;
35+
36+ versions
37+ }
38+
39+ fn package_version < ' a > ( lockfile : & ' a str , package_name : & str ) -> & ' a str {
40+ let versions = package_versions ( lockfile, package_name) ;
41+
42+ assert_eq ! (
43+ versions. len( ) ,
44+ 1 ,
45+ "{package_name} should have one locked version; found {versions:?}"
46+ ) ;
47+
48+ versions[ 0 ]
49+ }
50+
51+ fn version_tuple ( version : & str ) -> ( u64 , u64 , u64 ) {
52+ let mut parts = version. split ( '.' ) . map ( |part| {
53+ part. parse :: < u64 > ( )
54+ . unwrap_or_else ( |_| panic ! ( "{version} should contain numeric version parts" ) )
55+ } ) ;
56+
57+ let major = parts
58+ . next ( )
59+ . unwrap_or_else ( || panic ! ( "{version} should include a major version" ) ) ;
60+ let minor = parts
61+ . next ( )
62+ . unwrap_or_else ( || panic ! ( "{version} should include a minor version" ) ) ;
63+ let patch = parts
64+ . next ( )
65+ . unwrap_or_else ( || panic ! ( "{version} should include a patch version" ) ) ;
66+
67+ assert ! (
68+ parts. next( ) . is_none( ) ,
69+ "{version} should only include major.minor.patch"
70+ ) ;
71+
72+ ( major, minor, patch)
73+ }
74+
75+ fn assert_package_at_least ( lockfile : & str , package_name : & str , minimum : & str ) {
76+ let actual = package_version ( lockfile, package_name) ;
77+
78+ assert ! (
79+ version_tuple( actual) >= version_tuple( minimum) ,
80+ "{package_name} should stay on at least {minimum}; found {actual}"
81+ ) ;
82+ }
83+
84+ #[ test]
85+ fn cargo_lock_uses_patched_openssl ( ) {
86+ let lockfile = cargo_lock ( ) ;
87+
88+ assert_package_at_least ( & lockfile, "openssl" , "0.10.79" ) ;
89+ }
90+
91+ #[ test]
92+ fn cargo_lock_uses_patched_hickory_proto ( ) {
93+ let lockfile = cargo_lock ( ) ;
94+
95+ assert_package_at_least ( & lockfile, "hickory-proto" , "0.26.1" ) ;
96+ }
97+
98+ #[ test]
99+ fn cargo_lock_uses_patched_lz4_flex ( ) {
100+ let lockfile = cargo_lock ( ) ;
101+ let versions = package_versions ( & lockfile, "lz4_flex" ) ;
102+
103+ assert ! (
104+ versions. iter( ) . all( |version| {
105+ let parsed = version_tuple( version) ;
106+ parsed > version_tuple( "0.11.5" ) && parsed != version_tuple( "0.12.0" )
107+ } ) ,
108+ "lz4_flex should stay outside vulnerable ranges <=0.11.5 and 0.12.0; found {versions:?}"
109+ ) ;
110+ }
0 commit comments