From 42d49e602bb20e7e44856d78c186dfa29147b781 Mon Sep 17 00:00:00 2001 From: Adva Oren Date: Wed, 20 May 2026 16:07:21 +0300 Subject: [PATCH] feat(poetry): extract SHA-256 hashes from poetry.lock for SBOM generation Extend _extractMarkerData() to extract files[].hash entries per package from poetry.lock and populate graph entries with SHA-256 hashes. Prefers sdist (.tar.gz) hash over wheel hashes. The hashes flow through the existing base_pyproject pipeline into CycloneDX SBOM components. Implements TC-4333 Assisted-by: Claude Code --- src/providers/python_poetry.js | 41 +- .../expected_component_sbom.json | 74 +-- .../poetry_dev_deps/expected_stack_sbom.json | 196 ++++--- .../expected_component_sbom.json | 74 +-- .../expected_stack_sbom.json | 196 ++++--- .../poetry_lock/expected_component_sbom.json | 16 +- .../poetry_lock/expected_stack_sbom.json | 88 +++- .../expected_component_sbom.json | 134 ++--- .../poetry_only_deps/expected_stack_sbom.json | 478 ++++++++++-------- 9 files changed, 789 insertions(+), 508 deletions(-) diff --git a/src/providers/python_poetry.js b/src/providers/python_poetry.js index 0222f9c0..4c871a87 100644 --- a/src/providers/python_poetry.js +++ b/src/providers/python_poetry.js @@ -121,11 +121,12 @@ export default class Python_poetry extends Base_pyproject { * → transitiveMarkers['click']['colorama'] = "sys_platform == 'win32'" * @param {string|null} lockDir * @param {object} parsed - parsed pyproject.toml - * @returns {{directMarkers: Map, transitiveMarkers: Map>}} + * @returns {{directMarkers: Map, transitiveMarkers: Map>, hashMap: Map>}} */ _extractMarkerData(lockDir, parsed) { let directMarkers = new Map() let transitiveMarkers = new Map() + let hashMap = new Map() // Extract markers from PEP 621 dependency strings: "name[extras]>=ver ; marker" let deps = parsed.project?.dependencies || [] @@ -154,11 +155,33 @@ export default class Python_poetry extends Base_pyproject { transitiveMarkers.get(pkgKey).set(this._canonicalize(depName), markers) } } + + let sha256Hex = this._extractSha256FromFiles(pkg.files) + if (sha256Hex) { + hashMap.set(pkgKey, [{alg: "SHA-256", content: sha256Hex}]) + } } } } - return { directMarkers, transitiveMarkers } + return { directMarkers, transitiveMarkers, hashMap } + } + + /** + * Extract a SHA-256 hex digest from poetry.lock files array. + * Prefers the sdist (.tar.gz) hash; falls back to the first wheel hash. + * @param {Array<{file: string, hash: string}>} [files] + * @returns {string|null} + */ + _extractSha256FromFiles(files) { + if (!Array.isArray(files) || files.length === 0) { return null } + let sdist = files.find(f => f.file.endsWith('.tar.gz')) + let entry = sdist || files[0] + let hash = entry.hash + if (hash && hash.startsWith('sha256:')) { + return hash.slice(7) + } + return null } /** @@ -166,8 +189,8 @@ export default class Python_poetry extends Base_pyproject { * * @param {string} treeOutput * @param {Map} versionMap - canonical name -> resolved version - * @param {{directMarkers: Map, transitiveMarkers: Map>}} markerData - * @returns {{directDeps: string[], graph: Map}} + * @param {{directMarkers: Map, transitiveMarkers: Map>, hashMap: Map>}} markerData + * @returns {{directDeps: string[], graph: Map}>}} */ _parsePoetryTree(treeOutput, versionMap, markerData) { let lines = treeOutput.split(/\r?\n/) @@ -196,7 +219,10 @@ export default class Python_poetry extends Base_pyproject { directDeps.push(key) if (!graph.has(key)) { - graph.set(key, { name, version, children: [] }) + let entry = { name, version, children: [] } + let hashes = markerData.hashMap.get(key) + if (hashes) { entry.hashes = hashes } + graph.set(key, entry) } currentDirectDep = key stack = [{ key, depth: -1 }] @@ -243,7 +269,10 @@ export default class Python_poetry extends Base_pyproject { } if (!graph.has(depKey)) { - graph.set(depKey, { name: depName, version, children: [] }) + let entry = { name: depName, version, children: [] } + let hashes = markerData.hashMap.get(depKey) + if (hashes) { entry.hashes = hashes } + graph.set(depKey, entry) } if (parentKey) { diff --git a/test/providers/tst_manifests/pyproject/poetry_dev_deps/expected_component_sbom.json b/test/providers/tst_manifests/pyproject/poetry_dev_deps/expected_component_sbom.json index ab089523..7716b7a5 100644 --- a/test/providers/tst_manifests/pyproject/poetry_dev_deps/expected_component_sbom.json +++ b/test/providers/tst_manifests/pyproject/poetry_dev_deps/expected_component_sbom.json @@ -1,36 +1,42 @@ { - "bomFormat": "CycloneDX", - "specVersion": "1.4", - "version": 1, - "metadata": { - "timestamp": "2023-10-01T00:00:00.000Z", - "component": { - "name": "dev-deps-project", - "version": "1.0.0", - "purl": "pkg:pypi/dev-deps-project@1.0.0", - "type": "application", - "bom-ref": "pkg:pypi/dev-deps-project@1.0.0" - } - }, - "components": [ - { - "name": "requests", - "version": "2.33.1", - "purl": "pkg:pypi/requests@2.33.1", - "type": "library", - "bom-ref": "pkg:pypi/requests@2.33.1" - } - ], - "dependencies": [ - { - "ref": "pkg:pypi/dev-deps-project@1.0.0", - "dependsOn": [ - "pkg:pypi/requests@2.33.1" - ] - }, - { - "ref": "pkg:pypi/requests@2.33.1", - "dependsOn": [] - } - ] + "bomFormat": "CycloneDX", + "specVersion": "1.4", + "version": 1, + "metadata": { + "timestamp": "2023-10-01T00:00:00.000Z", + "component": { + "name": "dev-deps-project", + "version": "1.0.0", + "purl": "pkg:pypi/dev-deps-project@1.0.0", + "type": "application", + "bom-ref": "pkg:pypi/dev-deps-project@1.0.0" + } + }, + "components": [ + { + "name": "requests", + "version": "2.33.1", + "purl": "pkg:pypi/requests@2.33.1", + "type": "library", + "bom-ref": "pkg:pypi/requests@2.33.1", + "hashes": [ + { + "alg": "SHA-256", + "content": "18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517" + } + ] + } + ], + "dependencies": [ + { + "ref": "pkg:pypi/dev-deps-project@1.0.0", + "dependsOn": [ + "pkg:pypi/requests@2.33.1" + ] + }, + { + "ref": "pkg:pypi/requests@2.33.1", + "dependsOn": [] + } + ] } diff --git a/test/providers/tst_manifests/pyproject/poetry_dev_deps/expected_stack_sbom.json b/test/providers/tst_manifests/pyproject/poetry_dev_deps/expected_stack_sbom.json index 92aa7865..28591492 100644 --- a/test/providers/tst_manifests/pyproject/poetry_dev_deps/expected_stack_sbom.json +++ b/test/providers/tst_manifests/pyproject/poetry_dev_deps/expected_stack_sbom.json @@ -1,85 +1,115 @@ { - "bomFormat": "CycloneDX", - "specVersion": "1.4", - "version": 1, - "metadata": { - "timestamp": "2023-10-01T00:00:00.000Z", - "component": { - "name": "dev-deps-project", - "version": "1.0.0", - "purl": "pkg:pypi/dev-deps-project@1.0.0", - "type": "application", - "bom-ref": "pkg:pypi/dev-deps-project@1.0.0" - } - }, - "components": [ - { - "name": "requests", - "version": "2.33.1", - "purl": "pkg:pypi/requests@2.33.1", - "type": "library", - "bom-ref": "pkg:pypi/requests@2.33.1" - }, - { - "name": "certifi", - "version": "2026.2.25", - "purl": "pkg:pypi/certifi@2026.2.25", - "type": "library", - "bom-ref": "pkg:pypi/certifi@2026.2.25" - }, - { - "name": "charset-normalizer", - "version": "3.4.7", - "purl": "pkg:pypi/charset-normalizer@3.4.7", - "type": "library", - "bom-ref": "pkg:pypi/charset-normalizer@3.4.7" - }, - { - "name": "idna", - "version": "3.11", - "purl": "pkg:pypi/idna@3.11", - "type": "library", - "bom-ref": "pkg:pypi/idna@3.11" - }, - { - "name": "urllib3", - "version": "2.6.3", - "purl": "pkg:pypi/urllib3@2.6.3", - "type": "library", - "bom-ref": "pkg:pypi/urllib3@2.6.3" - } - ], - "dependencies": [ - { - "ref": "pkg:pypi/dev-deps-project@1.0.0", - "dependsOn": [ - "pkg:pypi/requests@2.33.1" - ] - }, - { - "ref": "pkg:pypi/requests@2.33.1", - "dependsOn": [ - "pkg:pypi/certifi@2026.2.25", - "pkg:pypi/charset-normalizer@3.4.7", - "pkg:pypi/idna@3.11", - "pkg:pypi/urllib3@2.6.3" - ] - }, - { - "ref": "pkg:pypi/certifi@2026.2.25", - "dependsOn": [] - }, - { - "ref": "pkg:pypi/charset-normalizer@3.4.7", - "dependsOn": [] - }, - { - "ref": "pkg:pypi/idna@3.11", - "dependsOn": [] - }, - { - "ref": "pkg:pypi/urllib3@2.6.3", - "dependsOn": [] - } - ] + "bomFormat": "CycloneDX", + "specVersion": "1.4", + "version": 1, + "metadata": { + "timestamp": "2023-10-01T00:00:00.000Z", + "component": { + "name": "dev-deps-project", + "version": "1.0.0", + "purl": "pkg:pypi/dev-deps-project@1.0.0", + "type": "application", + "bom-ref": "pkg:pypi/dev-deps-project@1.0.0" + } + }, + "components": [ + { + "name": "requests", + "version": "2.33.1", + "purl": "pkg:pypi/requests@2.33.1", + "type": "library", + "bom-ref": "pkg:pypi/requests@2.33.1", + "hashes": [ + { + "alg": "SHA-256", + "content": "18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517" + } + ] + }, + { + "name": "certifi", + "version": "2026.2.25", + "purl": "pkg:pypi/certifi@2026.2.25", + "type": "library", + "bom-ref": "pkg:pypi/certifi@2026.2.25", + "hashes": [ + { + "alg": "SHA-256", + "content": "e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7" + } + ] + }, + { + "name": "charset-normalizer", + "version": "3.4.7", + "purl": "pkg:pypi/charset-normalizer@3.4.7", + "type": "library", + "bom-ref": "pkg:pypi/charset-normalizer@3.4.7", + "hashes": [ + { + "alg": "SHA-256", + "content": "ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5" + } + ] + }, + { + "name": "idna", + "version": "3.11", + "purl": "pkg:pypi/idna@3.11", + "type": "library", + "bom-ref": "pkg:pypi/idna@3.11", + "hashes": [ + { + "alg": "SHA-256", + "content": "795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" + } + ] + }, + { + "name": "urllib3", + "version": "2.6.3", + "purl": "pkg:pypi/urllib3@2.6.3", + "type": "library", + "bom-ref": "pkg:pypi/urllib3@2.6.3", + "hashes": [ + { + "alg": "SHA-256", + "content": "1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed" + } + ] + } + ], + "dependencies": [ + { + "ref": "pkg:pypi/dev-deps-project@1.0.0", + "dependsOn": [ + "pkg:pypi/requests@2.33.1" + ] + }, + { + "ref": "pkg:pypi/requests@2.33.1", + "dependsOn": [ + "pkg:pypi/certifi@2026.2.25", + "pkg:pypi/charset-normalizer@3.4.7", + "pkg:pypi/idna@3.11", + "pkg:pypi/urllib3@2.6.3" + ] + }, + { + "ref": "pkg:pypi/certifi@2026.2.25", + "dependsOn": [] + }, + { + "ref": "pkg:pypi/charset-normalizer@3.4.7", + "dependsOn": [] + }, + { + "ref": "pkg:pypi/idna@3.11", + "dependsOn": [] + }, + { + "ref": "pkg:pypi/urllib3@2.6.3", + "dependsOn": [] + } + ] } diff --git a/test/providers/tst_manifests/pyproject/poetry_legacy_dev_deps/expected_component_sbom.json b/test/providers/tst_manifests/pyproject/poetry_legacy_dev_deps/expected_component_sbom.json index ab32f33c..2df5fb1b 100644 --- a/test/providers/tst_manifests/pyproject/poetry_legacy_dev_deps/expected_component_sbom.json +++ b/test/providers/tst_manifests/pyproject/poetry_legacy_dev_deps/expected_component_sbom.json @@ -1,36 +1,42 @@ { - "bomFormat": "CycloneDX", - "specVersion": "1.4", - "version": 1, - "metadata": { - "timestamp": "2023-10-01T00:00:00.000Z", - "component": { - "name": "legacy-dev-deps-project", - "version": "1.0.0", - "purl": "pkg:pypi/legacy-dev-deps-project@1.0.0", - "type": "application", - "bom-ref": "pkg:pypi/legacy-dev-deps-project@1.0.0" - } - }, - "components": [ - { - "name": "requests", - "version": "2.33.1", - "purl": "pkg:pypi/requests@2.33.1", - "type": "library", - "bom-ref": "pkg:pypi/requests@2.33.1" - } - ], - "dependencies": [ - { - "ref": "pkg:pypi/legacy-dev-deps-project@1.0.0", - "dependsOn": [ - "pkg:pypi/requests@2.33.1" - ] - }, - { - "ref": "pkg:pypi/requests@2.33.1", - "dependsOn": [] - } - ] + "bomFormat": "CycloneDX", + "specVersion": "1.4", + "version": 1, + "metadata": { + "timestamp": "2023-10-01T00:00:00.000Z", + "component": { + "name": "legacy-dev-deps-project", + "version": "1.0.0", + "purl": "pkg:pypi/legacy-dev-deps-project@1.0.0", + "type": "application", + "bom-ref": "pkg:pypi/legacy-dev-deps-project@1.0.0" + } + }, + "components": [ + { + "name": "requests", + "version": "2.33.1", + "purl": "pkg:pypi/requests@2.33.1", + "type": "library", + "bom-ref": "pkg:pypi/requests@2.33.1", + "hashes": [ + { + "alg": "SHA-256", + "content": "18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517" + } + ] + } + ], + "dependencies": [ + { + "ref": "pkg:pypi/legacy-dev-deps-project@1.0.0", + "dependsOn": [ + "pkg:pypi/requests@2.33.1" + ] + }, + { + "ref": "pkg:pypi/requests@2.33.1", + "dependsOn": [] + } + ] } diff --git a/test/providers/tst_manifests/pyproject/poetry_legacy_dev_deps/expected_stack_sbom.json b/test/providers/tst_manifests/pyproject/poetry_legacy_dev_deps/expected_stack_sbom.json index c53d88ad..de4290ea 100644 --- a/test/providers/tst_manifests/pyproject/poetry_legacy_dev_deps/expected_stack_sbom.json +++ b/test/providers/tst_manifests/pyproject/poetry_legacy_dev_deps/expected_stack_sbom.json @@ -1,85 +1,115 @@ { - "bomFormat": "CycloneDX", - "specVersion": "1.4", - "version": 1, - "metadata": { - "timestamp": "2023-10-01T00:00:00.000Z", - "component": { - "name": "legacy-dev-deps-project", - "version": "1.0.0", - "purl": "pkg:pypi/legacy-dev-deps-project@1.0.0", - "type": "application", - "bom-ref": "pkg:pypi/legacy-dev-deps-project@1.0.0" - } - }, - "components": [ - { - "name": "requests", - "version": "2.33.1", - "purl": "pkg:pypi/requests@2.33.1", - "type": "library", - "bom-ref": "pkg:pypi/requests@2.33.1" - }, - { - "name": "certifi", - "version": "2026.2.25", - "purl": "pkg:pypi/certifi@2026.2.25", - "type": "library", - "bom-ref": "pkg:pypi/certifi@2026.2.25" - }, - { - "name": "charset-normalizer", - "version": "3.4.7", - "purl": "pkg:pypi/charset-normalizer@3.4.7", - "type": "library", - "bom-ref": "pkg:pypi/charset-normalizer@3.4.7" - }, - { - "name": "idna", - "version": "3.11", - "purl": "pkg:pypi/idna@3.11", - "type": "library", - "bom-ref": "pkg:pypi/idna@3.11" - }, - { - "name": "urllib3", - "version": "2.6.3", - "purl": "pkg:pypi/urllib3@2.6.3", - "type": "library", - "bom-ref": "pkg:pypi/urllib3@2.6.3" - } - ], - "dependencies": [ - { - "ref": "pkg:pypi/legacy-dev-deps-project@1.0.0", - "dependsOn": [ - "pkg:pypi/requests@2.33.1" - ] - }, - { - "ref": "pkg:pypi/requests@2.33.1", - "dependsOn": [ - "pkg:pypi/certifi@2026.2.25", - "pkg:pypi/charset-normalizer@3.4.7", - "pkg:pypi/idna@3.11", - "pkg:pypi/urllib3@2.6.3" - ] - }, - { - "ref": "pkg:pypi/certifi@2026.2.25", - "dependsOn": [] - }, - { - "ref": "pkg:pypi/charset-normalizer@3.4.7", - "dependsOn": [] - }, - { - "ref": "pkg:pypi/idna@3.11", - "dependsOn": [] - }, - { - "ref": "pkg:pypi/urllib3@2.6.3", - "dependsOn": [] - } - ] + "bomFormat": "CycloneDX", + "specVersion": "1.4", + "version": 1, + "metadata": { + "timestamp": "2023-10-01T00:00:00.000Z", + "component": { + "name": "legacy-dev-deps-project", + "version": "1.0.0", + "purl": "pkg:pypi/legacy-dev-deps-project@1.0.0", + "type": "application", + "bom-ref": "pkg:pypi/legacy-dev-deps-project@1.0.0" + } + }, + "components": [ + { + "name": "requests", + "version": "2.33.1", + "purl": "pkg:pypi/requests@2.33.1", + "type": "library", + "bom-ref": "pkg:pypi/requests@2.33.1", + "hashes": [ + { + "alg": "SHA-256", + "content": "18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517" + } + ] + }, + { + "name": "certifi", + "version": "2026.2.25", + "purl": "pkg:pypi/certifi@2026.2.25", + "type": "library", + "bom-ref": "pkg:pypi/certifi@2026.2.25", + "hashes": [ + { + "alg": "SHA-256", + "content": "e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7" + } + ] + }, + { + "name": "charset-normalizer", + "version": "3.4.7", + "purl": "pkg:pypi/charset-normalizer@3.4.7", + "type": "library", + "bom-ref": "pkg:pypi/charset-normalizer@3.4.7", + "hashes": [ + { + "alg": "SHA-256", + "content": "ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5" + } + ] + }, + { + "name": "idna", + "version": "3.11", + "purl": "pkg:pypi/idna@3.11", + "type": "library", + "bom-ref": "pkg:pypi/idna@3.11", + "hashes": [ + { + "alg": "SHA-256", + "content": "795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" + } + ] + }, + { + "name": "urllib3", + "version": "2.6.3", + "purl": "pkg:pypi/urllib3@2.6.3", + "type": "library", + "bom-ref": "pkg:pypi/urllib3@2.6.3", + "hashes": [ + { + "alg": "SHA-256", + "content": "1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed" + } + ] + } + ], + "dependencies": [ + { + "ref": "pkg:pypi/legacy-dev-deps-project@1.0.0", + "dependsOn": [ + "pkg:pypi/requests@2.33.1" + ] + }, + { + "ref": "pkg:pypi/requests@2.33.1", + "dependsOn": [ + "pkg:pypi/certifi@2026.2.25", + "pkg:pypi/charset-normalizer@3.4.7", + "pkg:pypi/idna@3.11", + "pkg:pypi/urllib3@2.6.3" + ] + }, + { + "ref": "pkg:pypi/certifi@2026.2.25", + "dependsOn": [] + }, + { + "ref": "pkg:pypi/charset-normalizer@3.4.7", + "dependsOn": [] + }, + { + "ref": "pkg:pypi/idna@3.11", + "dependsOn": [] + }, + { + "ref": "pkg:pypi/urllib3@2.6.3", + "dependsOn": [] + } + ] } diff --git a/test/providers/tst_manifests/pyproject/poetry_lock/expected_component_sbom.json b/test/providers/tst_manifests/pyproject/poetry_lock/expected_component_sbom.json index 990ca08f..9c391182 100644 --- a/test/providers/tst_manifests/pyproject/poetry_lock/expected_component_sbom.json +++ b/test/providers/tst_manifests/pyproject/poetry_lock/expected_component_sbom.json @@ -18,14 +18,26 @@ "version": "3.1.3", "purl": "pkg:pypi/flask@3.1.3", "type": "library", - "bom-ref": "pkg:pypi/flask@3.1.3" + "bom-ref": "pkg:pypi/flask@3.1.3", + "hashes": [ + { + "alg": "SHA-256", + "content": "0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb" + } + ] }, { "name": "requests", "version": "2.33.0", "purl": "pkg:pypi/requests@2.33.0", "type": "library", - "bom-ref": "pkg:pypi/requests@2.33.0" + "bom-ref": "pkg:pypi/requests@2.33.0", + "hashes": [ + { + "alg": "SHA-256", + "content": "c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652" + } + ] } ], "dependencies": [ diff --git a/test/providers/tst_manifests/pyproject/poetry_lock/expected_stack_sbom.json b/test/providers/tst_manifests/pyproject/poetry_lock/expected_stack_sbom.json index bac66b4b..c10732b6 100644 --- a/test/providers/tst_manifests/pyproject/poetry_lock/expected_stack_sbom.json +++ b/test/providers/tst_manifests/pyproject/poetry_lock/expected_stack_sbom.json @@ -18,77 +18,143 @@ "version": "3.1.3", "purl": "pkg:pypi/flask@3.1.3", "type": "library", - "bom-ref": "pkg:pypi/flask@3.1.3" + "bom-ref": "pkg:pypi/flask@3.1.3", + "hashes": [ + { + "alg": "SHA-256", + "content": "0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb" + } + ] }, { "name": "requests", "version": "2.33.0", "purl": "pkg:pypi/requests@2.33.0", "type": "library", - "bom-ref": "pkg:pypi/requests@2.33.0" + "bom-ref": "pkg:pypi/requests@2.33.0", + "hashes": [ + { + "alg": "SHA-256", + "content": "c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652" + } + ] }, { "name": "blinker", "version": "1.9.0", "purl": "pkg:pypi/blinker@1.9.0", "type": "library", - "bom-ref": "pkg:pypi/blinker@1.9.0" + "bom-ref": "pkg:pypi/blinker@1.9.0", + "hashes": [ + { + "alg": "SHA-256", + "content": "b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf" + } + ] }, { "name": "itsdangerous", "version": "2.2.0", "purl": "pkg:pypi/itsdangerous@2.2.0", "type": "library", - "bom-ref": "pkg:pypi/itsdangerous@2.2.0" + "bom-ref": "pkg:pypi/itsdangerous@2.2.0", + "hashes": [ + { + "alg": "SHA-256", + "content": "e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173" + } + ] }, { "name": "jinja2", "version": "3.1.6", "purl": "pkg:pypi/jinja2@3.1.6", "type": "library", - "bom-ref": "pkg:pypi/jinja2@3.1.6" + "bom-ref": "pkg:pypi/jinja2@3.1.6", + "hashes": [ + { + "alg": "SHA-256", + "content": "0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d" + } + ] }, { "name": "markupsafe", "version": "3.0.3", "purl": "pkg:pypi/markupsafe@3.0.3", "type": "library", - "bom-ref": "pkg:pypi/markupsafe@3.0.3" + "bom-ref": "pkg:pypi/markupsafe@3.0.3", + "hashes": [ + { + "alg": "SHA-256", + "content": "722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698" + } + ] }, { "name": "werkzeug", "version": "3.1.7", "purl": "pkg:pypi/werkzeug@3.1.7", "type": "library", - "bom-ref": "pkg:pypi/werkzeug@3.1.7" + "bom-ref": "pkg:pypi/werkzeug@3.1.7", + "hashes": [ + { + "alg": "SHA-256", + "content": "fb8c01fe6ab13b9b7cdb46892b99b1d66754e1d7ab8e542e865ec13f526b5351" + } + ] }, { "name": "certifi", "version": "2026.2.25", "purl": "pkg:pypi/certifi@2026.2.25", "type": "library", - "bom-ref": "pkg:pypi/certifi@2026.2.25" + "bom-ref": "pkg:pypi/certifi@2026.2.25", + "hashes": [ + { + "alg": "SHA-256", + "content": "e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7" + } + ] }, { "name": "charset-normalizer", "version": "3.4.6", "purl": "pkg:pypi/charset-normalizer@3.4.6", "type": "library", - "bom-ref": "pkg:pypi/charset-normalizer@3.4.6" + "bom-ref": "pkg:pypi/charset-normalizer@3.4.6", + "hashes": [ + { + "alg": "SHA-256", + "content": "1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6" + } + ] }, { "name": "idna", "version": "3.11", "purl": "pkg:pypi/idna@3.11", "type": "library", - "bom-ref": "pkg:pypi/idna@3.11" + "bom-ref": "pkg:pypi/idna@3.11", + "hashes": [ + { + "alg": "SHA-256", + "content": "795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" + } + ] }, { "name": "urllib3", "version": "2.6.3", "purl": "pkg:pypi/urllib3@2.6.3", "type": "library", - "bom-ref": "pkg:pypi/urllib3@2.6.3" + "bom-ref": "pkg:pypi/urllib3@2.6.3", + "hashes": [ + { + "alg": "SHA-256", + "content": "1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed" + } + ] } ], "dependencies": [ diff --git a/test/providers/tst_manifests/pyproject/poetry_only_deps/expected_component_sbom.json b/test/providers/tst_manifests/pyproject/poetry_only_deps/expected_component_sbom.json index 37b91266..d87e46cd 100644 --- a/test/providers/tst_manifests/pyproject/poetry_only_deps/expected_component_sbom.json +++ b/test/providers/tst_manifests/pyproject/poetry_only_deps/expected_component_sbom.json @@ -1,60 +1,78 @@ { - "bomFormat": "CycloneDX", - "specVersion": "1.4", - "version": 1, - "metadata": { - "timestamp": "2023-10-01T00:00:00.000Z", - "component": { - "name": "poetry-only-project", - "version": "1.0.0", - "purl": "pkg:pypi/poetry-only-project@1.0.0", - "type": "application", - "bom-ref": "pkg:pypi/poetry-only-project@1.0.0" - } - }, - "components": [ - { - "name": "click", - "version": "8.1.8", - "purl": "pkg:pypi/click@8.1.8", - "type": "library", - "bom-ref": "pkg:pypi/click@8.1.8" - }, - { - "name": "flask", - "version": "2.3.3", - "purl": "pkg:pypi/flask@2.3.3", - "type": "library", - "bom-ref": "pkg:pypi/flask@2.3.3" - }, - { - "name": "requests", - "version": "2.32.4", - "purl": "pkg:pypi/requests@2.32.4", - "type": "library", - "bom-ref": "pkg:pypi/requests@2.32.4" - } - ], - "dependencies": [ - { - "ref": "pkg:pypi/poetry-only-project@1.0.0", - "dependsOn": [ - "pkg:pypi/click@8.1.8", - "pkg:pypi/flask@2.3.3", - "pkg:pypi/requests@2.32.4" - ] - }, - { - "ref": "pkg:pypi/click@8.1.8", - "dependsOn": [] - }, - { - "ref": "pkg:pypi/flask@2.3.3", - "dependsOn": [] - }, - { - "ref": "pkg:pypi/requests@2.32.4", - "dependsOn": [] - } - ] + "bomFormat": "CycloneDX", + "specVersion": "1.4", + "version": 1, + "metadata": { + "timestamp": "2023-10-01T00:00:00.000Z", + "component": { + "name": "poetry-only-project", + "version": "1.0.0", + "purl": "pkg:pypi/poetry-only-project@1.0.0", + "type": "application", + "bom-ref": "pkg:pypi/poetry-only-project@1.0.0" + } + }, + "components": [ + { + "name": "click", + "version": "8.1.8", + "purl": "pkg:pypi/click@8.1.8", + "type": "library", + "bom-ref": "pkg:pypi/click@8.1.8", + "hashes": [ + { + "alg": "SHA-256", + "content": "ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a" + } + ] + }, + { + "name": "flask", + "version": "2.3.3", + "purl": "pkg:pypi/flask@2.3.3", + "type": "library", + "bom-ref": "pkg:pypi/flask@2.3.3", + "hashes": [ + { + "alg": "SHA-256", + "content": "09c347a92aa7ff4a8e7f3206795f30d826654baf38b873d0744cd571ca609efc" + } + ] + }, + { + "name": "requests", + "version": "2.32.4", + "purl": "pkg:pypi/requests@2.32.4", + "type": "library", + "bom-ref": "pkg:pypi/requests@2.32.4", + "hashes": [ + { + "alg": "SHA-256", + "content": "27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422" + } + ] + } + ], + "dependencies": [ + { + "ref": "pkg:pypi/poetry-only-project@1.0.0", + "dependsOn": [ + "pkg:pypi/click@8.1.8", + "pkg:pypi/flask@2.3.3", + "pkg:pypi/requests@2.32.4" + ] + }, + { + "ref": "pkg:pypi/click@8.1.8", + "dependsOn": [] + }, + { + "ref": "pkg:pypi/flask@2.3.3", + "dependsOn": [] + }, + { + "ref": "pkg:pypi/requests@2.32.4", + "dependsOn": [] + } + ] } diff --git a/test/providers/tst_manifests/pyproject/poetry_only_deps/expected_stack_sbom.json b/test/providers/tst_manifests/pyproject/poetry_only_deps/expected_stack_sbom.json index d36acbfa..a047ffae 100644 --- a/test/providers/tst_manifests/pyproject/poetry_only_deps/expected_stack_sbom.json +++ b/test/providers/tst_manifests/pyproject/poetry_only_deps/expected_stack_sbom.json @@ -1,199 +1,283 @@ { - "bomFormat": "CycloneDX", - "specVersion": "1.4", - "version": 1, - "metadata": { - "timestamp": "2023-10-01T00:00:00.000Z", - "component": { - "name": "poetry-only-project", - "version": "1.0.0", - "purl": "pkg:pypi/poetry-only-project@1.0.0", - "type": "application", - "bom-ref": "pkg:pypi/poetry-only-project@1.0.0" - } - }, - "components": [ - { - "name": "click", - "version": "8.1.8", - "purl": "pkg:pypi/click@8.1.8", - "type": "library", - "bom-ref": "pkg:pypi/click@8.1.8" - }, - { - "name": "flask", - "version": "2.3.3", - "purl": "pkg:pypi/flask@2.3.3", - "type": "library", - "bom-ref": "pkg:pypi/flask@2.3.3" - }, - { - "name": "requests", - "version": "2.32.4", - "purl": "pkg:pypi/requests@2.32.4", - "type": "library", - "bom-ref": "pkg:pypi/requests@2.32.4" - }, - { - "name": "blinker", - "version": "1.8.2", - "purl": "pkg:pypi/blinker@1.8.2", - "type": "library", - "bom-ref": "pkg:pypi/blinker@1.8.2" - }, - { - "name": "importlib-metadata", - "version": "8.5.0", - "purl": "pkg:pypi/importlib-metadata@8.5.0", - "type": "library", - "bom-ref": "pkg:pypi/importlib-metadata@8.5.0" - }, - { - "name": "itsdangerous", - "version": "2.2.0", - "purl": "pkg:pypi/itsdangerous@2.2.0", - "type": "library", - "bom-ref": "pkg:pypi/itsdangerous@2.2.0" - }, - { - "name": "jinja2", - "version": "3.1.6", - "purl": "pkg:pypi/jinja2@3.1.6", - "type": "library", - "bom-ref": "pkg:pypi/jinja2@3.1.6" - }, - { - "name": "werkzeug", - "version": "3.0.6", - "purl": "pkg:pypi/werkzeug@3.0.6", - "type": "library", - "bom-ref": "pkg:pypi/werkzeug@3.0.6" - }, - { - "name": "zipp", - "version": "3.20.2", - "purl": "pkg:pypi/zipp@3.20.2", - "type": "library", - "bom-ref": "pkg:pypi/zipp@3.20.2" - }, - { - "name": "markupsafe", - "version": "2.1.5", - "purl": "pkg:pypi/markupsafe@2.1.5", - "type": "library", - "bom-ref": "pkg:pypi/markupsafe@2.1.5" - }, - { - "name": "certifi", - "version": "2026.2.25", - "purl": "pkg:pypi/certifi@2026.2.25", - "type": "library", - "bom-ref": "pkg:pypi/certifi@2026.2.25" - }, - { - "name": "charset-normalizer", - "version": "3.4.6", - "purl": "pkg:pypi/charset-normalizer@3.4.6", - "type": "library", - "bom-ref": "pkg:pypi/charset-normalizer@3.4.6" - }, - { - "name": "idna", - "version": "3.11", - "purl": "pkg:pypi/idna@3.11", - "type": "library", - "bom-ref": "pkg:pypi/idna@3.11" - }, - { - "name": "urllib3", - "version": "2.2.3", - "purl": "pkg:pypi/urllib3@2.2.3", - "type": "library", - "bom-ref": "pkg:pypi/urllib3@2.2.3" - } - ], - "dependencies": [ - { - "ref": "pkg:pypi/poetry-only-project@1.0.0", - "dependsOn": [ - "pkg:pypi/click@8.1.8", - "pkg:pypi/flask@2.3.3", - "pkg:pypi/requests@2.32.4" - ] - }, - { - "ref": "pkg:pypi/click@8.1.8", - "dependsOn": [] - }, - { - "ref": "pkg:pypi/flask@2.3.3", - "dependsOn": [ - "pkg:pypi/blinker@1.8.2", - "pkg:pypi/click@8.1.8", - "pkg:pypi/importlib-metadata@8.5.0", - "pkg:pypi/itsdangerous@2.2.0", - "pkg:pypi/jinja2@3.1.6", - "pkg:pypi/werkzeug@3.0.6" - ] - }, - { - "ref": "pkg:pypi/requests@2.32.4", - "dependsOn": [ - "pkg:pypi/certifi@2026.2.25", - "pkg:pypi/charset-normalizer@3.4.6", - "pkg:pypi/idna@3.11", - "pkg:pypi/urllib3@2.2.3" - ] - }, - { - "ref": "pkg:pypi/blinker@1.8.2", - "dependsOn": [] - }, - { - "ref": "pkg:pypi/importlib-metadata@8.5.0", - "dependsOn": [ - "pkg:pypi/zipp@3.20.2" - ] - }, - { - "ref": "pkg:pypi/itsdangerous@2.2.0", - "dependsOn": [] - }, - { - "ref": "pkg:pypi/jinja2@3.1.6", - "dependsOn": [ - "pkg:pypi/markupsafe@2.1.5" - ] - }, - { - "ref": "pkg:pypi/werkzeug@3.0.6", - "dependsOn": [ - "pkg:pypi/markupsafe@2.1.5" - ] - }, - { - "ref": "pkg:pypi/zipp@3.20.2", - "dependsOn": [] - }, - { - "ref": "pkg:pypi/markupsafe@2.1.5", - "dependsOn": [] - }, - { - "ref": "pkg:pypi/certifi@2026.2.25", - "dependsOn": [] - }, - { - "ref": "pkg:pypi/charset-normalizer@3.4.6", - "dependsOn": [] - }, - { - "ref": "pkg:pypi/idna@3.11", - "dependsOn": [] - }, - { - "ref": "pkg:pypi/urllib3@2.2.3", - "dependsOn": [] - } - ] + "bomFormat": "CycloneDX", + "specVersion": "1.4", + "version": 1, + "metadata": { + "timestamp": "2023-10-01T00:00:00.000Z", + "component": { + "name": "poetry-only-project", + "version": "1.0.0", + "purl": "pkg:pypi/poetry-only-project@1.0.0", + "type": "application", + "bom-ref": "pkg:pypi/poetry-only-project@1.0.0" + } + }, + "components": [ + { + "name": "click", + "version": "8.1.8", + "purl": "pkg:pypi/click@8.1.8", + "type": "library", + "bom-ref": "pkg:pypi/click@8.1.8", + "hashes": [ + { + "alg": "SHA-256", + "content": "ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a" + } + ] + }, + { + "name": "flask", + "version": "2.3.3", + "purl": "pkg:pypi/flask@2.3.3", + "type": "library", + "bom-ref": "pkg:pypi/flask@2.3.3", + "hashes": [ + { + "alg": "SHA-256", + "content": "09c347a92aa7ff4a8e7f3206795f30d826654baf38b873d0744cd571ca609efc" + } + ] + }, + { + "name": "requests", + "version": "2.32.4", + "purl": "pkg:pypi/requests@2.32.4", + "type": "library", + "bom-ref": "pkg:pypi/requests@2.32.4", + "hashes": [ + { + "alg": "SHA-256", + "content": "27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422" + } + ] + }, + { + "name": "blinker", + "version": "1.8.2", + "purl": "pkg:pypi/blinker@1.8.2", + "type": "library", + "bom-ref": "pkg:pypi/blinker@1.8.2", + "hashes": [ + { + "alg": "SHA-256", + "content": "8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83" + } + ] + }, + { + "name": "importlib-metadata", + "version": "8.5.0", + "purl": "pkg:pypi/importlib-metadata@8.5.0", + "type": "library", + "bom-ref": "pkg:pypi/importlib-metadata@8.5.0", + "hashes": [ + { + "alg": "SHA-256", + "content": "71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7" + } + ] + }, + { + "name": "itsdangerous", + "version": "2.2.0", + "purl": "pkg:pypi/itsdangerous@2.2.0", + "type": "library", + "bom-ref": "pkg:pypi/itsdangerous@2.2.0", + "hashes": [ + { + "alg": "SHA-256", + "content": "e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173" + } + ] + }, + { + "name": "jinja2", + "version": "3.1.6", + "purl": "pkg:pypi/jinja2@3.1.6", + "type": "library", + "bom-ref": "pkg:pypi/jinja2@3.1.6", + "hashes": [ + { + "alg": "SHA-256", + "content": "0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d" + } + ] + }, + { + "name": "werkzeug", + "version": "3.0.6", + "purl": "pkg:pypi/werkzeug@3.0.6", + "type": "library", + "bom-ref": "pkg:pypi/werkzeug@3.0.6", + "hashes": [ + { + "alg": "SHA-256", + "content": "a8dd59d4de28ca70471a34cba79bed5f7ef2e036a76b3ab0835474246eb41f8d" + } + ] + }, + { + "name": "zipp", + "version": "3.20.2", + "purl": "pkg:pypi/zipp@3.20.2", + "type": "library", + "bom-ref": "pkg:pypi/zipp@3.20.2", + "hashes": [ + { + "alg": "SHA-256", + "content": "bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29" + } + ] + }, + { + "name": "markupsafe", + "version": "2.1.5", + "purl": "pkg:pypi/markupsafe@2.1.5", + "type": "library", + "bom-ref": "pkg:pypi/markupsafe@2.1.5", + "hashes": [ + { + "alg": "SHA-256", + "content": "d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b" + } + ] + }, + { + "name": "certifi", + "version": "2026.2.25", + "purl": "pkg:pypi/certifi@2026.2.25", + "type": "library", + "bom-ref": "pkg:pypi/certifi@2026.2.25", + "hashes": [ + { + "alg": "SHA-256", + "content": "e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7" + } + ] + }, + { + "name": "charset-normalizer", + "version": "3.4.6", + "purl": "pkg:pypi/charset-normalizer@3.4.6", + "type": "library", + "bom-ref": "pkg:pypi/charset-normalizer@3.4.6", + "hashes": [ + { + "alg": "SHA-256", + "content": "1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6" + } + ] + }, + { + "name": "idna", + "version": "3.11", + "purl": "pkg:pypi/idna@3.11", + "type": "library", + "bom-ref": "pkg:pypi/idna@3.11", + "hashes": [ + { + "alg": "SHA-256", + "content": "795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" + } + ] + }, + { + "name": "urllib3", + "version": "2.2.3", + "purl": "pkg:pypi/urllib3@2.2.3", + "type": "library", + "bom-ref": "pkg:pypi/urllib3@2.2.3", + "hashes": [ + { + "alg": "SHA-256", + "content": "e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9" + } + ] + } + ], + "dependencies": [ + { + "ref": "pkg:pypi/poetry-only-project@1.0.0", + "dependsOn": [ + "pkg:pypi/click@8.1.8", + "pkg:pypi/flask@2.3.3", + "pkg:pypi/requests@2.32.4" + ] + }, + { + "ref": "pkg:pypi/click@8.1.8", + "dependsOn": [] + }, + { + "ref": "pkg:pypi/flask@2.3.3", + "dependsOn": [ + "pkg:pypi/blinker@1.8.2", + "pkg:pypi/click@8.1.8", + "pkg:pypi/importlib-metadata@8.5.0", + "pkg:pypi/itsdangerous@2.2.0", + "pkg:pypi/jinja2@3.1.6", + "pkg:pypi/werkzeug@3.0.6" + ] + }, + { + "ref": "pkg:pypi/requests@2.32.4", + "dependsOn": [ + "pkg:pypi/certifi@2026.2.25", + "pkg:pypi/charset-normalizer@3.4.6", + "pkg:pypi/idna@3.11", + "pkg:pypi/urllib3@2.2.3" + ] + }, + { + "ref": "pkg:pypi/blinker@1.8.2", + "dependsOn": [] + }, + { + "ref": "pkg:pypi/importlib-metadata@8.5.0", + "dependsOn": [ + "pkg:pypi/zipp@3.20.2" + ] + }, + { + "ref": "pkg:pypi/itsdangerous@2.2.0", + "dependsOn": [] + }, + { + "ref": "pkg:pypi/jinja2@3.1.6", + "dependsOn": [ + "pkg:pypi/markupsafe@2.1.5" + ] + }, + { + "ref": "pkg:pypi/werkzeug@3.0.6", + "dependsOn": [ + "pkg:pypi/markupsafe@2.1.5" + ] + }, + { + "ref": "pkg:pypi/zipp@3.20.2", + "dependsOn": [] + }, + { + "ref": "pkg:pypi/markupsafe@2.1.5", + "dependsOn": [] + }, + { + "ref": "pkg:pypi/certifi@2026.2.25", + "dependsOn": [] + }, + { + "ref": "pkg:pypi/charset-normalizer@3.4.6", + "dependsOn": [] + }, + { + "ref": "pkg:pypi/idna@3.11", + "dependsOn": [] + }, + { + "ref": "pkg:pypi/urllib3@2.2.3", + "dependsOn": [] + } + ] }