up
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Build Test Deploy / docs (push) Has been cancelled
				
			
		
			
				
	
				Build Test Deploy / deploy (push) Has been cancelled
				
			
		
			
				
	
				Build Test Deploy / build-test (push) Has been cancelled
				
			
		
			
				
	
				Build Test Deploy / authority-container (push) Has been cancelled
				
			
		
			
				
	
				Docs CI / lint-and-preview (push) Has been cancelled
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Build Test Deploy / docs (push) Has been cancelled
				
			Build Test Deploy / deploy (push) Has been cancelled
				
			Build Test Deploy / build-test (push) Has been cancelled
				
			Build Test Deploy / authority-container (push) Has been cancelled
				
			Docs CI / lint-and-preview (push) Has been cancelled
				
			This commit is contained in:
		| @@ -35,12 +35,14 @@ public sealed class CertCcConnectorSnapshotTests : IAsyncLifetime | ||||
| { | ||||
|     private static readonly Uri SeptemberSummaryUri = new("https://www.kb.cert.org/vuls/api/2025/09/summary/"); | ||||
|     private static readonly Uri OctoberSummaryUri = new("https://www.kb.cert.org/vuls/api/2025/10/summary/"); | ||||
|     private static readonly Uri YearlySummaryUri = new("https://www.kb.cert.org/vuls/api/2025/summary/"); | ||||
|     private static readonly Uri NovemberSummaryUri = new("https://www.kb.cert.org/vuls/api/2025/11/summary/"); | ||||
|     private static readonly Uri NoteDetailUri = new("https://www.kb.cert.org/vuls/api/294418/"); | ||||
|     private static readonly Uri VendorsDetailUri = new("https://www.kb.cert.org/vuls/api/294418/vendors/"); | ||||
|     private static readonly Uri VulsDetailUri = new("https://www.kb.cert.org/vuls/api/294418/vuls/"); | ||||
|     private static readonly Uri VendorStatusesDetailUri = new("https://www.kb.cert.org/vuls/api/294418/vendors/vuls/"); | ||||
|  | ||||
|     private static readonly Uri YearlySummaryUri = new("https://www.kb.cert.org/vuls/api/2025/summary/"); | ||||
|  | ||||
|     private readonly MongoIntegrationFixture _fixture; | ||||
|     private ConnectorTestHarness? _harness; | ||||
|  | ||||
| @@ -75,9 +77,24 @@ public sealed class CertCcConnectorSnapshotTests : IAsyncLifetime | ||||
|  | ||||
|         harness.TimeProvider.Advance(TimeSpan.FromMinutes(30)); | ||||
|         RegisterSummaryNotModifiedResponses(harness.Handler); | ||||
|         RegisterDetailNotModifiedResponses(harness.Handler); | ||||
|  | ||||
|         await connector.FetchAsync(harness.ServiceProvider, CancellationToken.None); | ||||
|         var recordedRequests = harness.Handler.Requests | ||||
|             .Select(request => request.Uri.ToString()) | ||||
|             .ToArray(); | ||||
|         recordedRequests.Should().Equal(new[] | ||||
|         { | ||||
|             SeptemberSummaryUri.ToString(), | ||||
|             OctoberSummaryUri.ToString(), | ||||
|             NoteDetailUri.ToString(), | ||||
|             VendorsDetailUri.ToString(), | ||||
|             VulsDetailUri.ToString(), | ||||
|             VendorStatusesDetailUri.ToString(), | ||||
|             YearlySummaryUri.ToString(), | ||||
|             OctoberSummaryUri.ToString(), | ||||
|             NovemberSummaryUri.ToString(), | ||||
|             YearlySummaryUri.ToString(), | ||||
|         }); | ||||
|         harness.Handler.AssertNoPendingResponses(); | ||||
|  | ||||
|         var requestsSnapshot = BuildRequestsSnapshot(harness.Handler.Requests); | ||||
| @@ -128,7 +145,7 @@ public sealed class CertCcConnectorSnapshotTests : IAsyncLifetime | ||||
|         { | ||||
|             SeptemberSummaryUri, | ||||
|             OctoberSummaryUri, | ||||
|             new Uri("https://www.kb.cert.org/vuls/api/2025/11/summary/"), | ||||
|             NovemberSummaryUri, | ||||
|             YearlySummaryUri, | ||||
|             NoteDetailUri, | ||||
|             VendorsDetailUri, | ||||
| @@ -255,17 +272,13 @@ public sealed class CertCcConnectorSnapshotTests : IAsyncLifetime | ||||
|     { | ||||
|         AddJsonResponse(handler, SeptemberSummaryUri, "summary-2025-09.json", "\"certcc-summary-2025-09\"", new DateTimeOffset(2025, 9, 30, 12, 0, 0, TimeSpan.Zero)); | ||||
|         AddJsonResponse(handler, OctoberSummaryUri, "summary-2025-10.json", "\"certcc-summary-2025-10\"", new DateTimeOffset(2025, 10, 31, 12, 0, 0, TimeSpan.Zero)); | ||||
|         var novemberUri = new Uri("https://www.kb.cert.org/vuls/api/2025/11/summary/"); | ||||
|         AddJsonResponse(handler, novemberUri, "summary-2025-11.json", "\"certcc-summary-2025-11\"", new DateTimeOffset(2025, 11, 1, 8, 0, 0, TimeSpan.Zero)); | ||||
|         AddJsonResponse(handler, YearlySummaryUri, "summary-2025.json", "\"certcc-summary-2025\"", new DateTimeOffset(2025, 10, 31, 12, 1, 0, TimeSpan.Zero)); | ||||
|     } | ||||
|  | ||||
|     private static void RegisterSummaryNotModifiedResponses(CannedHttpMessageHandler handler) | ||||
|     { | ||||
|         AddNotModified(handler, SeptemberSummaryUri, "\"certcc-summary-2025-09\""); | ||||
|         AddNotModified(handler, OctoberSummaryUri, "\"certcc-summary-2025-10\""); | ||||
|         var novemberUri = new Uri("https://www.kb.cert.org/vuls/api/2025/11/summary/"); | ||||
|         AddNotModified(handler, novemberUri, "\"certcc-summary-2025-11\""); | ||||
|         AddNotModified(handler, NovemberSummaryUri, "\"certcc-summary-2025-11\""); | ||||
|         AddNotModified(handler, YearlySummaryUri, "\"certcc-summary-2025\""); | ||||
|     } | ||||
|  | ||||
| @@ -277,14 +290,6 @@ public sealed class CertCcConnectorSnapshotTests : IAsyncLifetime | ||||
|         AddJsonResponse(handler, VendorStatusesDetailUri, "vendor-statuses-294418.json", "\"certcc-vendor-statuses-294418\"", new DateTimeOffset(2025, 10, 9, 17, 12, 0, TimeSpan.Zero)); | ||||
|     } | ||||
|  | ||||
|     private static void RegisterDetailNotModifiedResponses(CannedHttpMessageHandler handler) | ||||
|     { | ||||
|         AddNotModified(handler, NoteDetailUri, "\"certcc-note-294418\""); | ||||
|         AddNotModified(handler, VendorsDetailUri, "\"certcc-vendors-294418\""); | ||||
|         AddNotModified(handler, VulsDetailUri, "\"certcc-vuls-294418\""); | ||||
|         AddNotModified(handler, VendorStatusesDetailUri, "\"certcc-vendor-statuses-294418\""); | ||||
|     } | ||||
|  | ||||
|     private static void AddJsonResponse(CannedHttpMessageHandler handler, Uri uri, string fixtureName, string etag, DateTimeOffset lastModified) | ||||
|     { | ||||
|         var payload = ReadFixture(fixtureName); | ||||
|   | ||||
| @@ -227,6 +227,46 @@ public sealed class CertCcConnectorTests : IAsyncLifetime | ||||
|         pendingSummaries.Should().Be(0); | ||||
|     } | ||||
|  | ||||
|     [Fact] | ||||
|     public async Task Fetch_PartialDetailEndpointsMissing_CompletesAndMaps() | ||||
|     { | ||||
|         await using var provider = await BuildServiceProviderAsync(); | ||||
|         SeedSummaryResponses(); | ||||
|         SeedDetailResponses( | ||||
|             vulsStatus: HttpStatusCode.NotFound, | ||||
|             vendorStatusesStatus: HttpStatusCode.NotFound); | ||||
|  | ||||
|         var connector = provider.GetRequiredService<CertCcConnector>(); | ||||
|         await connector.FetchAsync(provider, CancellationToken.None); | ||||
|         await connector.ParseAsync(provider, CancellationToken.None); | ||||
|         await connector.MapAsync(provider, CancellationToken.None); | ||||
|  | ||||
|         var advisoryStore = provider.GetRequiredService<IAdvisoryStore>(); | ||||
|         var advisories = await advisoryStore.GetRecentAsync(10, CancellationToken.None); | ||||
|         advisories.Should().NotBeNull(); | ||||
|         advisories!.Should().Contain(advisory => advisory.AdvisoryKey == "certcc/vu-294418"); | ||||
|  | ||||
|         var documentStore = provider.GetRequiredService<IDocumentStore>(); | ||||
|         var vendorsDocument = await documentStore.FindBySourceAndUriAsync(CertCcConnectorPlugin.SourceName, VendorsUri.ToString(), CancellationToken.None); | ||||
|         vendorsDocument.Should().NotBeNull(); | ||||
|         vendorsDocument!.Status.Should().Be(DocumentStatuses.Mapped); | ||||
|         var vulsDocument = await documentStore.FindBySourceAndUriAsync(CertCcConnectorPlugin.SourceName, VulsUri.ToString(), CancellationToken.None); | ||||
|         vulsDocument.Should().BeNull(); | ||||
|         var noteDocument = await documentStore.FindBySourceAndUriAsync(CertCcConnectorPlugin.SourceName, NoteDetailUri.ToString(), CancellationToken.None); | ||||
|         noteDocument.Should().NotBeNull(); | ||||
|         noteDocument!.Status.Should().Be(DocumentStatuses.Mapped); | ||||
|  | ||||
|         var stateRepository = provider.GetRequiredService<ISourceStateRepository>(); | ||||
|         var state = await stateRepository.TryGetAsync(CertCcConnectorPlugin.SourceName, CancellationToken.None); | ||||
|         state.Should().NotBeNull(); | ||||
|         state!.Cursor.TryGetValue("pendingNotes", out var pendingNotesValue).Should().BeTrue(); | ||||
|         pendingNotesValue!.AsBsonArray.Should().BeEmpty(); | ||||
|         state.Cursor.TryGetValue("pendingDocuments", out var pendingDocsValue).Should().BeTrue(); | ||||
|         pendingDocsValue!.AsBsonArray.Should().BeEmpty(); | ||||
|         state.Cursor.TryGetValue("pendingMappings", out var pendingMappingsValue).Should().BeTrue(); | ||||
|         pendingMappingsValue!.AsBsonArray.Should().BeEmpty(); | ||||
|     } | ||||
|  | ||||
|     public Task InitializeAsync() => Task.CompletedTask; | ||||
|  | ||||
|     public async Task DisposeAsync() | ||||
| @@ -333,7 +373,9 @@ public sealed class CertCcConnectorTests : IAsyncLifetime | ||||
|         string vendorsEtag = "\"vendors-etag\"", | ||||
|         string vulsEtag = "\"vuls-etag\"", | ||||
|         string vendorStatusesEtag = "\"vendor-statuses-etag\"", | ||||
|         HttpStatusCode vendorsStatus = HttpStatusCode.OK) | ||||
|         HttpStatusCode vendorsStatus = HttpStatusCode.OK, | ||||
|         HttpStatusCode vulsStatus = HttpStatusCode.OK, | ||||
|         HttpStatusCode vendorStatusesStatus = HttpStatusCode.OK) | ||||
|     { | ||||
|         AddJsonResponse(NoteDetailUri, ReadFixture("vu-294418.json"), detailEtag); | ||||
|  | ||||
| @@ -354,8 +396,39 @@ public sealed class CertCcConnectorTests : IAsyncLifetime | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         AddJsonResponse(VulsUri, ReadFixture("vu-294418-vuls.json"), vulsEtag); | ||||
|         AddJsonResponse(VendorStatusesUri, ReadFixture("vendor-statuses-294418.json"), vendorStatusesEtag); | ||||
|         if (vulsStatus == HttpStatusCode.OK) | ||||
|         { | ||||
|             AddJsonResponse(VulsUri, ReadFixture("vu-294418-vuls.json"), vulsEtag); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             _handler.AddResponse(VulsUri, () => | ||||
|             { | ||||
|                 var response = new HttpResponseMessage(vulsStatus) | ||||
|                 { | ||||
|                     Content = new StringContent("vuls error", Encoding.UTF8, "text/plain"), | ||||
|                 }; | ||||
|                 response.Headers.ETag = new EntityTagHeaderValue(vulsEtag); | ||||
|                 return response; | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         if (vendorStatusesStatus == HttpStatusCode.OK) | ||||
|         { | ||||
|             AddJsonResponse(VendorStatusesUri, ReadFixture("vendor-statuses-294418.json"), vendorStatusesEtag); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             _handler.AddResponse(VendorStatusesUri, () => | ||||
|             { | ||||
|                 var response = new HttpResponseMessage(vendorStatusesStatus) | ||||
|                 { | ||||
|                     Content = new StringContent("vendor statuses error", Encoding.UTF8, "text/plain"), | ||||
|                 }; | ||||
|                 response.Headers.ETag = new EntityTagHeaderValue(vendorStatusesEtag); | ||||
|                 return response; | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void SeedDetailNotModifiedResponses(string detailEtag, string vendorsEtag, string vulsEtag, string vendorStatusesEtag) | ||||
|   | ||||
| @@ -0,0 +1,374 @@ | ||||
| [ | ||||
|   { | ||||
|     "advisoryKey": "certcc/vu-294418", | ||||
|     "affectedPackages": [ | ||||
|       { | ||||
|         "type": "vendor", | ||||
|         "identifier": "DrayTek Corporation", | ||||
|         "platform": null, | ||||
|         "versionRanges": [ | ||||
|           { | ||||
|             "fixedVersion": null, | ||||
|             "introducedVersion": null, | ||||
|             "lastAffectedVersion": null, | ||||
|             "primitives": { | ||||
|               "evr": null, | ||||
|               "hasVendorExtensions": true, | ||||
|               "nevra": null, | ||||
|               "semVer": null, | ||||
|               "vendorExtensions": { | ||||
|                 "certcc.vendor.name": "DrayTek Corporation", | ||||
|                 "certcc.vendor.statement.raw": "The issue is confirmed, and here is the patch list\nV3912/V3910/V2962/V1000B 4.4.3.6/4.4.5.1\nV2927/V2865/V2866 4.5.1\nV2765/V2766/V2763/V2135 4.5.1\nV2915 4.4.6.1\nV2862/V2926 3.9.9.12\nV2952/3220 3.9.8.8\nV2860/V2925 3.9.8.6\nV2133/V2762/V2832 3.9.9.4\nV2620/LTE200 3.9.9.5", | ||||
|                 "certcc.vendor.contactDate": "2025-09-15T19:03:33.6643450+00:00", | ||||
|                 "certcc.vendor.statementDate": "2025-09-16T02:27:51.3463350+00:00", | ||||
|                 "certcc.vendor.updated": "2025-10-03T11:35:31.1906610+00:00", | ||||
|                 "certcc.vendor.statuses": "CVE-2025-10547=affected", | ||||
|                 "certcc.vendor.patches": "3220=3.9.8.8;LTE200=3.9.9.5;V1000B=4.4.5.1;V2133=3.9.9.4;V2135=4.5.1;V2620=3.9.9.5;V2762=3.9.9.4;V2763=4.5.1;V2765=4.5.1;V2766=4.5.1;V2832=3.9.9.4;V2860=3.9.8.6;V2862=3.9.9.12;V2865=4.5.1;V2866=4.5.1;V2915=4.4.6.1;V2925=3.9.8.6;V2926=3.9.9.12;V2927=4.5.1;V2952=3.9.8.8;V2962=4.4.5.1;V3910=4.4.3.6;V3912=4.4.3.6" | ||||
|               } | ||||
|             }, | ||||
|             "provenance": { | ||||
|               "source": "cert-cc", | ||||
|               "kind": "vendor-range", | ||||
|               "value": "DrayTek Corporation", | ||||
|               "decisionReason": null, | ||||
|               "recordedAt": "2025-11-01T08:00:00+00:00", | ||||
|               "fieldMask": [] | ||||
|             }, | ||||
|             "rangeExpression": null, | ||||
|             "rangeKind": "vendor" | ||||
|           } | ||||
|         ], | ||||
|         "normalizedVersions": [ | ||||
|           { | ||||
|             "scheme": "certcc.vendor", | ||||
|             "type": "exact", | ||||
|             "min": null, | ||||
|             "minInclusive": null, | ||||
|             "max": null, | ||||
|             "maxInclusive": null, | ||||
|             "value": "3.9.8.6", | ||||
|             "notes": "DrayTek Corporation::V2860" | ||||
|           }, | ||||
|           { | ||||
|             "scheme": "certcc.vendor", | ||||
|             "type": "exact", | ||||
|             "min": null, | ||||
|             "minInclusive": null, | ||||
|             "max": null, | ||||
|             "maxInclusive": null, | ||||
|             "value": "3.9.8.6", | ||||
|             "notes": "DrayTek Corporation::V2925" | ||||
|           }, | ||||
|           { | ||||
|             "scheme": "certcc.vendor", | ||||
|             "type": "exact", | ||||
|             "min": null, | ||||
|             "minInclusive": null, | ||||
|             "max": null, | ||||
|             "maxInclusive": null, | ||||
|             "value": "3.9.8.8", | ||||
|             "notes": "DrayTek Corporation::3220" | ||||
|           }, | ||||
|           { | ||||
|             "scheme": "certcc.vendor", | ||||
|             "type": "exact", | ||||
|             "min": null, | ||||
|             "minInclusive": null, | ||||
|             "max": null, | ||||
|             "maxInclusive": null, | ||||
|             "value": "3.9.8.8", | ||||
|             "notes": "DrayTek Corporation::V2952" | ||||
|           }, | ||||
|           { | ||||
|             "scheme": "certcc.vendor", | ||||
|             "type": "exact", | ||||
|             "min": null, | ||||
|             "minInclusive": null, | ||||
|             "max": null, | ||||
|             "maxInclusive": null, | ||||
|             "value": "3.9.9.12", | ||||
|             "notes": "DrayTek Corporation::V2862" | ||||
|           }, | ||||
|           { | ||||
|             "scheme": "certcc.vendor", | ||||
|             "type": "exact", | ||||
|             "min": null, | ||||
|             "minInclusive": null, | ||||
|             "max": null, | ||||
|             "maxInclusive": null, | ||||
|             "value": "3.9.9.12", | ||||
|             "notes": "DrayTek Corporation::V2926" | ||||
|           }, | ||||
|           { | ||||
|             "scheme": "certcc.vendor", | ||||
|             "type": "exact", | ||||
|             "min": null, | ||||
|             "minInclusive": null, | ||||
|             "max": null, | ||||
|             "maxInclusive": null, | ||||
|             "value": "3.9.9.4", | ||||
|             "notes": "DrayTek Corporation::V2133" | ||||
|           }, | ||||
|           { | ||||
|             "scheme": "certcc.vendor", | ||||
|             "type": "exact", | ||||
|             "min": null, | ||||
|             "minInclusive": null, | ||||
|             "max": null, | ||||
|             "maxInclusive": null, | ||||
|             "value": "3.9.9.4", | ||||
|             "notes": "DrayTek Corporation::V2762" | ||||
|           }, | ||||
|           { | ||||
|             "scheme": "certcc.vendor", | ||||
|             "type": "exact", | ||||
|             "min": null, | ||||
|             "minInclusive": null, | ||||
|             "max": null, | ||||
|             "maxInclusive": null, | ||||
|             "value": "3.9.9.4", | ||||
|             "notes": "DrayTek Corporation::V2832" | ||||
|           }, | ||||
|           { | ||||
|             "scheme": "certcc.vendor", | ||||
|             "type": "exact", | ||||
|             "min": null, | ||||
|             "minInclusive": null, | ||||
|             "max": null, | ||||
|             "maxInclusive": null, | ||||
|             "value": "3.9.9.5", | ||||
|             "notes": "DrayTek Corporation::LTE200" | ||||
|           }, | ||||
|           { | ||||
|             "scheme": "certcc.vendor", | ||||
|             "type": "exact", | ||||
|             "min": null, | ||||
|             "minInclusive": null, | ||||
|             "max": null, | ||||
|             "maxInclusive": null, | ||||
|             "value": "3.9.9.5", | ||||
|             "notes": "DrayTek Corporation::V2620" | ||||
|           }, | ||||
|           { | ||||
|             "scheme": "certcc.vendor", | ||||
|             "type": "exact", | ||||
|             "min": null, | ||||
|             "minInclusive": null, | ||||
|             "max": null, | ||||
|             "maxInclusive": null, | ||||
|             "value": "4.4.3.6", | ||||
|             "notes": "DrayTek Corporation::V3910" | ||||
|           }, | ||||
|           { | ||||
|             "scheme": "certcc.vendor", | ||||
|             "type": "exact", | ||||
|             "min": null, | ||||
|             "minInclusive": null, | ||||
|             "max": null, | ||||
|             "maxInclusive": null, | ||||
|             "value": "4.4.3.6", | ||||
|             "notes": "DrayTek Corporation::V3912" | ||||
|           }, | ||||
|           { | ||||
|             "scheme": "certcc.vendor", | ||||
|             "type": "exact", | ||||
|             "min": null, | ||||
|             "minInclusive": null, | ||||
|             "max": null, | ||||
|             "maxInclusive": null, | ||||
|             "value": "4.4.5.1", | ||||
|             "notes": "DrayTek Corporation::V1000B" | ||||
|           }, | ||||
|           { | ||||
|             "scheme": "certcc.vendor", | ||||
|             "type": "exact", | ||||
|             "min": null, | ||||
|             "minInclusive": null, | ||||
|             "max": null, | ||||
|             "maxInclusive": null, | ||||
|             "value": "4.4.5.1", | ||||
|             "notes": "DrayTek Corporation::V2962" | ||||
|           }, | ||||
|           { | ||||
|             "scheme": "certcc.vendor", | ||||
|             "type": "exact", | ||||
|             "min": null, | ||||
|             "minInclusive": null, | ||||
|             "max": null, | ||||
|             "maxInclusive": null, | ||||
|             "value": "4.4.6.1", | ||||
|             "notes": "DrayTek Corporation::V2915" | ||||
|           }, | ||||
|           { | ||||
|             "scheme": "certcc.vendor", | ||||
|             "type": "exact", | ||||
|             "min": null, | ||||
|             "minInclusive": null, | ||||
|             "max": null, | ||||
|             "maxInclusive": null, | ||||
|             "value": "4.5.1", | ||||
|             "notes": "DrayTek Corporation::V2135" | ||||
|           }, | ||||
|           { | ||||
|             "scheme": "certcc.vendor", | ||||
|             "type": "exact", | ||||
|             "min": null, | ||||
|             "minInclusive": null, | ||||
|             "max": null, | ||||
|             "maxInclusive": null, | ||||
|             "value": "4.5.1", | ||||
|             "notes": "DrayTek Corporation::V2763" | ||||
|           }, | ||||
|           { | ||||
|             "scheme": "certcc.vendor", | ||||
|             "type": "exact", | ||||
|             "min": null, | ||||
|             "minInclusive": null, | ||||
|             "max": null, | ||||
|             "maxInclusive": null, | ||||
|             "value": "4.5.1", | ||||
|             "notes": "DrayTek Corporation::V2765" | ||||
|           }, | ||||
|           { | ||||
|             "scheme": "certcc.vendor", | ||||
|             "type": "exact", | ||||
|             "min": null, | ||||
|             "minInclusive": null, | ||||
|             "max": null, | ||||
|             "maxInclusive": null, | ||||
|             "value": "4.5.1", | ||||
|             "notes": "DrayTek Corporation::V2766" | ||||
|           }, | ||||
|           { | ||||
|             "scheme": "certcc.vendor", | ||||
|             "type": "exact", | ||||
|             "min": null, | ||||
|             "minInclusive": null, | ||||
|             "max": null, | ||||
|             "maxInclusive": null, | ||||
|             "value": "4.5.1", | ||||
|             "notes": "DrayTek Corporation::V2865" | ||||
|           }, | ||||
|           { | ||||
|             "scheme": "certcc.vendor", | ||||
|             "type": "exact", | ||||
|             "min": null, | ||||
|             "minInclusive": null, | ||||
|             "max": null, | ||||
|             "maxInclusive": null, | ||||
|             "value": "4.5.1", | ||||
|             "notes": "DrayTek Corporation::V2866" | ||||
|           }, | ||||
|           { | ||||
|             "scheme": "certcc.vendor", | ||||
|             "type": "exact", | ||||
|             "min": null, | ||||
|             "minInclusive": null, | ||||
|             "max": null, | ||||
|             "maxInclusive": null, | ||||
|             "value": "4.5.1", | ||||
|             "notes": "DrayTek Corporation::V2927" | ||||
|           } | ||||
|         ], | ||||
|         "statuses": [ | ||||
|           { | ||||
|             "provenance": { | ||||
|               "source": "cert-cc", | ||||
|               "kind": "vendor-status", | ||||
|               "value": "DrayTek Corporation:CVE-2025-10547", | ||||
|               "decisionReason": null, | ||||
|               "recordedAt": "2025-11-01T08:00:00+00:00", | ||||
|               "fieldMask": [] | ||||
|             }, | ||||
|             "status": "affected" | ||||
|           } | ||||
|         ], | ||||
|         "provenance": [ | ||||
|           { | ||||
|             "source": "cert-cc", | ||||
|             "kind": "vendor", | ||||
|             "value": "DrayTek Corporation", | ||||
|             "decisionReason": null, | ||||
|             "recordedAt": "2025-11-01T08:00:00+00:00", | ||||
|             "fieldMask": [] | ||||
|           } | ||||
|         ] | ||||
|       } | ||||
|     ], | ||||
|     "aliases": [ | ||||
|       "CVE-2025-10547", | ||||
|       "VU#294418" | ||||
|     ], | ||||
|     "credits": [], | ||||
|     "cvssMetrics": [], | ||||
|     "exploitKnown": false, | ||||
|     "language": "en", | ||||
|     "modified": "2025-10-03T11:40:09.876722+00:00", | ||||
|     "provenance": [ | ||||
|       { | ||||
|         "source": "cert-cc", | ||||
|         "kind": "document", | ||||
|         "value": "https://www.kb.cert.org/vuls/api/294418/", | ||||
|         "decisionReason": null, | ||||
|         "recordedAt": "2025-11-01T08:00:00+00:00", | ||||
|         "fieldMask": [] | ||||
|       }, | ||||
|       { | ||||
|         "source": "cert-cc", | ||||
|         "kind": "map", | ||||
|         "value": "VU#294418", | ||||
|         "decisionReason": null, | ||||
|         "recordedAt": "2025-11-01T08:00:00+00:00", | ||||
|         "fieldMask": [] | ||||
|       } | ||||
|     ], | ||||
|     "published": "2025-10-03T11:35:31.026053+00:00", | ||||
|     "references": [ | ||||
|       { | ||||
|         "kind": "reference", | ||||
|         "provenance": { | ||||
|           "source": "cert-cc", | ||||
|           "kind": "reference", | ||||
|           "value": "https://www.kb.cert.org/vuls/id/294418", | ||||
|           "decisionReason": null, | ||||
|           "recordedAt": "2025-11-01T08:00:00+00:00", | ||||
|           "fieldMask": [] | ||||
|         }, | ||||
|         "sourceTag": "certcc.public", | ||||
|         "summary": null, | ||||
|         "url": "https://www.draytek.com/about/security-advisory/use-of-uninitialized-variable-vulnerabilities/" | ||||
|       }, | ||||
|       { | ||||
|         "kind": "reference", | ||||
|         "provenance": { | ||||
|           "source": "cert-cc", | ||||
|           "kind": "reference", | ||||
|           "value": "https://www.kb.cert.org/vuls/id/294418", | ||||
|           "decisionReason": null, | ||||
|           "recordedAt": "2025-11-01T08:00:00+00:00", | ||||
|           "fieldMask": [] | ||||
|         }, | ||||
|         "sourceTag": "certcc.public", | ||||
|         "summary": null, | ||||
|         "url": "https://www.draytek.com/support/resources?type=version" | ||||
|       }, | ||||
|       { | ||||
|         "kind": "advisory", | ||||
|         "provenance": { | ||||
|           "source": "cert-cc", | ||||
|           "kind": "reference", | ||||
|           "value": "https://www.kb.cert.org/vuls/id/294418", | ||||
|           "decisionReason": null, | ||||
|           "recordedAt": "2025-11-01T08:00:00+00:00", | ||||
|           "fieldMask": [] | ||||
|         }, | ||||
|         "sourceTag": "certcc.note", | ||||
|         "summary": null, | ||||
|         "url": "https://www.kb.cert.org/vuls/id/294418" | ||||
|       } | ||||
|     ], | ||||
|     "severity": null, | ||||
|     "summary": "Overview\nA remote code execution (RCE) vulnerability, tracked as CVE-2025-10547, was discovered through the EasyVPN and LAN web administration interface of Vigor routers by Draytek. A script in the LAN web administration interface uses an unitialized variable, allowing an attacker to send specially crafted HTTP requests that cause memory corruption and potentially allow arbitrary code execution.\nDescription\nVigor routers are business-grade routers, designed for small to medium-sized businesses, made by Draytek. These routers provide routing, firewall, VPN, content-filtering, bandwidth management, LAN (local area network), and multi-WAN (wide area network) features. Draytek utilizes a proprietary firmware, DrayOS, on the Vigor router line. DrayOS features the EasyVPN and LAN Web Administrator tool s to facilitate LAN and VPN setup. According to the DrayTek website, \"with EasyVPN, users no longer need to generate WireGuard keys, import OpenVPN configuration files, or upload certificates. Instead, VPN can be successfully established by simply entering the username and password or getting the OTP code by email.\"\nThe LAN Web Administrator provides a browser-based user interface for router management. When a user interacts with the LAN Web Administration interface, the user interface elements trigger actions that generate HTTP requests to interact with the local server. This process contains an uninitialized variable. Due to the uninitialized variable, an unauthenticated attacker could perform memory corruption on the router via specially crafted HTTP requests to hijack execution or inject malicious payloads. If EasyVPN is enabled, the flaw could be remotely exploited through the VPN interface.\nImpact\nA remote, unathenticated attacker can exploit this vulnerability through accessing the LAN interface—or potentially the WAN interface—if EasyVPN is enabled or remote administration over the internet is activated. If a remote, unauthenticated attacker leverages this vulnerability, they can execute arbitrary code on the router (RCE) and gain full control of the device. A successful attack could result in a attacker gaining root access to a Vigor router to then install backdoors, reconfigure network settings, or block traffic. An attacker may also pivot for lateral movement via intercepting internal communications and bypassing VPNs.\nSolution\nThe DrayTek Security team has developed a series of patches to remediate the vulnerability, and all users of Vigor routers should upgrade to the latest version ASAP. The patches can be found on the resources page of the DrayTek webpage, and the security advisory can be found within the about section of the DrayTek webpage. Consult either the CVE listing or the advisory page for a full list of affected products.\nAcknowledgements\nThanks to the reporter, Pierre-Yves MAES of ChapsVision (pymaes@chapsvision.com). This document was written by Ayushi Kriplani.", | ||||
|     "title": "Vigor routers running DrayOS are vulnerable to RCE via EasyVPN and LAN web administration interface" | ||||
|   } | ||||
| ] | ||||
| @@ -10,7 +10,7 @@ | ||||
|       "certcc.year": "2025", | ||||
|       "fetchedAt": "2025-11-01T08:00:00.0000000+00:00" | ||||
|     }, | ||||
|     "sha256": "75e6271ec1a0a3a099d13aa23393d8ddf5fa9638a06748a84538f9b8891ea792", | ||||
|     "sha256": "0475f0766d6b96d7dc7683cf6b418055c8ecbef88a73ab5d75ce428fbd0900fc", | ||||
|     "status": "pending-parse", | ||||
|     "uri": "https://www.kb.cert.org/vuls/api/2025/09/summary/" | ||||
|   }, | ||||
| @@ -25,7 +25,7 @@ | ||||
|       "certcc.year": "2025", | ||||
|       "fetchedAt": "2025-11-01T08:00:00.0000000+00:00" | ||||
|     }, | ||||
|     "sha256": "75e6271ec1a0a3a099d13aa23393d8ddf5fa9638a06748a84538f9b8891ea792", | ||||
|     "sha256": "363e3ddcd31770e5f41913328318ca0e5bf384bb059d5673ba14392f29f7296f", | ||||
|     "status": "pending-parse", | ||||
|     "uri": "https://www.kb.cert.org/vuls/api/2025/10/summary/" | ||||
|   }, | ||||
| @@ -39,7 +39,7 @@ | ||||
|       "certcc.year": "2025", | ||||
|       "fetchedAt": "2025-11-01T08:00:00.0000000+00:00" | ||||
|     }, | ||||
|     "sha256": "75e6271ec1a0a3a099d13aa23393d8ddf5fa9638a06748a84538f9b8891ea792", | ||||
|     "sha256": "363e3ddcd31770e5f41913328318ca0e5bf384bb059d5673ba14392f29f7296f", | ||||
|     "status": "pending-parse", | ||||
|     "uri": "https://www.kb.cert.org/vuls/api/2025/summary/" | ||||
|   }, | ||||
| @@ -54,7 +54,7 @@ | ||||
|       "certcc.vuid": "VU#294418", | ||||
|       "fetchedAt": "2025-11-01T08:00:00.0000000+00:00" | ||||
|     }, | ||||
|     "sha256": "fac27a3caa47fe319a2eb2a88145452daebaf3c8c6b5afe62cf9634b0824c003", | ||||
|     "sha256": "5dd5c9bcd6ed6f20a2fc07a308af9f420b9a07120fe5934de2a1c26724eb36d3", | ||||
|     "status": "pending-parse", | ||||
|     "uri": "https://www.kb.cert.org/vuls/api/294418/" | ||||
|   }, | ||||
| @@ -69,10 +69,25 @@ | ||||
|       "certcc.vuid": "VU#294418", | ||||
|       "fetchedAt": "2025-11-01T08:00:00.0000000+00:00" | ||||
|     }, | ||||
|     "sha256": "34036a3cdda490adf8ad26d74ac8ae3b85d591579e7dd26b1fd2f78fe5e401b8", | ||||
|     "sha256": "b81aad835ab289c2ac68262825d0f0d5eb9212bc7b3569c84921d0fe5160734f", | ||||
|     "status": "pending-parse", | ||||
|     "uri": "https://www.kb.cert.org/vuls/api/294418/vendors/" | ||||
|   }, | ||||
|   { | ||||
|     "contentType": "application/json; charset=utf-8", | ||||
|     "etag": "\"certcc-vendor-statuses-294418\"", | ||||
|     "lastModified": "2025-10-09T17:12:00.0000000+00:00", | ||||
|     "metadata": { | ||||
|       "attempts": "1", | ||||
|       "certcc.endpoint": "vendors-vuls", | ||||
|       "certcc.noteId": "294418", | ||||
|       "certcc.vuid": "VU#294418", | ||||
|       "fetchedAt": "2025-11-01T08:00:00.0000000+00:00" | ||||
|     }, | ||||
|     "sha256": "6ad928c8a1b0410693417869d83062347747a79da6946404d94d14a2458c23ea", | ||||
|     "status": "pending-parse", | ||||
|     "uri": "https://www.kb.cert.org/vuls/api/294418/vendors/vuls/" | ||||
|   }, | ||||
|   { | ||||
|     "contentType": "application/json; charset=utf-8", | ||||
|     "etag": "\"certcc-vuls-294418\"", | ||||
| @@ -84,7 +99,7 @@ | ||||
|       "certcc.vuid": "VU#294418", | ||||
|       "fetchedAt": "2025-11-01T08:00:00.0000000+00:00" | ||||
|     }, | ||||
|     "sha256": "84c7c17fc37bffdee37cd020b43ec6cadc217a573135ba1c5cc2f0495846030a", | ||||
|     "sha256": "5de3b82f360e1ff06f15873f55ff10b7c4fc11ca65a5f77a3941a82018a8a7de", | ||||
|     "status": "pending-parse", | ||||
|     "uri": "https://www.kb.cert.org/vuls/api/294418/vuls/" | ||||
|   } | ||||
|   | ||||
| @@ -0,0 +1,92 @@ | ||||
| [ | ||||
|   { | ||||
|     "headers": { | ||||
|       "accept": "application/json", | ||||
|       "ifModifiedSince": null, | ||||
|       "ifNoneMatch": null | ||||
|     }, | ||||
|     "method": "GET", | ||||
|     "uri": "https://www.kb.cert.org/vuls/api/2025/09/summary/" | ||||
|   }, | ||||
|   { | ||||
|     "headers": { | ||||
|       "accept": "application/json", | ||||
|       "ifModifiedSince": null, | ||||
|       "ifNoneMatch": null | ||||
|     }, | ||||
|     "method": "GET", | ||||
|     "uri": "https://www.kb.cert.org/vuls/api/2025/10/summary/" | ||||
|   }, | ||||
|   { | ||||
|     "headers": { | ||||
|       "accept": "application/json", | ||||
|       "ifModifiedSince": null, | ||||
|       "ifNoneMatch": null | ||||
|     }, | ||||
|     "method": "GET", | ||||
|     "uri": "https://www.kb.cert.org/vuls/api/294418/" | ||||
|   }, | ||||
|   { | ||||
|     "headers": { | ||||
|       "accept": "application/json", | ||||
|       "ifModifiedSince": null, | ||||
|       "ifNoneMatch": null | ||||
|     }, | ||||
|     "method": "GET", | ||||
|     "uri": "https://www.kb.cert.org/vuls/api/294418/vendors/" | ||||
|   }, | ||||
|   { | ||||
|     "headers": { | ||||
|       "accept": "application/json", | ||||
|       "ifModifiedSince": null, | ||||
|       "ifNoneMatch": null | ||||
|     }, | ||||
|     "method": "GET", | ||||
|     "uri": "https://www.kb.cert.org/vuls/api/294418/vuls/" | ||||
|   }, | ||||
|   { | ||||
|     "headers": { | ||||
|       "accept": "application/json", | ||||
|       "ifModifiedSince": null, | ||||
|       "ifNoneMatch": null | ||||
|     }, | ||||
|     "method": "GET", | ||||
|     "uri": "https://www.kb.cert.org/vuls/api/294418/vendors/vuls/" | ||||
|   }, | ||||
|   { | ||||
|     "headers": { | ||||
|       "accept": "application/json", | ||||
|       "ifModifiedSince": null, | ||||
|       "ifNoneMatch": null | ||||
|     }, | ||||
|     "method": "GET", | ||||
|     "uri": "https://www.kb.cert.org/vuls/api/2025/summary/" | ||||
|   }, | ||||
|   { | ||||
|     "headers": { | ||||
|       "accept": "application/json", | ||||
|       "ifModifiedSince": "Fri, 31 Oct 2025 12:00:00 GMT", | ||||
|       "ifNoneMatch": "\"certcc-summary-2025-10\"" | ||||
|     }, | ||||
|     "method": "GET", | ||||
|     "uri": "https://www.kb.cert.org/vuls/api/2025/10/summary/" | ||||
|   }, | ||||
|   { | ||||
|     "headers": { | ||||
|       "accept": "application/json", | ||||
|       "ifModifiedSince": null, | ||||
|       "ifNoneMatch": null | ||||
|     }, | ||||
|     "method": "GET", | ||||
|     "uri": "https://www.kb.cert.org/vuls/api/2025/11/summary/" | ||||
|   }, | ||||
|   { | ||||
|     "headers": { | ||||
|       "accept": "application/json", | ||||
|       "ifModifiedSince": "Fri, 31 Oct 2025 12:01:00 GMT", | ||||
|       "ifNoneMatch": "\"certcc-summary-2025\"" | ||||
|     }, | ||||
|     "method": "GET", | ||||
|     "uri": "https://www.kb.cert.org/vuls/api/2025/summary/" | ||||
|   } | ||||
| ] | ||||
| @@ -0,0 +1,58 @@ | ||||
| using System.Text; | ||||
| using System.Text.Json; | ||||
| using StellaOps.Feedser.Source.CertCc.Internal; | ||||
| using Xunit; | ||||
|  | ||||
| namespace StellaOps.Feedser.Source.CertCc.Tests.Internal; | ||||
|  | ||||
| public sealed class CertCcSummaryParserTests | ||||
| { | ||||
|     [Fact] | ||||
|     public void ParseNotes_ReturnsTokens_FromStringArray() | ||||
|     { | ||||
|         var payload = Encoding.UTF8.GetBytes("{\"notes\":[\"VU#123456\",\"VU#654321\"]}"); | ||||
|  | ||||
|         var notes = CertCcSummaryParser.ParseNotes(payload); | ||||
|  | ||||
|         Assert.Equal(new[] { "VU#123456", "VU#654321" }, notes); | ||||
|     } | ||||
|  | ||||
|     [Fact] | ||||
|     public void ParseNotes_DeduplicatesTokens_IgnoringCaseAndWhitespace() | ||||
|     { | ||||
|         var payload = Encoding.UTF8.GetBytes("{\"notes\":[\"VU#123456\",\"vu#123456\",\" 123456 \"]}"); | ||||
|  | ||||
|         var notes = CertCcSummaryParser.ParseNotes(payload); | ||||
|  | ||||
|         Assert.Single(notes); | ||||
|         Assert.Equal("VU#123456", notes[0], ignoreCase: true); | ||||
|     } | ||||
|  | ||||
|     [Fact] | ||||
|     public void ParseNotes_ReadsTokens_FromObjectEntries() | ||||
|     { | ||||
|         var payload = Encoding.UTF8.GetBytes("{\"notes\":[{\"id\":\"VU#294418\"},{\"idnumber\":\"257161\"}]}"); | ||||
|  | ||||
|         var notes = CertCcSummaryParser.ParseNotes(payload); | ||||
|  | ||||
|         Assert.Equal(new[] { "VU#294418", "257161" }, notes); | ||||
|     } | ||||
|  | ||||
|     [Fact] | ||||
|     public void ParseNotes_SupportsArrayRoot() | ||||
|     { | ||||
|         var payload = Encoding.UTF8.GetBytes("[\"VU#360686\",\"VU#760160\"]"); | ||||
|  | ||||
|         var notes = CertCcSummaryParser.ParseNotes(payload); | ||||
|  | ||||
|         Assert.Equal(new[] { "VU#360686", "VU#760160" }, notes); | ||||
|     } | ||||
|  | ||||
|     [Fact] | ||||
|     public void ParseNotes_InvalidStructure_Throws() | ||||
|     { | ||||
|         var payload = Encoding.UTF8.GetBytes("\"invalid\""); | ||||
|  | ||||
|         Assert.Throws<JsonException>(() => CertCcSummaryParser.ParseNotes(payload)); | ||||
|     } | ||||
| } | ||||
| @@ -8,6 +8,9 @@ | ||||
|     <ProjectReference Include="../StellaOps.Feedser.Source.Common/StellaOps.Feedser.Source.Common.csproj" /> | ||||
|     <ProjectReference Include="../StellaOps.Feedser.Source.CertCc/StellaOps.Feedser.Source.CertCc.csproj" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="FluentAssertions" Version="6.12.0" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <None Update="Fixtures\*.json"> | ||||
|       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user