Skip to content

Commit c354fd2

Browse files
committed
Renewed architecture and security scan + fixes
1 parent 7ecdc58 commit c354fd2

20 files changed

Lines changed: 495 additions & 944 deletions

Dockerfile

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Build stage
2-
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
2+
# .NET 10.0 manifest digest resolved 2026-09-11.
3+
FROM mcr.microsoft.com/dotnet/sdk:10.0@sha256:60a2b2230a0d052bc54c0d453e97e331219ed503c5411470ee226859420e693c AS build
34
WORKDIR /app
45

56
# Install CA certificates so HTTPS requests to CDNs (cdnjs) work
@@ -22,7 +23,8 @@ WORKDIR /app/src/RfcBuddy.Web
2223
RUN dotnet publish -c Release -o /out
2324

2425
# Runtime stage
25-
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
26+
# .NET 10.0 manifest digest resolved 2026-09-11.
27+
FROM mcr.microsoft.com/dotnet/aspnet:10.0@sha256:900c2dd83cc0cef53db0aaf786f12fe766ee075b6334750a664c9e77e7a7c0c5 AS runtime
2628
WORKDIR /app
2729
COPY --from=build /out ./
2830

@@ -38,8 +40,7 @@ VOLUME /app/data
3840
EXPOSE 8080
3941
ENV ASPNETCORE_URLS=http://+:8080
4042
ENV \
41-
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 \
42-
LC_ALL=en_US.UTF-8 \
43-
LANG=en_US.UTF-8
43+
LC_ALL=C.UTF-8 \
44+
LANG=C.UTF-8
4445

4546
ENTRYPOINT ["dotnet", "RfcBuddy.Web.dll"]

docs/architecture.md

Lines changed: 221 additions & 175 deletions
Large diffs are not rendered by default.

docs/security-review.md

Lines changed: 88 additions & 515 deletions
Large diffs are not rendered by default.

packages.lock.json

Lines changed: 0 additions & 84 deletions
This file was deleted.

src/RfcBuddy.App.Tests/RfcBuddy.App.Tests.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1616
</PackageReference>
1717
<PackageReference Include="JunitXml.TestLogger" Version="8.0.0" />
18-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.7.0" />
19-
<PackageReference Include="MSTest.TestAdapter" Version="4.2.3" />
20-
<PackageReference Include="MSTest.TestFramework" Version="4.2.3" />
18+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.10.0" />
19+
<PackageReference Include="MSTest.TestAdapter" Version="4.4.0" />
20+
<PackageReference Include="MSTest.TestFramework" Version="4.4.0" />
2121
</ItemGroup>
2222

2323
<ItemGroup>

src/RfcBuddy.App.Tests/Services/UserRegistryServiceTests.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ public void BootstrapFirstUserAsAdminAndManageAdminStatus()
3838
Assert.IsTrue(registry.SetAdmin("user-1", false, "user-2"));
3939
Assert.IsFalse(registry.IsAdmin("user-1"));
4040

41+
// Non-admin and unknown requesters cannot change administrative status.
42+
Assert.IsFalse(registry.SetAdmin("user-1", true, "user-1"));
43+
Assert.IsFalse(registry.SetAdmin("user-1", true, "missing-user"));
44+
4145
// Attempting to demote the last remaining admin (user-2) should fail
4246
Assert.IsFalse(registry.SetAdmin("user-2", false, "user-1"));
4347
Assert.IsTrue(registry.IsAdmin("user-2")); // Still admin

src/RfcBuddy.App.Tests/Services/UserServiceTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ public void GetAndSaveUserKeywordsRoundTrip()
8080

8181
userService.GetUserKeywords(out var ministry, out var general, out var ignore);
8282

83-
CollectionAssert.AreEqual(new List<string> { "ministry" }, ministry);
84-
CollectionAssert.AreEqual(new List<string> { "general1", "general2" }, general);
85-
CollectionAssert.AreEqual(new List<string> { "ignore" }, ignore);
83+
Assert.AreSequenceEqual(new List<string> { "ministry" }, ministry);
84+
Assert.AreSequenceEqual(new List<string> { "general1", "general2" }, general);
85+
Assert.AreSequenceEqual(new List<string> { "ignore" }, ignore);
8686
}
8787
finally
8888
{

src/RfcBuddy.App.Tests/packages.lock.json

Lines changed: 49 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -16,32 +16,34 @@
1616
},
1717
"Microsoft.NET.Test.Sdk": {
1818
"type": "Direct",
19-
"requested": "[18.7.0, )",
20-
"resolved": "18.7.0",
21-
"contentHash": "49xH9j4UzCh2hMohJp53g3wUTvyycECw7CtVht4gfCz5ykudB1uBcF6D0TtgJPjCtP76UPW53bQElKdCeX+dUg==",
19+
"requested": "[18.10.0, )",
20+
"resolved": "18.10.0",
21+
"contentHash": "0CZZ688UBvpQFB9HKo7wrNJNVe+AOM4UtipgwpER3TTM+d2DDM6QyKkIJZTR3mpbn9/zFbZEtAgVIz4SPTjQZQ==",
2222
"dependencies": {
23-
"Microsoft.CodeCoverage": "18.7.0",
24-
"Microsoft.TestPlatform.TestHost": "18.7.0"
23+
"Microsoft.CodeCoverage": "18.10.0",
24+
"Microsoft.TestPlatform.TestHost": "18.10.0"
2525
}
2626
},
2727
"MSTest.TestAdapter": {
2828
"type": "Direct",
29-
"requested": "[4.2.3, )",
30-
"resolved": "4.2.3",
31-
"contentHash": "oVV/luk0bBghnVvLaw8MwFlD7It0Cx9P2nKobeqIafmTQqFFWY69Wo801dxjeNaLzO/o9WQ84WSK84gXJuubhg==",
29+
"requested": "[4.4.0, )",
30+
"resolved": "4.4.0",
31+
"contentHash": "xZdlCr2r/6XFOo67jcemJsfm46tTgXdqtnISFIuDUAJf5Yxk07XpTC9eMUErLdlYD8QM9zw07eOPre7+nTayUg==",
3232
"dependencies": {
33-
"MSTest.TestFramework": "4.2.3",
34-
"Microsoft.Testing.Extensions.VSTestBridge": "2.2.3",
35-
"Microsoft.Testing.Platform.MSBuild": "2.2.3"
33+
"MSTest.TestFramework": "4.4.0",
34+
"Microsoft.TestPlatform.ObjectModel": "18.9.0",
35+
"Microsoft.Testing.Extensions.Telemetry": "2.4.0",
36+
"Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.4.0",
37+
"Microsoft.Testing.Platform.MSBuild": "2.4.0"
3638
}
3739
},
3840
"MSTest.TestFramework": {
3941
"type": "Direct",
40-
"requested": "[4.2.3, )",
41-
"resolved": "4.2.3",
42-
"contentHash": "9zzij59YLh+tf+FRLNqhzHjmdspR91bol+jdQxLlxxTGMAML6LDbvuyXKMGdcrE84+QpKkk6KjTVBC5PBGrDeA==",
42+
"requested": "[4.4.0, )",
43+
"resolved": "4.4.0",
44+
"contentHash": "sfJzj3ntdU/GahJd2n+WQLgKqbV0RRMJVXtjRwRQLhblevlzLSDAOocOKANhfMaCEQ5dMt6348ztbVq/Ap60xA==",
4345
"dependencies": {
44-
"MSTest.Analyzers": "4.2.3"
46+
"MSTest.Analyzers": "4.4.0"
4547
}
4648
},
4749
"DocX": {
@@ -73,99 +75,82 @@
7375
},
7476
"Microsoft.CodeCoverage": {
7577
"type": "Transitive",
76-
"resolved": "18.7.0",
77-
"contentHash": "+wFfx9s7D9wegM0RziXMj2kvYDT4qcqXXtyjiQwSZOGQ2wwcOAJQcD6eQXk02jt0MvRNawtp8TJxTrV+wD8X1g=="
78+
"resolved": "18.10.0",
79+
"contentHash": "zo1hqV+Nz6FlIwdc+aPfrvGzqfKAPPaZ6OPj1W8C0QQE2xH951AZwTUytL3K7XX8brVBUFd7MhrLDl3CtK3U/A=="
7880
},
7981
"Microsoft.Extensions.Configuration.Abstractions": {
8082
"type": "Transitive",
81-
"resolved": "10.0.9",
82-
"contentHash": "qGhRPd3VxfLV9UqatVOiD9mAeUbj2KiMwGFYC5uXlzExiZQoe4X/hdmzGIU7BQjNLTqCnnbTHVyBglG3668/HA==",
83+
"resolved": "10.0.12",
84+
"contentHash": "8xaGcvS/qZ1otoxPQCEJkNva389CVL/plNcvIETZhQTETYdRkYDPEYhUMoAGONo4FU45ufdfE0j29AfWVVj0wA==",
8385
"dependencies": {
84-
"Microsoft.Extensions.Primitives": "10.0.9"
86+
"Microsoft.Extensions.Primitives": "10.0.12"
8587
}
8688
},
8789
"Microsoft.Extensions.DependencyInjection.Abstractions": {
8890
"type": "Transitive",
89-
"resolved": "10.0.9",
90-
"contentHash": "g41l/30G3K4B/d/L8kjux0+30e27c8D0FVQ/PFCpbekgfDpj9mnDhieP67EqXWvl1EWNeZh2rpR4F5B/jcDOHA=="
91+
"resolved": "10.0.12",
92+
"contentHash": "9/qymSh7hVDMGTGwrLz8MRp5zRyXy9adGDOs4HwRdnLil3oZGYuWeZjbmHgCQ9BL1qBroVfgUK3U/nb61617Cw=="
9193
},
9294
"Microsoft.Extensions.Logging.Abstractions": {
9395
"type": "Transitive",
94-
"resolved": "10.0.9",
95-
"contentHash": "9S/DFt4cohlMPpzIxjG6kk0L8MuN2vDm9pbMCulxtJzzk82oJHVLBd8vuQxaPskaYQwKqmFmbannf5eoChgjYg==",
96+
"resolved": "10.0.12",
97+
"contentHash": "+24lC4plfbEDNfLAdTV/SWKS7dW+16X4HdydO3R++134kSNTzcbYA4KpR1Hdh6uWisB8Za3AzwyOn+K+NxWIug==",
9698
"dependencies": {
97-
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.9"
99+
"Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12"
98100
}
99101
},
100102
"Microsoft.Extensions.Primitives": {
101103
"type": "Transitive",
102-
"resolved": "10.0.9",
103-
"contentHash": "fmEbAUFsaIKirgLt/lYhuFRBwhcSJN31jjHgCdbQxJiWOum6EdLjkbgGuukSP9z/a+9LibaxII/kF+GwOXgC4g=="
104+
"resolved": "10.0.12",
105+
"contentHash": "dYfCLR52UA+3DL7C4I/pvSaRPkNqxrUAQmbFL2u0zvYKKzqgrFCJl08Df+F1aYc8leu9JvpC9bsURUdpExcBXQ=="
104106
},
105107
"Microsoft.Testing.Extensions.Telemetry": {
106108
"type": "Transitive",
107-
"resolved": "2.2.3",
108-
"contentHash": "mLdW+JOR3kXYGTdgR/qc/UZBA0r+eCR2k6bUxTcuDj5w9WdIQ7Lol5MBUU7YOSGd9bs9bvhSYWAptgz0YtQqCA==",
109+
"resolved": "2.4.0",
110+
"contentHash": "JeP1RFqBa11fWmBk8xEfZcMKr4rxWSyI6OZ+659V069CaMkTEOQBW2UdSSeNz3absOsygcn7JJkzerC4LGnZ9w==",
109111
"dependencies": {
110112
"Microsoft.ApplicationInsights": "2.23.0",
111-
"Microsoft.Testing.Platform": "2.2.3"
113+
"Microsoft.Testing.Platform": "[2.4.0, 3.0.0)"
112114
}
113115
},
114116
"Microsoft.Testing.Extensions.TrxReport.Abstractions": {
115117
"type": "Transitive",
116-
"resolved": "2.2.3",
117-
"contentHash": "hntvxJEkmUAx6C2xXc/PO38DqEQl4rimzOgSvTR1hAMruMid7R4RcXOrzzF33J66gKaN7jRaQ0TMW/nNfaV9jw==",
118+
"resolved": "2.4.0",
119+
"contentHash": "uRb+4qM42dFDg4kWJZ2kFEcwESNVCRZJjItp5vETorN3rSJGysP617LqYirOcrCYmxg+obPreHMM5JcsNDKtBw==",
118120
"dependencies": {
119-
"Microsoft.Testing.Platform": "2.2.3"
120-
}
121-
},
122-
"Microsoft.Testing.Extensions.VSTestBridge": {
123-
"type": "Transitive",
124-
"resolved": "2.2.3",
125-
"contentHash": "7WlJISO8QKUK+d+WhgnANwy4ACwUrvICnviY/mthPwjZ2gVeDaSUAeBnMy2cxfzZgm8VATtGUDbYzUxsgV2CyQ==",
126-
"dependencies": {
127-
"Microsoft.TestPlatform.ObjectModel": "18.3.0",
128-
"Microsoft.Testing.Extensions.Telemetry": "2.2.3",
129-
"Microsoft.Testing.Extensions.TrxReport.Abstractions": "2.2.3",
130-
"Microsoft.Testing.Platform": "2.2.3"
121+
"Microsoft.Testing.Platform": "[2.4.0, 3.0.0)"
131122
}
132123
},
133124
"Microsoft.Testing.Platform": {
134125
"type": "Transitive",
135-
"resolved": "2.2.3",
136-
"contentHash": "LhM1/Qoi8Ams5QcD4r3f09CSOono9iQr3NEJQItFtyzWB55nWTgEOsVqXqMWWWIwk3nkPqc+XfnlJmp8xUI5fg=="
126+
"resolved": "2.4.0",
127+
"contentHash": "dp1N3P1ujb0ztwFgqz2o/ItEvq+pm19/AiA+Xq7Zpcy7oPMcxDBZLYGtf0LlU1MveEBJuKVjZhI+LnkfcH/0jQ=="
137128
},
138129
"Microsoft.Testing.Platform.MSBuild": {
139130
"type": "Transitive",
140-
"resolved": "2.2.3",
141-
"contentHash": "Q22jJYJLx4srTinsAuoCskqmzjrBJC8YeGJMHHIcrf1dQeHoEZ7wsqDzTlENkMoke2qfufF7U+9u58nlZunH/Q==",
131+
"resolved": "2.4.0",
132+
"contentHash": "qr5M6h16YHMJLFDcWELFVMMpGte2BUmveBZKT5YoBV+bmuJRPu9bv/Zqke4yQuOEKRNxoAETrG5jr+/6Rnr3Hg==",
142133
"dependencies": {
143-
"Microsoft.Testing.Platform": "2.2.3"
134+
"Microsoft.Testing.Platform": "[2.4.0, 3.0.0)"
144135
}
145136
},
146137
"Microsoft.TestPlatform.ObjectModel": {
147138
"type": "Transitive",
148-
"resolved": "18.7.0",
149-
"contentHash": "6rmgU4q3/WOpOPcncI0YW0Q/QpcQtwR2TTEXDR5+4TfSimPBAk6Z/BgKLeGgp1SOun0ROVUCCafXhRLwsHaPpA=="
139+
"resolved": "18.10.0",
140+
"contentHash": "aXA7DDbnt6MYu3eLBEDksNdl09QxJ/PpMzrFcSxqEOk3RvRGJzhKI5mezVCEOcvveRhjfw/mvn6xVXC/rTaOog=="
150141
},
151142
"Microsoft.TestPlatform.TestHost": {
152143
"type": "Transitive",
153-
"resolved": "18.7.0",
154-
"contentHash": "kYwfmebCs8992zaxEDkvG7S+YEouTeKfYVKUFEkwh1W2dIoOaevBt80XSKVXCUFEhusjOIm1sFfHBnoJgygrRA==",
144+
"resolved": "18.10.0",
145+
"contentHash": "Spu6C7bgz1PmWN+Y7wS0PfGCw9Kwv5RvJbCwyQaEWjcwpMire+qYMrcZ7qk8agjy6nd0sdHLc3ugCSrGuSbZdw==",
155146
"dependencies": {
156-
"Microsoft.TestPlatform.ObjectModel": "18.7.0",
157-
"Newtonsoft.Json": "13.0.3"
147+
"Microsoft.TestPlatform.ObjectModel": "18.10.0"
158148
}
159149
},
160150
"MSTest.Analyzers": {
161151
"type": "Transitive",
162-
"resolved": "4.2.3",
163-
"contentHash": "dxOZt8/LWuiox7rugInJoIa5Mmu3pBmXdfaoZOx/mxx8+sUFFpjBXPlWXQXGeWzpkVPNC3x1Jf7rt2h2Zjyvvg=="
164-
},
165-
"Newtonsoft.Json": {
166-
"type": "Transitive",
167-
"resolved": "13.0.3",
168-
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
152+
"resolved": "4.4.0",
153+
"contentHash": "8g2KL2LjsXXVywvQb0uWQBANOONVnRf6cTSbON9ar6gIw08oSBY7Z3PJlC8sHbqOVvCr7srrcWiKzb12SUoV2w=="
169154
},
170155
"SkiaSharp": {
171156
"type": "Transitive",
@@ -197,8 +182,8 @@
197182
"DocX": "[5.2.0, )",
198183
"ExcelDataReader": "[3.9.0, )",
199184
"ExcelDataReader.DataSet": "[3.9.0, )",
200-
"Microsoft.Extensions.Configuration.Abstractions": "[10.0.9, )",
201-
"Microsoft.Extensions.Logging.Abstractions": "[10.0.9, )"
185+
"Microsoft.Extensions.Configuration.Abstractions": "[10.0.12, )",
186+
"Microsoft.Extensions.Logging.Abstractions": "[10.0.12, )"
202187
}
203188
}
204189
}

src/RfcBuddy.App/RfcBuddy.App.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
<PackageReference Include="DocX" Version="5.2.0" />
1111
<PackageReference Include="ExcelDataReader" Version="3.9.0" />
1212
<PackageReference Include="ExcelDataReader.DataSet" Version="3.9.0" />
13-
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.9" />
14-
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.9" />
13+
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.12" />
14+
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.12" />
1515
</ItemGroup>
1616

1717
<ItemGroup>

src/RfcBuddy.App/Services/ExcelService.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ public class ExcelService(IAppSettingsService appSettingsService) : IRfcService
6767
private const int colRisk = 7;
6868

6969
private const string excelFileName = "ServiceNow-365-Day-Changes.xlsx";
70+
private static readonly TimeSpan sourceRequestTimeout = TimeSpan.FromSeconds(30);
7071

7172
private string ExcelFile => Path.Join(_appSettings.DataFolder, excelFileName);
7273

@@ -87,6 +88,7 @@ public async Task GetLatestChanges()
8788
handler.Credentials = new NetworkCredential(_appSettings.SourceUser, _appSettings.SourcePassword);
8889
}
8990
using HttpClient client = new(handler);
91+
client.Timeout = sourceRequestTimeout;
9092
using var responseStream = await client.GetStreamAsync(_appSettings.SourceUrl).ConfigureAwait(true);
9193
using var fileStream = new FileStream(ExcelFile, FileMode.Create);
9294
await responseStream.CopyToAsync(fileStream).ConfigureAwait(true);

0 commit comments

Comments
 (0)