Skip to content

Commit

Permalink
feat: Packagesprops support (#5605)
Browse files Browse the repository at this point in the history
Co-authored-by: DmitriyLewen <[email protected]>
  • Loading branch information
yuriShafet and DmitriyLewen authored Nov 28, 2023
1 parent 372efc9 commit 16b757d
Show file tree
Hide file tree
Showing 16 changed files with 344 additions and 32 deletions.
4 changes: 4 additions & 0 deletions docs/docs/coverage/language/dotnet.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ The following table provides an outline of the features Trivy offers.
|:---------------:|--------------------|:-----------------------:|:----------------:|:------------------------------------:|:--------:|
| .Net Core | *.deps.json || Excluded | - ||
| NuGet | packages.config || Excluded | - | - |
| NuGet | *Packages.props | - | Excluded | - | - |
| NuGet | packages.lock.json || Included |||

## *.deps.json
Expand All @@ -23,6 +24,9 @@ Trivy parses `*.deps.json` files. Trivy currently excludes dev dependencies from
## packages.config
Trivy only finds dependency names and versions from `packages.config` files. To build dependency graph, it is better to use `packages.lock.json` files.

## *Packages.props
Trivy parses `*Packages.props` files. Both legacy `Packages.props` and modern `Directory.Packages.props` are supported.

### license detection
`packages.config` files don't have information about the licenses used.
Trivy uses [*.nuspec][nuspec] files from [global packages folder][global-packages] to detect licenses.
Expand Down
2 changes: 2 additions & 0 deletions docs/docs/coverage/language/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ On the other hand, when the target is a post-build artifact, like a container im
| [.NET](dotnet.md) | packages.lock.json |||||
| | packages.config |||||
| | .deps.json |||||
| | *Packages.props[^11] |||||
| [Java](java.md) | JAR/WAR/PAR/EAR[^4] ||| - | - |
| | pom.xml | - | - |||
| | *gradle.lockfile | - | - |||
Expand Down Expand Up @@ -65,3 +66,4 @@ Example: [Dockerfile](https://github.com/aquasecurity/trivy-ci-test/blob/main/Do
[^8]: ✅ means "enabled" and `-` means "disabled" in the git repository scanning
[^9]: ✅ means that Trivy detects line numbers where each dependency is declared in the scanned file. Only supported in [json](../../configuration/reporting.md#json) and [sarif](../../configuration/reporting.md#sarif) formats. SARIF uses `startline == 1 and endline == 1` for unsupported file types
[^10]: To scan a filename other than the default filename use [file-patterns](../../configuration/skipping.md#file-patterns)
[^11]: `Directory.Packages.props` and legacy `Packages.props` file names are supported
9 changes: 9 additions & 0 deletions integration/repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,15 @@ func TestRepository(t *testing.T) {
},
golden: "testdata/dotnet.json.golden",
},
{
name: "packages-props",
args: args{
scanner: types.VulnerabilityScanner,
listAllPkgs: true,
input: "testdata/fixtures/repo/packagesprops",
},
golden: "testdata/packagesprops.json.golden",
},
{
name: "swift",
args: args{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project>

<ItemGroup>

<PackageVersion Include="Newtonsoft.Json" Version="9.0.1" />

</ItemGroup>
</Project>
69 changes: 69 additions & 0 deletions integration/testdata/packagesprops.json.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"SchemaVersion": 2,
"CreatedAt": "2021-08-25T12:20:30.000000005Z",
"ArtifactName": "testdata/fixtures/repo/packagesprops",
"ArtifactType": "repository",
"Metadata": {
"ImageConfig": {
"architecture": "",
"created": "0001-01-01T00:00:00Z",
"os": "",
"rootfs": {
"type": "",
"diff_ids": null
},
"config": {}
}
},
"Results": [
{
"Target": "Directory.Packages.props",
"Class": "lang-pkgs",
"Type": "packages-props",
"Packages": [
{
"ID": "[email protected]",
"Name": "Newtonsoft.Json",
"Version": "9.0.1",
"Layer": {}
}
],
"Vulnerabilities": [
{
"VulnerabilityID": "GHSA-5crp-9r3c-p9vr",
"PkgID": "[email protected]",
"PkgName": "Newtonsoft.Json",
"InstalledVersion": "9.0.1",
"FixedVersion": "13.0.1",
"Status": "fixed",
"Layer": {},
"SeveritySource": "ghsa",
"PrimaryURL": "https://github.com/advisories/GHSA-5crp-9r3c-p9vr",
"DataSource": {
"ID": "ghsa",
"Name": "GitHub Security Advisory Nuget",
"URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Anuget"
},
"Title": "Improper Handling of Exceptional Conditions in Newtonsoft.Json",
"Description": "Newtonsoft.Json prior to version 13.0.1 is vulnerable to Insecure Defaults due to improper handling of expressions with high nesting level that lead to StackOverFlow exception or high CPU and RAM usage.",
"Severity": "HIGH",
"CweIDs": [
"CWE-755"
],
"CVSS": {
"ghsa": {
"V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"V3Score": 7.5
}
},
"References": [
"https://alephsecurity.com/2018/10/22/StackOverflowException/",
"https://alephsecurity.com/vulns/aleph-2018004"
],
"PublishedDate": "2022-06-22T15:08:47Z",
"LastModifiedDate": "2022-06-27T18:37:23Z"
}
]
}
]
}
2 changes: 1 addition & 1 deletion pkg/detector/library/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func NewDriver(libType ftypes.LangType) (Driver, bool) {
case ftypes.Npm, ftypes.Yarn, ftypes.Pnpm, ftypes.NodePkg, ftypes.JavaScript:
ecosystem = vulnerability.Npm
comparer = npm.Comparer{}
case ftypes.NuGet, ftypes.DotNetCore:
case ftypes.NuGet, ftypes.DotNetCore, ftypes.PackagesProps:
ecosystem = vulnerability.NuGet
comparer = compare.GenericComparer{}
case ftypes.Pipenv, ftypes.Poetry, ftypes.Pip, ftypes.PythonPkg:
Expand Down
1 change: 1 addition & 0 deletions pkg/fanal/analyzer/all/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/dart/pub"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/dotnet/deps"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/dotnet/nuget"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/dotnet/packagesprops"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/elixir/mix"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/golang/binary"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/golang/mod"
Expand Down
6 changes: 4 additions & 2 deletions pkg/fanal/analyzer/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ const (
TypePnpm Type = "pnpm"

// .NET
TypeNuget Type = "nuget"
TypeDotNetCore Type = "dotnet-core"
TypeNuget Type = "nuget"
TypeDotNetCore Type = "dotnet-core"
TypePackagesProps Type = "packages-props"

// Conda
TypeCondaPkg Type = "conda-pkg"
Expand Down Expand Up @@ -171,6 +172,7 @@ var (
TypePnpm,
TypeNuget,
TypeDotNetCore,
TypePackagesProps,
TypeCondaPkg,
TypePythonPkg,
TypePip,
Expand Down
49 changes: 49 additions & 0 deletions pkg/fanal/analyzer/language/dotnet/packagesprops/packagesprops.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package packagesprops

import (
"context"
"os"
"strings"

"golang.org/x/xerrors"

props "github.com/aquasecurity/go-dep-parser/pkg/nuget/packagesprops"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer/language"
"github.com/aquasecurity/trivy/pkg/fanal/types"
)

func init() {
analyzer.RegisterAnalyzer(&packagesPropsAnalyzer{})
}

const (
version = 1
packagesPropsSuffix = "packages.props" // https://github.com/dotnet/roslyn-tools/blob/b4c5220f5dfc4278847b6d38eff91cc1188f8066/src/RoslynInsertionTool/RoslynInsertionTool/CoreXT.cs#L39-L40
)

type packagesPropsAnalyzer struct{}

func (a packagesPropsAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) {
parser := props.NewParser()
res, err := language.Analyze(types.PackagesProps, input.FilePath, input.Content, parser)
if err != nil {
return nil, xerrors.Errorf("*Packages.props dependencies analysis error: %w", err)
}

return res, nil
}

func (a packagesPropsAnalyzer) Required(filePath string, _ os.FileInfo) bool {
// There is no information about this in the documentation,
// but NuGet works correctly with lowercase filenames
return strings.HasSuffix(strings.ToLower(filePath), packagesPropsSuffix)
}

func (a packagesPropsAnalyzer) Type() analyzer.Type {
return analyzer.TypePackagesProps
}

func (a packagesPropsAnalyzer) Version() int {
return version
}
134 changes: 134 additions & 0 deletions pkg/fanal/analyzer/language/dotnet/packagesprops/packagesprops_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package packagesprops

import (
"context"
"os"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
"github.com/aquasecurity/trivy/pkg/fanal/types"
)

func Test_packagesPropsAnalyzer_Analyze(t *testing.T) {
tests := []struct {
name string
inputFile string
want *analyzer.AnalysisResult
wantErr string
}{
{
name: "happy path packages props",
inputFile: "testdata/Packages.props",
want: &analyzer.AnalysisResult{
Applications: []types.Application{
{
Type: types.PackagesProps,
FilePath: "testdata/Packages.props",
Libraries: types.Packages{
{
ID: "[email protected]",
Name: "Package1",
Version: "22.1.4",
},
{
ID: "[email protected]",
Name: "Package2",
Version: "2.3.0",
},
},
},
},
},
},
{
name: "happy path directory packages props",
inputFile: "testdata/Directory.Packages.props",
want: &analyzer.AnalysisResult{
Applications: []types.Application{
{
Type: types.PackagesProps,
FilePath: "testdata/Directory.Packages.props",
Libraries: types.Packages{
{
ID: "[email protected]",
Name: "Package1",
Version: "4.2.1",
},
{
ID: "[email protected]",
Name: "Package2",
Version: "8.2.0",
},
},
},
},
},
},
{
name: "sad path",
inputFile: "testdata/invalid.txt",
wantErr: "*Packages.props dependencies analysis error",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f, err := os.Open(tt.inputFile)
require.NoError(t, err)
defer f.Close()

a := packagesPropsAnalyzer{}
ctx := context.Background()
got, err := a.Analyze(ctx, analyzer.AnalysisInput{
FilePath: tt.inputFile,
Content: f,
})

if tt.wantErr != "" {
assert.ErrorContains(t, err, tt.wantErr)
return
}

assert.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}

func Test_packagesPropsAnalyzer_Required(t *testing.T) {
tests := []struct {
name string
filePath string
want bool
}{
{
name: "directory packages props",
filePath: "test/Directory.Packages.props",
want: true,
},
{
name: "packages props",
filePath: "test/Packages.props",
want: true,
},
{
name: "packages props lower case",
filePath: "test/packages.props",
want: true,
},
{
name: "zip",
filePath: "test.zip",
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := packagesPropsAnalyzer{}
got := a.Required(tt.filePath, nil)
assert.Equal(t, tt.want, got)
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<Project>

<ItemGroup>
<PackageVersion Include="Package1" Version="4.2.1" />
<PackageVersion Include="Package2" Version="8.2.0" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project>

<ItemGroup>

<PackageVersion Include="Package1" Version="22.1.4" />
<PackageVersion Include="Package2" Version="2.3.0" />

</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test
Loading

0 comments on commit 16b757d

Please sign in to comment.