Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Duplicate entry in SBOM with local NPM dependencies #2559

Open
atl-mk opened this issue Jan 29, 2024 · 6 comments
Open

Duplicate entry in SBOM with local NPM dependencies #2559

atl-mk opened this issue Jan 29, 2024 · 6 comments
Labels
bug Something isn't working

Comments

@atl-mk
Copy link

atl-mk commented Jan 29, 2024

What happened:

Syft creates two entries in the SBOM for the local dependency, one of which doesn't have the details like version or license

What you expected to happen:

For there to only be one entry in the SBOM output

Steps to reproduce the issue:

  1. Create a package.json for your main project, e.g.
{
  "name": "test",
  "private": true,
  "dependencies": {
    "jquery": "file:./packages/example"
  }
}
  1. Create a local dependency, e.g. un the relative folder ./packages/example

  2. Create a package.json file for the local dependency, e.g.

{
  "name": "example",
  "private": true,
  "version": "8.8.8",
  "license": "Apache-2.0"
}
  1. Build and run Syft to generate the SBOM like so

npm i && syft . -o cyclonedx-json=sbom.cyclonedx.json --exclude './packages/*'

  1. Read the SBOM, Syft has created two components based on reading the package-lock.json, one for the declared dependency in the package.json, and another from NPM resolving it to the local dependency.

E.g.

{
  "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
  "bomFormat": "CycloneDX",
  "specVersion": "1.5",
  "serialNumber": "urn:uuid:7e154fa4-8d8a-41ed-aaba-6f67b5659164",
  "version": 1,
  "metadata": {
    "timestamp": "2024-01-29T21:41:02+01:00",
    "tools": {
      "components": [
        {
          "type": "application",
          "author": "anchore",
          "name": "syft",
          "version": "0.101.1"
        }
      ]
    },
    "component": { "bom-ref": "af63bd4c8601b7f1", "type": "file", "name": "." }
  },
  "components": [
    {
      "bom-ref": "pkg:npm/example?package-id=da2139eb2ab28c91",
      "type": "library",
      "name": "example",
      "cpe": "cpe:2.3:a:example:example:*:*:*:*:*:*:*:*",
      "purl": "pkg:npm/example",
      "properties": [
        {
          "name": "syft:package:foundBy",
          "value": "javascript-lock-cataloger"
        },
        { "name": "syft:package:language", "value": "javascript" },
        { "name": "syft:package:type", "value": "npm" },
        {
          "name": "syft:package:metadataType",
          "value": "javascript-npm-package-lock-entry"
        },
        { "name": "syft:location:0:path", "value": "/package-lock.json" }
      ]
    },
    {
      "bom-ref": "pkg:npm/packages/[email protected]?package-id=4043fd3647ed6400",
      "type": "library",
      "name": "packages/example",
      "version": "8.8.8",
      "licenses": [{ "license": { "id": "Apache-2.0" } }],
      "cpe": "cpe:2.3:a:packages\\/example:packages\\/example:8.8.8:*:*:*:*:*:*:*",
      "purl": "pkg:npm/packages/[email protected]",
      "properties": [
        {
          "name": "syft:package:foundBy",
          "value": "javascript-lock-cataloger"
        },
        { "name": "syft:package:language", "value": "javascript" },
        { "name": "syft:package:type", "value": "npm" },
        {
          "name": "syft:package:metadataType",
          "value": "javascript-npm-package-lock-entry"
        },
        { "name": "syft:location:0:path", "value": "/package-lock.json" }
      ]
    },
    {
      "bom-ref": "pkg:npm/test?package-id=d4cce09498717bb5",
      "type": "library",
      "name": "test",
      "cpe": "cpe:2.3:a:test:test:*:*:*:*:*:*:*:*",
      "purl": "pkg:npm/test",
      "properties": [
        {
          "name": "syft:package:foundBy",
          "value": "javascript-lock-cataloger"
        },
        { "name": "syft:package:language", "value": "javascript" },
        { "name": "syft:package:type", "value": "npm" },
        {
          "name": "syft:package:metadataType",
          "value": "javascript-npm-package-lock-entry"
        },
        { "name": "syft:location:0:path", "value": "/package-lock.json" }
      ]
    }
  ]
}

Anything else we need to know?:

Purged the package-lock.json and node_modules folder before npm i and generating the SBOMs just to make sure it's fresh

Environment:

  • Node v20.10.0

  • NPM 10.2.3

  • Output of syft version:

Application: syft
Version:    0.101.1
BuildDate:  2024-01-19T22:02:04Z
GitCommit:  3eab5932e5271eea5506ab9710239b1415c827f8
GitDescription: v0.101.1
Platform:   linux/amd64
GoVersion:  go1.21.5
Compiler:   gc
  • OS (e.g: cat /etc/os-release or similar):

WSL2 Ubuntu 22.04

NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.3 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
@atl-mk atl-mk added the bug Something isn't working label Jan 29, 2024
@spiffcs
Copy link
Contributor

spiffcs commented Jan 30, 2024

@atl-mk I definitely see your point that the "link" package in the package-lock.json is does not seem to be handled correctly here.

I'm unsure on which package do you think should be dropped here.

Is exclude working on the way you expect it ?

Do you think that we should be dropping the package that is under the path that was excluded?

Or

Do you think we should be dropping the link packages and not reporting on those as part of the SBOM?

How do you think these "link" packages should be reported as so that the SBOM doesn't lose this information and can express the resolved nature of what was declared in the package.json vs what was cataloged by syft?

cc @willmurphyscode

@spiffcs spiffcs moved this to Awaiting Response in OSS Jan 30, 2024
@atl-mk
Copy link
Author

atl-mk commented Jan 31, 2024

@spiffcs Exclude has no effect here, because Syft is only creating the SBOM from the package-lock.json file. It doesn't matter if the exclude argument is used. I only included it in the reproduction steps to show that's not the issue.

The top and middle one should be merged, like so:

    {
      "bom-ref": "pkg:npm/example?package-id=4790f192e386e4d1",
      "type": "library",
      "name": "example",
      "version": "8.8.8",
      "licenses": [
        {
          "license": {
            "id": "Apache-2.0"
          }
        }
      ],
      "cpe": "cpe:2.3:a:packages\\/example:packages\\/example:8.8.8:*:*:*:*:*:*:*",
      "purl": "pkg:npm/packages/[email protected]",
      "properties": [
        {
          "name": "syft:package:foundBy",
          "value": "javascript-lock-cataloger"
        },
        {
          "name": "syft:package:language",
          "value": "javascript"
        },
        {
          "name": "syft:package:type",
          "value": "npm"
        },
        {
          "name": "syft:package:metadataType",
          "value": "javascript-npm-package-lock-entry"
        },
        {
          "name": "syft:location:0:path",
          "value": "/package-lock.json"
        }
      ]
    },

The name should reflect what is declared normally (just example), and CPE+PackageURL should map to the local files on disk.

@atl-mk
Copy link
Author

atl-mk commented Jan 31, 2024

For reference, this is what the package-lock.json file looks like

{
  "name": "test",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "test",
      "dependencies": {
        "example": "file:./packages/example"
      }
    },
    "node_modules/example": {
      "resolved": "packages/example",
      "link": true
    },
    "packages/example": {
      "version": "8.8.8",
      "license": "Apache-2.0"
    }
  }
}

So clearly Syft has some level of resolving linked dependencies from the package-lock.json, I don't see any reason to report the dependency alias.

Here's a related use-case that I'm not personally worried about, but could be of interest to you: https://gist.github.com/nandorojo/1b969a0d88cf81ca8a2a334a5bd2ee4a

@wagoodman wagoodman removed the status in OSS Feb 7, 2024
@spiffcs spiffcs moved this to Ready in OSS Feb 15, 2024
@spiffcs
Copy link
Contributor

spiffcs commented Feb 15, 2024

@anchore/tools I've added this one to Ready.

Based on our team sync we have agreed on removing node_modules/example from the final SBOM.

Here is the documentation for package-lock.json:
https://docs.npmjs.com/cli/v9/configuring-npm/package-lock-json#packages

There does not seem to be extra information in the "link" package so taking one over the other (not merging) should be the action here.

No relationships need to be updated for this ticket.

@willmurphyscode
Copy link
Contributor

This was also reported on discourse as causing false positives in Grype: https://anchorecommunity.discourse.group/t/grype-refers-to-file-in-repo-after-nextjs-upgrade/252/6?u=willmurphy

@wagoodman wagoodman moved this from Ready to Backlog in OSS Nov 27, 2024
@WTPascoe
Copy link

This was also reported on discourse as causing false positives in Grype: https://anchorecommunity.discourse.group/t/grype-refers-to-file-in-repo-after-nextjs-upgrade/252/6?u=willmurphy

The auth0-specific duplicates discussed here have been resolved by the Auth0 team in auth0/nextjs-auth0#1833

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
Status: Backlog
Development

No branches or pull requests

4 participants