Repository: google/osv-scanner
Pull request #305 (2023-03-19 14:19:03)

chore(deps): update golang:alpine docker digest to 913de96
[![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| golang | stage | digest | `74a3829` -> `913de96` |

---

### Configuration

📅 **Schedule**: Branch creation - "before 6am on monday" in timezone Australia/Sydney, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://app.renovatebot.com/dashboard#github/google/osv-scanner).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNS44LjMiLCJ1cGRhdGVkSW5WZXIiOiIzNS40OC4yIn0=-->



Patch:
diff --git a/Dockerfile b/Dockerfile
index a8a75a5..eb0affb 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-FROM golang:alpine@sha256:74a382917f6eaa7cc2d000dc2cd412a7f823f343b3b6268b20d84d057bc56718
+FROM golang:alpine@sha256:913de96707b0460bcfdfe422796bb6e559fc300f6c53286777805a9a3010a5ea
 
 RUN mkdir /src
 WORKDIR /src
diff --git a/go.mod b/go.mod
index 7bc11dc..df6c7bb 100644
--- a/go.mod
+++ b/go.mod
@@ -11,12 +11,12 @@ require (
 	github.com/jedib0t/go-pretty/v6 v6.4.6
 	github.com/kr/pretty v0.3.1
 	github.com/package-url/packageurl-go v0.1.0
-	github.com/spdx/tools-golang v0.4.0
-	github.com/urfave/cli/v2 v2.25.1
+	github.com/spdx/tools-golang v0.5.0
+	github.com/urfave/cli/v2 v2.25.3
 	golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53
 	golang.org/x/mod v0.10.0
-	golang.org/x/sync v0.1.0
-	golang.org/x/term v0.7.0
+	golang.org/x/sync v0.2.0
+	golang.org/x/term v0.8.0
 	golang.org/x/tools v0.8.0
 	golang.org/x/vuln v0.0.0-20230303230808-d3042fecc4e3
 	gopkg.in/yaml.v3 v3.0.1
@@ -26,6 +26,7 @@ require (
 	github.com/Microsoft/go-winio v0.5.2 // indirect
 	github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect
 	github.com/acomagu/bufpipe v1.0.4 // indirect
+	github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
 	github.com/cloudflare/circl v1.1.0 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
 	github.com/emirpasic/gods v1.18.1 // indirect
@@ -46,6 +47,6 @@ require (
 	github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
 	golang.org/x/crypto v0.6.0 // indirect
 	golang.org/x/net v0.9.0 // indirect
-	golang.org/x/sys v0.7.0 // indirect
+	golang.org/x/sys v0.8.0 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
 )
diff --git a/go.sum b/go.sum
index 2e1c01a..b17803f 100644
--- a/go.sum
+++ b/go.sum
@@ -8,6 +8,8 @@ github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwF
 github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
 github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ=
 github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
+github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc=
+github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA=
 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
@@ -36,7 +38,6 @@ github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlK
 github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo=
 github.com/go-git/go-git/v5 v5.6.1 h1:q4ZRqQl4pR/ZJHc1L5CFjGA1a10u76aV1iC+nh+bHsk=
 github.com/go-git/go-git/v5 v5.6.1/go.mod h1:mvyoL6Unz0PiTQrGQfSfiLFhBH1c1e84ylC2MDs4ee8=
-github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
@@ -86,18 +87,21 @@ github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXi
 github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM=
 github.com/spdx/gordf v0.0.0-20221230105357-b735bd5aac89 h1:dArkMwZ7Mf2JiU8OfdmqIv8QaHT4oyifLIe1UhsF1SY=
 github.com/spdx/gordf v0.0.0-20221230105357-b735bd5aac89/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM=
-github.com/spdx/tools-golang v0.4.0 h1:jdhnW8zYelURCbYTphiviFKZkWu51in0E4A1KT2csP0=
-github.com/spdx/tools-golang v0.4.0/go.mod h1:VHzvNsKAfAGqs4ZvwRL+7a0dNsL20s7lGui4K9C0xQM=
+github.com/spdx/tools-golang v0.5.0 h1:/fqihV2Jna7fmow65dHpgKNsilgLK7ICpd2tkCnPEyY=
+github.com/spdx/tools-golang v0.5.0/go.mod h1:kkGlrSXXfHwuSzHQZJRV3aKu9ZXCq/MSf2+xyiJH1lM=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
-github.com/urfave/cli/v2 v2.25.1 h1:zw8dSP7ghX0Gmm8vugrs6q9Ku0wzweqPyshy+syu9Gw=
-github.com/urfave/cli/v2 v2.25.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/urfave/cli/v2 v2.25.3 h1:VJkt6wvEBOoSjPFQvOkv6iWIrsJyCrKGtCtxXWwmGeY=
+github.com/urfave/cli/v2 v2.25.3/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
 github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
 github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
 github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
@@ -130,8 +134,8 @@ golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
 golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
-golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
+golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -149,15 +153,15 @@ golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
-golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
-golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
+golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -174,7 +178,6 @@ golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
 golang.org/x/vuln v0.0.0-20230303230808-d3042fecc4e3 h1:9GJsAwSzB/ztwMwsEm3ihUgCXHCULbNsubxqIrdKa44=
 golang.org/x/vuln v0.0.0-20230303230808-d3042fecc4e3/go.mod h1:LTLnfk/dpXDNKsX6aCg/cI4LyCVnTyrQhgV/yLJuly0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/internal/sbom/spdx.go b/internal/sbom/spdx.go
index d4538c7..53c4a4b 100644
--- a/internal/sbom/spdx.go
+++ b/internal/sbom/spdx.go
@@ -8,9 +8,9 @@ import (
 	"strings"
 
 	spdx_json "github.com/spdx/tools-golang/json"
-	"github.com/spdx/tools-golang/rdfloader"
-	"github.com/spdx/tools-golang/spdx/v2_3"
-	"github.com/spdx/tools-golang/tvloader"
+	"github.com/spdx/tools-golang/rdf"
+	"github.com/spdx/tools-golang/spdx/v2/v2_3"
+	"github.com/spdx/tools-golang/tagvalue"
 )
 
 type SPDX struct{}
@@ -25,15 +25,15 @@ var (
 	spdxLoaders = []loader{
 		{
 			name:   "json",
-			loader: spdx_json.Load2_3,
+			loader: spdx_json.Read,
 		},
 		{
 			name:   "rdf",
-			loader: rdfloader.Load2_3,
+			loader: rdf.Read,
 		},
 		{
 			name:   "tv",
-			loader: tvloader.Load2_3,
+			loader: tagvalue.Read,
 		},
 	}
 )
diff --git a/pkg/lockfile/fixtures/pip/cyclic-r-complex-1.txt b/pkg/lockfile/fixtures/pip/cyclic-r-complex-1.txt
new file mode 100644
index 0000000..b0a64dd
--- /dev/null
+++ b/pkg/lockfile/fixtures/pip/cyclic-r-complex-1.txt
@@ -0,0 +1,3 @@
+-r ./cyclic-r-complex-2.txt
+
+cyclic-r-complex==1
diff --git a/pkg/lockfile/fixtures/pip/cyclic-r-complex-2.txt b/pkg/lockfile/fixtures/pip/cyclic-r-complex-2.txt
new file mode 100644
index 0000000..22a5685
--- /dev/null
+++ b/pkg/lockfile/fixtures/pip/cyclic-r-complex-2.txt
@@ -0,0 +1,4 @@
+-r ./../pip/cyclic-r-complex-1.txt
+-r ./cyclic-r-complex-3.txt
+
+cyclic-r-complex==2
diff --git a/pkg/lockfile/fixtures/pip/cyclic-r-complex-3.txt b/pkg/lockfile/fixtures/pip/cyclic-r-complex-3.txt
new file mode 100644
index 0000000..da9fa11
--- /dev/null
+++ b/pkg/lockfile/fixtures/pip/cyclic-r-complex-3.txt
@@ -0,0 +1,4 @@
+-r ./cyclic-r-complex-1.txt
+-r ./cyclic-r-complex-2.txt
+
+cyclic-r-complex==3
diff --git a/pkg/lockfile/fixtures/pip/cyclic-r-self.txt b/pkg/lockfile/fixtures/pip/cyclic-r-self.txt
new file mode 100644
index 0000000..99af87f
--- /dev/null
+++ b/pkg/lockfile/fixtures/pip/cyclic-r-self.txt
@@ -0,0 +1,4 @@
+-r ./cyclic-r-self.txt
+
+requests==1.2.3
+pandas==0.23.4
diff --git a/pkg/lockfile/fixtures/pip/duplicate-r-base.txt b/pkg/lockfile/fixtures/pip/duplicate-r-base.txt
new file mode 100644
index 0000000..123602f
--- /dev/null
+++ b/pkg/lockfile/fixtures/pip/duplicate-r-base.txt
@@ -0,0 +1,1 @@
+django==0.1.0
diff --git a/pkg/lockfile/fixtures/pip/duplicate-r-dev.txt b/pkg/lockfile/fixtures/pip/duplicate-r-dev.txt
new file mode 100644
index 0000000..44645b8
--- /dev/null
+++ b/pkg/lockfile/fixtures/pip/duplicate-r-dev.txt
@@ -0,0 +1,5 @@
+-r ./duplicate-r-base.txt
+-r ./duplicate-r-test.txt
+
+pandas==0.23.4
+requests==1.2.3
diff --git a/pkg/lockfile/fixtures/pnpm/empty.yaml b/pkg/lockfile/fixtures/pnpm/empty.yaml
index 338e41a..809b4b2 100644
--- a/pkg/lockfile/fixtures/pnpm/empty.yaml
+++ b/pkg/lockfile/fixtures/pnpm/empty.yaml
@@ -1,7 +1,1 @@
-lockfileVersion: 5.3
-
-specifiers:
-
-dependencies:
-
-packages:
+# this is an empty file!
diff --git a/pkg/lockfile/fixtures/pnpm/no-packages.yaml b/pkg/lockfile/fixtures/pnpm/no-packages.yaml
new file mode 100644
index 0000000..338e41a
--- /dev/null
+++ b/pkg/lockfile/fixtures/pnpm/no-packages.yaml
@@ -0,0 +1,7 @@
+lockfileVersion: 5.3
+
+specifiers:
+
+dependencies:
+
+packages:
diff --git a/pkg/lockfile/parse-pnpm-lock.go b/pkg/lockfile/parse-pnpm-lock.go
index 4e36794..75c3aa2 100644
--- a/pkg/lockfile/parse-pnpm-lock.go
+++ b/pkg/lockfile/parse-pnpm-lock.go
@@ -177,5 +177,10 @@ func ParsePnpmLock(pathToLockfile string) ([]PackageDetails, error) {
 		return []PackageDetails{}, fmt.Errorf("could not parse %s: %w", pathToLockfile, err)
 	}
 
+	// this will happen if the file is empty
+	if parsedLockfile == nil {
+		parsedLockfile = &PnpmLockfile{}
+	}
+
 	return parsePnpmLock(*parsedLockfile), nil
 }
diff --git a/pkg/lockfile/parse-requirements-txt.go b/pkg/lockfile/parse-requirements-txt.go
index 4f46a0b..d7564a4 100644
--- a/pkg/lockfile/parse-requirements-txt.go
+++ b/pkg/lockfile/parse-requirements-txt.go
@@ -94,6 +94,9 @@ func isNotRequirementLine(line string) bool {
 }
 
 func ParseRequirementsTxt(pathToLockfile string) ([]PackageDetails, error) {
+	return parseRequirementsTxt(pathToLockfile, map[string]struct{}{})
+}
+func parseRequirementsTxt(pathToLockfile string, requiredAlready map[string]struct{}) ([]PackageDetails, error) {
 	packages := map[string]PackageDetails{}
 
 	file, err := os.Open(pathToLockfile)
@@ -107,10 +110,16 @@ func ParseRequirementsTxt(pathToLockfile string) ([]PackageDetails, error) {
 	for scanner.Scan() {
 		line := removeComments(scanner.Text())
 
-		if strings.HasPrefix(line, "-r ") {
-			details, err := ParseRequirementsTxt(
-				filepath.Join(filepath.Dir(pathToLockfile), strings.TrimPrefix(line, "-r ")),
-			)
+		if ar := strings.TrimPrefix(line, "-r "); ar != line {
+			ar = filepath.Join(filepath.Dir(pathToLockfile), ar)
+
+			if _, ok := requiredAlready[ar]; ok {
+				continue
+			}
+
+			requiredAlready[ar] = struct{}{}
+
+			details, err := parseRequirementsTxt(ar, requiredAlready)
 
 			if err != nil {
 				return []PackageDetails{}, fmt.Errorf("failed to include %s: %w", line, err)


Test patch:
diff --git a/pkg/lockfile/fixtures/pip/duplicate-r-test.txt b/pkg/lockfile/fixtures/pip/duplicate-r-test.txt
new file mode 100644
index 0000000..13d13c6
--- /dev/null
+++ b/pkg/lockfile/fixtures/pip/duplicate-r-test.txt
@@ -0,0 +1,4 @@
+-r ./duplicate-r-base.txt
+
+requests==1.2.3
+unittest==1.0.0
diff --git a/pkg/lockfile/parse-pnpm-lock_test.go b/pkg/lockfile/parse-pnpm-lock_test.go
index 72638f4..05b379d 100644
--- a/pkg/lockfile/parse-pnpm-lock_test.go
+++ b/pkg/lockfile/parse-pnpm-lock_test.go
@@ -23,7 +23,7 @@ func TestParsePnpmLock_InvalidYaml(t *testing.T) {
 	expectPackages(t, packages, []lockfile.PackageDetails{})
 }
 
-func TestParsePnpmLock_NoPackages(t *testing.T) {
+func TestParsePnpmLock_Empty(t *testing.T) {
 	t.Parallel()
 
 	packages, err := lockfile.ParsePnpmLock("fixtures/pnpm/empty.yaml")
@@ -35,6 +35,18 @@ func TestParsePnpmLock_NoPackages(t *testing.T) {
 	expectPackages(t, packages, []lockfile.PackageDetails{})
 }
 
+func TestParsePnpmLock_NoPackages(t *testing.T) {
+	t.Parallel()
+
+	packages, err := lockfile.ParsePnpmLock("fixtures/pnpm/no-packages.yaml")
+
+	if err != nil {
+		t.Errorf("Got unexpected error: %v", err)
+	}
+
+	expectPackages(t, packages, []lockfile.PackageDetails{})
+}
+
 func TestParsePnpmLock_OnePackage(t *testing.T) {
 	t.Parallel()
 
diff --git a/pkg/lockfile/parse-requirements-txt_test.go b/pkg/lockfile/parse-requirements-txt_test.go
index 767c9a8..845bb15 100644
--- a/pkg/lockfile/parse-requirements-txt_test.go
+++ b/pkg/lockfile/parse-requirements-txt_test.go
@@ -432,3 +432,94 @@ func TestParseRequirementsTxt_WithBadROption(t *testing.T) {
 	expectErrContaining(t, err, "could not open")
 	expectPackages(t, packages, []lockfile.PackageDetails{})
 }
+
+func TestParseRequirementsTxt_DuplicateROptions(t *testing.T) {
+	t.Parallel()
+
+	packages, err := lockfile.ParseRequirementsTxt("fixtures/pip/duplicate-r-dev.txt")
+
+	if err != nil {
+		t.Errorf("Got unexpected error: %v", err)
+	}
+
+	expectPackages(t, packages, []lockfile.PackageDetails{
+		{
+			Name:      "django",
+			Version:   "0.1.0",
+			Ecosystem: lockfile.PipEcosystem,
+			CompareAs: lockfile.PipEcosystem,
+		},
+		{
+			Name:      "pandas",
+			Version:   "0.23.4",
+			Ecosystem: lockfile.PipEcosystem,
+			CompareAs: lockfile.PipEcosystem,
+		},
+		{
+			Name:      "requests",
+			Version:   "1.2.3",
+			Ecosystem: lockfile.PipEcosystem,
+			CompareAs: lockfile.PipEcosystem,
+		},
+		{
+			Name:      "unittest",
+			Version:   "1.0.0",
+			Ecosystem: lockfile.PipEcosystem,
+			CompareAs: lockfile.PipEcosystem,
+		},
+	})
+}
+func TestParseRequirementsTxt_CyclicRSelf(t *testing.T) {
+	t.Parallel()
+
+	packages, err := lockfile.ParseRequirementsTxt("fixtures/pip/cyclic-r-self.txt")
+
+	if err != nil {
+		t.Errorf("Got unexpected error: %v", err)
+	}
+
+	expectPackages(t, packages, []lockfile.PackageDetails{
+		{
+			Name:      "pandas",
+			Version:   "0.23.4",
+			Ecosystem: lockfile.PipEcosystem,
+			CompareAs: lockfile.PipEcosystem,
+		},
+		{
+			Name:      "requests",
+			Version:   "1.2.3",
+			Ecosystem: lockfile.PipEcosystem,
+			CompareAs: lockfile.PipEcosystem,
+		},
+	})
+}
+func TestParseRequirementsTxt_CyclicRComplex(t *testing.T) {
+	t.Parallel()
+
+	packages, err := lockfile.ParseRequirementsTxt("fixtures/pip/cyclic-r-complex-1.txt")
+
+	if err != nil {
+		t.Errorf("Got unexpected error: %v", err)
+	}
+
+	expectPackages(t, packages, []lockfile.PackageDetails{
+		{
+			Name:      "cyclic-r-complex",
+			Version:   "1",
+			Ecosystem: lockfile.PipEcosystem,
+			CompareAs: lockfile.PipEcosystem,
+		},
+		{
+			Name:      "cyclic-r-complex",
+			Version:   "2",
+			Ecosystem: lockfile.PipEcosystem,
+			CompareAs: lockfile.PipEcosystem,
+		},
+		{
+			Name:      "cyclic-r-complex",
+			Version:   "3",
+			Ecosystem: lockfile.PipEcosystem,
+			CompareAs: lockfile.PipEcosystem,
+		},
+	})
+}