@@ -1027,6 +1027,7 @@ pub struct BashOptions {
10271027pub struct SnapshotOptions {
10281028 pub exclude_filesystem : Option < bool > ,
10291029 pub exclude_functions : Option < bool > ,
1030+ pub hmac_key : Option < napi:: bindgen_prelude:: Buffer > ,
10301031}
10311032
10321033fn default_opts ( ) -> BashOptions {
@@ -1055,10 +1056,9 @@ fn default_opts() -> BashOptions {
10551056 }
10561057}
10571058
1058- fn to_snapshot_options ( options : Option < SnapshotOptions > ) -> RustSnapshotOptions {
1059+ fn to_snapshot_options ( options : Option < & SnapshotOptions > ) -> RustSnapshotOptions {
10591060 RustSnapshotOptions {
10601061 exclude_filesystem : options
1061- . as_ref ( )
10621062 . and_then ( |options| options. exclude_filesystem )
10631063 . unwrap_or ( false ) ,
10641064 exclude_functions : options
@@ -1067,6 +1067,24 @@ fn to_snapshot_options(options: Option<SnapshotOptions>) -> RustSnapshotOptions
10671067 }
10681068}
10691069
1070+ fn snapshot_hmac_key ( options : Option < & SnapshotOptions > ) -> Option < & [ u8 ] > {
1071+ options. and_then ( |options| options. hmac_key . as_deref ( ) )
1072+ }
1073+
1074+ fn require_snapshot_hmac_key < ' a > ( options : Option < & ' a SnapshotOptions > ) -> napi:: Result < & ' a [ u8 ] > {
1075+ let Some ( key) = snapshot_hmac_key ( options) else {
1076+ return Err ( napi:: Error :: from_reason (
1077+ "BashTool snapshots require SnapshotOptions.hmacKey for HMAC authentication" ,
1078+ ) ) ;
1079+ } ;
1080+ if key. is_empty ( ) {
1081+ return Err ( napi:: Error :: from_reason (
1082+ "BashTool snapshots require a non-empty SnapshotOptions.hmacKey" ,
1083+ ) ) ;
1084+ }
1085+ Ok ( key)
1086+ }
1087+
10701088// ============================================================================
10711089// SharedState — all mutable state behind Arc to avoid raw pointer issues
10721090// ============================================================================
@@ -1389,23 +1407,36 @@ impl Bash {
13891407 & self ,
13901408 options : Option < SnapshotOptions > ,
13911409 ) -> napi:: Result < napi:: bindgen_prelude:: Buffer > {
1392- let options = to_snapshot_options ( options) ;
1410+ let snapshot_options = to_snapshot_options ( options. as_ref ( ) ) ;
1411+ let hmac_key = snapshot_hmac_key ( options. as_ref ( ) ) . map ( Vec :: from) ;
13931412 block_on_with ( & self . state , |s| async move {
13941413 let bash = s. inner . lock ( ) . await ;
1395- let bytes = bash
1396- . snapshot_with_options ( options)
1397- . map_err ( |e| napi:: Error :: from_reason ( e. to_string ( ) ) ) ?;
1414+ let bytes = if let Some ( key) = hmac_key. as_deref ( ) {
1415+ bash. snapshot_to_bytes_keyed_with_options ( key, snapshot_options)
1416+ } else {
1417+ bash. snapshot_with_options ( snapshot_options)
1418+ }
1419+ . map_err ( |e| napi:: Error :: from_reason ( e. to_string ( ) ) ) ?;
13981420 Ok ( napi:: bindgen_prelude:: Buffer :: from ( bytes) )
13991421 } )
14001422 }
14011423
14021424 /// Restore interpreter state from a snapshot previously created with `snapshot()`.
14031425 #[ napi]
1404- pub fn restore_snapshot ( & self , data : napi:: bindgen_prelude:: Buffer ) -> napi:: Result < ( ) > {
1426+ pub fn restore_snapshot (
1427+ & self ,
1428+ data : napi:: bindgen_prelude:: Buffer ,
1429+ options : Option < SnapshotOptions > ,
1430+ ) -> napi:: Result < ( ) > {
1431+ let hmac_key = snapshot_hmac_key ( options. as_ref ( ) ) . map ( Vec :: from) ;
14051432 block_on_with ( & self . state , |s| async move {
14061433 let mut bash = s. inner . lock ( ) . await ;
1407- bash. restore_snapshot ( & data)
1408- . map_err ( |e| napi:: Error :: from_reason ( e. to_string ( ) ) )
1434+ if let Some ( key) = hmac_key. as_deref ( ) {
1435+ bash. restore_snapshot_keyed ( & data, key)
1436+ } else {
1437+ bash. restore_snapshot ( & data)
1438+ }
1439+ . map_err ( |e| napi:: Error :: from_reason ( e. to_string ( ) ) )
14091440 } )
14101441 }
14111442
@@ -1417,16 +1448,18 @@ impl Bash {
14171448 pub fn from_snapshot (
14181449 data : napi:: bindgen_prelude:: Buffer ,
14191450 options : Option < BashOptions > ,
1451+ snapshot_options : Option < SnapshotOptions > ,
14201452 ) -> napi:: Result < Self > {
14211453 let opts = options. unwrap_or_else ( default_opts) ;
14221454 let mut state = shared_state_from_opts ( opts, None ) ?;
14231455
14241456 // restore_snapshot preserves the instance's limits while restoring shell state
1425- state
1426- . inner
1427- . get_mut ( )
1428- . restore_snapshot ( & data)
1429- . map_err ( |e| napi:: Error :: from_reason ( e. to_string ( ) ) ) ?;
1457+ if let Some ( key) = snapshot_hmac_key ( snapshot_options. as_ref ( ) ) {
1458+ state. inner . get_mut ( ) . restore_snapshot_keyed ( & data, key)
1459+ } else {
1460+ state. inner . get_mut ( ) . restore_snapshot ( & data)
1461+ }
1462+ . map_err ( |e| napi:: Error :: from_reason ( e. to_string ( ) ) ) ?;
14301463
14311464 Ok ( Self {
14321465 state : Arc :: new ( state) ,
@@ -1872,22 +1905,28 @@ impl BashTool {
18721905 & self ,
18731906 options : Option < SnapshotOptions > ,
18741907 ) -> napi:: Result < napi:: bindgen_prelude:: Buffer > {
1875- let options = to_snapshot_options ( options) ;
1908+ let key = require_snapshot_hmac_key ( options. as_ref ( ) ) ?. to_vec ( ) ;
1909+ let snapshot_options = to_snapshot_options ( options. as_ref ( ) ) ;
18761910 block_on_with ( & self . state , |s| async move {
18771911 let bash = s. inner . lock ( ) . await ;
18781912 let bytes = bash
1879- . snapshot_with_options ( options )
1913+ . snapshot_to_bytes_keyed_with_options ( & key , snapshot_options )
18801914 . map_err ( |e| napi:: Error :: from_reason ( e. to_string ( ) ) ) ?;
18811915 Ok ( napi:: bindgen_prelude:: Buffer :: from ( bytes) )
18821916 } )
18831917 }
18841918
18851919 /// Restore interpreter state from a snapshot previously created with `snapshot()`.
18861920 #[ napi]
1887- pub fn restore_snapshot ( & self , data : napi:: bindgen_prelude:: Buffer ) -> napi:: Result < ( ) > {
1921+ pub fn restore_snapshot (
1922+ & self ,
1923+ data : napi:: bindgen_prelude:: Buffer ,
1924+ options : Option < SnapshotOptions > ,
1925+ ) -> napi:: Result < ( ) > {
1926+ let key = require_snapshot_hmac_key ( options. as_ref ( ) ) ?. to_vec ( ) ;
18881927 block_on_with ( & self . state , |s| async move {
18891928 let mut bash = s. inner . lock ( ) . await ;
1890- bash. restore_snapshot ( & data)
1929+ bash. restore_snapshot_keyed ( & data, & key )
18911930 . map_err ( |e| napi:: Error :: from_reason ( e. to_string ( ) ) )
18921931 } )
18931932 }
@@ -1900,14 +1939,16 @@ impl BashTool {
19001939 pub fn from_snapshot (
19011940 data : napi:: bindgen_prelude:: Buffer ,
19021941 options : Option < BashOptions > ,
1942+ snapshot_options : Option < SnapshotOptions > ,
19031943 ) -> napi:: Result < Self > {
19041944 let opts = options. unwrap_or_else ( default_opts) ;
1945+ let key = require_snapshot_hmac_key ( snapshot_options. as_ref ( ) ) ?. to_vec ( ) ;
19051946 let mut state = shared_state_from_opts ( opts, None ) ?;
19061947
19071948 state
19081949 . inner
19091950 . get_mut ( )
1910- . restore_snapshot ( & data)
1951+ . restore_snapshot_keyed ( & data, & key )
19111952 . map_err ( |e| napi:: Error :: from_reason ( e. to_string ( ) ) ) ?;
19121953
19131954 Ok ( Self {
0 commit comments