1010using System . Text ;
1111using System . Text . Json ;
1212using System . Text . Json . Serialization ;
13+ using System . Text . RegularExpressions ;
1314using System . Threading . Tasks ;
1415
1516using EnsureThat ;
@@ -48,6 +49,19 @@ public class Bundle
4849 /// </summary>
4950 protected ILogger Logger { get ; }
5051
52+ /// <summary>
53+ /// Gets the list of sensitive names. Regex patterns are supported.
54+ /// </summary>
55+ /// <remarks>
56+ /// These names are not allowed for add or delete.
57+ /// The entries with these names are only resolved with <see cref="ReadSource.Bundle"/>.
58+ /// </remarks>
59+ protected List < string > ProtectedNames { get ; } = new ( )
60+ {
61+ ".manifest.ec" ,
62+ ".signatures.ec" ,
63+ } ;
64+
5165 /// <summary>
5266 /// Gets the default name of the bundle.
5367 /// </summary>
@@ -140,6 +154,26 @@ protected void EnsureWritable()
140154 }
141155 }
142156
157+ /// <summary>
158+ /// Checks whether the entry name is protected and throws an exception if it is.
159+ /// </summary>
160+ /// <param name="entryName">The name of the entry to check.</param>
161+ /// <param name="throwException">Whether to throw an exception if the entry name is protected.</param>
162+ /// <exception cref="UnauthorizedAccessException"></exception>
163+ /// <returns>True if the entry name is not protected; otherwise, false.</returns>
164+ protected bool CheckEntryNameSecurity ( string entryName , bool throwException = true )
165+ {
166+ foreach ( var pattern in ProtectedNames )
167+ {
168+ if ( Regex . IsMatch ( entryName , pattern ) )
169+ {
170+ return throwException ? throw new UnauthorizedAccessException ( "Entry name is protected: " + entryName ) : false ;
171+ }
172+ }
173+
174+ return true ;
175+ }
176+
143177 private void EvictIfNecessary ( long incomingFileSize )
144178 {
145179 while ( _currentCacheSize + incomingFileSize > _maxCacheSize && ! _cache . IsEmpty )
@@ -317,14 +351,17 @@ public void AddEntry(string path, string destinationPath = "./", string? rootPat
317351
318352 using var file = File . OpenRead ( path ) ;
319353 string name = Manifest . GetNormalizedEntryName ( Path . GetRelativePath ( rootPath , path ) ) ;
354+
355+ CheckEntryNameSecurity ( name ) ;
356+
320357 var hash = ComputeSHA512Hash ( file ) ;
321358
322359 if ( Manifest . StoreOriginalFiles && ! string . IsNullOrEmpty ( destinationPath ) && destinationPath != "./" )
323360 {
324361 name = destinationPath + name ;
325362 }
326363
327- Logger . LogDebug ( "Adding entry: {name} with hash {hash} to manifest" , name , string . Format ( "X2 ", hash ) ) ;
364+ Logger . LogDebug ( "Adding entry: {name} with hash {hash} to manifest" , name , BitConverter . ToString ( hash ) . Replace ( "- ", string . Empty ) ) ;
328365 Manifest . AddEntry ( name , hash ) ;
329366
330367 if ( Manifest . StoreOriginalFiles )
@@ -346,6 +383,9 @@ public void DeleteEntry(string entryName)
346383
347384 Logger . LogInformation ( "Deleting entry: {name}" , entryName ) ;
348385
386+ CheckEntryNameSecurity ( entryName ) ;
387+
388+ Logger . LogDebug ( "Deleting entry: {name} from manifest" , entryName ) ;
349389 Manifest . DeleteEntry ( entryName ) ;
350390
351391 if ( Manifest . StoreOriginalFiles )
@@ -552,6 +592,11 @@ public Stream GetStream(string entryName, ReadSource readSource = ReadSource.Bot
552592
553593 Stream stream ;
554594
595+ if ( ! CheckEntryNameSecurity ( entryName , false ) )
596+ {
597+ readSource = ReadSource . Bundle ;
598+ }
599+
555600 if ( readSource != ReadSource . Disk && ( readSource == ReadSource . Bundle || Manifest . StoreOriginalFiles ) )
556601 {
557602 Logger . LogDebug ( "Reading file: {name} from the bundle" , entryName ) ;
0 commit comments