Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package grype for openSUSE:Factory checked in at 2025-03-07 16:44:07 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/grype (Old) and /work/SRC/openSUSE:Factory/.grype.new.19136 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "grype" Fri Mar 7 16:44:07 2025 rev:85 rq:1251111 version:0.89.0 Changes: -------- --- /work/SRC/openSUSE:Factory/grype/grype.changes 2025-03-06 14:50:01.436698625 +0100 +++ /work/SRC/openSUSE:Factory/.grype.new.19136/grype.changes 2025-03-07 16:48:23.737060773 +0100 @@ -1,0 +2,17 @@ +Fri Mar 07 06:41:48 UTC 2025 - opensuse_buildserv...@ojkastl.de + +- Update to version 0.89.0: + * chore(deps): bump github.com/muesli/termenv from 0.15.2 to + 0.16.0 (#2509) + * chore(deps): bump golang.org/x/tools from 0.30.0 to 0.31.0 + (#2510) + * fix regression to allow for reading listing from local FS + (#2508) + * chore(deps): bump golang.org/x/time from 0.10.0 to 0.11.0 + (#2503) + * chore(deps): update tools to latest versions (#2506) + * Add suggested fixed version when there are multiple fixes + available (#2271) + * remove v6 development configuration (#2504) + +------------------------------------------------------------------- Old: ---- grype-0.88.0.obscpio New: ---- grype-0.89.0.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ grype.spec ++++++ --- /var/tmp/diff_new_pack.maZ307/_old 2025-03-07 16:48:25.253124753 +0100 +++ /var/tmp/diff_new_pack.maZ307/_new 2025-03-07 16:48:25.257124922 +0100 @@ -17,7 +17,7 @@ Name: grype -Version: 0.88.0 +Version: 0.89.0 Release: 0 Summary: A vulnerability scanner for container images and filesystems License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.maZ307/_old 2025-03-07 16:48:25.285126104 +0100 +++ /var/tmp/diff_new_pack.maZ307/_new 2025-03-07 16:48:25.289126272 +0100 @@ -3,7 +3,7 @@ <param name="url">https://github.com/anchore/grype</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v0.88.0</param> + <param name="revision">v0.89.0</param> <param name="match-tag">v*</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.maZ307/_old 2025-03-07 16:48:25.317127454 +0100 +++ /var/tmp/diff_new_pack.maZ307/_new 2025-03-07 16:48:25.321127623 +0100 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/anchore/grype</param> - <param name="changesrevision">6ee276f0c8363518c08b8d48fae302ee6001c295</param></service></servicedata> + <param name="changesrevision">1bf47c38bede40dea7b72bbe4712191820f1aa15</param></service></servicedata> (No newline at EOF) ++++++ grype-0.88.0.obscpio -> grype-0.89.0.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.88.0/.binny.yaml new/grype-0.89.0/.binny.yaml --- old/grype-0.88.0/.binny.yaml 2025-03-05 17:26:43.000000000 +0100 +++ new/grype-0.89.0/.binny.yaml 2025-03-06 17:12:29.000000000 +0100 @@ -98,7 +98,7 @@ # used for triggering a release - name: gh version: - want: v2.67.0 + want: v2.68.0 method: github-release with: repo: cli/cli diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.88.0/go.mod new/grype-0.89.0/go.mod --- old/grype-0.88.0/go.mod 2025-03-05 17:26:43.000000000 +0100 +++ new/grype-0.89.0/go.mod 2025-03-06 17:12:29.000000000 +0100 @@ -64,14 +64,16 @@ github.com/wagoodman/go-progress v0.0.0-20230925121702-07e42b3cdba0 github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 - golang.org/x/time v0.10.0 - golang.org/x/tools v0.30.0 + golang.org/x/time v0.11.0 + golang.org/x/tools v0.31.0 gopkg.in/yaml.v3 v3.0.1 gorm.io/gorm v1.25.12 ) require github.com/DataDog/zstd v1.5.5 // indirect +require github.com/muesli/termenv v0.16.0 + require ( cel.dev/expr v0.16.1 // indirect cloud.google.com/go v0.116.0 // indirect @@ -211,7 +213,6 @@ github.com/moby/term v0.5.0 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect - github.com/muesli/termenv v0.15.2 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/nwaples/rardecode v1.1.3 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect @@ -274,14 +275,14 @@ go.opentelemetry.io/otel/trace v1.33.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.33.0 // indirect - golang.org/x/mod v0.23.0 // indirect - golang.org/x/net v0.35.0 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/mod v0.24.0 // indirect + golang.org/x/net v0.37.0 // indirect golang.org/x/oauth2 v0.25.0 // indirect - golang.org/x/sync v0.11.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/term v0.29.0 // indirect - golang.org/x/text v0.22.0 // indirect + golang.org/x/sync v0.12.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/term v0.30.0 // indirect + golang.org/x/text v0.23.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/api v0.215.0 // indirect google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.88.0/go.sum new/grype-0.89.0/go.sum --- old/grype-0.88.0/go.sum 2025-03-05 17:26:43.000000000 +0100 +++ new/grype-0.89.0/go.sum 2025-03-06 17:12:29.000000000 +0100 @@ -1337,8 +1337,8 @@ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= -github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= -github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= @@ -1649,8 +1649,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1713,8 +1713,8 @@ golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= -golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1784,8 +1784,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1838,8 +1838,8 @@ golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1943,8 +1943,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= 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= @@ -1960,8 +1960,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1983,16 +1983,16 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= -golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -2059,8 +2059,8 @@ golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= -golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= +golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= +golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.88.0/grype/db/v6/distribution/client.go new/grype-0.89.0/grype/db/v6/distribution/client.go --- old/grype-0.88.0/grype/db/v6/distribution/client.go 2025-03-05 17:26:43.000000000 +0100 +++ new/grype-0.89.0/grype/db/v6/distribution/client.go 2025-03-06 17:12:29.000000000 +0100 @@ -44,10 +44,10 @@ } type client struct { - fs afero.Fs - latestHTTPClient *http.Client - updateDownloader file.Getter - config Config + fs afero.Fs + dbDownloader file.Getter + listingDownloader file.Getter + config Config } func DefaultConfig() Config { @@ -72,10 +72,10 @@ } return client{ - fs: fs, - latestHTTPClient: latestClient, - updateDownloader: file.NewGetter(cfg.ID, dbClient), - config: cfg, + fs: fs, + listingDownloader: file.NewGetter(cfg.ID, latestClient), + dbDownloader: file.NewGetter(cfg.ID, dbClient), + config: cfg, }, nil } @@ -157,7 +157,7 @@ u.RawQuery = query.Encode() // go-getter will automatically extract all files within the archive to the temp dir - err = c.updateDownloader.GetToDir(tempDir, u.String(), downloadProgress) + err = c.dbDownloader.GetToDir(tempDir, u.String(), downloadProgress) if err != nil { removeAllOrLog(afero.NewOsFs(), tempDir) return "", fmt.Errorf("unable to download db: %w", err) @@ -168,17 +168,23 @@ // Latest loads a LatestDocument from the configured URL. func (c client) Latest() (*LatestDocument, error) { - resp, err := c.latestHTTPClient.Get(c.latestURL()) + tempFile, err := afero.TempFile(c.fs, "", "grype-db-listing") if err != nil { - return nil, fmt.Errorf("unable to fetch latest.json: %w", err) - } - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("unable to fetch latest.json: %s", resp.Status) + return nil, fmt.Errorf("unable to create listing temp file: %w", err) } + defer func() { + err := c.fs.RemoveAll(tempFile.Name()) + if err != nil { + log.WithFields("error", err, "file", tempFile.Name()).Errorf("failed to remove file") + } + }() - defer func() { _ = resp.Body.Close() }() + err = c.listingDownloader.GetFile(tempFile.Name(), c.latestURL()) + if err != nil { + return nil, fmt.Errorf("unable to download listing: %w", err) + } - return NewLatestFromReader(resp.Body) + return NewLatestFromFile(c.fs, tempFile.Name()) } func (c client) latestURL() string { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.88.0/grype/db/v6/distribution/client_test.go new/grype-0.89.0/grype/db/v6/distribution/client_test.go --- old/grype-0.88.0/grype/db/v6/distribution/client_test.go 2025-03-05 17:26:43.000000000 +0100 +++ new/grype-0.89.0/grype/db/v6/distribution/client_test.go 2025-03-06 17:12:29.000000000 +0100 @@ -3,12 +3,11 @@ import ( "encoding/json" "errors" - "net/http" - "net/http/httptest" "path/filepath" "testing" "time" + "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -18,16 +17,31 @@ "github.com/anchore/grype/internal/schemaver" ) -func TestClient_LatestFromURL(t *testing.T) { +type mockGetter struct { + mock.Mock +} + +func (m *mockGetter) GetFile(dst, src string, manuals ...*progress.Manual) error { + args := m.Called(dst, src, manuals) + return args.Error(0) +} + +func (m *mockGetter) GetToDir(dst, src string, manuals ...*progress.Manual) error { + args := m.Called(dst, src, manuals) + return args.Error(0) +} + +func TestClient_Latest(t *testing.T) { tests := []struct { - name string - setupServer func() *httptest.Server - expectedDoc *LatestDocument - expectedErr require.ErrorAssertionFunc + name string + latestResponse []byte + getFileErr error + expectedDoc *LatestDocument + expectedErr require.ErrorAssertionFunc }{ { name: "go case", - setupServer: func() *httptest.Server { + latestResponse: func() []byte { doc := LatestDocument{ Status: "active", Archive: Archive{ @@ -39,15 +53,10 @@ Checksum: "checksum123", }, } - data, _ := json.Marshal(doc) - - return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Header().Set("Content-Type", "application/json") - _, err := w.Write(data) - require.NoError(t, err) - })) - }, + data, err := json.Marshal(doc) + require.NoError(t, err) + return data + }(), expectedDoc: &LatestDocument{ Status: "active", Archive: Archive{ @@ -61,28 +70,18 @@ }, }, { - name: "error response", - setupServer: func() *httptest.Server { - return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusInternalServerError) - })) - }, + name: "download error", + getFileErr: errors.New("failed to download file"), expectedDoc: nil, expectedErr: func(t require.TestingT, err error, _ ...interface{}) { require.Error(t, err) - require.Contains(t, err.Error(), "500 Internal Server Error") + require.Contains(t, err.Error(), "unable to download listing") }, }, { - name: "malformed JSON response", - setupServer: func() *httptest.Server { - return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - _, err := w.Write([]byte("malformed json")) - require.NoError(t, err) - })) - }, - expectedDoc: nil, + name: "malformed JSON response", + latestResponse: []byte("malformed json"), + expectedDoc: nil, expectedErr: func(t require.TestingT, err error, _ ...interface{}) { require.Error(t, err) require.Contains(t, err.Error(), "invalid character 'm' looking for beginning of value") @@ -95,16 +94,28 @@ if tt.expectedErr == nil { tt.expectedErr = require.NoError } + mockFs := afero.NewMemMapFs() + + mg := new(mockGetter) - server := tt.setupServer() - defer server.Close() + mg.On("GetFile", mock.Anything, "http://localhost:8080/latest.json", mock.Anything).Run(func(args mock.Arguments) { + if tt.getFileErr != nil { + return + } + + dst := args.String(0) + err := afero.WriteFile(mockFs, dst, tt.latestResponse, 0644) + require.NoError(t, err) + }).Return(tt.getFileErr) c, err := NewClient(Config{ - LatestURL: server.URL, + LatestURL: "http://localhost:8080/latest.json", }) require.NoError(t, err) cl := c.(client) + cl.fs = mockFs + cl.listingDownloader = mg doc, err := cl.Latest() tt.expectedErr(t, err) @@ -113,24 +124,11 @@ } require.Equal(t, tt.expectedDoc, doc) + mg.AssertExpectations(t) }) } } -type mockGetter struct { - mock.Mock -} - -func (m *mockGetter) GetFile(dst, src string, manuals ...*progress.Manual) error { - args := m.Called(dst, src, manuals) - return args.Error(0) -} - -func (m *mockGetter) GetToDir(dst, src string, manuals ...*progress.Manual) error { - args := m.Called(dst, src, manuals) - return args.Error(0) -} - func TestClient_Download(t *testing.T) { destDir := t.TempDir() archive := &Archive{ @@ -147,7 +145,7 @@ require.NoError(t, err) cl := c.(client) - cl.updateDownloader = mg + cl.dbDownloader = mg return cl, mg } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.88.0/grype/db/v6/distribution/latest.go new/grype-0.89.0/grype/db/v6/distribution/latest.go --- old/grype-0.88.0/grype/db/v6/distribution/latest.go 2025-03-05 17:26:43.000000000 +0100 +++ new/grype-0.89.0/grype/db/v6/distribution/latest.go 2025-03-06 17:12:29.000000000 +0100 @@ -74,6 +74,15 @@ return &l, nil } +func NewLatestFromFile(fs afero.Fs, path string) (*LatestDocument, error) { + fh, err := fs.Open(path) + if err != nil { + return nil, fmt.Errorf("failed to read listing file: %w", err) + } + defer fh.Close() + return NewLatestFromReader(fh) +} + func NewArchive(path string, t time.Time, model, revision, addition int) (*Archive, error) { checksum, err := calculateArchiveDigest(path) if err != nil { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.88.0/grype/presenter/internal/test_helpers.go new/grype-0.89.0/grype/presenter/internal/test_helpers.go --- old/grype-0.88.0/grype/presenter/internal/test_helpers.go 2025-03-05 17:26:43.000000000 +0100 +++ new/grype-0.89.0/grype/presenter/internal/test_helpers.go 2025-03-06 17:12:29.000000000 +0100 @@ -111,7 +111,7 @@ Namespace: "source-1", }, Fix: vulnerability.Fix{ - Versions: []string{"the-next-version"}, + Versions: []string{"1.2.1", "2.1.3", "3.4.0"}, State: vulnerability.FixStateFixed, }, }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.88.0/grype/presenter/json/test-fixtures/snapshot/TestJsonDirsPresenter.golden new/grype-0.89.0/grype/presenter/json/test-fixtures/snapshot/TestJsonDirsPresenter.golden --- old/grype-0.88.0/grype/presenter/json/test-fixtures/snapshot/TestJsonDirsPresenter.golden 2025-03-05 17:26:43.000000000 +0100 +++ new/grype-0.89.0/grype/presenter/json/test-fixtures/snapshot/TestJsonDirsPresenter.golden 2025-03-06 17:12:29.000000000 +0100 @@ -20,7 +20,9 @@ ], "fix": { "versions": [ - "the-next-version" + "1.2.1", + "2.1.3", + "3.4.0" ], "state": "fixed" }, @@ -39,6 +41,9 @@ }, "found": { "constraint": ">= 20" + }, + "fix": { + "suggestedVersion": "1.2.1" } } ], diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.88.0/grype/presenter/json/test-fixtures/snapshot/TestJsonImgsPresenter.golden new/grype-0.89.0/grype/presenter/json/test-fixtures/snapshot/TestJsonImgsPresenter.golden --- old/grype-0.88.0/grype/presenter/json/test-fixtures/snapshot/TestJsonImgsPresenter.golden 2025-03-05 17:26:43.000000000 +0100 +++ new/grype-0.89.0/grype/presenter/json/test-fixtures/snapshot/TestJsonImgsPresenter.golden 2025-03-06 17:12:29.000000000 +0100 @@ -20,7 +20,9 @@ ], "fix": { "versions": [ - "the-next-version" + "1.2.1", + "2.1.3", + "3.4.0" ], "state": "fixed" }, @@ -39,6 +41,9 @@ }, "found": { "constraint": ">= 20" + }, + "fix": { + "suggestedVersion": "1.2.1" } } ], diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.88.0/grype/presenter/models/document_test.go new/grype-0.89.0/grype/presenter/models/document_test.go --- old/grype-0.88.0/grype/presenter/models/document_test.go 2025-03-05 17:26:43.000000000 +0100 +++ new/grype-0.89.0/grype/presenter/models/document_test.go 2025-03-06 17:12:29.000000000 +0100 @@ -103,6 +103,55 @@ assert.Equal(t, []string{"CVE-1999-0001", "CVE-1999-0003", "CVE-1999-0002"}, actualVulnerabilities) } +func TestFixSuggestedVersion(t *testing.T) { + + var pkg1 = pkg.Package{ + ID: "package-1-id", + Name: "package-1", + Version: "1.1.1", + Type: syftPkg.PythonPkg, + } + + var match1 = match.Match{ + Vulnerability: vulnerability.Vulnerability{ + Fix: vulnerability.Fix{ + Versions: []string{"1.0.0", "1.2.0", "1.1.2"}, + }, + Reference: vulnerability.Reference{ID: "CVE-1999-0003"}, + }, + Package: pkg1, + Details: match.Details{ + { + Type: match.ExactDirectMatch, + }, + }, + } + + matches := match.NewMatches() + matches.Add(match1) + + packages := []pkg.Package{pkg1} + + ctx := pkg.Context{ + Source: &syftSource.Description{ + Metadata: syftSource.DirectoryMetadata{}, + }, + Distro: &linux.Release{ + ID: "centos", + IDLike: []string{"rhel"}, + Version: "8.0", + }, + } + doc, err := NewDocument(clio.Identification{}, packages, ctx, matches, nil, NewMetadataMock(), nil, nil, SortByPackage) + if err != nil { + t.Fatalf("unable to get document: %+v", err) + } + + actualSuggestedFixedVersion := doc.Matches[0].MatchDetails[0].Fix.SuggestedVersion + + assert.Equal(t, "1.1.2", actualSuggestedFixedVersion) +} + func TestTimestampValidFormat(t *testing.T) { matches := match.NewMatches() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.88.0/grype/presenter/models/match.go new/grype-0.89.0/grype/presenter/models/match.go --- old/grype-0.88.0/grype/presenter/models/match.go 2025-03-05 17:26:43.000000000 +0100 +++ new/grype-0.89.0/grype/presenter/models/match.go 2025-03-06 17:12:29.000000000 +0100 @@ -2,10 +2,13 @@ import ( "fmt" + "sort" "github.com/anchore/grype/grype/match" "github.com/anchore/grype/grype/pkg" + "github.com/anchore/grype/grype/version" "github.com/anchore/grype/grype/vulnerability" + "github.com/anchore/grype/internal/log" ) // Match is a single item for the JSON array reported @@ -22,6 +25,12 @@ Matcher string `json:"matcher"` SearchedBy interface{} `json:"searchedBy"` // The specific attributes that were used to search (other than package name and version) --this indicates "how" the match was made. Found interface{} `json:"found"` // The specific attributes on the vulnerability object that were matched with --this indicates "what" was matched on / within. + Fix *FixDetails `json:"fix,omitempty"` +} + +// FixDetails contains any data that is relevant to fixing the vulnerability specific to the package searched with +type FixDetails struct { + SuggestedVersion string `json:"suggestedVersion"` } func newMatch(m match.Match, p pkg.Package, metadataProvider vulnerability.MetadataProvider) (*Match, error) { @@ -47,6 +56,8 @@ } } + format := version.FormatFromPkg(p) + details := make([]MatchDetails, len(m.Details)) for idx, d := range m.Details { details[idx] = MatchDetails{ @@ -54,13 +65,79 @@ Matcher: string(d.Matcher), SearchedBy: d.SearchedBy, Found: d.Found, + Fix: getFix(m, p, format), } } return &Match{ - Vulnerability: NewVulnerability(m.Vulnerability, metadata), + Vulnerability: NewVulnerability(m.Vulnerability, metadata, format), Artifact: newPackage(p), RelatedVulnerabilities: relatedVulnerabilities, MatchDetails: details, }, nil } + +func getFix(m match.Match, p pkg.Package, format version.Format) *FixDetails { + suggested := calculateSuggestedFixedVersion(p, m.Vulnerability.Fix.Versions, format) + if suggested == "" { + return nil + } + return &FixDetails{ + SuggestedVersion: suggested, + } +} + +func calculateSuggestedFixedVersion(p pkg.Package, fixedVersions []string, format version.Format) string { + if len(fixedVersions) == 0 { + return "" + } + + if len(fixedVersions) == 1 { + return fixedVersions[0] + } + + parseConstraint := func(constStr string) (version.Constraint, error) { + constraint, err := version.GetConstraint(constStr, format) + if err != nil { + log.WithFields("package", p.Name).Trace("skipping sorting fixed versions") + } + return constraint, err + } + + checkSatisfaction := func(constraint version.Constraint, v *version.Version) bool { + satisfied, err := constraint.Satisfied(v) + if err != nil { + log.WithFields("package", p.Name).Trace("error while checking version satisfaction for sorting") + } + return satisfied && err == nil + } + + sort.SliceStable(fixedVersions, func(i, j int) bool { + v1, err1 := version.NewVersion(fixedVersions[i], format) + v2, err2 := version.NewVersion(fixedVersions[j], format) + if err1 != nil || err2 != nil { + log.WithFields("package", p.Name).Trace("error while parsing version for sorting") + return false + } + + packageConstraint, err := parseConstraint(fmt.Sprintf("<=%s", p.Version)) + if err != nil { + return false + } + + v1Satisfied := checkSatisfaction(packageConstraint, v1) + v2Satisfied := checkSatisfaction(packageConstraint, v2) + + if v1Satisfied != v2Satisfied { + return !v1Satisfied + } + + internalConstraint, err := parseConstraint(fmt.Sprintf("<=%s", v1.Raw)) + if err != nil { + return false + } + return !checkSatisfaction(internalConstraint, v2) + }) + + return fixedVersions[0] +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.88.0/grype/presenter/models/vulnerability.go new/grype-0.89.0/grype/presenter/models/vulnerability.go --- old/grype-0.88.0/grype/presenter/models/vulnerability.go 2025-03-05 17:26:43.000000000 +0100 +++ new/grype-0.89.0/grype/presenter/models/vulnerability.go 2025-03-06 17:12:29.000000000 +0100 @@ -1,7 +1,11 @@ package models import ( + "sort" + + "github.com/anchore/grype/grype/version" "github.com/anchore/grype/grype/vulnerability" + "github.com/anchore/grype/internal/log" ) type Vulnerability struct { @@ -20,7 +24,7 @@ Link string `json:"link"` } -func NewVulnerability(vuln vulnerability.Vulnerability, metadata *vulnerability.Metadata) Vulnerability { +func NewVulnerability(vuln vulnerability.Vulnerability, metadata *vulnerability.Metadata, versionFormat version.Format) Vulnerability { if metadata == nil { return Vulnerability{ VulnerabilityMetadata: NewVulnerabilityMetadata(vuln.ID, vuln.Namespace, metadata), @@ -44,9 +48,45 @@ return Vulnerability{ VulnerabilityMetadata: NewVulnerabilityMetadata(vuln.ID, vuln.Namespace, metadata), Fix: Fix{ - Versions: fixedInVersions, + Versions: sortVersions(fixedInVersions, versionFormat), State: string(vuln.Fix.State), }, Advisories: advisories, } } +func sortVersions(fixedVersions []string, format version.Format) []string { + if len(fixedVersions) <= 1 { + return fixedVersions + } + + // First, create Version objects from strings (only once) + versionObjs := make([]*version.Version, 0, len(fixedVersions)) + for _, vStr := range fixedVersions { + v, err := version.NewVersion(vStr, format) + if err != nil { + log.WithFields("version", vStr, "error", err).Trace("error parsing version, skipping") + continue + } + versionObjs = append(versionObjs, v) + } + + // Sort the Version objects + sort.Slice(versionObjs, func(i, j int) bool { + // Compare returns -1 if v[i] < v[j], so we negate for descending order + // (higher versions first) + comparison, err := versionObjs[i].Compare(versionObjs[j]) + if err != nil { + log.WithFields("error", err).Trace("error comparing versions") + return false + } + return comparison > 0 // Descending order + }) + + // Convert back to strings + result := make([]string, len(versionObjs)) + for i, v := range versionObjs { + result[i] = v.Raw + } + + return result +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.88.0/grype/presenter/sarif/test-fixtures/snapshot/TestSarifPresenter_directory.golden new/grype-0.89.0/grype/presenter/sarif/test-fixtures/snapshot/TestSarifPresenter_directory.golden --- old/grype-0.88.0/grype/presenter/sarif/test-fixtures/snapshot/TestSarifPresenter_directory.golden 2025-03-05 17:26:43.000000000 +0100 +++ new/grype-0.89.0/grype/presenter/sarif/test-fixtures/snapshot/TestSarifPresenter_directory.golden 2025-03-06 17:12:29.000000000 +0100 @@ -20,8 +20,8 @@ }, "helpUri": "https://github.com/anchore/grype", "help": { - "text": "Vulnerability CVE-1999-0001\nSeverity: low\nPackage: package-1\nVersion: 1.1.1\nFix Version: the-next-version\nType: rpm\nLocation: /some/path/somefile-1.txt\nData Namespace: source-1\nLink: CVE-1999-0001", - "markdown": "**Vulnerability CVE-1999-0001**\n| Severity | Package | Version | Fix Version | Type | Location | Data Namespace | Link |\n| --- | --- | --- | --- | --- | --- | --- | --- |\n| low | package-1 | 1.1.1 | the-next-version | rpm | /some/path/somefile-1.txt | source-1 | CVE-1999-0001 |\n" + "text": "Vulnerability CVE-1999-0001\nSeverity: low\nPackage: package-1\nVersion: 1.1.1\nFix Version: 1.2.1,2.1.3,3.4.0\nType: rpm\nLocation: /some/path/somefile-1.txt\nData Namespace: source-1\nLink: CVE-1999-0001", + "markdown": "**Vulnerability CVE-1999-0001**\n| Severity | Package | Version | Fix Version | Type | Location | Data Namespace | Link |\n| --- | --- | --- | --- | --- | --- | --- | --- |\n| low | package-1 | 1.1.1 | 1.2.1,2.1.3,3.4.0 | rpm | /some/path/somefile-1.txt | source-1 | CVE-1999-0001 |\n" }, "properties": { "security-severity": "4.0" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.88.0/grype/presenter/sarif/test-fixtures/snapshot/TestSarifPresenter_image.golden new/grype-0.89.0/grype/presenter/sarif/test-fixtures/snapshot/TestSarifPresenter_image.golden --- old/grype-0.88.0/grype/presenter/sarif/test-fixtures/snapshot/TestSarifPresenter_image.golden 2025-03-05 17:26:43.000000000 +0100 +++ new/grype-0.89.0/grype/presenter/sarif/test-fixtures/snapshot/TestSarifPresenter_image.golden 2025-03-06 17:12:29.000000000 +0100 @@ -20,8 +20,8 @@ }, "helpUri": "https://github.com/anchore/grype", "help": { - "text": "Vulnerability CVE-1999-0001\nSeverity: low\nPackage: package-1\nVersion: 1.1.1\nFix Version: the-next-version\nType: rpm\nLocation: somefile-1.txt\nData Namespace: source-1\nLink: CVE-1999-0001", - "markdown": "**Vulnerability CVE-1999-0001**\n| Severity | Package | Version | Fix Version | Type | Location | Data Namespace | Link |\n| --- | --- | --- | --- | --- | --- | --- | --- |\n| low | package-1 | 1.1.1 | the-next-version | rpm | somefile-1.txt | source-1 | CVE-1999-0001 |\n" + "text": "Vulnerability CVE-1999-0001\nSeverity: low\nPackage: package-1\nVersion: 1.1.1\nFix Version: 1.2.1,2.1.3,3.4.0\nType: rpm\nLocation: somefile-1.txt\nData Namespace: source-1\nLink: CVE-1999-0001", + "markdown": "**Vulnerability CVE-1999-0001**\n| Severity | Package | Version | Fix Version | Type | Location | Data Namespace | Link |\n| --- | --- | --- | --- | --- | --- | --- | --- |\n| low | package-1 | 1.1.1 | 1.2.1,2.1.3,3.4.0 | rpm | somefile-1.txt | source-1 | CVE-1999-0001 |\n" }, "properties": { "security-severity": "4.0" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.88.0/grype/presenter/table/__snapshots__/presenter_test.snap new/grype-0.89.0/grype/presenter/table/__snapshots__/presenter_test.snap --- old/grype-0.88.0/grype/presenter/table/__snapshots__/presenter_test.snap 2025-03-05 17:26:43.000000000 +0100 +++ new/grype-0.89.0/grype/presenter/table/__snapshots__/presenter_test.snap 2025-03-06 17:12:29.000000000 +0100 @@ -1,15 +1,15 @@ [TestTablePresenter/no_color - 1] -NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY -package-1 1.1.1 the-next-version rpm CVE-1999-0001 Low -package-2 2.2.2 deb CVE-1999-0002 Critical +NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY +package-1 1.1.1 *1.2.1, 2.1.3, 3.4.0 rpm CVE-1999-0001 Low +package-2 2.2.2 deb CVE-1999-0002 Critical --- [TestTablePresenter/with_color - 1] -NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY -package-1 1.1.1 the-next-version rpm CVE-1999-0001 [0;32mLow[0m -package-2 2.2.2 deb CVE-1999-0002 [1;31mCritical[0m +NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY +package-1 1.1.1 [1;4;36;4m1[0m[1;4;36;4m.[0m[1;4;36;4m2[0m[1;4;36;4m.[0m[1;4;36;4m1[0m, 2.1.3, 3.4.0 rpm CVE-1999-0001 [0;32mLow[0m +package-2 2.2.2 deb CVE-1999-0002 [1;31mCritical[0m --- @@ -19,18 +19,18 @@ --- [TestHidesIgnoredMatches - 1] -NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY -package-1 1.1.1 the-next-version rpm CVE-1999-0001 Low -package-2 2.2.2 deb CVE-1999-0002 Critical +NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY +package-1 1.1.1 *1.2.1, 2.1.3, 3.4.0 rpm CVE-1999-0001 Low +package-2 2.2.2 deb CVE-1999-0002 Critical --- [TestDisplaysIgnoredMatches - 1] -NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY -package-1 1.1.1 the-next-version rpm CVE-1999-0001 Low -package-2 2.2.2 deb CVE-1999-0002 Critical -package-2 2.2.2 deb CVE-1999-0001 Low (suppressed) -package-2 2.2.2 deb CVE-1999-0002 Critical (suppressed) -package-2 2.2.2 deb CVE-1999-0004 Critical (suppressed by VEX) +NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY +package-1 1.1.1 *1.2.1, 2.1.3, 3.4.0 rpm CVE-1999-0001 Low +package-2 2.2.2 deb CVE-1999-0002 Critical +package-2 2.2.2 deb CVE-1999-0001 Low (suppressed) +package-2 2.2.2 deb CVE-1999-0002 Critical (suppressed) +package-2 2.2.2 deb CVE-1999-0004 Critical (suppressed by VEX) --- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.88.0/grype/presenter/table/presenter.go new/grype-0.89.0/grype/presenter/table/presenter.go --- old/grype-0.88.0/grype/presenter/table/presenter.go 2025-03-05 17:26:43.000000000 +0100 +++ new/grype-0.89.0/grype/presenter/table/presenter.go 2025-03-06 17:12:29.000000000 +0100 @@ -6,6 +6,7 @@ "github.com/charmbracelet/lipgloss" "github.com/olekukonko/tablewriter" + "github.com/scylladb/go-set/strset" "github.com/anchore/grype/grype/presenter/models" "github.com/anchore/grype/grype/vulnerability" @@ -21,20 +22,39 @@ document models.Document showSuppressed bool withColor bool + + recommendedFixStyle lipgloss.Style +} + +type rows []row + +type row struct { + Name string + Version string + Fix string + PackageType string + VulnerabilityID string + Severity string } // NewPresenter is a *Presenter constructor func NewPresenter(pb models.PresenterConfig, showSuppressed bool) *Presenter { + withColor := supportsColor() + fixStyle := lipgloss.NewStyle().Border(lipgloss.Border{Left: "*"}, false, false, false, true) + if withColor { + fixStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("6")).Bold(true).Underline(true) + } return &Presenter{ - document: pb.Document, - showSuppressed: showSuppressed, - withColor: supportsColor(), + document: pb.Document, + showSuppressed: showSuppressed, + withColor: withColor, + recommendedFixStyle: fixStyle, } } // Present creates a JSON-based reporting func (p *Presenter) Present(output io.Writer) error { - rs := getRows(p.document, p.showSuppressed) + rs := p.getRows(p.document, p.showSuppressed) if len(rs) == 0 { _, err := io.WriteString(output, "No vulnerabilities found\n") @@ -57,9 +77,16 @@ table.SetNoWhiteSpace(true) if p.withColor { - for _, row := range rs.Render() { - severityColor := getSeverityColor(row[len(row)-1]) - table.Rich(row, []tablewriter.Colors{{}, {}, {}, {}, {}, severityColor}) + for _, row := range rs.Deduplicate() { + severityColor := getSeverityColor(row.Severity) + table.Rich(row.Columns(), []tablewriter.Colors{ + {}, // name + {}, // version + {}, // fix + {}, // package type + {}, // vulnerability ID + severityColor, // severity + }) } } else { table.AppendBulk(rs.Render()) @@ -70,12 +97,12 @@ return nil } -func getRows(doc models.Document, showSuppressed bool) rows { +func (p *Presenter) getRows(doc models.Document, showSuppressed bool) rows { var rs rows // generate rows for matching vulnerabilities for _, m := range doc.Matches { - rs = append(rs, newRow(m, "")) + rs = append(rs, p.newRow(m, "")) } // generate rows for suppressed vulnerabilities @@ -89,7 +116,7 @@ } } } - rs = append(rs, newRow(m.Match, msg)) + rs = append(rs, p.newRow(m.Match, msg)) } } return rs @@ -99,41 +126,53 @@ return lipgloss.NewStyle().Foreground(lipgloss.Color("5")).Render("") != "" } -type rows []row - -type row struct { - Name string - Version string - Fix string - PackageType string - VulnerabilityID string - Severity string -} - -func newRow(m models.Match, severitySuffix string) row { +func (p *Presenter) newRow(m models.Match, severitySuffix string) row { severity := m.Vulnerability.Severity if severity != "" { severity += severitySuffix } - fixVersion := strings.Join(m.Vulnerability.Fix.Versions, ", ") - switch m.Vulnerability.Fix.State { - case vulnerability.FixStateWontFix.String(): - fixVersion = "(won't fix)" - case vulnerability.FixStateUnknown.String(): - fixVersion = "" - } - return row{ Name: m.Artifact.Name, Version: m.Artifact.Version, - Fix: fixVersion, + Fix: p.formatFix(m), PackageType: string(m.Artifact.Type), VulnerabilityID: m.Vulnerability.ID, Severity: severity, } } +func (p *Presenter) formatFix(m models.Match) string { + switch m.Vulnerability.Fix.State { + case vulnerability.FixStateWontFix.String(): + return "(won't fix)" + case vulnerability.FixStateUnknown.String(): + return "" + } + + recommended := strset.New() + for _, d := range m.MatchDetails { + if d.Fix == nil { + continue + } + if d.Fix.SuggestedVersion != "" { + recommended.Add(d.Fix.SuggestedVersion) + } + } + + var vers []string + hasMultipleVersions := len(m.Vulnerability.Fix.Versions) > 1 + for _, v := range m.Vulnerability.Fix.Versions { + if hasMultipleVersions && recommended.Has(v) { + vers = append(vers, p.recommendedFixStyle.Render(v)) + continue + } + vers = append(vers, v) + } + + return strings.Join(vers, ", ") +} + func (r row) Columns() []string { return []string{r.Name, r.Version, r.Fix, r.PackageType, r.VulnerabilityID, r.Severity} } @@ -143,6 +182,15 @@ } func (rs rows) Render() [][]string { + deduped := rs.Deduplicate() + out := make([][]string, len(deduped)) + for idx, r := range deduped { + out[idx] = r.Columns() + } + return out +} + +func (rs rows) Deduplicate() []row { // deduplicate seen := map[string]row{} var deduped rows @@ -159,11 +207,7 @@ } // render final columns - out := make([][]string, len(deduped)) - for idx, r := range deduped { - out[idx] = r.Columns() - } - return out + return deduped } func getSeverityColor(severity string) tablewriter.Colors { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.88.0/grype/presenter/table/presenter_test.go new/grype-0.89.0/grype/presenter/table/presenter_test.go --- old/grype-0.88.0/grype/presenter/table/presenter_test.go 2025-03-05 17:26:43.000000000 +0100 +++ new/grype-0.89.0/grype/presenter/table/presenter_test.go 2025-03-06 17:12:29.000000000 +0100 @@ -4,8 +4,10 @@ "bytes" "testing" + "github.com/charmbracelet/lipgloss" "github.com/gkampitakis/go-snaps/snaps" "github.com/google/go-cmp/cmp" + "github.com/muesli/termenv" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -22,11 +24,15 @@ pkg1 := models.Package{ ID: "package-1-id", Name: "package-1", - Version: "1.0.1", + Version: "2.0.0", Type: syftPkg.DebPkg, } match1 := models.Match{ Vulnerability: models.Vulnerability{ + Fix: models.Fix{ + Versions: []string{"1.0.2", "2.0.1", "3.0.4"}, + State: vulnerability.FixStateFixed.String(), + }, VulnerabilityMetadata: models.VulnerabilityMetadata{ ID: "CVE-1999-0001", Namespace: "source-1", @@ -48,6 +54,9 @@ { Type: match.ExactDirectMatch.String(), Matcher: match.DpkgMatcher.String(), + Fix: &models.FixDetails{ + SuggestedVersion: "2.0.1", + }, }, }, } @@ -61,19 +70,20 @@ name: "create row for vulnerability", match: match1, severitySuffix: "", - expectedRow: []string{match1.Artifact.Name, match1.Artifact.Version, "", string(match1.Artifact.Type), match1.Vulnerability.ID, "Low"}, + expectedRow: []string{match1.Artifact.Name, match1.Artifact.Version, "1.0.2, *2.0.1, 3.0.4", string(match1.Artifact.Type), match1.Vulnerability.ID, "Low"}, }, { name: "create row for suppressed vulnerability", match: match1, severitySuffix: appendSuppressed, - expectedRow: []string{match1.Artifact.Name, match1.Artifact.Version, "", string(match1.Artifact.Type), match1.Vulnerability.ID, "Low (suppressed)"}, + expectedRow: []string{match1.Artifact.Name, match1.Artifact.Version, "1.0.2, *2.0.1, 3.0.4", string(match1.Artifact.Type), match1.Vulnerability.ID, "Low (suppressed)"}, }, } for _, testCase := range cases { t.Run(testCase.name, func(t *testing.T) { - row := newRow(testCase.match, testCase.severitySuffix) + p := NewPresenter(models.PresenterConfig{}, false) + row := p.newRow(testCase.match, testCase.severitySuffix) cols := rows{row}.Render()[0] assert.Equal(t, testCase.expectedRow, cols) @@ -83,11 +93,11 @@ func TestTablePresenter(t *testing.T) { pb := internal.GeneratePresenterConfig(t, internal.ImageSource) - pres := NewPresenter(pb, false) t.Run("no color", func(t *testing.T) { var buffer bytes.Buffer - pres.withColor = false + lipgloss.SetColorProfile(termenv.Ascii) + pres := NewPresenter(pb, false) err := pres.Present(&buffer) require.NoError(t, err) @@ -98,7 +108,12 @@ t.Run("with color", func(t *testing.T) { var buffer bytes.Buffer - pres.withColor = true + lipgloss.SetColorProfile(termenv.TrueColor) + t.Cleanup(func() { + // don't affect other tests + lipgloss.SetColorProfile(termenv.Ascii) + }) + pres := NewPresenter(pb, false) err := pres.Present(&buffer) require.NoError(t, err) @@ -226,7 +241,6 @@ }) } -// Helper function to create a test row func createTestRow(name, version, fix, pkgType, vulnID, severity string, fixState vulnerability.FixState) (row, error) { m := models.Match{ Vulnerability: models.Vulnerability{ @@ -246,7 +260,8 @@ }, } - r := newRow(m, "") + p := NewPresenter(models.PresenterConfig{}, false) + r := p.newRow(m, "") return r, nil } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.88.0/grype/version/version.go new/grype-0.89.0/grype/version/version.go --- old/grype-0.88.0/grype/version/version.go 2025-03-05 17:26:43.000000000 +0100 +++ new/grype-0.89.0/grype/version/version.go 2025-03-06 17:12:29.000000000 +0100 @@ -118,3 +118,55 @@ func (v Version) String() string { return fmt.Sprintf("%s (%s)", v.Raw, v.Format) } + +func (v Version) Compare(other *Version) (int, error) { + if other == nil { + return -1, ErrNoVersionProvided + } + + if other.Format == v.Format { + return v.compareSameFormat(other) + } + + // different formats, try to convert to a common format + common, err := finalizeComparisonVersion(other, v.Format) + if err != nil { + return -1, err + } + + return v.compareSameFormat(common) +} + +func (v Version) compareSameFormat(other *Version) (int, error) { + switch v.Format { + case SemanticFormat: + return v.rich.semVer.verObj.Compare(other.rich.semVer.verObj), nil + case ApkFormat: + return v.rich.apkVer.Compare(other) + case DebFormat: + return v.rich.debVer.Compare(other) + case GolangFormat: + return v.rich.golangVersion.Compare(other) + case MavenFormat: + return v.rich.mavenVer.Compare(other) + case RpmFormat: + return v.rich.rpmVer.Compare(other) + case PythonFormat: + return v.rich.pep440version.Compare(other) + case KBFormat: + return v.rich.kbVer.Compare(other) + case GemFormat: + return v.rich.semVer.verObj.Compare(other.rich.semVer.verObj), nil + case PortageFormat: + return v.rich.portVer.Compare(other) + case JVMFormat: + return v.rich.jvmVersion.Compare(other) + } + + v1, err := newFuzzyVersion(v.Raw) + if err != nil { + return -1, fmt.Errorf("unable to parse version (%s) as a fuzzy version: %w", v.Raw, err) + } + + return v1.Compare(other) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.88.0/grype/version/version_test.go new/grype-0.89.0/grype/version/version_test.go --- old/grype-0.88.0/grype/version/version_test.go 1970-01-01 01:00:00.000000000 +0100 +++ new/grype-0.89.0/grype/version/version_test.go 2025-03-06 17:12:29.000000000 +0100 @@ -0,0 +1,133 @@ +package version + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestVersionCompare(t *testing.T) { + tests := []struct { + name string + version1 string + version2 string + format Format + expectedResult int + expectErr bool + }{ + { + name: "v1 greater than v2", + version1: "2.0.0", + version2: "1.0.0", + format: SemanticFormat, + expectedResult: 1, + expectErr: false, + }, + { + name: "v1 less than v2", + version1: "1.0.0", + version2: "2.0.0", + format: SemanticFormat, + expectedResult: -1, + expectErr: false, + }, + { + name: "v1 equal to v2", + version1: "1.0.0", + version2: "1.0.0", + format: SemanticFormat, + expectedResult: 0, + expectErr: false, + }, + { + name: "compare with nil version", + version1: "1.0.0", + version2: "", + format: SemanticFormat, + expectedResult: -1, + expectErr: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + v1, err := NewVersion(tc.version1, tc.format) + require.NoError(t, err, "failed to create version1") + + var v2 *Version + if tc.version2 == "" { + v2 = nil // test nil case + } else if tc.name == "different formats" { + // use a different format for the second version + v2, err = NewVersion(tc.version2, PythonFormat) + require.NoError(t, err, "failed to create version2 with different format") + } else { + v2, err = NewVersion(tc.version2, tc.format) + require.NoError(t, err, "failed to create version2") + } + + result, err := v1.Compare(v2) + + if tc.expectErr { + assert.Error(t, err, "expected an error but got none") + } else { + assert.NoError(t, err, "unexpected error during comparison") + assert.Equal(t, tc.expectedResult, result, "comparison result mismatch") + } + }) + } +} + +func Test_UpgradeUnknownRightSideComparison(t *testing.T) { + v1, err := NewVersion("1.0.0", SemanticFormat) + require.NoError(t, err) + + // test if we can upgrade an unknown format to a known format when the left hand side is known + v2, err := NewVersion("1.0.0", UnknownFormat) + require.NoError(t, err) + + result, err := v1.Compare(v2) + assert.NoError(t, err) + assert.Equal(t, 0, result, "versions should be equal after format conversion") +} + +func TestVersionCompareSameFormat(t *testing.T) { + formats := []struct { + name string + format Format + }{ + {"Semantic", SemanticFormat}, + {"APK", ApkFormat}, + {"Deb", DebFormat}, + {"Golang", GolangFormat}, + {"Maven", MavenFormat}, + {"RPM", RpmFormat}, + {"Python", PythonFormat}, + {"KB", KBFormat}, + {"Gem", GemFormat}, + {"Portage", PortageFormat}, + {"JVM", JVMFormat}, + {"Unknown", UnknownFormat}, + } + + for _, fmt := range formats { + t.Run(fmt.name, func(t *testing.T) { + // just test that we can create and compare versions of this format + // without errors - not testing the actual comparison logic + v1, err := NewVersion("1.0.0", fmt.format) + if err != nil { + t.Skipf("Skipping %s format, couldn't create version: %v", fmt.name, err) + } + + v2, err := NewVersion("1.0.0", fmt.format) + if err != nil { + t.Skipf("Skipping %s format, couldn't create second version: %v", fmt.name, err) + } + + result, err := v1.Compare(v2) + assert.NoError(t, err, "comparison error") + assert.Equal(t, 0, result, "equal versions should return 0") + }) + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.88.0/test/quality/.grype.yaml new/grype-0.89.0/test/quality/.grype.yaml --- old/grype-0.88.0/test/quality/.grype.yaml 2025-03-05 17:26:43.000000000 +0100 +++ new/grype-0.89.0/test/quality/.grype.yaml 2025-03-06 17:12:29.000000000 +0100 @@ -23,8 +23,3 @@ stock: using-cpes: true -# while we are merging https://github.com/anchore/grype/pull/2494 to support v6 there are a few examples that are -# worse, however, the overall matching behavior is correct, thus we should not block on these examples -ignore: - - vulnerability: CVE-2012-0979 - - vulnerability: CVE-2019-5736 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grype-0.88.0/test/quality/.yardstick.yaml new/grype-0.89.0/test/quality/.yardstick.yaml --- old/grype-0.88.0/test/quality/.yardstick.yaml 2025-03-05 17:26:43.000000000 +0100 +++ new/grype-0.89.0/test/quality/.yardstick.yaml 2025-03-06 17:12:29.000000000 +0100 @@ -116,9 +116,7 @@ # are testing with is not too stale. # version: git:current-commit+import-db=db.tar.zst # for local build of grype, use for example: -# we need to disable this while we are in pre-release of v6 (restore after release of v6) -# version: path:../../+import-db=db.tar.zst - version: path:../../ + version: path:../../+import-db=db.tar.zst takes: SBOM label: candidate @@ -128,9 +126,7 @@ # By pinning the DB the grype code itself becomes the independent variable under test (and not the # every-changing DB). That being said, we should be updating this DB periodically to ensure what we # are testing with is not too stale. -# we need to disable this while we are in pre-release of v6 (restore after release of v6) -# version: latest+import-db=db.tar.zst - version: latest + version: latest+import-db=db.tar.zst takes: SBOM label: reference pr_vs_latest_via_sbom_2022: @@ -161,9 +157,7 @@ # are testing with is not too stale. # version: git:current-commit+import-db=db.tar.zst # for local build of grype, use for example: -# we need to disable this while we are in pre-release of v6 (restore after release of v6) -# version: path:../../+import-db=db.tar.zst - version: path:../../ + version: path:../../+import-db=db.tar.zst takes: SBOM label: candidate # is candidate better than the current baseline? @@ -173,8 +167,6 @@ # By pinning the DB the grype code itself becomes the independent variable under test (and not the # every-changing DB). That being said, we should be updating this DB periodically to ensure what we # are testing with is not too stale. -# we need to disable this while we are in pre-release of v6 (restore after release of v6) -# version: latest+import-db=db.tar.zst - version: latest + version: latest+import-db=db.tar.zst takes: SBOM label: reference # this run is the current baseline ++++++ grype.obsinfo ++++++ --- /var/tmp/diff_new_pack.maZ307/_old 2025-03-07 16:48:28.793274152 +0100 +++ /var/tmp/diff_new_pack.maZ307/_new 2025-03-07 16:48:28.797274321 +0100 @@ -1,5 +1,5 @@ name: grype -version: 0.88.0 -mtime: 1741192003 -commit: 6ee276f0c8363518c08b8d48fae302ee6001c295 +version: 0.89.0 +mtime: 1741277549 +commit: 1bf47c38bede40dea7b72bbe4712191820f1aa15 ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/grype/vendor.tar.gz /work/SRC/openSUSE:Factory/.grype.new.19136/vendor.tar.gz differ: char 5, line 1