@@ -546,7 +546,33 @@ impl InMemoryFs {
546546 /// # Ok(())
547547 /// # }
548548 /// ```
549+ // THREAT[TM-ESC-012]: Enforce VFS limits to prevent bypass via restore()
549550 pub fn restore ( & self , snapshot : & VfsSnapshot ) {
551+ // Validate ALL snapshot entries before clearing existing state.
552+ // If any validation fails, return early WITHOUT clearing.
553+ let mut total_bytes = 0u64 ;
554+ let mut file_count = 0u64 ;
555+
556+ for entry in & snapshot. entries {
557+ if self . limits . validate_path ( & entry. path ) . is_err ( ) {
558+ return ;
559+ }
560+ if let VfsEntryKind :: File { content } = & entry. kind {
561+ if self . limits . check_file_size ( content. len ( ) as u64 ) . is_err ( ) {
562+ return ;
563+ }
564+ total_bytes += content. len ( ) as u64 ;
565+ file_count += 1 ;
566+ }
567+ }
568+
569+ if total_bytes > self . limits . max_total_bytes {
570+ return ;
571+ }
572+ if self . limits . check_file_count ( file_count) . is_err ( ) {
573+ return ;
574+ }
575+
550576 let mut entries = self . entries . write ( ) . unwrap ( ) ;
551577 entries. clear ( ) ;
552578
@@ -655,11 +681,26 @@ impl InMemoryFs {
655681 /// // Add a readonly file
656682 /// fs.add_file("/etc/version", "1.0.0", 0o444);
657683 /// ```
684+ // THREAT[TM-ESC-012]: Enforce VFS limits to prevent bypass via add_file()
658685 pub fn add_file ( & self , path : impl AsRef < Path > , content : impl AsRef < [ u8 ] > , mode : u32 ) {
659686 let path = Self :: normalize_path ( path. as_ref ( ) ) ;
660687 let content = content. as_ref ( ) ;
688+
689+ // Validate path before acquiring write lock
690+ if self . limits . validate_path ( & path) . is_err ( ) {
691+ return ;
692+ }
693+
661694 let mut entries = self . entries . write ( ) . unwrap ( ) ;
662695
696+ // Check write limits (file size, file count, total bytes)
697+ if self
698+ . check_write_limits ( & entries, & path, content. len ( ) )
699+ . is_err ( )
700+ {
701+ return ;
702+ }
703+
663704 // Ensure parent directories exist
664705 if let Some ( parent) = path. parent ( ) {
665706 let mut current = PathBuf :: from ( "/" ) ;
@@ -1518,4 +1559,42 @@ mod tests {
15181559 let content = fs. read_file ( Path :: new ( "/tmp/file.txt" ) ) . await . unwrap ( ) ;
15191560 assert_eq ! ( content, b"updated" ) ;
15201561 }
1562+
1563+ // --- #406: VFS limit bypass tests (TM-ESC-012) ---
1564+
1565+ #[ tokio:: test]
1566+ async fn test_add_file_respects_file_size_limit ( ) {
1567+ let limits = FsLimits {
1568+ max_file_size : 100 ,
1569+ ..FsLimits :: default ( )
1570+ } ;
1571+ let fs = InMemoryFs :: with_limits ( limits) ;
1572+ fs. add_file ( "/tmp/huge.bin" , vec ! [ 0u8 ; 200 ] , 0o644 ) ;
1573+ assert ! ( !fs. exists( Path :: new( "/tmp/huge.bin" ) ) . await . unwrap( ) ) ;
1574+ }
1575+
1576+ #[ tokio:: test]
1577+ async fn test_add_file_respects_total_bytes_limit ( ) {
1578+ let limits = FsLimits {
1579+ max_total_bytes : 50 ,
1580+ ..FsLimits :: default ( )
1581+ } ;
1582+ let fs = InMemoryFs :: with_limits ( limits) ;
1583+ fs. add_file ( "/tmp/big.bin" , vec ! [ 0u8 ; 60 ] , 0o644 ) ;
1584+ assert ! ( !fs. exists( Path :: new( "/tmp/big.bin" ) ) . await . unwrap( ) ) ;
1585+ }
1586+
1587+ #[ tokio:: test]
1588+ async fn test_restore_respects_file_size_limit ( ) {
1589+ let unlimited = InMemoryFs :: with_limits ( FsLimits :: unlimited ( ) ) ;
1590+ unlimited. add_file ( "/tmp/huge.bin" , vec ! [ 0u8 ; 200 ] , 0o644 ) ;
1591+ let snapshot = unlimited. snapshot ( ) ;
1592+
1593+ let limited = InMemoryFs :: with_limits ( FsLimits {
1594+ max_file_size : 100 ,
1595+ ..FsLimits :: default ( )
1596+ } ) ;
1597+ limited. restore ( & snapshot) ;
1598+ assert ! ( !limited. exists( Path :: new( "/tmp/huge.bin" ) ) . await . unwrap( ) ) ;
1599+ }
15211600}
0 commit comments