diff --git a/lib/helpers/utils.js b/lib/helpers/utils.js index 4f15730b8f..7a69ce04de 100644 --- a/lib/helpers/utils.js +++ b/lib/helpers/utils.js @@ -17,14 +17,14 @@ import { } from "node:fs"; import { homedir, platform, tmpdir } from "node:os"; import path, { - basename, delimiter as _delimiter, + sep as _sep, + basename, dirname, extname, join, - resolve, relative, - sep as _sep, + resolve, } from "node:path"; import process from "node:process"; import { URL, fileURLToPath } from "node:url"; @@ -4902,6 +4902,7 @@ export function parsePyProjectTomlFile(tomlFile) { } } + let poetryV2Mode = false; let poetryMode = false; let uvMode = false; let hatchMode = false; @@ -4924,6 +4925,17 @@ export function parsePyProjectTomlFile(tomlFile) { ) { poetryMode = true; } + + const buildRequires = tomlData?.["build-system"]?.["requires"]; + if (buildRequires && Array.isArray(buildRequires)) { + for (const req of buildRequires) { + if (req.startsWith("poetry-core") && req.includes(">=2.0")) { + poetryV2Mode = true; + break; + } + } + } + if (tomlData?.tool?.uv) { uvMode = true; } @@ -5027,6 +5039,7 @@ export function parsePyProjectTomlFile(tomlFile) { return { parentComponent: pkg, poetryMode, + poetryV2Mode, uvMode, hatchMode, workspacePaths, @@ -13526,6 +13539,23 @@ export function getPipFrozenTree( }); thoughtLog("Performing poetry install"); let poetryInstallArgs = ["-m", "poetry", "install", "-n", "--no-root"]; + + // Storing pyProjectpath after checking the existence of pyproject.toml file + const pyprojectpath = safeExistsSync(join(basePath, "pyproject.toml")) + ? join(basePath, "pyproject.toml") + : null; + let poetryV2Mode = false; + // Check if the pyproject.toml is not null + if (pyprojectpath) { + poetryV2Mode = + parsePyProjectTomlFile(pyprojectpath).poetryV2Mode || false; + } + // checking if poetryV2 is true or not + if (poetryV2Mode) { + // Include all dependency groups and extras (Poetry v2+) + poetryInstallArgs.push("--all-groups", "--all-extras"); + } + // Attempt to perform poetry install result = safeSpawnSync(PYTHON_CMD, poetryInstallArgs, { cwd: basePath, @@ -13539,6 +13569,11 @@ export function getPipFrozenTree( "Hmm, poetry doesn't seem to be available as a module. Perhaps it was installed directly 🤔?", ); poetryInstallArgs = ["install", "-n", "--no-root"]; + + if (poetryV2Mode) { + // Also include flags when calling poetry directly + poetryInstallArgs.push("--all-groups", "--all-extras"); + } // Attempt to perform poetry install result = safeSpawnSync("poetry", poetryInstallArgs, { cwd: basePath, diff --git a/lib/helpers/utils.test.js b/lib/helpers/utils.test.js index 302877711e..5fb5560c48 100644 --- a/lib/helpers/utils.test.js +++ b/lib/helpers/utils.test.js @@ -11,12 +11,10 @@ import { findLicenseId, getCratesMetadata, getDartMetadata, - getGoPkgLicense, getLicenses, getMvnMetadata, getNugetMetadata, getPyMetadata, - getRepoLicense, guessPypiMatchingVersion, hasAnyProjectType, isPackageManagerAllowed, @@ -4979,6 +4977,38 @@ test("parse pyproject.toml", () => { }); }); +test("parse pyproject.toml with poetryv2 requirement", () => { + const retMap = parsePyProjectTomlFile("./test/data/pyproject_poetryv2.toml"); + expect(retMap.parentComponent).toEqual({ + name: "blint", + version: "2.4.2", + description: "Linter and SBOM generator for binary files.", + license: "MIT", + authors: [{ name: "Team AppThreat", email: "cloud@appthreat.com" }], + homepage: { url: "https://github.com/owasp-dep-scan/blint" }, + repository: { url: "https://github.com/owasp-dep-scan/blint" }, + tags: ["binary", "linter", "sast", "security"], + properties: [{ name: "cdx:pypi:requiresPython", value: ">=3.10,<3.14" }], + type: "application", + "bom-ref": "pkg:pypi/blint@2.4.2", + purl: "pkg:pypi/blint@2.4.2", + evidence: { + identity: { + field: "purl", + confidence: 1, + methods: [ + { + technique: "manifest-analysis", + confidence: 1, + value: "./test/data/pyproject_poetryv2.toml", + }, + ], + }, + }, + }); + expect(retMap.poetryV2Mode).toBeTruthy(); +}); + test("parse pyproject.toml with custom poetry source", () => { const retMap = parsePyProjectTomlFile( "./test/data/pyproject_with_custom_poetry_source.toml", diff --git a/test/data/pyproject_poetryv2.toml b/test/data/pyproject_poetryv2.toml new file mode 100644 index 0000000000..10e1a505a0 --- /dev/null +++ b/test/data/pyproject_poetryv2.toml @@ -0,0 +1,81 @@ +[project] +name = "blint" +version = "2.4.2" +description = "Linter and SBOM generator for binary files." +authors = [ + {name= "Team AppThreat", email = "cloud@appthreat.com"}, +] +dependencies = [ + "lief>=0.16.6", + "rich>=14.0.0", + "PyYAML>=6.0.2", + "defusedxml>=0.7.1", + "pydantic[email]>=2.11.3", + "orjson>=3.10.16", + "symbolic==10.2.1", + "ar>=1.0.0", + "custom-json-diff>=2.1.6", + "appdirs>=1.4.4", + "apsw>=3.49.1.0", + "packageurl-python>=0.16.0", + "oras>=0.2.28", +] +license = "MIT" +readme = "README.md" +homepage = "https://github.com/owasp-dep-scan/blint" +repository = "https://github.com/owasp-dep-scan/blint" +keywords = ["linter", "binary", "security", "sast"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Intended Audience :: System Administrators", + "Topic :: Utilities", + "Topic :: Security", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Operating System :: OS Independent", +] +requires-python = ">=3.10,<3.14" +include = ["blint/data/*.yml"] + +[project.urls] +"CI" = "https://github.com/owasp-dep-scan/blint/actions" + +[project.scripts] +blint = 'blint.cli:main' + +[project.optional-dependencies] +dev = [ + "pytest>=8.3.5", + "black>=25.1.0", + "flake8>=7.2.0", + "pylint>=3.3.6", + "pytest-cov>=6.1.1", + "pyinstaller>=6.12.0" +] + +[tool.black] +line-length = 99 + +[build-system] +requires = ["poetry-core>=2.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.pytest.ini_options] +addopts = "--verbose --cov-append --cov-report term --cov blint" + +[tool.pylint] +generated-members = ["lief", "orjson"] +ignore-paths = ["blint/cyclonedx/*", "tests/*"] +# Let's not fuss about long strings +ignore-long-lines = "[r|f]\"" +disable = ["missing-module-docstring", "logging-fstring-interpolation"] + +[tool.pylint.format] +max-line-length = 99 + +[tool.pylint.design] +max-args = 6 +max-nested-blocks = 6 \ No newline at end of file