Skip to content

Commit

Permalink
add nuget pinned dependency checks
Browse files Browse the repository at this point in the history
  • Loading branch information
balteravishay committed Mar 22, 2023
1 parent 9831629 commit c0b4ad7
Show file tree
Hide file tree
Showing 9 changed files with 217 additions and 10 deletions.
4 changes: 3 additions & 1 deletion checker/raw_result.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,10 @@ const (
DependencyUseTypeChocoCommand DependencyUseType = "chocoCommand"
// DependencyUseTypeNpmCommand is an npm command.
DependencyUseTypeNpmCommand DependencyUseType = "npmCommand"
// DependencyUseTypePipCommand is a pipp command.
// DependencyUseTypePipCommand is a pip command.
DependencyUseTypePipCommand DependencyUseType = "pipCommand"
// DependencyUseTypeNugetCommand is a nuget command.
DependencyUseTypeNugetCommand DependencyUseType = "nugetCommand"
)

// PinningDependenciesData represents pinned dependency data.
Expand Down
18 changes: 15 additions & 3 deletions checks/raw/pinned_dependencies_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ func TestGithubWorkflowPkgManagerPinning(t *testing.T) {
{
name: "npm packages without verification",
filename: "./testdata/.github/workflows/github-workflow-pkg-managers.yaml",
warns: 46,
warns: 48,
},
}
for _, tt := range tests {
Expand Down Expand Up @@ -819,6 +819,18 @@ func TestShellscriptInsecureDownloadsLineNumber(t *testing.T) {
endLine: 56,
t: checker.DependencyUseTypePipCommand,
},
{
snippet: "nuget install some-package",
startLine: 59,
endLine: 59,
t: checker.DependencyUseTypeNugetCommand,
},
{
snippet: "dotnet add package some-package",
startLine: 62,
endLine: 62,
t: checker.DependencyUseTypeNugetCommand,
},
},
},
}
Expand Down Expand Up @@ -971,7 +983,7 @@ func TestDockerfileScriptDownload(t *testing.T) {
{
name: "pkg managers",
filename: "./testdata/Dockerfile-pkg-managers",
warns: 57,
warns: 59,
},
{
name: "download with some python",
Expand Down Expand Up @@ -1089,7 +1101,7 @@ func TestShellScriptDownload(t *testing.T) {
{
name: "pkg managers",
filename: "./testdata/script-pkg-managers",
warns: 53,
warns: 55,
},
{
name: "invalid shell script",
Expand Down
100 changes: 98 additions & 2 deletions checks/raw/shell_download_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ var (
pythonInterpreters = []string{"python", "python3", "python2.7"}
shellInterpreters = append([]string{"exec", "su"}, shellNames...)
otherInterpreters = []string{"perl", "ruby", "php", "node", "nodejs", "java"}
interpreters = append(otherInterpreters,
append(shellInterpreters, append(shellNames, pythonInterpreters...)...)...)
dotnetInterpreters = []string{"dotnet", "nuget"}
interpreters = append(dotnetInterpreters, append(otherInterpreters,
append(shellInterpreters, append(shellNames, pythonInterpreters...)...)...)...)
)

// Note: aws is handled separately because it uses different
Expand Down Expand Up @@ -696,6 +697,83 @@ func isChocoUnpinnedDownload(cmd []string) bool {
return true
}

func isUnpinnedNugetCliInstall(cmd []string) bool {
// Search for nuget commands.
if !isBinaryName("nuget", cmd[0]) {
return false
}

// Search for install commands.
if !strings.EqualFold(cmd[1], "install") {
return false
}

// package.config schema has required version field
// https://learn.microsoft.com/en-us/nuget/reference/packages-config#schema
if strings.HasSuffix(cmd[2], "packages.config") {
return false
}

hasVersion := false
for i := 2; i < len(cmd); i++ {
// look for version flag
if strings.EqualFold(cmd[i], "-Version") {
hasVersion = true
break
}
}

if hasVersion {
return !hasVersion
}

return true
}

func isUnpinnedDotNetCliInstall(cmd []string) bool {
// Search for dotnet commands.
if !isBinaryName("dotnet", cmd[0]) {
return false
}

// Search for add package commands.
if !strings.EqualFold(cmd[1], "add") && !strings.EqualFold(cmd[2], "package") {
return false
}

hasVersion := false
for i := 3; i < len(cmd); i++ {
// look for version flag
// https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-add-package
if strings.EqualFold(cmd[i], "-v") || strings.EqualFold(cmd[i], "--version") {
hasVersion = true
break
}
}

if hasVersion {
return !hasVersion
}

return true
}

func isNugetUnpinnedDownload(cmd []string) bool {
if len(cmd) < 2 {
return false
}

if isUnpinnedDotNetCliInstall(cmd) {
return true
}

if isUnpinnedNugetCliInstall(cmd) {
return true
}

return false
}

func collectUnpinnedPakageManagerDownload(startLine, endLine uint, node syntax.Node,
cmd, pathfn string, r *checker.PinningDependenciesData,
) {
Expand Down Expand Up @@ -782,6 +860,24 @@ func collectUnpinnedPakageManagerDownload(startLine, endLine uint, node syntax.N

return
}

// Nuget install.
if isNugetUnpinnedDownload(c) {
r.Dependencies = append(r.Dependencies,
checker.Dependency{
Location: &checker.File{
Path: pathfn,
Type: finding.FileTypeSource,
Offset: startLine,
EndOffset: endLine,
Snippet: cmd,
},
Type: checker.DependencyUseTypeNugetCommand,
},
)

return
}
// TODO(laurent): add other package managers.
}

Expand Down
61 changes: 61 additions & 0 deletions checks/raw/shell_download_validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,67 @@ func TestValidateShellFile(t *testing.T) {
}
}

func Test_isDotNetUnpinnedDownload(t *testing.T) {
type args struct {
cmd []string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "nuget install",
args: args{
cmd: []string{"nuget", "install", "Newtonsoft.Json"},
},
want: true,
},
{
name: "nuget install with -Version",
args: args{
cmd: []string{"nuget", "install", "Newtonsoft.Json", "-Version", "2"},
},
want: false,
},
{
name: "nuget install with packages.config",
args: args{
cmd: []string{"nuget", "install", "config\\packages.config"},
},
want: false,
},
{
name: "dotnet add",
args: args{
cmd: []string{"dotnet", "add", "package", "Newtonsoft.Json"},
},
want: true,
},
{
name: "dotnet add with -v",
args: args{
cmd: []string{"dotnet", "add", "package", "Newtonsoft.Json", "-v", "2.0"},
},
want: false,
},
{
name: "dotnet add with --version",
args: args{
cmd: []string{"dotnet", "add", "package", "Newtonsoft.Json", "--version", "2.0"},
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isNugetUnpinnedDownload(tt.args.cmd); got != tt.want {
t.Errorf("isNugetUnpinnedDownload() = %v, want %v", got, tt.want)
}
})
}
}

func Test_isGoUnpinnedDownload(t *testing.T) {
type args struct {
cmd []string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,4 +161,18 @@ jobs:
- name:
run: choco install --requirechecksums 'some-package'
- name:
run: choco install --require-checksums 'some-package'
run: choco install --require-checksums 'some-package'
- name:
run: nuget install 'some-package'
- name:
run: dotnet add package 'some-package'
- name:
run: nuget install 'some-package' -Version 1.2.3
- name:
run: nuget install packages.config
- name:
run: nuget install packages/packages.config
- name:
run: dotnet add package 'some-package' -v 1.2.3
- name:
run: dotnet add package 'some-package' --version 1.2.3
10 changes: 9 additions & 1 deletion checks/raw/testdata/Dockerfile-pkg-managers
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,12 @@ RUN choco install 'some-package'
RUN choco install 'some-other-package'
RUN choco install --requirechecksum 'some-package'
RUN choco install --requirechecksums 'some-package'
RUN choco install --require-checksums 'some-package'
RUN choco install --require-checksums 'some-package'


RUN nuget install some-package
RUN nuget install some-package -Version 1.2.3
RUN nuget install packages.config
RUN dotnet add package some-package
RUN dotnet add package some-package -v 1.2.3
RUN dotnet add package some-package --version 1.2.3
7 changes: 7 additions & 0 deletions checks/raw/testdata/script-pkg-managers
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,10 @@ choco install 'some-other-package'
choco install --requirechecksum 'some-package'
choco install --requirechecksums 'some-package'
choco install --require-checksums 'some-package'

nuget install some-package
nuget install some-package -Version 1.2.3
nuget install packages.config
dotnet add package some-package
dotnet add package some-package -v 1.2.3
dotnet add package some-package --version 1.2.3
9 changes: 8 additions & 1 deletion checks/raw/testdata/shell-download-lines.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,11 @@ pip install --no-deps -e . git+https://github.com/username/repo.git
pip install --no-deps -e . git+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package

python -m pip install --no-deps -e git+https://github.com/username/repo.git
python -m pip install --no-deps -e git+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
python -m pip install --no-deps -e git+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package

nuget install some-package
nuget install some-package -Version 1.2.3
nuget install packages.config
dotnet add package some-package
dotnet add package some-package -v 1.2.3
dotnet add package some-package --version 1.2.3
2 changes: 1 addition & 1 deletion docs/checks.md
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ dependencies using the [GitHub dependency graph](https://docs.github.com/en/code


**Remediation steps**
- If your project is producing an application, declare all your dependencies with specific versions in your package format file (e.g. `package.json` for npm, `requirements.txt` for python). For C/C++, check in the code from a trusted source and add a `README` on the specific version used (and the archive SHA hashes).
- If your project is producing an application, declare all your dependencies with specific versions in your package format file (e.g. `package.json` for npm, `requirements.txt` for python, `packages.config` for nuget). For C/C++, check in the code from a trusted source and add a `README` on the specific version used (and the archive SHA hashes).
- If your project is producing an application and the package manager supports lock files (e.g. `package-lock.json` for npm), make sure to check these in the source code as well. These files maintain signatures for the entire dependency tree and saves from future exploitation in case the package is compromised.
- For Dockerfiles used in building and releasing your project, pin dependencies by hash. See [Dockerfile](https://github.com/ossf/scorecard/blob/main/cron/internal/worker/Dockerfile) for example. If you are using a manifest list to support builds across multiple architectures, you can pin to the manifest list hash instead of a single image hash. You can use a tool like [crane](https://github.com/google/go-containerregistry/blob/main/cmd/crane/README.md) to obtain the hash of the manifest list like in this [example](https://github.com/ossf/scorecard/issues/1773#issuecomment-1076699039).
- For GitHub workflows used in building and releasing your project, pin dependencies by hash. See [main.yaml](https://github.com/ossf/scorecard/blob/f55b86d6627cc3717e3a0395e03305e81b9a09be/.github/workflows/main.yml#L27) for example. To determine the permissions needed for your workflows, you may use [StepSecurity's online tool](https://app.stepsecurity.io/) by ticking the "Pin actions to a full length commit SHA". You may also tick the "Restrict permissions for GITHUB_TOKEN" to fix issues found by the Token-Permissions check.
Expand Down

0 comments on commit c0b4ad7

Please sign in to comment.