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

🐛 Handle editable pip installs #2731

Merged
merged 12 commits into from
Mar 13, 2023
126 changes: 123 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: 36,
warns: 46,
},
}
for _, tt := range tests {
Expand Down Expand Up @@ -557,6 +557,66 @@ func TestDockerfileInsecureDownloadsLineNumber(t *testing.T) {
endLine: 42,
t: checker.DependencyUseTypePipCommand,
},
{
snippet: "pip install --no-deps -e hg+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package",
startLine: 46,
endLine: 46,
t: checker.DependencyUseTypePipCommand,
},
{
snippet: "pip install --no-deps -e svn+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package",
startLine: 47,
endLine: 47,
t: checker.DependencyUseTypePipCommand,
},
{
snippet: "pip install --no-deps -e bzr+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package",
startLine: 48,
endLine: 48,
t: checker.DependencyUseTypePipCommand,
},
{
snippet: "pip install --no-deps -e git+https://github.com/username/repo.git",
startLine: 49,
endLine: 49,
t: checker.DependencyUseTypePipCommand,
},
{
snippet: "pip install --no-deps -e git+https://github.com/username/repo.git#egg=package",
startLine: 50,
endLine: 50,
t: checker.DependencyUseTypePipCommand,
},
{
snippet: "pip install --no-deps -e git+https://github.com/username/repo.git@v1.0",
startLine: 51,
endLine: 51,
t: checker.DependencyUseTypePipCommand,
},
{
snippet: "pip install --no-deps -e git+https://github.com/username/repo.git@v1.0#egg=package",
startLine: 52,
endLine: 52,
t: checker.DependencyUseTypePipCommand,
},
{
snippet: "pip install -e git+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package",
startLine: 60,
endLine: 60,
t: checker.DependencyUseTypePipCommand,
},
{
snippet: "pip install --no-deps -e . git+https://github.com/username/repo.git",
startLine: 61,
endLine: 61,
t: checker.DependencyUseTypePipCommand,
},
{
snippet: "python -m pip install --no-deps -e git+https://github.com/username/repo.git",
startLine: 64,
endLine: 64,
t: checker.DependencyUseTypePipCommand,
},
},
},
{
Expand Down Expand Up @@ -699,6 +759,66 @@ func TestShellscriptInsecureDownloadsLineNumber(t *testing.T) {
endLine: 31,
t: checker.DependencyUseTypeChocoCommand,
},
{
snippet: "pip install --no-deps -e hg+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package",
startLine: 38,
endLine: 38,
t: checker.DependencyUseTypePipCommand,
},
{
snippet: "pip install --no-deps -e svn+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package",
startLine: 39,
endLine: 39,
t: checker.DependencyUseTypePipCommand,
},
{
snippet: "pip install --no-deps -e bzr+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package",
startLine: 40,
endLine: 40,
t: checker.DependencyUseTypePipCommand,
},
{
snippet: "pip install --no-deps -e git+https://github.com/username/repo.git",
startLine: 41,
endLine: 41,
t: checker.DependencyUseTypePipCommand,
},
{
snippet: "pip install --no-deps -e git+https://github.com/username/repo.git#egg=package",
startLine: 42,
endLine: 42,
t: checker.DependencyUseTypePipCommand,
},
{
snippet: "pip install --no-deps -e git+https://github.com/username/repo.git@v1.0",
startLine: 43,
endLine: 43,
t: checker.DependencyUseTypePipCommand,
},
{
snippet: "pip install --no-deps -e git+https://github.com/username/repo.git@v1.0#egg=package",
startLine: 44,
endLine: 44,
t: checker.DependencyUseTypePipCommand,
},
{
snippet: "pip install -e git+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package",
startLine: 52,
endLine: 52,
t: checker.DependencyUseTypePipCommand,
},
{
snippet: "pip install --no-deps -e . git+https://github.com/username/repo.git",
startLine: 53,
endLine: 53,
t: checker.DependencyUseTypePipCommand,
},
{
snippet: "python -m pip install --no-deps -e git+https://github.com/username/repo.git",
startLine: 56,
endLine: 56,
t: checker.DependencyUseTypePipCommand,
},
},
},
}
Expand Down Expand Up @@ -851,7 +971,7 @@ func TestDockerfileScriptDownload(t *testing.T) {
{
name: "pkg managers",
filename: "./testdata/Dockerfile-pkg-managers",
warns: 47,
warns: 57,
},
{
name: "download with some python",
Expand Down Expand Up @@ -969,7 +1089,7 @@ func TestShellScriptDownload(t *testing.T) {
{
name: "pkg managers",
filename: "./testdata/script-pkg-managers",
warns: 43,
warns: 53,
},
{
name: "invalid shell script",
Expand Down
64 changes: 64 additions & 0 deletions checks/raw/shell_download_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -487,12 +487,35 @@ func isGoUnpinnedDownload(cmd []string) bool {
return found
}

func isPinnedEditableSource(pkgSource string) bool {
regexRemoteSource := regexp.MustCompile(`^(git|svn|hg|bzr).+$`)
// Is from local source
if !regexRemoteSource.MatchString(pkgSource) {
return true
}
// Is VCS install from Git and it's pinned
// https://pip.pypa.io/en/latest/topics/vcs-support/#vcs-support
regexGitSource := regexp.MustCompile(`^git(\+(https?|ssh|git))?\:\/\/.*(.git)?@[a-fA-F0-9]{40}(#egg=.*)?$`)
return regexGitSource.MatchString(pkgSource)
// Disclaimer: We are not handling if Subversion (svn),
// Mercurial (hg) or Bazaar (bzr) remote sources are pinned
// because they are not common on GitHub repos
}

func isFlag(cmd string) bool {
regexFlag := regexp.MustCompile(`^(\-\-?\w+)+$`)
return regexFlag.MatchString(cmd)
}

func isUnpinnedPipInstall(cmd []string) bool {
if !isBinaryName("pip", cmd[0]) && !isBinaryName("pip3", cmd[0]) {
return false
}

isInstall := false
hasNoDeps := false
isEditableInstall := false
isPinnedEditableInstall := true
hasRequireHashes := false
hasAdditionalArgs := false
hasWheel := false
Expand All @@ -507,12 +530,33 @@ func isUnpinnedPipInstall(cmd []string) bool {
break
}

// Require --no-deps to not install the dependencies when doing editable install
// because we can't verify if dependencies are pinned
// https://pip.pypa.io/en/stable/topics/secure-installs/#do-not-use-setuptools-directly
// https://github.com/pypa/pip/issues/4995
if strings.EqualFold(cmd[i], "--no-deps") {
laurentsimon marked this conversation as resolved.
Show resolved Hide resolved
hasNoDeps = true
continue
}

// https://pip.pypa.io/en/stable/cli/pip_install/#cmdoption-e
if slices.Contains([]string{"-e", "--editable"}, cmd[i]) {
isEditableInstall = true
continue
}

// https://github.com/ossf/scorecard/issues/1306#issuecomment-974539197.
if strings.EqualFold(cmd[i], "--require-hashes") {
hasRequireHashes = true
break
}

// Catch not handled flags, otherwise is package
if isFlag(cmd[i]) {
continue
}

// Wheel package
// Exclude *.whl as they're mostly used
// for tests. See https://github.com/ossf/scorecard/pull/611.
if strings.HasSuffix(cmd[i], ".whl") {
Expand All @@ -522,9 +566,29 @@ func isUnpinnedPipInstall(cmd []string) bool {
continue
}

// Editable install package source
if isEditableInstall {
isPinned := isPinnedEditableSource(cmd[i])
if !isPinned {
isPinnedEditableInstall = false
}
continue
}

hasAdditionalArgs = true
}

// --require-hashes and -e flags cannot be used together in pip install
// -e and *.whl package cannot be used together in pip install

// If is editable install, it's secure if package is from local source
// or from remote (VCS install) pinned by hash, and if dependencies are
// not installed.
// Example: `pip install --no-deps -e git+https://git.repo/some_pkg.git@da39a3ee5e6b4b0d3255bfef95601890afd80709`
if isEditableInstall {
return !hasNoDeps || !isPinnedEditableInstall
gabibguti marked this conversation as resolved.
Show resolved Hide resolved
}

// If hashes are required, it's pinned.
if hasRequireHashes {
return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,44 @@ jobs:
run: pip3 install somepkg==1.2.3
- name:
run: /bin/pip3 install -X -H somepkg
- name:
run: pip install --no-deps --editable .
- name:
run: pip install --no-deps -e .
- name:
run: pip install --no-deps -e hg+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
- name:
run: pip install --no-deps -e svn+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
- name:
run: pip install --no-deps -e bzr+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
- name:
run: pip install --no-deps -e git+https://github.com/username/repo.git
- name:
run: pip install --no-deps -e git+https://github.com/username/repo.git#egg=package
- name:
run: pip install --no-deps -e git+https://github.com/username/repo.git@v1.0
- name:
run: pip install --no-deps -e git+https://github.com/username/repo.git@v1.0#egg=package
- name:
run: pip install --no-deps -e git+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567
- name:
run: pip install --no-deps -e git+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
- name:
run: pip install --no-deps -e git+https://github.com/username/repo@0123456789abcdef0123456789abcdef01234567#egg=package
- name:
run: pip install --no-deps -e git+http://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
- name:
run: pip install --no-deps -e git+ssh://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
- name:
run: pip install --no-deps -e git+git://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
- name:
run: pip install --no-deps -e git://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
- name:
run: pip install -e git+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
- name:
run: pip install --no-deps -e . git+https://github.com/username/repo.git
- name:
run: pip install --no-deps -e . git+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
- name:
run: python -m notpip -X bla
- name:
Expand All @@ -108,6 +146,10 @@ jobs:
run: python -m pip install 'some-pkg==1.2.3'
- name:
run: python -m pip install 'some-pkg>1.2.3'
- name:
run: python -m pip install --no-deps -e git+https://github.com/username/repo.git
- name:
run: python -m pip install --no-deps -e git+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
- name:
run: pip3 install -r bla-requirements.txt --require-hashes && pip3 install --require-hashes -r bla-requirements.txt
- name:
Expand Down
25 changes: 24 additions & 1 deletion checks/raw/testdata/Dockerfile-download-lines
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,27 @@ RUN echo hello && \
#RUN curl -s ifconfig.co/json | grep "China" > /dev/null && \
# pip install -r requirements.txt -i https://pypi.doubanio.com/simple --trusted-host pypi.doubanio.com || \
RUN bla && \
pip install -r requirements.txt
pip install -r requirements.txt

RUN pip install --no-deps --editable .
RUN pip install --no-deps -e .
RUN pip install --no-deps -e hg+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
RUN pip install --no-deps -e svn+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
RUN pip install --no-deps -e bzr+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
RUN pip install --no-deps -e git+https://github.com/username/repo.git
RUN pip install --no-deps -e git+https://github.com/username/repo.git#egg=package
RUN pip install --no-deps -e git+https://github.com/username/repo.git@v1.0
RUN pip install --no-deps -e git+https://github.com/username/repo.git@v1.0#egg=package
RUN pip install --no-deps -e git+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567
RUN pip install --no-deps -e git+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
RUN pip install --no-deps -e git+https://github.com/username/repo@0123456789abcdef0123456789abcdef01234567#egg=package
RUN pip install --no-deps -e git+http://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
RUN pip install --no-deps -e git+ssh://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
RUN pip install --no-deps -e git+git://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
RUN pip install --no-deps -e git://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
RUN pip install -e git+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
RUN pip install --no-deps -e . git+https://github.com/username/repo.git
RUN pip install --no-deps -e . git+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package

RUN python -m pip install --no-deps -e git+https://github.com/username/repo.git
RUN python -m pip install --no-deps -e git+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
23 changes: 23 additions & 0 deletions checks/raw/testdata/Dockerfile-pkg-managers
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,26 @@ RUN pip install somepkg
RUN pip3 install somepkg==1.2.3
RUN /bin/pip3 install -X -H somepkg

RUN pip install --no-deps --editable .
RUN pip install --no-deps -e .
RUN pip install --no-deps -e hg+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
RUN pip install --no-deps -e svn+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
RUN pip install --no-deps -e bzr+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
RUN pip install --no-deps -e git+https://github.com/username/repo.git
RUN pip install --no-deps -e git+https://github.com/username/repo.git#egg=package
RUN pip install --no-deps -e git+https://github.com/username/repo.git@v1.0
RUN pip install --no-deps -e git+https://github.com/username/repo.git@v1.0#egg=package
RUN pip install --no-deps -e git+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567
RUN pip install --no-deps -e git+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
RUN pip install --no-deps -e git+https://github.com/username/repo@0123456789abcdef0123456789abcdef01234567#egg=package
RUN pip install --no-deps -e git+http://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
RUN pip install --no-deps -e git+ssh://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
RUN pip install --no-deps -e git+git://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
RUN pip install --no-deps -e git://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
RUN pip install -e git+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package
RUN pip install --no-deps -e . git+https://github.com/username/repo.git
RUN pip install --no-deps -e . git+https://github.com/username/repo.git@0123456789abcdef0123456789abcdef01234567#egg=package

RUN python -m notpip -X bla

RUN python2.7 -m pip install -X -H somepkg \
Expand All @@ -84,6 +104,9 @@ RUN python -m pip install -r file
RUN python -m pip install 'some-pkg==1.2.3'
RUN python -m pip install 'some-pkg>1.2.3'

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

RUN npm install typescript
RUN npm install -g typescript
RUN npm i typescript
Expand Down