Repository: anchore/grype (go)

Issue:
vex documents from the --vex flag do get processed or applied to the output correctly
**What happened**:

When following the example [here](https://github.com/anchore/grype?tab=readme-ov-file#vex-support) using the vex document specified, the vulnerability is rendered in the outputted report. This happens in any format.

vex.json
```json
{
  "@context": "https://openvex.dev/ns/v0.2.0",
  "@id": "https://openvex.dev/docs/public/vex-d4e9020b6d0d26f131d535e055902dd6ccf3e2088bce3079a8cd3588a4b14c78",
  "author": "A Grype User <jdoe@example.com>",
  "timestamp": "2023-07-17T18:28:47.696004345-06:00",
  "version": 1,
  "statements": [
    {
      "vulnerability": {
        "name": "CVE-2023-1255"
      },
      "products": [
        {
          "@id": "pkg:oci/alpine@sha256%3A124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126",
          "subcomponents": [
            { "@id": "pkg:apk/alpine/libssl3@3.0.8-r3" },
            { "@id": "pkg:apk/alpine/libcrypto3@3.0.8-r3" }
          ]
        }
      ],
      "status": "fixed"
    }
  ]
}
```
command
```bash
docker run -it -v $PWD/vex.json:/vex.json  anchore/grype alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126 --vex /vex.json
 ✔ Vulnerability DB                [updated]  
 ✔ Parsed image                                         sha256:51e60588ff2cd9f45792b23de89bfface0a7fbd711d17c5f5ce900a4f6b16260
 ✔ Cataloged contents                                          b5a5b7ce4eabc8414bf367761a28f4e8b16952ce5de537c15ed917b71b245f11
   ├── ✔ Packages                        [15 packages]  
   ├── ✔ File digests                    [78 files]  
   ├── ✔ File metadata                   [78 locations]  
   └── ✔ Executables                     [17 executables]  
 ✔ Scanned for vulnerabilities     [22 vulnerability matches]  
   ├── by severity: 0 critical, 2 high, 16 medium, 0 low, 0 negligible (4 unknown)
   └── by status:   22 fixed, 0 not-fixed, 0 ignored 
NAME        INSTALLED  FIXED-IN   TYPE  VULNERABILITY  SEVERITY 
...
libcrypto3  3.0.8-r3   3.0.8-r4   apk   CVE-2023-1255  Medium  
...
```

vexctl filter works
```grype alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126 --output sarif | vexctl filter - vex.json | jq '.runs[].results[].ruleId'  | grep 1255; echo $?
1
```


**What you expected to happen**:

I do not expect the vulnerability to be reported.
Maybe I am missing something here?

**How to reproduce it (as minimally and precisely as possible)**:
see above
**Anything else we need to know?**:

**Environment**:
- Output of `grype version`: 0.77.1
- OS (e.g: `cat /etc/os-release` or similar): mac/linux - tested both



Pull request description:
fix: append vex ignore rules if vex doc is present
Previously, grype would disregard default vex behavior in the presence of other ignore rules. Instead, append default vex ignore rules if any vex documents are used, even if other ignore rules exist.

Fixes #1836 

Patch:
diff --git a/.github/workflows/validations.yaml b/.github/workflows/validations.yaml
index 75c3340b..a1377153 100644
--- a/.github/workflows/validations.yaml
+++ b/.github/workflows/validations.yaml
@@ -280,6 +280,6 @@ jobs:
       - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
 
       - name: Delete snapshot cache
-        run: gh cache delete "snapshot-build-${{ github.run_id }}"
+        run: gh cache delete "snapshot-build-${{ github.run_id }}" || echo "Cache deletion failed or cache not found - continuing"
         env:
           GH_TOKEN: ${{ github.token }}
diff --git a/cmd/grype/cli/commands/root.go b/cmd/grype/cli/commands/root.go
index 937797e0..8d845197 100644
--- a/cmd/grype/cli/commands/root.go
+++ b/cmd/grype/cli/commands/root.go
@@ -173,7 +173,6 @@ func runGrype(app clio.Application, opts *options.Grype, userInput string) (errs
 			return nil
 		},
 	)
-
 	if err != nil {
 		return err
 	}
@@ -402,7 +401,9 @@ func validateRootArgs(cmd *cobra.Command, args []string) error {
 }
 
 func applyVexRules(opts *options.Grype) error {
-	if len(opts.Ignore) == 0 && len(opts.VexDocuments) > 0 {
+	// If any vex documents are provided, assume the user intends to ignore vulnerabilities that those
+	// vex documents list as "fixed" or "not_affected".
+	if len(opts.VexDocuments) > 0 {
 		opts.Ignore = append(opts.Ignore, ignoreVEXFixedNotAffected...)
 	}
 


Test patch:
diff --git a/cmd/grype/cli/commands/root_test.go b/cmd/grype/cli/commands/root_test.go
index e50d3408..edfa10dc 100644
--- a/cmd/grype/cli/commands/root_test.go
+++ b/cmd/grype/cli/commands/root_test.go
@@ -6,10 +6,13 @@ import (
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 
 	"github.com/anchore/clio"
 	"github.com/anchore/grype/cmd/grype/cli/options"
+	"github.com/anchore/grype/grype/match"
 	"github.com/anchore/grype/grype/pkg"
+	"github.com/anchore/grype/grype/vex"
 	"github.com/anchore/stereoscope/pkg/image"
 	"github.com/anchore/syft/syft"
 	"github.com/anchore/syft/syft/cataloging"
@@ -86,3 +89,118 @@ func Test_getProviderConfig(t *testing.T) {
 		})
 	}
 }
+
+func Test_applyVexRules(t *testing.T) {
+	tests := []struct {
+		name                   string
+		initialIgnoreRules     []match.IgnoreRule
+		vexDocuments           []string
+		vexAdd                 []string
+		expectedIgnoreRules    []match.IgnoreRule
+		expectError            bool
+		expectedErrorSubstring string
+	}{
+		{
+			name:                "no VEX documents provided - no rules added",
+			initialIgnoreRules:  []match.IgnoreRule{},
+			vexDocuments:        []string{},
+			vexAdd:              []string{},
+			expectedIgnoreRules: []match.IgnoreRule{},
+			expectError:         false,
+		},
+		{
+			name:               "VEX documents provided with empty ignore rules - automatic rules added",
+			initialIgnoreRules: []match.IgnoreRule{},
+			vexDocuments:       []string{"path/to/vex.json"},
+			vexAdd:             []string{},
+			expectedIgnoreRules: []match.IgnoreRule{
+				{VexStatus: string(vex.StatusNotAffected)},
+				{VexStatus: string(vex.StatusFixed)},
+			},
+			expectError: false,
+		},
+		{
+			name: "VEX documents provided with existing ignore rules - automatic rules still added",
+			initialIgnoreRules: []match.IgnoreRule{
+				{Vulnerability: "CVE-2023-1234"},
+			},
+			vexDocuments: []string{"path/to/vex.json"},
+			vexAdd:       []string{},
+			expectedIgnoreRules: []match.IgnoreRule{
+				{Vulnerability: "CVE-2023-1234"},
+				{VexStatus: string(vex.StatusNotAffected)},
+				{VexStatus: string(vex.StatusFixed)},
+			},
+			expectError: false,
+		},
+		{
+			name:               "vex-add with valid statuses",
+			initialIgnoreRules: []match.IgnoreRule{},
+			vexDocuments:       []string{"path/to/vex.json"},
+			vexAdd:             []string{"affected", "under_investigation"},
+			expectedIgnoreRules: []match.IgnoreRule{
+				{VexStatus: string(vex.StatusNotAffected)},
+				{VexStatus: string(vex.StatusFixed)},
+				{VexStatus: string(vex.StatusAffected)},
+				{VexStatus: string(vex.StatusUnderInvestigation)},
+			},
+			expectError: false,
+		},
+		{
+			name:                   "vex-add with invalid status",
+			initialIgnoreRules:     []match.IgnoreRule{},
+			vexDocuments:           []string{"path/to/vex.json"},
+			vexAdd:                 []string{"invalid_status"},
+			expectedIgnoreRules:    nil,
+			expectError:            true,
+			expectedErrorSubstring: "invalid VEX status in vex-add setting: invalid_status",
+		},
+		{
+			name:                   "vex-add attempting to use fixed status",
+			initialIgnoreRules:     []match.IgnoreRule{},
+			vexDocuments:           []string{"path/to/vex.json"},
+			vexAdd:                 []string{"fixed"},
+			expectedIgnoreRules:    nil,
+			expectError:            true,
+			expectedErrorSubstring: "invalid VEX status in vex-add setting: fixed",
+		},
+		{
+			name: "multiple VEX documents with existing rules",
+			initialIgnoreRules: []match.IgnoreRule{
+				{Vulnerability: "CVE-2023-1234"},
+				{FixState: "unknown"},
+			},
+			vexDocuments: []string{"vex1.json", "vex2.json"},
+			vexAdd:       []string{"affected"},
+			expectedIgnoreRules: []match.IgnoreRule{
+				{Vulnerability: "CVE-2023-1234"},
+				{FixState: "unknown"},
+				{VexStatus: string(vex.StatusNotAffected)},
+				{VexStatus: string(vex.StatusFixed)},
+				{VexStatus: string(vex.StatusAffected)},
+			},
+			expectError: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			opts := &options.Grype{
+				Ignore:       append([]match.IgnoreRule{}, tt.initialIgnoreRules...),
+				VexDocuments: tt.vexDocuments,
+				VexAdd:       tt.vexAdd,
+			}
+
+			err := applyVexRules(opts)
+
+			if tt.expectError {
+				require.Error(t, err)
+				assert.Contains(t, err.Error(), tt.expectedErrorSubstring)
+				return
+			}
+
+			require.NoError(t, err)
+			assert.Equal(t, tt.expectedIgnoreRules, opts.Ignore)
+		})
+	}
+}