@@ -199,6 +199,13 @@ pub struct PackageOptions {
199199 /// Direct URL to download the package from (makes it a "local" package).
200200 pub url : Option < String > ,
201201
202+ /// Expected BLAKE3 checksum (hex) of the downloaded artifact, for `url`/`github`/
203+ /// `gitlab` packages. When set, soar verifies the download against it and refuses to
204+ /// install on mismatch.
205+ /// Without it, these user-declared sources install on implicit trust.
206+ /// Has no effect on registry packages, which already ship their own checksum.
207+ pub bsum : Option < String > ,
208+
202209 /// GitHub repository in "owner/repo" format for installing from releases.
203210 /// When set, soar fetches the latest release and downloads the matching asset.
204211 pub github : Option < String > ,
@@ -301,6 +308,7 @@ pub struct ResolvedPackage {
301308 pub version : Option < String > ,
302309 pub repo : Option < String > ,
303310 pub url : Option < String > ,
311+ pub bsum : Option < String > ,
304312 pub github : Option < String > ,
305313 pub gitlab : Option < String > ,
306314 pub asset_pattern : Option < String > ,
@@ -340,6 +348,7 @@ impl PackageSpec {
340348 version,
341349 repo : None ,
342350 url : None ,
351+ bsum : None ,
343352 github : None ,
344353 gitlab : None ,
345354 asset_pattern : None ,
@@ -376,6 +385,7 @@ impl PackageSpec {
376385 version,
377386 repo : opts. repo . clone ( ) ,
378387 url : opts. url . clone ( ) ,
388+ bsum : opts. bsum . clone ( ) ,
379389 github : opts. github . clone ( ) ,
380390 gitlab : opts. gitlab . clone ( ) ,
381391 asset_pattern : opts. asset_pattern . clone ( ) ,
@@ -663,6 +673,34 @@ special = { profile = "isolated" }
663673 assert_eq ! ( resolved[ 0 ] . profile, Some ( "isolated" . to_string( ) ) ) ;
664674 }
665675
676+ #[ test]
677+ fn test_bsum_pin_resolved ( ) {
678+ let toml_str = r#"
679+ [packages]
680+ myapp = { url = "https://example.com/myapp-1.0.AppImage", bsum = "abc123" }
681+ nopin = { url = "https://example.com/nopin-1.0.AppImage" }
682+ "# ;
683+ let config: PackagesConfig = toml:: from_str ( toml_str) . unwrap ( ) ;
684+ let resolved = config. resolved_packages ( ) ;
685+
686+ let myapp = resolved. iter ( ) . find ( |p| p. name == "myapp" ) . unwrap ( ) ;
687+ let nopin = resolved. iter ( ) . find ( |p| p. name == "nopin" ) . unwrap ( ) ;
688+
689+ assert_eq ! ( myapp. bsum, Some ( "abc123" . to_string( ) ) ) ;
690+ assert_eq ! ( nopin. bsum, None ) ;
691+ }
692+
693+ #[ test]
694+ fn test_bsum_none_for_simple_spec ( ) {
695+ let toml_str = r#"
696+ [packages]
697+ curl = "*"
698+ "# ;
699+ let config: PackagesConfig = toml:: from_str ( toml_str) . unwrap ( ) ;
700+ let resolved = config. resolved_packages ( ) ;
701+ assert_eq ! ( resolved[ 0 ] . bsum, None ) ;
702+ }
703+
666704 #[ test]
667705 fn test_portable_config ( ) {
668706 let toml_str = r#"
0 commit comments