up
This commit is contained in:
		
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -0,0 +1,7 @@ | ||||
| <html> | ||||
|   <body> | ||||
|     <ul> | ||||
|       <li><a href="/materialy/uyazvimosti/bulletin-legacy.json.zip" title="Legacy Bulletin">Legacy Bulletin</a></li> | ||||
|     </ul> | ||||
|   </body> | ||||
| </html> | ||||
| @@ -3,5 +3,8 @@ | ||||
|     <ul> | ||||
|       <li><a href="/materialy/uyazvimosti/bulletin-sample.json.zip" title="Bulletin Sample">Bulletin Sample</a></li> | ||||
|     </ul> | ||||
|     <div class="pagination"> | ||||
|       <a class="pagination__link" href="/materialy/uyazvimosti/?PAGEN_1=2">2</a> | ||||
|     </div> | ||||
|   </body> | ||||
| </html> | ||||
|   | ||||
| @@ -3,11 +3,57 @@ | ||||
|     "advisoryKey": "BDU:2025-01001", | ||||
|     "affectedPackages": [ | ||||
|       { | ||||
|         "type": "vendor", | ||||
|         "identifier": "SampleSCADA <= 4.2", | ||||
|         "platform": null, | ||||
|         "versionRanges": [], | ||||
|         "normalizedVersions": [], | ||||
|         "type": "ics-vendor", | ||||
|         "identifier": "SampleVendor SampleGateway", | ||||
|         "platform": "Energy, ICS", | ||||
|         "versionRanges": [ | ||||
|           { | ||||
|             "fixedVersion": null, | ||||
|             "introducedVersion": "2.0", | ||||
|             "lastAffectedVersion": null, | ||||
|             "primitives": { | ||||
|               "evr": null, | ||||
|               "hasVendorExtensions": false, | ||||
|               "nevra": null, | ||||
|               "semVer": { | ||||
|                 "constraintExpression": ">= 2.0", | ||||
|                 "exactValue": null, | ||||
|                 "fixed": null, | ||||
|                 "fixedInclusive": false, | ||||
|                 "introduced": "2.0", | ||||
|                 "introducedInclusive": true, | ||||
|                 "lastAffected": null, | ||||
|                 "lastAffectedInclusive": false, | ||||
|                 "style": "greaterThanOrEqual" | ||||
|               }, | ||||
|               "vendorExtensions": null | ||||
|             }, | ||||
|             "provenance": { | ||||
|               "source": "ru-nkcki", | ||||
|               "kind": "package-range", | ||||
|               "value": "SampleVendor SampleGateway >= 2.0 All platforms", | ||||
|               "decisionReason": null, | ||||
|               "recordedAt": "2025-10-12T00:01:00+00:00", | ||||
|               "fieldMask": [ | ||||
|                 "affectedpackages[].versionranges[]" | ||||
|               ] | ||||
|             }, | ||||
|             "rangeExpression": ">= 2.0", | ||||
|             "rangeKind": "semver" | ||||
|           } | ||||
|         ], | ||||
|         "normalizedVersions": [ | ||||
|           { | ||||
|             "scheme": "semver", | ||||
|             "type": "gte", | ||||
|             "min": "2.0", | ||||
|             "minInclusive": true, | ||||
|             "max": null, | ||||
|             "maxInclusive": null, | ||||
|             "value": null, | ||||
|             "notes": "SampleVendor SampleGateway >= 2.0 All platforms" | ||||
|           } | ||||
|         ], | ||||
|         "statuses": [ | ||||
|           { | ||||
|             "provenance": { | ||||
| @@ -15,7 +61,7 @@ | ||||
|               "kind": "package-status", | ||||
|               "value": "patch_available", | ||||
|               "decisionReason": null, | ||||
|               "recordedAt": "2025-09-22T00:00:00+00:00", | ||||
|               "recordedAt": "2025-10-12T00:01:00+00:00", | ||||
|               "fieldMask": [ | ||||
|                 "affectedpackages[].statuses[]" | ||||
|               ] | ||||
| @@ -27,9 +73,89 @@ | ||||
|           { | ||||
|             "source": "ru-nkcki", | ||||
|             "kind": "package", | ||||
|             "value": "SampleSCADA <= 4.2", | ||||
|             "value": "SampleVendor SampleGateway >= 2.0 All platforms", | ||||
|             "decisionReason": null, | ||||
|             "recordedAt": "2025-09-22T00:00:00+00:00", | ||||
|             "recordedAt": "2025-10-12T00:01:00+00:00", | ||||
|             "fieldMask": [ | ||||
|               "affectedpackages[]" | ||||
|             ] | ||||
|           } | ||||
|         ] | ||||
|       }, | ||||
|       { | ||||
|         "type": "ics-vendor", | ||||
|         "identifier": "SampleVendor SampleSCADA", | ||||
|         "platform": "Energy, ICS", | ||||
|         "versionRanges": [ | ||||
|           { | ||||
|             "fixedVersion": null, | ||||
|             "introducedVersion": null, | ||||
|             "lastAffectedVersion": "4.2", | ||||
|             "primitives": { | ||||
|               "evr": null, | ||||
|               "hasVendorExtensions": false, | ||||
|               "nevra": null, | ||||
|               "semVer": { | ||||
|                 "constraintExpression": "<= 4.2", | ||||
|                 "exactValue": null, | ||||
|                 "fixed": null, | ||||
|                 "fixedInclusive": false, | ||||
|                 "introduced": null, | ||||
|                 "introducedInclusive": true, | ||||
|                 "lastAffected": "4.2", | ||||
|                 "lastAffectedInclusive": true, | ||||
|                 "style": "lessThanOrEqual" | ||||
|               }, | ||||
|               "vendorExtensions": null | ||||
|             }, | ||||
|             "provenance": { | ||||
|               "source": "ru-nkcki", | ||||
|               "kind": "package-range", | ||||
|               "value": "SampleVendor SampleSCADA <= 4.2", | ||||
|               "decisionReason": null, | ||||
|               "recordedAt": "2025-10-12T00:01:00+00:00", | ||||
|               "fieldMask": [ | ||||
|                 "affectedpackages[].versionranges[]" | ||||
|               ] | ||||
|             }, | ||||
|             "rangeExpression": "<= 4.2", | ||||
|             "rangeKind": "semver" | ||||
|           } | ||||
|         ], | ||||
|         "normalizedVersions": [ | ||||
|           { | ||||
|             "scheme": "semver", | ||||
|             "type": "lte", | ||||
|             "min": null, | ||||
|             "minInclusive": null, | ||||
|             "max": "4.2", | ||||
|             "maxInclusive": true, | ||||
|             "value": null, | ||||
|             "notes": "SampleVendor SampleSCADA <= 4.2" | ||||
|           } | ||||
|         ], | ||||
|         "statuses": [ | ||||
|           { | ||||
|             "provenance": { | ||||
|               "source": "ru-nkcki", | ||||
|               "kind": "package-status", | ||||
|               "value": "patch_available", | ||||
|               "decisionReason": null, | ||||
|               "recordedAt": "2025-10-12T00:01:00+00:00", | ||||
|               "fieldMask": [ | ||||
|                 "affectedpackages[].statuses[]" | ||||
|               ] | ||||
|             }, | ||||
|             "status": "fixed" | ||||
|           } | ||||
|         ], | ||||
|         "provenance": [ | ||||
|           { | ||||
|             "source": "ru-nkcki", | ||||
|             "kind": "package", | ||||
|             "value": "SampleVendor SampleSCADA <= 4.2", | ||||
|             "decisionReason": null, | ||||
|             "recordedAt": "2025-10-12T00:01:00+00:00", | ||||
|             "fieldMask": [ | ||||
|               "affectedpackages[]" | ||||
|             ] | ||||
| @@ -51,13 +177,29 @@ | ||||
|           "kind": "cvss", | ||||
|           "value": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H", | ||||
|           "decisionReason": null, | ||||
|           "recordedAt": "2025-09-22T00:00:00+00:00", | ||||
|           "recordedAt": "2025-10-12T00:01:00+00:00", | ||||
|           "fieldMask": [ | ||||
|             "cvssmetrics[]" | ||||
|           ] | ||||
|         }, | ||||
|         "vector": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H", | ||||
|         "version": "3.1" | ||||
|       }, | ||||
|       { | ||||
|         "baseScore": 6.4, | ||||
|         "baseSeverity": "medium", | ||||
|         "provenance": { | ||||
|           "source": "ru-nkcki", | ||||
|           "kind": "cvss", | ||||
|           "value": "CVSS:4.0/AV:N/AC:H/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H", | ||||
|           "decisionReason": null, | ||||
|           "recordedAt": "2025-10-12T00:01:00+00:00", | ||||
|           "fieldMask": [ | ||||
|             "cvssmetrics[]" | ||||
|           ] | ||||
|         }, | ||||
|         "vector": "CVSS:4.0/AV:N/AC:H/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H", | ||||
|         "version": "4.0" | ||||
|       } | ||||
|     ], | ||||
|     "exploitKnown": true, | ||||
| @@ -69,7 +211,7 @@ | ||||
|         "kind": "advisory", | ||||
|         "value": "BDU:2025-01001", | ||||
|         "decisionReason": null, | ||||
|         "recordedAt": "2025-09-22T00:00:00+00:00", | ||||
|         "recordedAt": "2025-10-12T00:01:00+00:00", | ||||
|         "fieldMask": [ | ||||
|           "advisory" | ||||
|         ] | ||||
| @@ -84,7 +226,7 @@ | ||||
|           "kind": "reference", | ||||
|           "value": "https://bdu.fstec.ru/vul/2025-01001", | ||||
|           "decisionReason": null, | ||||
|           "recordedAt": "2025-09-22T00:00:00+00:00", | ||||
|           "recordedAt": "2025-10-12T00:01:00+00:00", | ||||
|           "fieldMask": [ | ||||
|             "references[]" | ||||
|           ] | ||||
| @@ -100,23 +242,7 @@ | ||||
|           "kind": "reference", | ||||
|           "value": "https://cert.gov.ru/materialy/uyazvimosti/2025-01001", | ||||
|           "decisionReason": null, | ||||
|           "recordedAt": "2025-09-22T00:00:00+00:00", | ||||
|           "fieldMask": [ | ||||
|             "references[]" | ||||
|           ] | ||||
|         }, | ||||
|         "sourceTag": null, | ||||
|         "summary": null, | ||||
|         "url": "https://cert.gov.ru/materialy/uyazvimosti/2025-01001" | ||||
|       }, | ||||
|       { | ||||
|         "kind": "details", | ||||
|         "provenance": { | ||||
|           "source": "ru-nkcki", | ||||
|           "kind": "reference", | ||||
|           "value": "https://cert.gov.ru/materialy/uyazvimosti/2025-01001", | ||||
|           "decisionReason": null, | ||||
|           "recordedAt": "2025-09-22T00:00:00+00:00", | ||||
|           "recordedAt": "2025-10-12T00:01:00+00:00", | ||||
|           "fieldMask": [ | ||||
|             "references[]" | ||||
|           ] | ||||
| @@ -132,7 +258,7 @@ | ||||
|           "kind": "reference", | ||||
|           "value": "https://cwe.mitre.org/data/definitions/321.html", | ||||
|           "decisionReason": null, | ||||
|           "recordedAt": "2025-09-22T00:00:00+00:00", | ||||
|           "recordedAt": "2025-10-12T00:01:00+00:00", | ||||
|           "fieldMask": [ | ||||
|             "references[]" | ||||
|           ] | ||||
| @@ -148,7 +274,7 @@ | ||||
|           "kind": "reference", | ||||
|           "value": "https://vendor.example/advisories/sample-scada", | ||||
|           "decisionReason": null, | ||||
|           "recordedAt": "2025-09-22T00:00:00+00:00", | ||||
|           "recordedAt": "2025-10-12T00:01:00+00:00", | ||||
|           "fieldMask": [ | ||||
|             "references[]" | ||||
|           ] | ||||
| @@ -161,5 +287,209 @@ | ||||
|     "severity": "critical", | ||||
|     "summary": "Authenticated RCE in Sample SCADA", | ||||
|     "title": "Authenticated RCE in Sample SCADA" | ||||
|   }, | ||||
|   { | ||||
|     "advisoryKey": "BDU:2024-00011", | ||||
|     "affectedPackages": [ | ||||
|       { | ||||
|         "type": "cpe", | ||||
|         "identifier": "LegacyPanel", | ||||
|         "platform": "Software", | ||||
|         "versionRanges": [ | ||||
|           { | ||||
|             "fixedVersion": null, | ||||
|             "introducedVersion": null, | ||||
|             "lastAffectedVersion": "2.5", | ||||
|             "primitives": { | ||||
|               "evr": null, | ||||
|               "hasVendorExtensions": false, | ||||
|               "nevra": null, | ||||
|               "semVer": { | ||||
|                 "constraintExpression": "<= 2.5", | ||||
|                 "exactValue": null, | ||||
|                 "fixed": null, | ||||
|                 "fixedInclusive": false, | ||||
|                 "introduced": null, | ||||
|                 "introducedInclusive": true, | ||||
|                 "lastAffected": "2.5", | ||||
|                 "lastAffectedInclusive": true, | ||||
|                 "style": "lessThanOrEqual" | ||||
|               }, | ||||
|               "vendorExtensions": null | ||||
|             }, | ||||
|             "provenance": { | ||||
|               "source": "ru-nkcki", | ||||
|               "kind": "package-range", | ||||
|               "value": "LegacyPanel 1.0 - 2.5", | ||||
|               "decisionReason": null, | ||||
|               "recordedAt": "2025-10-12T00:01:00+00:00", | ||||
|               "fieldMask": [ | ||||
|                 "affectedpackages[].versionranges[]" | ||||
|               ] | ||||
|             }, | ||||
|             "rangeExpression": "<= 2.5", | ||||
|             "rangeKind": "semver" | ||||
|           }, | ||||
|           { | ||||
|             "fixedVersion": null, | ||||
|             "introducedVersion": "1.0", | ||||
|             "lastAffectedVersion": null, | ||||
|             "primitives": { | ||||
|               "evr": null, | ||||
|               "hasVendorExtensions": false, | ||||
|               "nevra": null, | ||||
|               "semVer": { | ||||
|                 "constraintExpression": ">= 1.0", | ||||
|                 "exactValue": null, | ||||
|                 "fixed": null, | ||||
|                 "fixedInclusive": false, | ||||
|                 "introduced": "1.0", | ||||
|                 "introducedInclusive": true, | ||||
|                 "lastAffected": null, | ||||
|                 "lastAffectedInclusive": false, | ||||
|                 "style": "greaterThanOrEqual" | ||||
|               }, | ||||
|               "vendorExtensions": null | ||||
|             }, | ||||
|             "provenance": { | ||||
|               "source": "ru-nkcki", | ||||
|               "kind": "package-range", | ||||
|               "value": "LegacyPanel 1.0 - 2.5", | ||||
|               "decisionReason": null, | ||||
|               "recordedAt": "2025-10-12T00:01:00+00:00", | ||||
|               "fieldMask": [ | ||||
|                 "affectedpackages[].versionranges[]" | ||||
|               ] | ||||
|             }, | ||||
|             "rangeExpression": ">= 1.0", | ||||
|             "rangeKind": "semver" | ||||
|           } | ||||
|         ], | ||||
|         "normalizedVersions": [ | ||||
|           { | ||||
|             "scheme": "semver", | ||||
|             "type": "gte", | ||||
|             "min": "1.0", | ||||
|             "minInclusive": true, | ||||
|             "max": null, | ||||
|             "maxInclusive": null, | ||||
|             "value": null, | ||||
|             "notes": "LegacyPanel 1.0 - 2.5" | ||||
|           }, | ||||
|           { | ||||
|             "scheme": "semver", | ||||
|             "type": "lte", | ||||
|             "min": null, | ||||
|             "minInclusive": null, | ||||
|             "max": "2.5", | ||||
|             "maxInclusive": true, | ||||
|             "value": null, | ||||
|             "notes": "LegacyPanel 1.0 - 2.5" | ||||
|           } | ||||
|         ], | ||||
|         "statuses": [ | ||||
|           { | ||||
|             "provenance": { | ||||
|               "source": "ru-nkcki", | ||||
|               "kind": "package-status", | ||||
|               "value": "affected", | ||||
|               "decisionReason": null, | ||||
|               "recordedAt": "2025-10-12T00:01:00+00:00", | ||||
|               "fieldMask": [ | ||||
|                 "affectedpackages[].statuses[]" | ||||
|               ] | ||||
|             }, | ||||
|             "status": "affected" | ||||
|           } | ||||
|         ], | ||||
|         "provenance": [ | ||||
|           { | ||||
|             "source": "ru-nkcki", | ||||
|             "kind": "package", | ||||
|             "value": "LegacyPanel 1.0 - 2.5", | ||||
|             "decisionReason": null, | ||||
|             "recordedAt": "2025-10-12T00:01:00+00:00", | ||||
|             "fieldMask": [ | ||||
|               "affectedpackages[]" | ||||
|             ] | ||||
|           } | ||||
|         ] | ||||
|       } | ||||
|     ], | ||||
|     "aliases": [ | ||||
|       "BDU:2024-00011" | ||||
|     ], | ||||
|     "credits": [], | ||||
|     "cvssMetrics": [ | ||||
|       { | ||||
|         "baseScore": 8.8, | ||||
|         "baseSeverity": "high", | ||||
|         "provenance": { | ||||
|           "source": "ru-nkcki", | ||||
|           "kind": "cvss", | ||||
|           "value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", | ||||
|           "decisionReason": null, | ||||
|           "recordedAt": "2025-10-12T00:01:00+00:00", | ||||
|           "fieldMask": [ | ||||
|             "cvssmetrics[]" | ||||
|           ] | ||||
|         }, | ||||
|         "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", | ||||
|         "version": "3.1" | ||||
|       } | ||||
|     ], | ||||
|     "exploitKnown": true, | ||||
|     "language": "ru", | ||||
|     "modified": "2024-08-02T00:00:00+00:00", | ||||
|     "provenance": [ | ||||
|       { | ||||
|         "source": "ru-nkcki", | ||||
|         "kind": "advisory", | ||||
|         "value": "BDU:2024-00011", | ||||
|         "decisionReason": null, | ||||
|         "recordedAt": "2025-10-12T00:01:00+00:00", | ||||
|         "fieldMask": [ | ||||
|           "advisory" | ||||
|         ] | ||||
|       } | ||||
|     ], | ||||
|     "published": "2024-08-01T00:00:00+00:00", | ||||
|     "references": [ | ||||
|       { | ||||
|         "kind": "details", | ||||
|         "provenance": { | ||||
|           "source": "ru-nkcki", | ||||
|           "kind": "reference", | ||||
|           "value": "https://bdu.fstec.ru/vul/2024-00011", | ||||
|           "decisionReason": null, | ||||
|           "recordedAt": "2025-10-12T00:01:00+00:00", | ||||
|           "fieldMask": [ | ||||
|             "references[]" | ||||
|           ] | ||||
|         }, | ||||
|         "sourceTag": "bdu", | ||||
|         "summary": null, | ||||
|         "url": "https://bdu.fstec.ru/vul/2024-00011" | ||||
|       }, | ||||
|       { | ||||
|         "kind": "details", | ||||
|         "provenance": { | ||||
|           "source": "ru-nkcki", | ||||
|           "kind": "reference", | ||||
|           "value": "https://cert.gov.ru/materialy/uyazvimosti/2024-00011", | ||||
|           "decisionReason": null, | ||||
|           "recordedAt": "2025-10-12T00:01:00+00:00", | ||||
|           "fieldMask": [ | ||||
|             "references[]" | ||||
|           ] | ||||
|         }, | ||||
|         "sourceTag": "ru-nkcki", | ||||
|         "summary": null, | ||||
|         "url": "https://cert.gov.ru/materialy/uyazvimosti/2024-00011" | ||||
|       } | ||||
|     ], | ||||
|     "severity": "high", | ||||
|     "summary": "Legacy panel overflow", | ||||
|     "title": "Legacy panel overflow" | ||||
|   } | ||||
| ] | ||||
| @@ -33,7 +33,9 @@ namespace StellaOps.Feedser.Source.Ru.Nkcki.Tests; | ||||
| public sealed class RuNkckiConnectorTests : IAsyncLifetime | ||||
| { | ||||
|     private static readonly Uri ListingUri = new("https://cert.gov.ru/materialy/uyazvimosti/"); | ||||
|     private static readonly Uri ListingPage2Uri = new("https://cert.gov.ru/materialy/uyazvimosti/?PAGEN_1=2"); | ||||
|     private static readonly Uri BulletinUri = new("https://cert.gov.ru/materialy/uyazvimosti/bulletin-sample.json.zip"); | ||||
|     private static readonly Uri LegacyBulletinUri = new("https://cert.gov.ru/materialy/uyazvimosti/bulletin-legacy.json.zip"); | ||||
|  | ||||
|     private readonly MongoIntegrationFixture _fixture; | ||||
|     private readonly FakeTimeProvider _timeProvider; | ||||
| @@ -60,13 +62,13 @@ public sealed class RuNkckiConnectorTests : IAsyncLifetime | ||||
|  | ||||
|         var advisoryStore = provider.GetRequiredService<IAdvisoryStore>(); | ||||
|         var advisories = await advisoryStore.GetRecentAsync(10, CancellationToken.None); | ||||
|         Assert.Single(advisories); | ||||
|         Assert.Equal(2, advisories.Count); | ||||
|  | ||||
|         var snapshot = SnapshotSerializer.ToSnapshot(advisories); | ||||
|         WriteOrAssertSnapshot(snapshot, "nkcki-advisories.snapshot.json"); | ||||
|  | ||||
|         var documentStore = provider.GetRequiredService<IDocumentStore>(); | ||||
|         var document = await documentStore.FindBySourceAndUriAsync(RuNkckiConnectorPlugin.SourceName, "https://cert.gov.ru/materialy/uyazvimosti/BDU:2025-01001", CancellationToken.None); | ||||
|         var document = await documentStore.FindBySourceAndUriAsync(RuNkckiConnectorPlugin.SourceName, "https://cert.gov.ru/materialy/uyazvimosti/2025-01001", CancellationToken.None); | ||||
|         Assert.NotNull(document); | ||||
|         Assert.Equal(DocumentStatuses.Mapped, document!.Status); | ||||
|  | ||||
| @@ -85,18 +87,25 @@ public sealed class RuNkckiConnectorTests : IAsyncLifetime | ||||
|  | ||||
|         var connector = provider.GetRequiredService<RuNkckiConnector>(); | ||||
|         await connector.FetchAsync(provider, CancellationToken.None); | ||||
|         await connector.ParseAsync(provider, CancellationToken.None); | ||||
|         await connector.MapAsync(provider, CancellationToken.None); | ||||
|  | ||||
|         _handler.Clear(); | ||||
|         _handler.AddResponse(ListingUri, () => new HttpResponseMessage(HttpStatusCode.InternalServerError) | ||||
|         for (var i = 0; i < 3; i++) | ||||
|         { | ||||
|             Content = new StringContent("error", Encoding.UTF8, "text/plain"), | ||||
|         }); | ||||
|             _handler.AddResponse(ListingUri, () => new HttpResponseMessage(HttpStatusCode.InternalServerError) | ||||
|             { | ||||
|                 Content = new StringContent("error", Encoding.UTF8, "text/plain"), | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         var advisoryStore = provider.GetRequiredService<IAdvisoryStore>(); | ||||
|         var before = await advisoryStore.GetRecentAsync(10, CancellationToken.None); | ||||
|         Assert.NotEmpty(before); | ||||
|         Assert.Equal(2, before.Count); | ||||
|  | ||||
|         await connector.FetchAsync(provider, CancellationToken.None); | ||||
|         await connector.ParseAsync(provider, CancellationToken.None); | ||||
|         await connector.MapAsync(provider, CancellationToken.None); | ||||
|  | ||||
|         var after = await advisoryStore.GetRecentAsync(10, CancellationToken.None); | ||||
|         Assert.Equal(before.Select(advisory => advisory.AdvisoryKey).OrderBy(static key => key), after.Select(advisory => advisory.AdvisoryKey).OrderBy(static key => key)); | ||||
| @@ -106,18 +115,7 @@ public sealed class RuNkckiConnectorTests : IAsyncLifetime | ||||
|  | ||||
|     private async Task<ServiceProvider> BuildServiceProviderAsync() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             await _fixture.Client.DropDatabaseAsync(_fixture.Database.DatabaseNamespace.DatabaseName); | ||||
|         } | ||||
|         catch (MongoConnectionException ex) | ||||
|         { | ||||
|             Assert.Skip($"Mongo runner unavailable: {ex.Message}"); | ||||
|         } | ||||
|         catch (TimeoutException ex) | ||||
|         { | ||||
|             Assert.Skip($"Mongo runner unavailable: {ex.Message}"); | ||||
|         } | ||||
|         await _fixture.Client.DropDatabaseAsync(_fixture.Database.DatabaseNamespace.DatabaseName); | ||||
|  | ||||
|         _handler.Clear(); | ||||
|  | ||||
| @@ -138,7 +136,9 @@ public sealed class RuNkckiConnectorTests : IAsyncLifetime | ||||
|             options.BaseAddress = new Uri("https://cert.gov.ru/"); | ||||
|             options.ListingPath = "/materialy/uyazvimosti/"; | ||||
|             options.MaxBulletinsPerFetch = 2; | ||||
|             options.MaxListingPagesPerFetch = 2; | ||||
|             options.MaxVulnerabilitiesPerFetch = 50; | ||||
|             options.ListingCacheDuration = TimeSpan.Zero; | ||||
|             var cacheRoot = Path.Combine(Path.GetTempPath(), "stellaops-tests", _fixture.Database.DatabaseNamespace.DatabaseName); | ||||
|             Directory.CreateDirectory(cacheRoot); | ||||
|             options.CacheDirectory = Path.Combine(cacheRoot, "ru-nkcki"); | ||||
| @@ -150,23 +150,10 @@ public sealed class RuNkckiConnectorTests : IAsyncLifetime | ||||
|             builderOptions.HttpMessageHandlerBuilderActions.Add(builder => builder.PrimaryHandler = _handler); | ||||
|         }); | ||||
|  | ||||
|         try | ||||
|         { | ||||
|             var provider = services.BuildServiceProvider(); | ||||
|             var bootstrapper = provider.GetRequiredService<MongoBootstrapper>(); | ||||
|             await bootstrapper.InitializeAsync(CancellationToken.None); | ||||
|             return provider; | ||||
|         } | ||||
|         catch (MongoConnectionException ex) | ||||
|         { | ||||
|             Assert.Skip($"Mongo runner unavailable: {ex.Message}"); | ||||
|             throw; // Unreachable | ||||
|         } | ||||
|         catch (TimeoutException ex) | ||||
|         { | ||||
|             Assert.Skip($"Mongo runner unavailable: {ex.Message}"); | ||||
|             throw; | ||||
|         } | ||||
|         var provider = services.BuildServiceProvider(); | ||||
|         var bootstrapper = provider.GetRequiredService<MongoBootstrapper>(); | ||||
|         await bootstrapper.InitializeAsync(CancellationToken.None); | ||||
|         return provider; | ||||
|     } | ||||
|  | ||||
|     private void SeedListingAndBulletin() | ||||
| @@ -174,6 +161,9 @@ public sealed class RuNkckiConnectorTests : IAsyncLifetime | ||||
|         var listingHtml = ReadFixture("listing.html"); | ||||
|         _handler.AddTextResponse(ListingUri, listingHtml, "text/html"); | ||||
|  | ||||
|         var listingPage2Html = ReadFixture("listing-page2.html"); | ||||
|         _handler.AddTextResponse(ListingPage2Uri, listingPage2Html, "text/html"); | ||||
|  | ||||
|         var bulletinBytes = ReadBulletinFixture("bulletin-sample.json.zip"); | ||||
|         _handler.AddResponse(BulletinUri, () => | ||||
|         { | ||||
| @@ -185,6 +175,18 @@ public sealed class RuNkckiConnectorTests : IAsyncLifetime | ||||
|             response.Content.Headers.LastModified = new DateTimeOffset(2025, 9, 22, 0, 0, 0, TimeSpan.Zero); | ||||
|             return response; | ||||
|         }); | ||||
|  | ||||
|         var legacyBytes = ReadBulletinFixture("bulletin-legacy.json.zip"); | ||||
|         _handler.AddResponse(LegacyBulletinUri, () => | ||||
|         { | ||||
|             var response = new HttpResponseMessage(HttpStatusCode.OK) | ||||
|             { | ||||
|                 Content = new ByteArrayContent(legacyBytes), | ||||
|             }; | ||||
|             response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/zip"); | ||||
|             response.Content.Headers.LastModified = new DateTimeOffset(2024, 8, 2, 0, 0, 0, TimeSpan.Zero); | ||||
|             return response; | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     private static bool IsEmptyArray(BsonDocument document, string field) | ||||
|   | ||||
| @@ -18,14 +18,24 @@ public sealed class RuNkckiJsonParserTests | ||||
|   "patch_available": true, | ||||
|   "description": "Test description", | ||||
|   "cwe": {"cwe_number": 79, "cwe_description": "Cross-site scripting"}, | ||||
|   "product_category": "Web", | ||||
|   "mitigation": "Apply update", | ||||
|   "vulnerable_software": {"software_text": "ExampleApp 1.0", "cpe": false}, | ||||
|   "cvss": {"cvss_score": 8.8, "cvss_vector": "AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", "cvss_score_v4": 5.5, "cvss_vector_v4": "AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H"}, | ||||
|   "product_category": ["Web", "CMS"], | ||||
|   "mitigation": ["Apply update", "Review configuration"], | ||||
|   "vulnerable_software": { | ||||
|     "software_text": "ExampleCMS <= 1.0", | ||||
|     "software": [{"vendor": "Example", "name": "ExampleCMS", "version": "<= 1.0"}], | ||||
|     "cpe": false | ||||
|   }, | ||||
|   "cvss": { | ||||
|     "cvss_score": 8.8, | ||||
|     "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", | ||||
|     "cvss_score_v4": 5.5, | ||||
|     "cvss_vector_v4": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H" | ||||
|   }, | ||||
|   "impact": "ACE", | ||||
|   "method_of_exploitation": "Special request", | ||||
|   "user_interaction": false, | ||||
|   "urls": ["https://example.com/advisory", "https://cert.gov.ru/materialy/uyazvimosti/2025-00001"] | ||||
|   "urls": ["https://example.com/advisory", {"url": "https://cert.gov.ru/materialy/uyazvimosti/2025-00001"}], | ||||
|   "tags": ["cms"] | ||||
| } | ||||
| """; | ||||
|  | ||||
| @@ -35,9 +45,16 @@ public sealed class RuNkckiJsonParserTests | ||||
|         Assert.Equal("BDU:2025-00001", dto.FstecId); | ||||
|         Assert.Equal("CVE-2025-0001", dto.MitreId); | ||||
|         Assert.Equal(8.8, dto.CvssScore); | ||||
|         Assert.Equal("AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", dto.CvssVector); | ||||
|         Assert.Equal("CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", dto.CvssVector); | ||||
|         Assert.True(dto.PatchAvailable); | ||||
|         Assert.Equal(79, dto.Cwe?.Number); | ||||
|         Assert.Contains("Web", dto.ProductCategories); | ||||
|         Assert.Contains("CMS", dto.ProductCategories); | ||||
|         Assert.Single(dto.VulnerableSoftwareEntries); | ||||
|         var entry = dto.VulnerableSoftwareEntries[0]; | ||||
|         Assert.Equal("Example ExampleCMS", entry.Identifier); | ||||
|         Assert.Contains("<= 1.0", entry.RangeExpressions); | ||||
|         Assert.Equal(2, dto.Urls.Length); | ||||
|         Assert.Contains("cms", dto.Tags); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -14,6 +14,12 @@ public sealed class RuNkckiMapperTests | ||||
|     [Fact] | ||||
|     public void Map_ConstructsCanonicalAdvisory() | ||||
|     { | ||||
|         var softwareEntries = ImmutableArray.Create( | ||||
|             new RuNkckiSoftwareEntry( | ||||
|                 "SampleVendor SampleSCADA", | ||||
|                 "SampleVendor SampleSCADA <= 4.2", | ||||
|                 ImmutableArray.Create("<= 4.2"))); | ||||
|  | ||||
|         var dto = new RuNkckiVulnerabilityDto( | ||||
|             FstecId: "BDU:2025-00001", | ||||
|             MitreId: "CVE-2025-0001", | ||||
| @@ -23,18 +29,20 @@ public sealed class RuNkckiMapperTests | ||||
|             PatchAvailable: true, | ||||
|             Description: "Test NKCKI vulnerability", | ||||
|             Cwe: new RuNkckiCweDto(79, "Cross-site scripting"), | ||||
|             ProductCategory: "Web", | ||||
|             ProductCategories: ImmutableArray.Create("ICS", "Automation"), | ||||
|             Mitigation: "Apply update", | ||||
|             VulnerableSoftwareText: "ExampleApp <= 1.0", | ||||
|             VulnerableSoftwareText: null, | ||||
|             VulnerableSoftwareHasCpe: false, | ||||
|             VulnerableSoftwareEntries: softwareEntries, | ||||
|             CvssScore: 8.8, | ||||
|             CvssVector: "AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", | ||||
|             CvssScoreV4: null, | ||||
|             CvssVectorV4: null, | ||||
|             CvssVector: "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", | ||||
|             CvssScoreV4: 6.4, | ||||
|             CvssVectorV4: "CVSS:4.0/AV:N/AC:H/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H", | ||||
|             Impact: "ACE", | ||||
|             MethodOfExploitation: "Special request", | ||||
|             UserInteraction: false, | ||||
|             Urls: ImmutableArray.Create("https://example.com/advisory")); | ||||
|             Urls: ImmutableArray.Create("https://example.com/advisory", "https://cert.gov.ru/materialy/uyazvimosti/2025-00001"), | ||||
|             Tags: ImmutableArray.Create("ics")); | ||||
|  | ||||
|         var document = new DocumentRecord( | ||||
|             Guid.NewGuid(), | ||||
| @@ -62,7 +70,12 @@ public sealed class RuNkckiMapperTests | ||||
|         Assert.Equal("critical", advisory.Severity); | ||||
|         Assert.True(advisory.ExploitKnown); | ||||
|         Assert.Single(advisory.AffectedPackages); | ||||
|         Assert.Single(advisory.CvssMetrics); | ||||
|         var package = advisory.AffectedPackages[0]; | ||||
|         Assert.Equal(AffectedPackageTypes.IcsVendor, package.Type); | ||||
|         Assert.Single(package.NormalizedVersions); | ||||
|         Assert.Equal(2, advisory.CvssMetrics.Length); | ||||
|         Assert.Contains(advisory.CvssMetrics, metric => metric.Version == "4.0"); | ||||
|         Assert.Equal("critical", advisory.Severity); | ||||
|         Assert.Contains(advisory.References, reference => reference.Url.Contains("example.com", StringComparison.OrdinalIgnoreCase)); | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user