Skip to content

ClassifyScope treats top-level dependencies as roots when the lock file doesn't list the project #23

Open

Description

@andrew

ClassifyScope takes any package that originates a DEPENDS_ON edge and is never a target as a root. That holds for lock files that include the project itself: syft emits the "" entry of package-lock.json and the project's own crate in Cargo.lock as nodes with edges to the direct dependencies, and ClassifyScope gets both right. poetry.lock lists only dependencies, so every top-level dependency that has dependencies of its own gets taken as a root and drops out of the result, and its dependencies get labelled direct.

With this poetry.lock, where requests is the only direct dependency:

[[package]]
name = "requests"
version = "2.31.0"
optional = false
python-versions = ">=3.7"
files = []

[package.dependencies]
urllib3 = ">=1.21.1,<3"

[[package]]
name = "urllib3"
version = "2.1.0"
optional = false
python-versions = ">=3.8"
files = []

[metadata]
lock-version = "2.0"
python-versions = "^3.8"
content-hash = "x"

syft dir:. -o cyclonedx-json (syft 1.55.0) emits one edge, requests -> urllib3, and metadata.component is the scanned directory (type: file) with no edges of its own. Parsing that with sbom v0.1.7 (same code on main):

doc, _ := sbom.Parse(data)
fmt.Println(doc.ClassifyScope())
// map[pkg:pypi/urllib3@2.1.0?package-id=9d0487ec0b086dd6:direct]

requests is missing and urllib3 is reported as direct. Expected: requests direct, urllib3 transitive.

The graph alone can't tell the two cases apart, since in both the root and a top-level dependency have outgoing edges and no incoming ones. When the document names its root, the parser already keeps what's needed except the ID: normalizeCycloneDX copies every dependencies entry, including one whose ref is the metadata.component bom-ref, but Document.Component drops that bom-ref, and SPDX DESCRIBES edges pass through unchanged. Keeping the bom-ref on Document.Component would let ClassifyScope use it (or a DESCRIBES target) as the only root when present and fall back to in-degree only when there is none, and would let callers tell "this package is the project" from "this package was inferred to be a root". The README section on ClassifyScope should also say that inferred roots can be top-level dependencies for lock formats that leave out the project.

git-pkgs/proxy#626 has the same heuristic in its own code to skip a project's own entry in uploaded SBOMs, and drops requests the same way.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions