55using System . Collections . Generic ;
66using System . IO ;
77using System . Linq ;
8+ using System . Net ;
89using System . Text ;
910using System . Text . Json ;
1011using System . Threading ;
@@ -34,6 +35,11 @@ public async ValueTask WriteReportAsync(
3435 createdAt : DateTime . UtcNow ,
3536 generatorVersion : Constants . Version ) ;
3637
38+ // Serialize the dataset to JSON, then HTML-encode it for safe embedding in a data attribute.
39+ // This pattern avoids XSS vulnerabilities that can occur when embedding JSON directly in script blocks.
40+ string json = JsonSerializer . Serialize ( dataset , JsonUtilities . Compact . DatasetTypeInfo ) ;
41+ string htmlEncodedJson = WebUtility . HtmlEncode ( json ) ;
42+
3743 using var stream =
3844 new FileStream (
3945 reportFilePath ,
@@ -47,22 +53,12 @@ public async ValueTask WriteReportAsync(
4753
4854#if NET
4955 await writer . WriteAsync ( HtmlTemplateBefore . AsMemory ( ) , cancellationToken ) . ConfigureAwait ( false ) ;
50- await writer . FlushAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
51- #else
52- await writer . WriteAsync ( HtmlTemplateBefore ) . ConfigureAwait ( false ) ;
53- await writer . FlushAsync ( ) . ConfigureAwait ( false ) ;
54- #endif
55-
56- await JsonSerializer . SerializeAsync (
57- stream ,
58- dataset ,
59- JsonUtilities . Compact . DatasetTypeInfo ,
60- cancellationToken ) . ConfigureAwait ( false ) ;
61-
62- #if NET
56+ await writer . WriteAsync ( htmlEncodedJson . AsMemory ( ) , cancellationToken ) . ConfigureAwait ( false ) ;
6357 await writer . WriteAsync ( HtmlTemplateAfter . AsMemory ( ) , cancellationToken ) . ConfigureAwait ( false ) ;
6458 await writer . FlushAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
6559#else
60+ await writer . WriteAsync ( HtmlTemplateBefore ) . ConfigureAwait ( false ) ;
61+ await writer . WriteAsync ( htmlEncodedJson ) . ConfigureAwait ( false ) ;
6662 await writer . WriteAsync ( HtmlTemplateAfter ) . ConfigureAwait ( false ) ;
6763 await writer . FlushAsync ( ) . ConfigureAwait ( false ) ;
6864#endif
@@ -86,8 +82,8 @@ static HtmlReportWriter()
8682 using var reader = new StreamReader ( resourceStream ) ;
8783 string all = reader . ReadToEnd ( ) ;
8884
89- // This is the placeholder for the results array in the template .
90- const string SearchString = @"{scenarioRunResults:[]} ";
85+ // This is the placeholder in the data-dataset attribute on the root div .
86+ const string SearchString = "##DATASETS## ";
9187
9288 int start = all . IndexOf ( SearchString , StringComparison . Ordinal ) ;
9389 if ( start == - 1 )
0 commit comments