Skip to content

Unexpected behavior when the dependency package references a different version than the direct dependency #977

@wgnf

Description

@wgnf

Hello together,

we're starting to create SBOMs in our company and I setup a pipeline to create them. In the process I noticed a behavior, that is unexpected for me.

For the following examples I always run the following command using the .NET tool in version 5.5.0:

dotnet-cyclonedx .\ExampleConsumerWpf.csproj -F json -rs -tfm net8.0

Example 1 - One application with one package reference

graph TD;
    A[Application A]-->B[Package B];
    A-->N1((Newtonsoft.Json 13.0.4 - latest));
    B-->N2((Newtonsoft.Json 11.0.2. - vulnerable));
Loading

So we have a package (ExamplePackage) that looks like the following:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="Newtonsoft.Json" Version="11.0.2"/>
    </ItemGroup>
</Project>

And an application (ExampleConsumerWpf) that references this package:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <OutputType>WinExe</OutputType>
        <TargetFramework>net8.0-windows</TargetFramework>
        <UseWPF>true</UseWPF>
        <Platform>winx64</Platform>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="ExamplePackage" Version="1.0.0"/>
        <PackageReference Include="Newtonsoft.Json" Version="13.0.4"/>
    </ItemGroup>
</Project>

The project.assets.json correctly states that it is dependent on the package, which itself is dependent on Newtonsoft.Json 11.0.2. But the application is dependent on Newtonsoft.Json 13.0.4 (because it directly depends on it, and it is highest version in the dependency tree):

{
  "version": 3,
  "targets": {
    "net8.0-windows7.0": {
      "ExamplePackage/1.0.0": {
        "type": "package",
        "dependencies": {
          "Newtonsoft.Json": "11.0.2"
        },
        "compile": {
          "lib/net8.0/ExamplePackage.dll": {}
        },
        "runtime": {
          "lib/net8.0/ExamplePackage.dll": {}
        }
      },
      "Newtonsoft.Json/13.0.4": {
        "type": "package",
        "compile": {
          "lib/net6.0/Newtonsoft.Json.dll": {
            "related": ".xml"
          }
        },
        "runtime": {
          "lib/net6.0/Newtonsoft.Json.dll": {
            "related": ".xml"
          }
        }
      }
    }
  },
...

But the resulting SBOM states that the ExamplePackage actually depends on Newtonsoft.Json 13.0.4:

...
"dependencies": [
    {
      "ref": "[email protected]",
      "dependsOn": [
        "pkg:nuget/[email protected]",
        "pkg:nuget/[email protected]"
      ]
    },
    {
      "ref": "pkg:nuget/[email protected]",
      "dependsOn": [
        "pkg:nuget/[email protected]"
      ]
    },
    {
      "ref": "pkg:nuget/[email protected]",
      "dependsOn": []
    }
  ]
...

Which is factually incorrect (but not really an issue, because the ExampleConsumerWpf does not depend on Newtonsoft.Json 11.0.2).

Example 2 - The application additionally depends on a class library that depends on the package, without directly depending on Newtonsoft.Json 11.0.2

The chaos that NuGet and a large solution sometimes is, we have a structure that looks like the following (broken down real world example):

graph TD;
    A[Application A]-->B[Package B];
    A-->N1((Newtonsoft.Json 13.0.4 - latest));
    B-->N2((Newtonsoft.Json 11.0.2. - vulnerable));
    C[Class Library C]-->B[Package B];
    A-->C;
Loading

So we additionally have a class library that depends on the ExamplePackage:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="ExamplePackage" Version="1.0.0"/>
    </ItemGroup>
</Project>

and the ExampleConsumerWpf references that class library:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <OutputType>WinExe</OutputType>
        <TargetFramework>net8.0-windows</TargetFramework>
        <UseWPF>true</UseWPF>
        <Platform>winx64</Platform>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="ExamplePackage" Version="1.0.0"/>
        <PackageReference Include="Newtonsoft.Json" Version="13.0.4"/>
    </ItemGroup>
    <ItemGroup>
      <ProjectReference Include="..\ClassLibrary1\ClassLibrary1.csproj" />
    </ItemGroup>
</Project>

ExamplePackage stays unchanged.

The project.assets.json of the ExampleConsumerWpf correctly states that the application has dependencies on Newtonsoft.Json 13.0.4, ExamplePackage 1.0.0 (which depends on Newtonsoft.Json 11.0.2 and ClassLibrary 1.0.0 (which also depends on ExamplePackage 1.0.0):

{
  "version": 3,
  "targets": {
    "net8.0-windows7.0": {
      "ExamplePackage/1.0.0": {
        "type": "package",
        "dependencies": {
          "Newtonsoft.Json": "11.0.2"
        },
        "compile": {
          "lib/net8.0/ExamplePackage.dll": {}
        },
        "runtime": {
          "lib/net8.0/ExamplePackage.dll": {}
        }
      },
      "Newtonsoft.Json/13.0.4": {
        "type": "package",
        "compile": {
          "lib/net6.0/Newtonsoft.Json.dll": {
            "related": ".xml"
          }
        },
        "runtime": {
          "lib/net6.0/Newtonsoft.Json.dll": {
            "related": ".xml"
          }
        }
      },
      "ClassLibrary1/1.0.0": {
        "type": "project",
        "framework": ".NETCoreApp,Version=v8.0",
        "dependencies": {
          "ExamplePackage": "1.0.0"
        },
        "compile": {
          "bin/placeholder/ClassLibrary1.dll": {}
        },
        "runtime": {
          "bin/placeholder/ClassLibrary1.dll": {}
        }
      }
    }
  },
...

But creating an SBOM again outputs, that ExamplePackage depends on Newtonsoft.Json 13.0.4 (which is factually incorrect):

...
"dependencies": [
    {
      "ref": "[email protected]",
      "dependsOn": [
        "pkg:nuget/[email protected]",
        "pkg:nuget/[email protected]"
      ]
    },
    {
      "ref": "pkg:nuget/[email protected]",
      "dependsOn": [
        "pkg:nuget/[email protected]"
      ]
    },
    {
      "ref": "pkg:nuget/[email protected]",
      "dependsOn": []
    },
    {
      "ref": "pkg:nuget/[email protected]",
      "dependsOn": []
    }
  ]
...

But in this scenario, we'll also have Newtonsoft.Json 11.0.2 as a component listed in the SBOM (because of the recursive nature and project reference to ClassLibrary1):

...
{
      "type": "library",
      "bom-ref": "pkg:nuget/[email protected]",
      "authors": [
        {
          "name": "James Newton-King"
        }
      ],
      "name": "Newtonsoft.Json",
      "version": "11.0.2",
      "description": "Json.NET is a popular high-performance JSON framework for .NET",
      "scope": "required",
...

Which can be an issue however. Newtonsoft.Json 11.0.2 has a vulnerability and will be flagged as a Security Risk in tools like Dependency Track. BUT when one would like to analyze where that risk comes from, it would lead into a dead end, because nothing really depends on Newtonsoft.Json 11.0.2.


My questions regarding these information are:

  1. Is this caused by the problem that you cannot infer more information from the project.assets.json? Because, even though Newtonsoft.Json 11.0.2 is mentioned as a dependency for ExamplePackage, it has no entry under libraries (where information for the content, such as license files, are mentioned).
  2. Can (I think, yes) and will this be fixed in the future?

There might be a workaround to this, but the more I think about it, the more I come to the conclusion that a workaround is not feasable in a reald-world scenario.

Metadata

Metadata

Assignees

No one assigned

    Labels

    triageDon't know what to do with this yet

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions