@@ -38,6 +38,7 @@ pub struct UbiBuilder<'a> {
3838 is_musl : Option < bool > ,
3939 api_base_url : Option < & ' a str > ,
4040 forge : Option < ForgeType > ,
41+ min_age_days : Option < u32 > ,
4142}
4243
4344impl < ' a > UbiBuilder < ' a > {
@@ -60,8 +61,9 @@ impl<'a> UbiBuilder<'a> {
6061 self
6162 }
6263
63- /// Set the tag to download. By default the most recent release is downloaded. You cannot set
64- /// this with the `url` option.
64+ /// Set the tag to download. By default the most recent release is downloaded.
65+ ///
66+ /// You cannot set this with the `url` or `min_age_days` options.
6567 #[ must_use]
6668 pub fn tag ( mut self , tag : & ' a str ) -> Self {
6769 self . tag = Some ( tag) ;
@@ -73,7 +75,8 @@ impl<'a> UbiBuilder<'a> {
7375 /// to set a token env var except when downloading a release from a private repo when the URL is
7476 /// set.
7577 ///
76- /// You must set this or set `project`, but not both.
78+ /// You must set this or set `project`, but not both. You cannot set this with the `tag` or
79+ /// `min_age_days` options.
7780 #[ must_use]
7881 pub fn url ( mut self , url : & ' a str ) -> Self {
7982 self . url = Some ( url) ;
@@ -142,6 +145,17 @@ impl<'a> UbiBuilder<'a> {
142145 self
143146 }
144147
148+ /// Set the minimum age in days for releases. Only releases at least this many days old will be
149+ /// installed. This is useful for mitigating supply chain attacks. It's especially useful for
150+ /// projects that use GitHub's immutable releases feature.
151+ ///
152+ /// You cannot set this with the `tag` or `url` options.
153+ #[ must_use]
154+ pub fn min_age_days ( mut self , days : u32 ) -> Self {
155+ self . min_age_days = Some ( days) ;
156+ self
157+ }
158+
145159 /// Set a token to use for API requests. If this is not set, then `ubi` will look for a token in
146160 /// the appropriate env var:
147161 ///
@@ -233,6 +247,19 @@ impl<'a> UbiBuilder<'a> {
233247 "You cannot set rename_exe_to and enable extract_all"
234248 ) ) ;
235249 }
250+ if let Some ( days) = self . min_age_days {
251+ if self . url . is_some ( ) {
252+ return Err ( anyhow ! ( "You cannot set min_age_days with url" ) ) ;
253+ }
254+ if self . tag . is_some ( ) {
255+ return Err ( anyhow ! ( "You cannot set min_age_days with tag" ) ) ;
256+ }
257+ if days == 0 {
258+ return Err ( anyhow ! (
259+ "min_age_days must be a positive number (greater than 0)"
260+ ) ) ;
261+ }
262+ }
236263
237264 let platform = self . determine_platform ( ) ?;
238265
@@ -267,6 +294,7 @@ impl<'a> UbiBuilder<'a> {
267294 ) ,
268295 installer,
269296 reqwest_client ( ) ?,
297+ self . min_age_days ,
270298 ) )
271299 }
272300
@@ -492,4 +520,18 @@ mod test {
492520 ) {
493521 assert_eq ! ( super :: expect_exe_stem_name( exe, project_name) , expect) ;
494522 }
523+
524+ #[ test]
525+ fn min_age_days_zero_validation ( ) {
526+ let result = UbiBuilder :: new ( )
527+ . project ( "houseabsolute/ubi" )
528+ . min_age_days ( 0 )
529+ . build ( ) ;
530+
531+ assert ! ( result. is_err( ) ) ;
532+ assert ! ( result
533+ . unwrap_err( )
534+ . to_string( )
535+ . contains( "min_age_days must be a positive number" ) ) ;
536+ }
495537}
0 commit comments