Restructure solution layout by module
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
{
|
||||
"advisoryKey": "GHSA-qqqq-wwww-eeee",
|
||||
"affectedPackages": [
|
||||
{
|
||||
"type": "semver",
|
||||
"identifier": "npm:conflict/package",
|
||||
"platform": null,
|
||||
"versionRanges": [
|
||||
{
|
||||
"fixedVersion": "1.4.0",
|
||||
"introducedVersion": null,
|
||||
"lastAffectedVersion": null,
|
||||
"primitives": {
|
||||
"evr": null,
|
||||
"hasVendorExtensions": true,
|
||||
"nevra": null,
|
||||
"semVer": {
|
||||
"constraintExpression": "< 1.4.0",
|
||||
"exactValue": null,
|
||||
"fixed": "1.4.0",
|
||||
"fixedInclusive": false,
|
||||
"introduced": null,
|
||||
"introducedInclusive": true,
|
||||
"lastAffected": null,
|
||||
"lastAffectedInclusive": false,
|
||||
"style": "lessThan"
|
||||
},
|
||||
"vendorExtensions": {
|
||||
"ecosystem": "npm",
|
||||
"package": "conflict/package"
|
||||
}
|
||||
},
|
||||
"provenance": {
|
||||
"source": "ghsa",
|
||||
"kind": "affected-range",
|
||||
"value": "npm:conflict/package",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-03-04T08:30:00+00:00",
|
||||
"fieldMask": [
|
||||
"affectedpackages[].versionranges[]"
|
||||
]
|
||||
},
|
||||
"rangeExpression": "< 1.4.0",
|
||||
"rangeKind": "semver"
|
||||
}
|
||||
],
|
||||
"normalizedVersions": [
|
||||
{
|
||||
"scheme": "semver",
|
||||
"type": "lt",
|
||||
"min": null,
|
||||
"minInclusive": null,
|
||||
"max": "1.4.0",
|
||||
"maxInclusive": false,
|
||||
"value": null,
|
||||
"notes": "ghsa:npm:conflict/package"
|
||||
}
|
||||
],
|
||||
"statuses": [
|
||||
{
|
||||
"provenance": {
|
||||
"source": "ghsa",
|
||||
"kind": "affected-status",
|
||||
"value": "npm:conflict/package",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-03-04T08:30:00+00:00",
|
||||
"fieldMask": [
|
||||
"affectedpackages[].statuses[]"
|
||||
]
|
||||
},
|
||||
"status": "affected"
|
||||
}
|
||||
],
|
||||
"provenance": [
|
||||
{
|
||||
"source": "ghsa",
|
||||
"kind": "affected",
|
||||
"value": "npm:conflict/package",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-03-04T08:30:00+00:00",
|
||||
"fieldMask": [
|
||||
"affectedpackages[]"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"aliases": [
|
||||
"CVE-2025-4242",
|
||||
"GHSA-qqqq-wwww-eeee"
|
||||
],
|
||||
"canonicalMetricId": "ghsa:severity/high",
|
||||
"credits": [
|
||||
{
|
||||
"displayName": "maintainer-team",
|
||||
"role": "remediation_developer",
|
||||
"contacts": [
|
||||
"https://github.com/conflict/package"
|
||||
],
|
||||
"provenance": {
|
||||
"source": "ghsa",
|
||||
"kind": "credit",
|
||||
"value": "maintainer-team",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-03-04T08:30:00+00:00",
|
||||
"fieldMask": [
|
||||
"credits[]"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"displayName": "security-researcher",
|
||||
"role": "reporter",
|
||||
"contacts": [
|
||||
"https://github.com/sec-researcher"
|
||||
],
|
||||
"provenance": {
|
||||
"source": "ghsa",
|
||||
"kind": "credit",
|
||||
"value": "security-researcher",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-03-04T08:30:00+00:00",
|
||||
"fieldMask": [
|
||||
"credits[]"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"cvssMetrics": [],
|
||||
"cwes": [],
|
||||
"description": "Container escape vulnerability allowing privilege escalation in conflict-package.",
|
||||
"exploitKnown": false,
|
||||
"language": "en",
|
||||
"modified": "2025-03-02T12:00:00+00:00",
|
||||
"provenance": [
|
||||
{
|
||||
"source": "ghsa",
|
||||
"kind": "document",
|
||||
"value": "https://github.com/advisories/GHSA-qqqq-wwww-eeee",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-03-03T18:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"advisory"
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "ghsa",
|
||||
"kind": "mapping",
|
||||
"value": "GHSA-qqqq-wwww-eeee",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-03-04T08:30:00+00:00",
|
||||
"fieldMask": [
|
||||
"advisory"
|
||||
]
|
||||
}
|
||||
],
|
||||
"published": "2025-02-25T00:00:00+00:00",
|
||||
"references": [
|
||||
{
|
||||
"kind": "advisory",
|
||||
"provenance": {
|
||||
"source": "ghsa",
|
||||
"kind": "reference",
|
||||
"value": "https://github.com/advisories/GHSA-qqqq-wwww-eeee",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-03-04T08:30:00+00:00",
|
||||
"fieldMask": [
|
||||
"references[]"
|
||||
]
|
||||
},
|
||||
"sourceTag": null,
|
||||
"summary": null,
|
||||
"url": "https://github.com/advisories/GHSA-qqqq-wwww-eeee"
|
||||
},
|
||||
{
|
||||
"kind": "fix",
|
||||
"provenance": {
|
||||
"source": "ghsa",
|
||||
"kind": "reference",
|
||||
"value": "https://github.com/conflict/package/releases/tag/v1.4.0",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-03-04T08:30:00+00:00",
|
||||
"fieldMask": [
|
||||
"references[]"
|
||||
]
|
||||
},
|
||||
"sourceTag": null,
|
||||
"summary": null,
|
||||
"url": "https://github.com/conflict/package/releases/tag/v1.4.0"
|
||||
}
|
||||
],
|
||||
"severity": "high",
|
||||
"summary": "Container escape in conflict-package",
|
||||
"title": "Container escape in conflict-package"
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
{
|
||||
"advisoryKey": "GHSA-credit-parity",
|
||||
"affectedPackages": [],
|
||||
"aliases": [
|
||||
"CVE-2025-5555",
|
||||
"GHSA-credit-parity"
|
||||
],
|
||||
"credits": [
|
||||
{
|
||||
"displayName": "Bob Maintainer",
|
||||
"role": "remediation_developer",
|
||||
"contacts": [
|
||||
"https://github.com/acme/bob-maintainer"
|
||||
],
|
||||
"provenance": {
|
||||
"source": "ghsa",
|
||||
"kind": "credit",
|
||||
"value": "ghsa:bob-maintainer",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-10-10T15:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"credits[]"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"displayName": "Alice Researcher",
|
||||
"role": "reporter",
|
||||
"contacts": [
|
||||
"mailto:alice.researcher@example.com"
|
||||
],
|
||||
"provenance": {
|
||||
"source": "ghsa",
|
||||
"kind": "credit",
|
||||
"value": "ghsa:alice-researcher",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-10-10T15:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"credits[]"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"cvssMetrics": [],
|
||||
"exploitKnown": false,
|
||||
"language": "en",
|
||||
"modified": "2025-10-10T12:00:00+00:00",
|
||||
"provenance": [
|
||||
{
|
||||
"source": "ghsa",
|
||||
"kind": "document",
|
||||
"value": "security/advisories/GHSA-credit-parity",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-10-10T15:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"advisory"
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "ghsa",
|
||||
"kind": "mapping",
|
||||
"value": "GHSA-credit-parity",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-10-10T15:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"advisory"
|
||||
]
|
||||
}
|
||||
],
|
||||
"published": "2025-10-09T18:30:00+00:00",
|
||||
"references": [
|
||||
{
|
||||
"kind": "patch",
|
||||
"provenance": {
|
||||
"source": "ghsa",
|
||||
"kind": "reference",
|
||||
"value": "https://example.com/ghsa/patch",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-10-10T15:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"references[]"
|
||||
]
|
||||
},
|
||||
"sourceTag": null,
|
||||
"summary": null,
|
||||
"url": "https://example.com/ghsa/patch"
|
||||
},
|
||||
{
|
||||
"kind": "advisory",
|
||||
"provenance": {
|
||||
"source": "ghsa",
|
||||
"kind": "reference",
|
||||
"value": "https://github.com/advisories/GHSA-credit-parity",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-10-10T15:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"references[]"
|
||||
]
|
||||
},
|
||||
"sourceTag": null,
|
||||
"summary": null,
|
||||
"url": "https://github.com/advisories/GHSA-credit-parity"
|
||||
}
|
||||
],
|
||||
"severity": "medium",
|
||||
"summary": "Credit parity regression fixture",
|
||||
"title": "Credit parity regression fixture"
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
{
|
||||
"advisoryKey": "CVE-2025-5555",
|
||||
"affectedPackages": [],
|
||||
"aliases": [
|
||||
"CVE-2025-5555",
|
||||
"GHSA-credit-parity"
|
||||
],
|
||||
"credits": [
|
||||
{
|
||||
"displayName": "Bob Maintainer",
|
||||
"role": "remediation_developer",
|
||||
"contacts": [
|
||||
"https://github.com/acme/bob-maintainer"
|
||||
],
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "credit",
|
||||
"value": "nvd:bob-maintainer",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-10-10T15:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"credits[]"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"displayName": "Alice Researcher",
|
||||
"role": "reporter",
|
||||
"contacts": [
|
||||
"mailto:alice.researcher@example.com"
|
||||
],
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "credit",
|
||||
"value": "nvd:alice-researcher",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-10-10T15:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"credits[]"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"cvssMetrics": [],
|
||||
"exploitKnown": false,
|
||||
"language": "en",
|
||||
"modified": "2025-10-10T12:00:00+00:00",
|
||||
"provenance": [
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "document",
|
||||
"value": "https://services.nvd.nist.gov/vuln/detail/CVE-2025-5555",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-10-10T15:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"advisory"
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "nvd",
|
||||
"kind": "mapping",
|
||||
"value": "CVE-2025-5555",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-10-10T15:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"advisory"
|
||||
]
|
||||
}
|
||||
],
|
||||
"published": "2025-10-09T18:30:00+00:00",
|
||||
"references": [
|
||||
{
|
||||
"kind": "report",
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "reference",
|
||||
"value": "https://example.com/nvd/reference",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-10-10T15:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"references[]"
|
||||
]
|
||||
},
|
||||
"sourceTag": null,
|
||||
"summary": null,
|
||||
"url": "https://example.com/nvd/reference"
|
||||
},
|
||||
{
|
||||
"kind": "advisory",
|
||||
"provenance": {
|
||||
"source": "nvd",
|
||||
"kind": "reference",
|
||||
"value": "https://services.nvd.nist.gov/vuln/detail/CVE-2025-5555",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-10-10T15:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"references[]"
|
||||
]
|
||||
},
|
||||
"sourceTag": null,
|
||||
"summary": null,
|
||||
"url": "https://services.nvd.nist.gov/vuln/detail/CVE-2025-5555"
|
||||
}
|
||||
],
|
||||
"severity": "medium",
|
||||
"summary": "Credit parity regression fixture",
|
||||
"title": "Credit parity regression fixture"
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
{
|
||||
"advisoryKey": "GHSA-credit-parity",
|
||||
"affectedPackages": [],
|
||||
"aliases": [
|
||||
"CVE-2025-5555",
|
||||
"GHSA-credit-parity"
|
||||
],
|
||||
"credits": [
|
||||
{
|
||||
"displayName": "Bob Maintainer",
|
||||
"role": "remediation_developer",
|
||||
"contacts": [
|
||||
"https://github.com/acme/bob-maintainer"
|
||||
],
|
||||
"provenance": {
|
||||
"source": "osv",
|
||||
"kind": "credit",
|
||||
"value": "osv:bob-maintainer",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-10-10T15:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"credits[]"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"displayName": "Alice Researcher",
|
||||
"role": "reporter",
|
||||
"contacts": [
|
||||
"mailto:alice.researcher@example.com"
|
||||
],
|
||||
"provenance": {
|
||||
"source": "osv",
|
||||
"kind": "credit",
|
||||
"value": "osv:alice-researcher",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-10-10T15:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"credits[]"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"cvssMetrics": [],
|
||||
"exploitKnown": false,
|
||||
"language": "en",
|
||||
"modified": "2025-10-10T12:00:00+00:00",
|
||||
"provenance": [
|
||||
{
|
||||
"source": "osv",
|
||||
"kind": "document",
|
||||
"value": "https://osv.dev/vulnerability/GHSA-credit-parity",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-10-10T15:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"advisory"
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "osv",
|
||||
"kind": "mapping",
|
||||
"value": "GHSA-credit-parity",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-10-10T15:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"advisory"
|
||||
]
|
||||
}
|
||||
],
|
||||
"published": "2025-10-09T18:30:00+00:00",
|
||||
"references": [
|
||||
{
|
||||
"kind": "advisory",
|
||||
"provenance": {
|
||||
"source": "osv",
|
||||
"kind": "reference",
|
||||
"value": "https://github.com/advisories/GHSA-credit-parity",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-10-10T15:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"references[]"
|
||||
]
|
||||
},
|
||||
"sourceTag": null,
|
||||
"summary": null,
|
||||
"url": "https://github.com/advisories/GHSA-credit-parity"
|
||||
},
|
||||
{
|
||||
"kind": "advisory",
|
||||
"provenance": {
|
||||
"source": "osv",
|
||||
"kind": "reference",
|
||||
"value": "https://osv.dev/vulnerability/GHSA-credit-parity",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2025-10-10T15:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"references[]"
|
||||
]
|
||||
},
|
||||
"sourceTag": null,
|
||||
"summary": null,
|
||||
"url": "https://osv.dev/vulnerability/GHSA-credit-parity"
|
||||
}
|
||||
],
|
||||
"severity": "medium",
|
||||
"summary": "Credit parity regression fixture",
|
||||
"title": "Credit parity regression fixture"
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
{
|
||||
"advisoryKey": "GHSA-xxxx-yyyy-zzzz",
|
||||
"affectedPackages": [
|
||||
{
|
||||
"type": "semver",
|
||||
"identifier": "npm:example/package",
|
||||
"platform": null,
|
||||
"versionRanges": [
|
||||
{
|
||||
"fixedVersion": "1.5.0",
|
||||
"introducedVersion": null,
|
||||
"lastAffectedVersion": null,
|
||||
"primitives": {
|
||||
"evr": null,
|
||||
"hasVendorExtensions": true,
|
||||
"nevra": null,
|
||||
"semVer": {
|
||||
"constraintExpression": "< 1.5.0",
|
||||
"exactValue": null,
|
||||
"fixed": "1.5.0",
|
||||
"fixedInclusive": false,
|
||||
"introduced": null,
|
||||
"introducedInclusive": true,
|
||||
"lastAffected": null,
|
||||
"lastAffectedInclusive": false,
|
||||
"style": "lessThan"
|
||||
},
|
||||
"vendorExtensions": {
|
||||
"ecosystem": "npm",
|
||||
"package": "example/package"
|
||||
}
|
||||
},
|
||||
"provenance": {
|
||||
"source": "ghsa",
|
||||
"kind": "affected-range",
|
||||
"value": "npm:example/package",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-10-02T00:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"affectedpackages[].versionranges[]"
|
||||
]
|
||||
},
|
||||
"rangeExpression": "< 1.5.0",
|
||||
"rangeKind": "semver"
|
||||
}
|
||||
],
|
||||
"normalizedVersions": [
|
||||
{
|
||||
"scheme": "semver",
|
||||
"type": "lt",
|
||||
"min": null,
|
||||
"minInclusive": null,
|
||||
"max": "1.5.0",
|
||||
"maxInclusive": false,
|
||||
"value": null,
|
||||
"notes": "ghsa:npm:example/package"
|
||||
}
|
||||
],
|
||||
"statuses": [
|
||||
{
|
||||
"provenance": {
|
||||
"source": "ghsa",
|
||||
"kind": "affected-status",
|
||||
"value": "npm:example/package",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-10-02T00:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"affectedpackages[].statuses[]"
|
||||
]
|
||||
},
|
||||
"status": "affected"
|
||||
}
|
||||
],
|
||||
"provenance": [
|
||||
{
|
||||
"source": "ghsa",
|
||||
"kind": "affected",
|
||||
"value": "npm:example/package",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-10-02T00:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"affectedpackages[]"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"aliases": [
|
||||
"CVE-2024-1111",
|
||||
"GHSA-xxxx-yyyy-zzzz"
|
||||
],
|
||||
"canonicalMetricId": "3.1|CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"credits": [
|
||||
{
|
||||
"displayName": "maintainer-team",
|
||||
"role": "remediation_developer",
|
||||
"contacts": [
|
||||
"https://github.com/maintainer-team"
|
||||
],
|
||||
"provenance": {
|
||||
"source": "ghsa",
|
||||
"kind": "credit",
|
||||
"value": "maintainer-team",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-10-02T00:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"credits[]"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"displayName": "security-reporter",
|
||||
"role": "reporter",
|
||||
"contacts": [
|
||||
"https://github.com/security-reporter"
|
||||
],
|
||||
"provenance": {
|
||||
"source": "ghsa",
|
||||
"kind": "credit",
|
||||
"value": "security-reporter",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-10-02T00:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"credits[]"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"cvssMetrics": [
|
||||
{
|
||||
"baseScore": 9.8,
|
||||
"baseSeverity": "critical",
|
||||
"provenance": {
|
||||
"source": "ghsa",
|
||||
"kind": "cvss",
|
||||
"value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-10-02T00:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"cvssmetrics[]"
|
||||
]
|
||||
},
|
||||
"vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"version": "3.1"
|
||||
}
|
||||
],
|
||||
"cwes": [
|
||||
{
|
||||
"taxonomy": "cwe",
|
||||
"identifier": "CWE-79",
|
||||
"name": "Cross-site Scripting",
|
||||
"uri": "https://cwe.mitre.org/data/definitions/79.html",
|
||||
"provenance": [
|
||||
{
|
||||
"source": "unknown",
|
||||
"kind": "unspecified",
|
||||
"value": null,
|
||||
"decisionReason": null,
|
||||
"recordedAt": "1970-01-01T00:00:00+00:00",
|
||||
"fieldMask": []
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "An example advisory describing a supply chain risk.",
|
||||
"exploitKnown": false,
|
||||
"language": "en",
|
||||
"modified": "2024-09-20T12:00:00+00:00",
|
||||
"provenance": [
|
||||
{
|
||||
"source": "ghsa",
|
||||
"kind": "document",
|
||||
"value": "security/advisories/GHSA-xxxx-yyyy-zzzz",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-10-02T00:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"advisory"
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "ghsa",
|
||||
"kind": "mapping",
|
||||
"value": "GHSA-xxxx-yyyy-zzzz",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-10-02T00:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"advisory"
|
||||
]
|
||||
}
|
||||
],
|
||||
"published": "2024-09-10T13:00:00+00:00",
|
||||
"references": [
|
||||
{
|
||||
"kind": "fix",
|
||||
"provenance": {
|
||||
"source": "ghsa",
|
||||
"kind": "reference",
|
||||
"value": "https://example.com/patch",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-10-02T00:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"references[]"
|
||||
]
|
||||
},
|
||||
"sourceTag": "Vendor Fix",
|
||||
"summary": null,
|
||||
"url": "https://example.com/patch"
|
||||
},
|
||||
{
|
||||
"kind": "advisory",
|
||||
"provenance": {
|
||||
"source": "ghsa",
|
||||
"kind": "reference",
|
||||
"value": "https://github.com/advisories/GHSA-xxxx-yyyy-zzzz",
|
||||
"decisionReason": null,
|
||||
"recordedAt": "2024-10-02T00:00:00+00:00",
|
||||
"fieldMask": [
|
||||
"references[]"
|
||||
]
|
||||
},
|
||||
"sourceTag": null,
|
||||
"summary": null,
|
||||
"url": "https://github.com/advisories/GHSA-xxxx-yyyy-zzzz"
|
||||
}
|
||||
],
|
||||
"severity": "critical",
|
||||
"summary": "Example GHSA vulnerability",
|
||||
"title": "Example GHSA vulnerability"
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"ghsa_id": "GHSA-xxxx-yyyy-zzzz",
|
||||
"summary": "Example GHSA vulnerability",
|
||||
"description": "An example advisory describing a supply chain risk.",
|
||||
"severity": "CRITICAL",
|
||||
"published_at": "2024-09-10T13:00:00Z",
|
||||
"updated_at": "2024-09-20T12:00:00Z",
|
||||
"cve_ids": [
|
||||
"CVE-2024-1111"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"url": "https://github.com/advisories/GHSA-xxxx-yyyy-zzzz",
|
||||
"type": "ADVISORY"
|
||||
},
|
||||
{
|
||||
"url": "https://example.com/patch",
|
||||
"type": "FIX",
|
||||
"name": "Vendor Fix"
|
||||
}
|
||||
],
|
||||
"credits": [
|
||||
{
|
||||
"type": "reporter",
|
||||
"user": {
|
||||
"login": "security-reporter",
|
||||
"html_url": "https://github.com/security-reporter"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "remediation_developer",
|
||||
"user": {
|
||||
"login": "maintainer-team",
|
||||
"html_url": "https://github.com/maintainer-team"
|
||||
}
|
||||
}
|
||||
],
|
||||
"cvss": {
|
||||
"score": 9.8,
|
||||
"vector_string": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"severity": "CRITICAL"
|
||||
},
|
||||
"cwes": [
|
||||
{
|
||||
"cwe_id": "CWE-79",
|
||||
"name": "Cross-site Scripting"
|
||||
}
|
||||
],
|
||||
"vulnerabilities": [
|
||||
{
|
||||
"package": {
|
||||
"name": "example/package",
|
||||
"ecosystem": "npm"
|
||||
},
|
||||
"vulnerable_version_range": "< 1.5.0",
|
||||
"first_patched_version": {
|
||||
"identifier": "1.5.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"advisories": [
|
||||
{
|
||||
"ghsa_id": "GHSA-xxxx-yyyy-zzzz",
|
||||
"updated_at": "2024-09-20T12:00:00Z"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"has_next_page": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using StellaOps.Concelier.Models;
|
||||
using StellaOps.Concelier.Connector.Ghsa.Internal;
|
||||
using StellaOps.Concelier.Storage.Mongo.Documents;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Ghsa.Tests;
|
||||
|
||||
public sealed class GhsaConflictFixtureTests
|
||||
{
|
||||
[Fact]
|
||||
public void ConflictFixture_MatchesSnapshot()
|
||||
{
|
||||
var recordedAt = new DateTimeOffset(2025, 3, 4, 8, 30, 0, TimeSpan.Zero);
|
||||
var document = new DocumentRecord(
|
||||
Id: Guid.Parse("2f5c4d67-fcac-4ec9-a8d4-8a9c5a6d0fc9"),
|
||||
SourceName: GhsaConnectorPlugin.SourceName,
|
||||
Uri: "https://github.com/advisories/GHSA-qqqq-wwww-eeee",
|
||||
FetchedAt: new DateTimeOffset(2025, 3, 3, 18, 0, 0, TimeSpan.Zero),
|
||||
Sha256: "sha256-ghsa-conflict-fixture",
|
||||
Status: "completed",
|
||||
ContentType: "application/json",
|
||||
Headers: null,
|
||||
Metadata: null,
|
||||
Etag: "\"etag-ghsa-conflict\"",
|
||||
LastModified: new DateTimeOffset(2025, 3, 3, 18, 0, 0, TimeSpan.Zero),
|
||||
GridFsId: null);
|
||||
|
||||
var dto = new GhsaRecordDto
|
||||
{
|
||||
GhsaId = "GHSA-qqqq-wwww-eeee",
|
||||
Summary = "Container escape in conflict-package",
|
||||
Description = "Container escape vulnerability allowing privilege escalation in conflict-package.",
|
||||
Severity = "HIGH",
|
||||
PublishedAt = new DateTimeOffset(2025, 2, 25, 0, 0, 0, TimeSpan.Zero),
|
||||
UpdatedAt = new DateTimeOffset(2025, 3, 2, 12, 0, 0, TimeSpan.Zero),
|
||||
Aliases = new[] { "GHSA-qqqq-wwww-eeee", "CVE-2025-4242" },
|
||||
References = new[]
|
||||
{
|
||||
new GhsaReferenceDto
|
||||
{
|
||||
Url = "https://github.com/advisories/GHSA-qqqq-wwww-eeee",
|
||||
Type = "ADVISORY"
|
||||
},
|
||||
new GhsaReferenceDto
|
||||
{
|
||||
Url = "https://github.com/conflict/package/releases/tag/v1.4.0",
|
||||
Type = "FIX"
|
||||
}
|
||||
},
|
||||
Affected = new[]
|
||||
{
|
||||
new GhsaAffectedDto
|
||||
{
|
||||
PackageName = "conflict/package",
|
||||
Ecosystem = "npm",
|
||||
VulnerableRange = "< 1.4.0",
|
||||
PatchedVersion = "1.4.0"
|
||||
}
|
||||
},
|
||||
Credits = new[]
|
||||
{
|
||||
new GhsaCreditDto
|
||||
{
|
||||
Type = "reporter",
|
||||
Name = "security-researcher",
|
||||
Login = "sec-researcher",
|
||||
ProfileUrl = "https://github.com/sec-researcher"
|
||||
},
|
||||
new GhsaCreditDto
|
||||
{
|
||||
Type = "remediation_developer",
|
||||
Name = "maintainer-team",
|
||||
Login = "conflict-maintainer",
|
||||
ProfileUrl = "https://github.com/conflict/package"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var advisory = GhsaMapper.Map(dto, document, recordedAt);
|
||||
Assert.Equal("ghsa:severity/high", advisory.CanonicalMetricId);
|
||||
Assert.True(advisory.CvssMetrics.IsEmpty);
|
||||
var snapshot = SnapshotSerializer.ToSnapshot(advisory).Replace("\r\n", "\n").TrimEnd();
|
||||
|
||||
var expectedPath = Path.Combine(AppContext.BaseDirectory, "Fixtures", "conflict-ghsa.canonical.json");
|
||||
var expected = File.ReadAllText(expectedPath).Replace("\r\n", "\n").TrimEnd();
|
||||
|
||||
if (!string.Equals(expected, snapshot, StringComparison.Ordinal))
|
||||
{
|
||||
var actualPath = Path.Combine(AppContext.BaseDirectory, "Fixtures", "conflict-ghsa.canonical.actual.json");
|
||||
File.WriteAllText(actualPath, snapshot);
|
||||
}
|
||||
|
||||
Assert.Equal(expected, snapshot);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using MongoDB.Bson;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Concelier.Models;
|
||||
using StellaOps.Concelier.Connector.Common.Testing;
|
||||
using StellaOps.Concelier.Connector.Ghsa.Configuration;
|
||||
using StellaOps.Concelier.Connector.Ghsa.Internal;
|
||||
using StellaOps.Concelier.Testing;
|
||||
using StellaOps.Concelier.Storage.Mongo;
|
||||
using StellaOps.Concelier.Storage.Mongo.Advisories;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Ghsa.Tests;
|
||||
|
||||
[Collection("mongo-fixture")]
|
||||
public sealed class GhsaConnectorTests : IAsyncLifetime
|
||||
{
|
||||
private readonly MongoIntegrationFixture _fixture;
|
||||
private ConnectorTestHarness? _harness;
|
||||
|
||||
public GhsaConnectorTests(MongoIntegrationFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FetchParseMap_EmitsCanonicalAdvisory()
|
||||
{
|
||||
var initialTime = new DateTimeOffset(2024, 10, 2, 0, 0, 0, TimeSpan.Zero);
|
||||
await EnsureHarnessAsync(initialTime);
|
||||
var harness = _harness!;
|
||||
|
||||
var since = initialTime - TimeSpan.FromDays(30);
|
||||
var listUri = new Uri($"https://ghsa.test/security/advisories?updated_since={Uri.EscapeDataString(since.ToString("O"))}&updated_until={Uri.EscapeDataString(initialTime.ToString("O"))}&page=1&per_page=5");
|
||||
harness.Handler.AddJsonResponse(listUri, ReadFixture("Fixtures/ghsa-list.json"));
|
||||
harness.Handler.SetFallback(request =>
|
||||
{
|
||||
if (request.RequestUri is null)
|
||||
{
|
||||
return new HttpResponseMessage(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
if (request.RequestUri.AbsoluteUri.Equals("https://ghsa.test/security/advisories/GHSA-xxxx-yyyy-zzzz", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(ReadFixture("Fixtures/ghsa-GHSA-xxxx-yyyy-zzzz.json"), Encoding.UTF8, "application/json")
|
||||
};
|
||||
}
|
||||
|
||||
return new HttpResponseMessage(HttpStatusCode.NotFound);
|
||||
});
|
||||
|
||||
var connector = new GhsaConnectorPlugin().Create(harness.ServiceProvider);
|
||||
|
||||
await connector.FetchAsync(harness.ServiceProvider, CancellationToken.None);
|
||||
await connector.ParseAsync(harness.ServiceProvider, CancellationToken.None);
|
||||
await connector.MapAsync(harness.ServiceProvider, CancellationToken.None);
|
||||
|
||||
var advisoryStore = harness.ServiceProvider.GetRequiredService<IAdvisoryStore>();
|
||||
var advisory = await advisoryStore.FindAsync("GHSA-xxxx-yyyy-zzzz", CancellationToken.None);
|
||||
Assert.NotNull(advisory);
|
||||
|
||||
Assert.Collection(advisory!.Credits,
|
||||
credit =>
|
||||
{
|
||||
Assert.Equal("remediation_developer", credit.Role);
|
||||
Assert.Equal("maintainer-team", credit.DisplayName);
|
||||
Assert.Contains("https://github.com/maintainer-team", credit.Contacts);
|
||||
},
|
||||
credit =>
|
||||
{
|
||||
Assert.Equal("reporter", credit.Role);
|
||||
Assert.Equal("security-reporter", credit.DisplayName);
|
||||
Assert.Contains("https://github.com/security-reporter", credit.Contacts);
|
||||
});
|
||||
|
||||
var weakness = Assert.Single(advisory.Cwes);
|
||||
Assert.Equal("CWE-79", weakness.Identifier);
|
||||
Assert.Equal("https://cwe.mitre.org/data/definitions/79.html", weakness.Uri);
|
||||
|
||||
var metric = Assert.Single(advisory.CvssMetrics);
|
||||
Assert.Equal("3.1", metric.Version);
|
||||
Assert.Equal("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", metric.Vector);
|
||||
Assert.Equal("critical", metric.BaseSeverity);
|
||||
Assert.Equal("3.1|CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", advisory.CanonicalMetricId);
|
||||
|
||||
var snapshot = SnapshotSerializer.ToSnapshot(advisory).Replace("\r\n", "\n").TrimEnd();
|
||||
var expected = ReadFixture("Fixtures/expected-GHSA-xxxx-yyyy-zzzz.json").Replace("\r\n", "\n").TrimEnd();
|
||||
|
||||
if (!string.Equals(expected, snapshot, StringComparison.Ordinal))
|
||||
{
|
||||
var actualPath = Path.Combine(AppContext.BaseDirectory, "Fixtures", "expected-GHSA-xxxx-yyyy-zzzz.actual.json");
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(actualPath)!);
|
||||
File.WriteAllText(actualPath, snapshot);
|
||||
}
|
||||
|
||||
Assert.Equal(expected, snapshot);
|
||||
harness.Handler.AssertNoPendingResponses();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FetchAsync_RateLimitDefersWindowAndRecordsSnapshot()
|
||||
{
|
||||
var initialTime = new DateTimeOffset(2024, 10, 5, 0, 0, 0, TimeSpan.Zero);
|
||||
await EnsureHarnessAsync(initialTime);
|
||||
var harness = _harness!;
|
||||
|
||||
var since = initialTime - TimeSpan.FromDays(30);
|
||||
var until = initialTime;
|
||||
var listUri = new Uri($"https://ghsa.test/security/advisories?updated_since={Uri.EscapeDataString(since.ToString("O"))}&updated_until={Uri.EscapeDataString(until.ToString("O"))}&page=1&per_page=5");
|
||||
|
||||
harness.Handler.AddResponse(HttpMethod.Get, listUri, _ =>
|
||||
{
|
||||
var response = new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(ReadFixture("Fixtures/ghsa-list.json"), Encoding.UTF8, "application/json")
|
||||
};
|
||||
response.Headers.TryAddWithoutValidation("X-RateLimit-Resource", "core");
|
||||
response.Headers.TryAddWithoutValidation("X-RateLimit-Limit", "5000");
|
||||
response.Headers.TryAddWithoutValidation("X-RateLimit-Remaining", "0");
|
||||
return response;
|
||||
});
|
||||
|
||||
harness.Handler.SetFallback(_ => new HttpResponseMessage(HttpStatusCode.NotFound));
|
||||
|
||||
var connector = new GhsaConnectorPlugin().Create(harness.ServiceProvider);
|
||||
await connector.FetchAsync(harness.ServiceProvider, CancellationToken.None);
|
||||
|
||||
Assert.Single(harness.Handler.Requests);
|
||||
|
||||
var diagnostics = harness.ServiceProvider.GetRequiredService<GhsaDiagnostics>();
|
||||
var snapshot = diagnostics.GetLastRateLimitSnapshot();
|
||||
Assert.True(snapshot.HasValue);
|
||||
Assert.Equal("list", snapshot!.Value.Phase);
|
||||
Assert.Equal("core", snapshot.Value.Resource);
|
||||
Assert.Equal(0, snapshot.Value.Remaining);
|
||||
|
||||
var stateRepository = harness.ServiceProvider.GetRequiredService<ISourceStateRepository>();
|
||||
var state = await stateRepository.TryGetAsync(GhsaConnectorPlugin.SourceName, CancellationToken.None);
|
||||
Assert.NotNull(state);
|
||||
|
||||
Assert.True(state!.Cursor.TryGetValue("currentWindowStart", out var startValue));
|
||||
Assert.True(state.Cursor.TryGetValue("currentWindowEnd", out var endValue));
|
||||
Assert.True(state.Cursor.TryGetValue("nextPage", out var nextPageValue));
|
||||
|
||||
Assert.Equal(since.UtcDateTime, startValue.ToUniversalTime());
|
||||
Assert.Equal(until.UtcDateTime, endValue.ToUniversalTime());
|
||||
Assert.Equal(1, nextPageValue.AsInt32);
|
||||
|
||||
Assert.True(state.Cursor.TryGetValue("pendingDocuments", out var pendingDocs));
|
||||
Assert.Empty(pendingDocs.AsBsonArray);
|
||||
Assert.True(state.Cursor.TryGetValue("pendingMappings", out var pendingMappings));
|
||||
Assert.Empty(pendingMappings.AsBsonArray);
|
||||
}
|
||||
|
||||
private async Task EnsureHarnessAsync(DateTimeOffset initialTime)
|
||||
{
|
||||
if (_harness is not null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var harness = new ConnectorTestHarness(_fixture, initialTime, GhsaOptions.HttpClientName);
|
||||
await harness.EnsureServiceProviderAsync(services =>
|
||||
{
|
||||
services.AddLogging(builder => builder.AddProvider(NullLoggerProvider.Instance));
|
||||
services.AddGhsaConnector(options =>
|
||||
{
|
||||
options.BaseEndpoint = new Uri("https://ghsa.test/", UriKind.Absolute);
|
||||
options.ApiToken = "test-token";
|
||||
options.PageSize = 5;
|
||||
options.MaxPagesPerFetch = 2;
|
||||
options.RequestDelay = TimeSpan.Zero;
|
||||
options.InitialBackfill = TimeSpan.FromDays(30);
|
||||
options.SecondaryRateLimitBackoff = TimeSpan.FromMilliseconds(10);
|
||||
});
|
||||
});
|
||||
|
||||
_harness = harness;
|
||||
}
|
||||
|
||||
private static string ReadFixture(string relativePath)
|
||||
{
|
||||
var path = Path.Combine(AppContext.BaseDirectory, relativePath);
|
||||
return File.ReadAllText(path);
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
if (_harness is not null)
|
||||
{
|
||||
await _harness.DisposeAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using StellaOps.Concelier.Models;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Ghsa.Tests.Ghsa;
|
||||
|
||||
public sealed class GhsaCreditParityRegressionTests
|
||||
{
|
||||
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);
|
||||
|
||||
[Fact]
|
||||
public void CreditParity_FixturesRemainInSyncAcrossSources()
|
||||
{
|
||||
var ghsa = LoadFixture("credit-parity.ghsa.json");
|
||||
var osv = LoadFixture("credit-parity.osv.json");
|
||||
var nvd = LoadFixture("credit-parity.nvd.json");
|
||||
|
||||
var ghsaCredits = NormalizeCredits(ghsa);
|
||||
var osvCredits = NormalizeCredits(osv);
|
||||
var nvdCredits = NormalizeCredits(nvd);
|
||||
|
||||
Assert.NotEmpty(ghsaCredits);
|
||||
Assert.Equal(ghsaCredits, osvCredits);
|
||||
Assert.Equal(ghsaCredits, nvdCredits);
|
||||
}
|
||||
|
||||
private static Advisory LoadFixture(string fileName)
|
||||
{
|
||||
var path = Path.Combine(AppContext.BaseDirectory, "Fixtures", fileName);
|
||||
return JsonSerializer.Deserialize<Advisory>(File.ReadAllText(path), SerializerOptions)
|
||||
?? throw new InvalidOperationException($"Failed to deserialize fixture '{fileName}'.");
|
||||
}
|
||||
|
||||
private static HashSet<string> NormalizeCredits(Advisory advisory)
|
||||
{
|
||||
var set = new HashSet<string>(StringComparer.Ordinal);
|
||||
foreach (var credit in advisory.Credits)
|
||||
{
|
||||
var contactList = credit.Contacts.IsDefaultOrEmpty
|
||||
? Array.Empty<string>()
|
||||
: credit.Contacts.ToArray();
|
||||
var contacts = string.Join("|", contactList.OrderBy(static contact => contact, StringComparer.Ordinal));
|
||||
var key = string.Join("||", credit.Role ?? string.Empty, credit.DisplayName ?? string.Empty, contacts);
|
||||
set.Add(key);
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Concelier.Core.Jobs;
|
||||
using StellaOps.Concelier.Connector.Common.Http;
|
||||
using StellaOps.Concelier.Connector.Ghsa;
|
||||
using StellaOps.Concelier.Connector.Ghsa.Configuration;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Ghsa.Tests.Ghsa;
|
||||
|
||||
public sealed class GhsaDependencyInjectionRoutineTests
|
||||
{
|
||||
[Fact]
|
||||
public void Register_ConfiguresConnectorAndScheduler()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddLogging(builder => builder.AddProvider(NullLoggerProvider.Instance));
|
||||
services.AddOptions();
|
||||
services.AddSourceCommon();
|
||||
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string?>
|
||||
{
|
||||
["concelier:sources:ghsa:apiToken"] = "test-token",
|
||||
["concelier:sources:ghsa:pageSize"] = "25",
|
||||
["concelier:sources:ghsa:maxPagesPerFetch"] = "3",
|
||||
["concelier:sources:ghsa:initialBackfill"] = "1.00:00:00",
|
||||
})
|
||||
.Build();
|
||||
|
||||
var routine = new GhsaDependencyInjectionRoutine();
|
||||
routine.Register(services, configuration);
|
||||
|
||||
services.Configure<JobSchedulerOptions>(_ => { });
|
||||
|
||||
var provider = services.BuildServiceProvider(validateScopes: true);
|
||||
|
||||
var ghsaOptions = provider.GetRequiredService<IOptions<GhsaOptions>>().Value;
|
||||
Assert.Equal("test-token", ghsaOptions.ApiToken);
|
||||
Assert.Equal(25, ghsaOptions.PageSize);
|
||||
Assert.Equal(TimeSpan.FromDays(1), ghsaOptions.InitialBackfill);
|
||||
|
||||
var schedulerOptions = provider.GetRequiredService<IOptions<JobSchedulerOptions>>().Value;
|
||||
Assert.True(schedulerOptions.Definitions.TryGetValue(GhsaJobKinds.Fetch, out var fetchDefinition));
|
||||
Assert.True(schedulerOptions.Definitions.TryGetValue(GhsaJobKinds.Parse, out var parseDefinition));
|
||||
Assert.True(schedulerOptions.Definitions.TryGetValue(GhsaJobKinds.Map, out var mapDefinition));
|
||||
|
||||
Assert.Equal(typeof(GhsaFetchJob), fetchDefinition.JobType);
|
||||
Assert.Equal(TimeSpan.FromMinutes(6), fetchDefinition.Timeout);
|
||||
Assert.Equal(TimeSpan.FromMinutes(4), fetchDefinition.LeaseDuration);
|
||||
Assert.Equal("1,11,21,31,41,51 * * * *", fetchDefinition.CronExpression);
|
||||
Assert.True(fetchDefinition.Enabled);
|
||||
|
||||
Assert.Equal(typeof(GhsaParseJob), parseDefinition.JobType);
|
||||
Assert.Equal(TimeSpan.FromMinutes(5), parseDefinition.Timeout);
|
||||
Assert.Equal(TimeSpan.FromMinutes(4), parseDefinition.LeaseDuration);
|
||||
Assert.Equal("3,13,23,33,43,53 * * * *", parseDefinition.CronExpression);
|
||||
Assert.True(parseDefinition.Enabled);
|
||||
|
||||
Assert.Equal(typeof(GhsaMapJob), mapDefinition.JobType);
|
||||
Assert.Equal(TimeSpan.FromMinutes(5), mapDefinition.Timeout);
|
||||
Assert.Equal(TimeSpan.FromMinutes(4), mapDefinition.LeaseDuration);
|
||||
Assert.Equal("5,15,25,35,45,55 * * * *", mapDefinition.CronExpression);
|
||||
Assert.True(mapDefinition.Enabled);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using StellaOps.Concelier.Connector.Ghsa.Internal;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Ghsa.Tests.Ghsa;
|
||||
|
||||
public class GhsaDiagnosticsTests : IDisposable
|
||||
{
|
||||
private readonly GhsaDiagnostics diagnostics = new();
|
||||
|
||||
[Fact]
|
||||
public void RecordRateLimit_PersistsSnapshot()
|
||||
{
|
||||
var snapshot = new GhsaRateLimitSnapshot(
|
||||
Phase: "list",
|
||||
Resource: "core",
|
||||
Limit: 5000,
|
||||
Remaining: 100,
|
||||
Used: 4900,
|
||||
ResetAt: DateTimeOffset.UtcNow.AddMinutes(1),
|
||||
ResetAfter: TimeSpan.FromMinutes(1),
|
||||
RetryAfter: TimeSpan.FromSeconds(10));
|
||||
|
||||
diagnostics.RecordRateLimit(snapshot);
|
||||
|
||||
var stored = diagnostics.GetLastRateLimitSnapshot();
|
||||
Assert.NotNull(stored);
|
||||
Assert.Equal(snapshot, stored);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
diagnostics.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using StellaOps.Concelier.Connector.Ghsa.Internal;
|
||||
using StellaOps.Concelier.Storage.Mongo.Documents;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Ghsa.Tests;
|
||||
|
||||
public sealed class GhsaMapperTests
|
||||
{
|
||||
[Fact]
|
||||
public void Map_WhenCvssVectorMissing_UsesSeverityFallback()
|
||||
{
|
||||
var recordedAt = new DateTimeOffset(2025, 4, 10, 12, 0, 0, TimeSpan.Zero);
|
||||
var document = new DocumentRecord(
|
||||
Id: Guid.Parse("d7814678-3c3e-4e63-98c4-68e2f6d7ba6f"),
|
||||
SourceName: GhsaConnectorPlugin.SourceName,
|
||||
Uri: "https://github.com/advisories/GHSA-fallback-test",
|
||||
FetchedAt: recordedAt.AddHours(-2),
|
||||
Sha256: "sha256-ghsa-fallback-test",
|
||||
Status: "completed",
|
||||
ContentType: "application/json",
|
||||
Headers: null,
|
||||
Metadata: null,
|
||||
Etag: "\"etag-ghsa-fallback\"",
|
||||
LastModified: recordedAt.AddHours(-3),
|
||||
GridFsId: null);
|
||||
|
||||
var dto = new GhsaRecordDto
|
||||
{
|
||||
GhsaId = "GHSA-fallback-test",
|
||||
Summary = "Severity-only GHSA advisory",
|
||||
Description = "GHSA record where GitHub omitted CVSS vector/score.",
|
||||
Severity = null,
|
||||
PublishedAt = recordedAt.AddDays(-3),
|
||||
UpdatedAt = recordedAt.AddDays(-1),
|
||||
Aliases = new[] { "GHSA-fallback-test" },
|
||||
References = Array.Empty<GhsaReferenceDto>(),
|
||||
Affected = Array.Empty<GhsaAffectedDto>(),
|
||||
Credits = Array.Empty<GhsaCreditDto>(),
|
||||
Cwes = Array.Empty<GhsaWeaknessDto>(),
|
||||
Cvss = new GhsaCvssDto
|
||||
{
|
||||
Severity = "CRITICAL",
|
||||
Score = null,
|
||||
VectorString = null,
|
||||
}
|
||||
};
|
||||
|
||||
var advisory = GhsaMapper.Map(dto, document, recordedAt);
|
||||
|
||||
Assert.Equal("critical", advisory.Severity);
|
||||
Assert.Equal("ghsa:severity/critical", advisory.CanonicalMetricId);
|
||||
Assert.True(advisory.CvssMetrics.IsEmpty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using StellaOps.Concelier.Connector.Ghsa.Internal;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Concelier.Connector.Ghsa.Tests.Ghsa;
|
||||
|
||||
public class GhsaRateLimitParserTests
|
||||
{
|
||||
[Fact]
|
||||
public void TryParse_ReturnsSnapshot_WhenHeadersPresent()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var reset = now.AddMinutes(5);
|
||||
|
||||
var headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["X-RateLimit-Limit"] = "5000",
|
||||
["X-RateLimit-Remaining"] = "42",
|
||||
["X-RateLimit-Used"] = "4958",
|
||||
["X-RateLimit-Reset"] = reset.ToUnixTimeSeconds().ToString(CultureInfo.InvariantCulture),
|
||||
["X-RateLimit-Resource"] = "core"
|
||||
};
|
||||
|
||||
var snapshot = GhsaRateLimitParser.TryParse(headers, now, "list");
|
||||
|
||||
Assert.True(snapshot.HasValue);
|
||||
Assert.Equal("list", snapshot.Value.Phase);
|
||||
Assert.Equal("core", snapshot.Value.Resource);
|
||||
Assert.Equal(5000, snapshot.Value.Limit);
|
||||
Assert.Equal(42, snapshot.Value.Remaining);
|
||||
Assert.Equal(4958, snapshot.Value.Used);
|
||||
Assert.NotNull(snapshot.Value.ResetAfter);
|
||||
Assert.True(snapshot.Value.ResetAfter!.Value.TotalMinutes <= 5.1 && snapshot.Value.ResetAfter.Value.TotalMinutes >= 4.9);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_ReturnsNull_WhenHeadersMissing()
|
||||
{
|
||||
var snapshot = GhsaRateLimitParser.TryParse(null, DateTimeOffset.UtcNow, "list");
|
||||
Assert.Null(snapshot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_HandlesRetryAfter()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["Retry-After"] = "60"
|
||||
};
|
||||
|
||||
var snapshot = GhsaRateLimitParser.TryParse(headers, now, "detail");
|
||||
|
||||
Assert.True(snapshot.HasValue);
|
||||
Assert.Equal("detail", snapshot.Value.Phase);
|
||||
Assert.NotNull(snapshot.Value.RetryAfter);
|
||||
Assert.Equal(60, Math.Round(snapshot.Value.RetryAfter!.Value.TotalSeconds));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Connector.Ghsa/StellaOps.Concelier.Connector.Ghsa.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Connector.Common/StellaOps.Concelier.Connector.Common.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Storage.Mongo/StellaOps.Concelier.Storage.Mongo.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Testing/StellaOps.Concelier.Testing.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Fixtures/*.json" CopyToOutputDirectory="Always" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user