Repository: ratify-project/ratify
Pull request #1701 (2024-08-11 08:33:04)

chore: automated PR to main 2024-08-11
Automated Pull Request to main branch


Patch:
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index af5e4d9..43d4615 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -14,7 +14,7 @@
 # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/go/.devcontainer/base.Dockerfile
 
 # [Choice] Go version (use -bullseye variants on local arm64/Apple Silicon): 1.22-bullseye, 1.21-bullseye, 1, 1.19, 1.18, 1-bullseye, 1.19-bullseye, 1.18-bullseye, 1-buster, 1.19-buster, 1.18-buster
-FROM mcr.microsoft.com/vscode/devcontainers/go:1.22-bullseye@sha256:dca0f2ca25750feda8233b2d23c43da5ec1cf1a2afcd04f791981d9e647612ff
+FROM mcr.microsoft.com/vscode/devcontainers/go:1.22-bullseye@sha256:8cb4ef68d402b85b52c955ff16410e499cf7604527634d99a2a1922a93751795
 
 # [Choice] Node.js version: none, lts/*, 18, 16, 14
 ARG NODE_VERSION="none"
diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml
index bda3e7b..18ab101 100644
--- a/.github/workflows/build-pr.yml
+++ b/.github/workflows/build-pr.yml
@@ -70,7 +70,7 @@ jobs:
     environment: azure-test
     steps:
       - name: Harden Runner
-        uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
+        uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
         with:
           egress-policy: audit
 
diff --git a/.github/workflows/cache-cleanup.yml b/.github/workflows/cache-cleanup.yml
index 19fc677..5fbb3d0 100644
--- a/.github/workflows/cache-cleanup.yml
+++ b/.github/workflows/cache-cleanup.yml
@@ -12,7 +12,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:      
       - name: Harden Runner
-        uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
+        uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
         with:
           egress-policy: audit
 
diff --git a/.github/workflows/clean-dev-package.yml b/.github/workflows/clean-dev-package.yml
index da9602c..98cc5cf 100644
--- a/.github/workflows/clean-dev-package.yml
+++ b/.github/workflows/clean-dev-package.yml
@@ -13,7 +13,7 @@ jobs:
         packages: write
     steps:
       - name: Harden Runner
-        uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
+        uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
         with:
           egress-policy: audit
 
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 8d2f268..c4c55f1 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -27,7 +27,7 @@ jobs:
 
     steps:
       - name: Harden Runner
-        uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
+        uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
         with:
           egress-policy: audit
 
@@ -38,7 +38,7 @@ jobs:
         with:
           go-version: "1.22"
       - name: Initialize CodeQL
-        uses: github/codeql-action/init@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # tag=v3.25.15
+        uses: github/codeql-action/init@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # tag=v3.26.0
         with:
           languages: go
       - name: Run tidy
@@ -46,4 +46,4 @@ jobs:
       - name: Build CLI
         run: make build
       - name: Perform CodeQL Analysis
-        uses: github/codeql-action/analyze@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # tag=v3.25.15
+        uses: github/codeql-action/analyze@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # tag=v3.26.0
diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml
index cea034b..dd5bec5 100644
--- a/.github/workflows/golangci-lint.yml
+++ b/.github/workflows/golangci-lint.yml
@@ -15,7 +15,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Harden Runner
-        uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
+        uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
         with:
           egress-policy: audit
 
diff --git a/.github/workflows/high-availability.yml b/.github/workflows/high-availability.yml
index a7d1b00..09d3fe2 100644
--- a/.github/workflows/high-availability.yml
+++ b/.github/workflows/high-availability.yml
@@ -30,7 +30,7 @@ jobs:
         DAPR_VERSION: ["1.13.2"]
     steps:
       - name: Harden Runner
-        uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
+        uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
         with:
           egress-policy: audit
 
@@ -60,7 +60,7 @@ jobs:
           kubectl logs -n gatekeeper-system -l app=ratify --tail=-1 > logs-ratify-preinstall-${{ matrix.DAPR_VERSION }}.json
           kubectl logs -n gatekeeper-system -l app.kubernetes.io/name=ratify --tail=-1 > logs-ratify-${{ matrix.DAPR_VERSION }}.json
       - name: Upload artifacts
-        uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5
+        uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
         if: ${{ always() }}
         with:
           name: e2e-logs-${{ matrix.DAPR_VERSION }}
diff --git a/.github/workflows/pr-to-main.yml b/.github/workflows/pr-to-main.yml
index 6b3d2e1..3aecd8b 100644
--- a/.github/workflows/pr-to-main.yml
+++ b/.github/workflows/pr-to-main.yml
@@ -13,7 +13,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Harden Runner
-        uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
+        uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
         with:
           egress-policy: audit
 
diff --git a/.github/workflows/publish-charts.yml b/.github/workflows/publish-charts.yml
index a9aea64..cfb5789 100644
--- a/.github/workflows/publish-charts.yml
+++ b/.github/workflows/publish-charts.yml
@@ -13,7 +13,7 @@ jobs:
       contents: write
     steps:
       - name: Harden Runner
-        uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
+        uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
         with:
           egress-policy: audit
 
diff --git a/.github/workflows/publish-cosign-sample.yml b/.github/workflows/publish-cosign-sample.yml
index b195792..925284a 100644
--- a/.github/workflows/publish-cosign-sample.yml
+++ b/.github/workflows/publish-cosign-sample.yml
@@ -20,12 +20,12 @@ jobs:
       id-token: write
     steps:            
       - name: Harden Runner
-        uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
+        uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
         with:
           egress-policy: audit
 
       - name: Install cosign
-        uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0
+        uses: sigstore/cosign-installer@4959ce089c160fddf62f7b42464195ba1a56d382 # v3.6.0
 
       - name: Get repo
         run: |          
diff --git a/.github/workflows/publish-dev-assets.yml b/.github/workflows/publish-dev-assets.yml
index e133440..c5dc1ef 100644
--- a/.github/workflows/publish-dev-assets.yml
+++ b/.github/workflows/publish-dev-assets.yml
@@ -17,7 +17,7 @@ jobs:
     environment: azure-publish
     steps:
       - name: Harden Runner
-        uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
+        uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
         with:
           egress-policy: audit
       - name: Checkout
@@ -25,7 +25,7 @@ jobs:
       - name: Install Notation
         uses: notaryproject/notation-action/setup@104aa999103172f827373af8ac14dde7aa6d28f1 # v1.1.0
       - name: Install cosign
-        uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0
+        uses: sigstore/cosign-installer@4959ce089c160fddf62f7b42464195ba1a56d382 # v3.6.0
       - name: Az CLI login
         uses: azure/login@6c251865b4e6290e7b78be643ea2d005bc51f69a # v2.1.1
         with:
diff --git a/.github/workflows/publish-package.yml b/.github/workflows/publish-package.yml
index d191b43..32a893d 100644
--- a/.github/workflows/publish-package.yml
+++ b/.github/workflows/publish-package.yml
@@ -16,7 +16,7 @@ jobs:
       contents: read
     steps:
       - name: Harden Runner
-        uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
+        uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
         with:
           egress-policy: audit
       - name: Checkout
diff --git a/.github/workflows/publish-sample.yml b/.github/workflows/publish-sample.yml
index fc887ca..b21e884 100644
--- a/.github/workflows/publish-sample.yml
+++ b/.github/workflows/publish-sample.yml
@@ -19,7 +19,7 @@ jobs:
       packages: write
     steps:            
       - name: Harden Runner
-        uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
+        uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
         with:
           egress-policy: audit
 
diff --git a/.github/workflows/quick-start.yml b/.github/workflows/quick-start.yml
index 4693788..ca981e2 100644
--- a/.github/workflows/quick-start.yml
+++ b/.github/workflows/quick-start.yml
@@ -30,7 +30,7 @@ jobs:
         KUBERNETES_VERSION: ["1.29.2"]
     steps:
       - name: Harden Runner
-        uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
+        uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
         with:
           egress-policy: audit
 
@@ -59,7 +59,7 @@ jobs:
           kubectl logs -n gatekeeper-system -l app=ratify --tail=-1 > logs-ratify-preinstall-${{ matrix.KUBERNETES_VERSION }}-config-policy.json
           kubectl logs -n gatekeeper-system -l app.kubernetes.io/name=ratify --tail=-1 > logs-ratify-${{ matrix.KUBERNETES_VERSION }}-config-policy.json
       - name: Upload artifacts
-        uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5
+        uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
         if: ${{ always() }}
         with:
           name: e2e-logs-${{ matrix.KUBERNETES_VERSION }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 7f84c71..28769e4 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -16,7 +16,7 @@ jobs:
       contents: write
     steps:
     - name: Harden Runner
-      uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
+      uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
       with:
         egress-policy: audit
 
@@ -49,7 +49,7 @@ jobs:
         $RUNNER_TEMP/sbom-tool generate -b . -bc . -pn ratify -pv $GITHUB_REF_NAME -ps Microsoft -nsb https://microsoft.com -V Verbose
 
     - name: Upload a Build Artifact
-      uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # tag=v4.3.5
+      uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # tag=v4.3.6
       with:
         name: SBOM SPDX files
         path: _manifest/spdx_2.2/**
diff --git a/.github/workflows/run-full-validation.yml b/.github/workflows/run-full-validation.yml
index fa43e95..a3ad7f5 100644
--- a/.github/workflows/run-full-validation.yml
+++ b/.github/workflows/run-full-validation.yml
@@ -58,7 +58,7 @@ jobs:
     environment: azure-test
     steps:
       - name: Harden Runner
-        uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
+        uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
         with:
           egress-policy: audit
 
diff --git a/.github/workflows/scan-vulns.yaml b/.github/workflows/scan-vulns.yaml
index 0260104..7c0ff23 100644
--- a/.github/workflows/scan-vulns.yaml
+++ b/.github/workflows/scan-vulns.yaml
@@ -23,7 +23,7 @@ jobs:
     timeout-minutes: 15
     steps:
       - name: Harden Runner
-        uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
+        uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
         with:
           egress-policy: audit
 
@@ -39,7 +39,7 @@ jobs:
     timeout-minutes: 15
     steps:
       - name: Harden Runner
-        uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
+        uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
         with:
           egress-policy: audit
 
diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml
index 9d2d704..838e6d5 100644
--- a/.github/workflows/scorecards.yml
+++ b/.github/workflows/scorecards.yml
@@ -8,10 +8,12 @@ on:
     branches:
       - main
       - dev
+      - release-*
   pull_request:
     branches:
       - dev
       - main
+      - release-*
   workflow_dispatch:
 
 permissions: read-all
@@ -28,7 +30,7 @@ jobs:
     
     steps:
       - name: Harden Runner
-        uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
+        uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
         with:
           egress-policy: audit
 
@@ -46,13 +48,13 @@ jobs:
           publish_results: true
 
       - name: "Upload artifact"
-        uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # tag=v4.3.5
+        uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # tag=v4.3.6
         with:
           name: SARIF file
           path: results.sarif
           retention-days: 5
       
       - name: "Upload to code-scanning"
-        uses: github/codeql-action/upload-sarif@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # tag=v3.25.15
+        uses: github/codeql-action/upload-sarif@eb055d739abdc2e8de2e5f4ba1a8b246daa779aa # tag=v3.26.0
         with:
           sarif_file: results.sarif
diff --git a/.github/workflows/sync-gh-pages.yml b/.github/workflows/sync-gh-pages.yml
index 28fa382..fd44a65 100644
--- a/.github/workflows/sync-gh-pages.yml
+++ b/.github/workflows/sync-gh-pages.yml
@@ -17,7 +17,7 @@ jobs:
       repository-projects: write
     steps:
       - name: Harden Runner
-        uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
+        uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
         with:
           egress-policy: audit
 
diff --git a/README.md b/README.md
index 20daa0d..f2b9baa 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,7 @@ Is a verification engine as a binary executable and on Kubernetes which enables
 [![Go Report Card](https://goreportcard.com/badge/github.com/ratify-project/ratify)](https://goreportcard.com/report/github.com/ratify-project/ratify)
 [![build-pr](https://github.com/ratify-project/ratify/actions/workflows/build-pr.yml/badge.svg)](https://github.com/ratify-project/ratify/actions/workflows/build-pr.yml)
 [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/ratify-project/ratify/badge)](https://api.securityscorecards.dev/projects/github.com/ratify-project/ratify)
+[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/9334/badge)](https://www.bestpractices.dev/projects/9334)
 [![Go Reference](https://pkg.go.dev/badge/github.com/deislabs/ratify.svg)](https://pkg.go.dev/github.com/deislabs/ratify)
 
 ## Table of Contents
diff --git a/docs/proposals/Error-Messages-Improvements.md b/docs/proposals/Error-Messages-Improvements.md
new file mode 100644
index 0000000..d01b54e
--- /dev/null
+++ b/docs/proposals/Error-Messages-Improvements.md
@@ -0,0 +1,135 @@
+# Error messages improvements
+
+## Problem/Motivation
+
+Error messages are crucial because they provide specific information about what went wrong, helping users quickly identify and resolve issues. Detailed error messages can pinpoint the exact part of the configuration or code that caused the problem. Error messages can include links or references to documentation, guiding users on how to fix the issue promptly. While testing the Ratify policy for signature verification, we noticed that some error messages were difficult to comprehend from the user perspective.
+
+Error message example 1:
+
+```text
+time=2024-07-17T16:28:16.939576441Z level=warning msg=Original Error: (Original Error: (HEAD "https:/
+roacr.azurecr.io/v2/net-monitor/manifests/v2": GET "https://roacr.azurecr.io/oauth2/token?
+scope=repository%3Anet-monitor%3Apull&service=roacr.azurecr.io": response status code 401: unauthorized: 
+authentication required, visit https://aka.ms/acr/authorization for more information.), Error: repository 
+operation failure, Code: REPOSITORY_OPERATION_FAILURE, Plugin Name: oras), Error: get subject descriptor 
+failure, Code: GET_SUBJECT_DESCRIPTOR_FAILURE, Plugin Name: oras, Component Type: referrerStore, Detail: 
+failed to resolve the subject descriptor component-type=referrerStore go.version=go1.21.10 namespace= 
+trace-id=34b27888-5402-443e-9836-77124c840561
+```
+
+The above example indicated the an error happened, however, a warning level was set for the log. The error message was set to the field `msg`. It contained nested errors. The first original error message correctly described the source of the problem "401 unauthorized" and pointed to a document for resolution, however, errors following the original error were redundant and not well formatted, thus complicated the overall message. The overall message failed to describe the context of the error, although "401 unauthorized" explained the reason, but did this error happen during signature verification or else? What does the `subject` mean?
+
+Error message example 2:
+
+```text
+"verifierReports": [
+  {
+        "subject": "docker.io/library/hello-world@sha256:1408fec50309afee38f3535383f5b09419e6dc0925bc69891e79d84cc4cdce6",
+        "isSuccess": false,
+        "message": "verification failed: Error: no verifier report, Code: NO_VERIFIER _REPORT, Component Type: 
+        executor, Description: No verifier report was generatec preventing access to the registry, or the absence 
+        of appropriate verifiers corresponding to the referenced image artifacts."
+  }
+]
+```
+
+When Ratify completes artifact verification, the result is returned to the policy engine in the format of the json object `verifierReports`. The `verifierReports` is also recorded in an INFO log of Ratify. If `isSuccess` field is set to `false`, the `message` field is set to error messages. In above example, the message is not well formatted and lacked clarity on the problem, its cause, and remediation methods. It's hard for users to understand in what context the error happened and what users need to do. For example, is it an error for signature verification? What does "no verifier report" mean? We also observed that the `verifierReports` contains different supported fields when compared with the Ratify Config policy and Ratify Rego policy, which is inconsistent.
+
+Error message example 3:
+
+```text
+Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: 
+[ratify-constraint] Subject failed verification: huishwabbit1.azurecr.io/
+test8may24@sha256:c780036bc8a6f577910bf01151013aaa18e255057a1653c76d8f3572aa3f6ff6
+```
+
+The policy engine, for instance, Gatekeeper, has produced the above error message using a constraint template supplied by Ratify. It is the responsibility of the policy engine to tailor the constraint template for proper error messages to their requirements, however, it requires Ratify to provided useful verification reports as data inputs. In this example, the error message is not clear to users regarding the meaning of the term `Subject`, and fails to specify the context of failure, such as whether it was related to signature verification or SBOM verification or other verifications. Additionally, reasons behind the error were not provided. Furthermore, users may not be able to locate this error in the complete K8s logs to view more logs during error happened, because only artifact digest was shown and it is not enough to pinpoint the exact error in K8s logs.
+
+Further findings covering a range of cases such as Key Management Provider (KMP), Store, Verifier, Policy configuration, access control, and signature verification issues are recorded at [Ratify Error Handling Scenarios.md](../discussion/Ratify%20Error%20Handling%20Scenarios.md)
+
+In summary, the areas that need enhancement include:
+
+- Error messages similar to the first example will appear in Ratify logs. These may be found in the logs of Ratify Pods if Ratify is set up as a Kubernetes service, or they might be output by the Ratify CLI. The primary concerns include excessive nested errors, lacking error context, and not user-friendly error descriptions.
+- Error messages contained within `verifierReports` that are sent back to the policy engine, as seen in the second example. These error messages share issues similar to those mentioned for the first example. The policy engine can customize their Rego policies using the messages inside the `verifierReports` to return to users in the logs or UI of the policy engine.
+- Ratify failed to provide sufficient information for the policy engine to generate error messages, which is demonstrated in the third example.
+
+The document aims to provide solutions and guidelines to improve error messages.
+
+## Scenarios
+
+### Error messages displayed in the Ratify logs
+
+Alice works as a DevOps engineer at Contoso. She set up tasks to deploy containerized apps into Kubernetes clusters. The cluster is assigned with the policy to deny the deployment of images that don't pass policy evaluation including verification of signature, SBOM, vulnerability reports and other image metadata. Alice knows that behind the scene, it is the Ratify conduct the verification and returned results as reports to the policy engine. When policy evaluation fails, Alice sees clear and actionable error messages in Ratify logs. The error messages contain concise error descriptions, error reasons and error recommendations, allowing her to act on errors promptly.
+
+### Error messages displayed in verification reports used by the policy engine
+
+Bob is a software engineer on Contoso's Policy team, writing policies used during admissions in Kubernetes clusters. These policies evaluate images based on verifier reports generated by Ratify. If policy evaluation fails, Ratify sends back the reports with error messages to the policy engine. The reports, in JSON format, provide structured error messages that Bob utilizes to create clear and actionable error messages for the policy engine. These messages include concise error descriptions, error reasons, and error recommendations, allowing policy users to act on errors promptly.
+
+### Error messages returned by Ratify CLI commands
+
+Gina is a software engineer on the CI/CD team at Contoso, where she creates pipeline tasks incorporating Ratify CLI commands to assess artifacts according to policies. Should a policy check not pass, the corresponding artifacts are prevented from progressing in the pipeline. When policy evaluation fails, Gina sees concise, clear and actionable error messages returned by Ratify CLI commands. The error messages contain concise error descriptions, error reasons and error recommendations, allowing her to act on errors promptly.
+
+## Proposed solutions
+
+We won’t create new error message guidelines; instead, we’ll refer to the existing ones. [Azure CLI Error Handling Guidelines](https://github.com/Azure/azure-cli/blob/dev/doc/error_handling_guidelines.md#error-message) outlined a general pattern for error messages, consisting of:
+
+1. __What the error is.__
+2. __Why it happens.__
+3. __What users need to do to fix it.__
+
+The proposed improvements for Ratify error messages adhere to this general pattern and the detailed DOs and DON'Ts provided in the guidelines. The error message will also include an error code. Since Ratify already supports a list of error codes, these can be used to search for remediation in the troubleshooting guide. For example, search error code `CERT_INVALID` in [the troubleshooting guide](https://ratify.dev/docs/troubleshoot/key-management-provider/kmp-tsg#cert_invalid).
+
+The recommended format for an error message in the Ratify log is as following.
+
+```text
+"<Error Code>: <Error Description>: <Error Reason>: <Remediation>"
+```
+
+For the error messages displayed in `verifierReports`, it is recommended to add two new optional fields `errorReason` and `remediation`, which will be used when the field `isSuccess` is set to `false`:
+
+```text
+"verifierReports": [
+  {
+        "subject": "<Digest of the Artifact>"
+        "isSuccess": false,
+        "message": "<Error Description>",
+        "errorReason": "<Error Reason>",
+        "remediation": "<Remediation>"
+  }
+]
+```
+
+## Examples
+
+### Error messages displayed in the Ratify logs or returned by Ratify CLI commands
+
+For the above first example, the error message in the Ratify log can be improved to:
+
+```text
+REPOSITORY_OPERATION_FAILURE: Failed to resolve the artifact descriptor: HEAD "https://roacr.azurecr.io/v2/net-monitor/manifests/v2": GET "https://roacr.azurecr.io/oauth2/token?
+scope=repository%3Anet-monitor%3Apull&service=roacr.azurecr.io": response status code 401: unauthorized: 
+authentication required, visit https://aka.ms/acr/authorization for more information.
+```
+
+### Error messages displayed in `verifierReports`
+
+For the second example, the error message can be improved to:
+
+```text
+"verifierReports": [
+  {
+        "subject": "docker.io/library/hello-world@sha256:1408fec50309afee38f3535383f5b09419e6dc0925bc69891e79d84cc4cdce6",
+        "isSuccess": false,
+        "message": "NO_VERIFIER_REPORT: Failed to verify artifact docker.io/library/hello-world@sha256:1408fec50309afee38f3535383f5b09419e6dc0925bc69891e79d84cc4cdce6: 
+        "errorReason": "No signature is found or wrong configuration"
+        "remediation": "Please either sign the artifact or configure verifiers for signature verification. Learn more at https://ratify.dev/docs/plugins/verifier/notation."
+  }
+]
+```
+
+> This link https://ratify.dev/docs/plugins/verifier/notation is used as an example to illustrate the improvements. The link should vary depending on the particular error encountered.
+
+## References
+
+- [Azure CLI Error Handling Guidelines](https://github.com/Azure/azure-cli/blob/dev/doc/error_handling_guidelines.md)
+- [ORAS CLI Error Handling and Message Guideline](https://github.com/oras-project/oras/blob/v1.2.0/docs/proposals/error-handling-guideline.md)
diff --git a/errors/types.go b/errors/types.go
index 3a211c8..cb5e031 100644
--- a/errors/types.go
+++ b/errors/types.go
@@ -60,15 +60,16 @@ type ErrorCode int
 
 // Error provides a wrapper around ErrorCode with extra Details provided.
 type Error struct {
-	OriginalError error         `json:"originalError,omitempty"`
-	Code          ErrorCode     `json:"code"`
-	Message       string        `json:"message"`
-	Detail        interface{}   `json:"detail,omitempty"`
-	ComponentType ComponentType `json:"componentType,omitempty"`
-	PluginName    string        `json:"pluginName,omitempty"`
-	LinkToDoc     string        `json:"linkToDoc,omitempty"`
-	Stack         string        `json:"stack,omitempty"`
-	Description   string        `json:"description,omitempty"`
+	originalError error
+	code          ErrorCode
+	message       string
+	detail        interface{}
+	componentType ComponentType
+	remediation   string
+	pluginName    string
+	stack         string
+	description   string
+	isRootError   bool // isRootError is true if the originalError is either nil or not an Error type
 }
 
 // ErrorDescriptor provides relevant information about a given error code.
@@ -150,9 +151,9 @@ func (ec ErrorCode) WithComponentType(componentType ComponentType) Error {
 	return newError(ec, ec.Message()).WithComponentType(componentType)
 }
 
-// WithLinkToDoc returns a new Error object with attached link to the documentation.
-func (ec ErrorCode) WithLinkToDoc(link string) Error {
-	return newError(ec, ec.Message()).WithLinkToDoc(link)
+// WithRemediation returns a new Error object with remediation.
+func (ec ErrorCode) WithRemediation(link string) Error {
+	return newError(ec, ec.Message()).WithRemediation(link)
 }
 
 func (ec ErrorCode) WithDescription() Error {
@@ -165,27 +166,29 @@ func (ec ErrorCode) WithPluginName(pluginName string) Error {
 }
 
 // NewError returns a new Error object.
-func (ec ErrorCode) NewError(componentType ComponentType, pluginName, link string, err error, detail interface{}, printStackTrace bool) Error {
+func (ec ErrorCode) NewError(componentType ComponentType, pluginName, remediation string, err error, detail interface{}, printStackTrace bool) Error {
 	stack := ""
 	if printStackTrace {
 		stack = getStackTrace()
 	}
 	return Error{
-		Code:          ec,
-		Message:       ec.Message(),
-		OriginalError: err,
-		Detail:        detail,
-		ComponentType: componentType,
-		PluginName:    pluginName,
-		LinkToDoc:     link,
-		Stack:         stack,
+		code:          ec,
+		message:       ec.Message(),
+		originalError: err,
+		detail:        detail,
+		componentType: componentType,
+		pluginName:    pluginName,
+		remediation:   remediation,
+		stack:         stack,
+		isRootError:   err == nil || !errors.As(err, &Error{}),
 	}
 }
 
 func newError(code ErrorCode, message string) Error {
 	return Error{
-		Code:    code,
-		Message: message,
+		code:        code,
+		message:     message,
+		isRootError: true,
 	}
 }
 
@@ -193,90 +196,114 @@ func newError(code ErrorCode, message string) Error {
 func (e Error) Is(target error) bool {
 	t := &Error{}
 	if errors.As(target, t) {
-		return e.Code.ErrorCode() == t.Code.ErrorCode()
+		return e.code.ErrorCode() == t.code.ErrorCode()
 	}
 	return false
 }
 
 // ErrorCode returns the ID/Value of this Error
 func (e Error) ErrorCode() ErrorCode {
-	return e.Code
+	return e.code
 }
 
 // Unwrap returns the original error
 func (e Error) Unwrap() error {
-	return e.OriginalError
+	return e.originalError
 }
 
 // Error returns a human readable representation of the error.
+// An Error message includes the error code, detail from nested errors, root cause and remediation, all separated by ": ".
 func (e Error) Error() string {
-	var errStr string
-	if e.OriginalError != nil {
-		errStr += fmt.Sprintf("Original Error: (%s), ", e.OriginalError.Error())
+	err, details := e.getRootError()
+	if err.detail != nil {
+		details = append(details, fmt.Sprintf("%s", err.detail))
 	}
-
-	errStr += fmt.Sprintf("Error: %s, Code: %s", e.Message, e.Code.String())
-
-	if e.PluginName != "" {
-		errStr += fmt.Sprintf(", Plugin Name: %s", e.PluginName)
+	if err.originalError != nil {
+		details = append(details, err.originalError.Error())
 	}
 
-	if e.ComponentType != "" {
-		errStr += fmt.Sprintf(", Component Type: %s", e.ComponentType)
+	if err.remediation != "" {
+		details = append(details, err.remediation)
 	}
+	return fmt.Sprintf("%s: %s", err.ErrorCode().Descriptor().Value, strings.Join(details, ": "))
+}
 
-	if e.LinkToDoc != "" {
-		errStr += fmt.Sprintf(", Documentation: %s", e.LinkToDoc)
+// GetDetail returns details from all nested errors.
+func (e Error) GetDetail() string {
+	err, details := e.getRootError()
+	if err.originalError != nil && err.detail != nil {
+		details = append(details, fmt.Sprintf("%s", err.detail))
 	}
 
-	if e.Detail != nil {
-		errStr += fmt.Sprintf(", Detail: %v", e.Detail)
-	}
+	return strings.Join(details, ": ")
+}
 
-	if e.Description != "" {
-		errStr += fmt.Sprintf(", Description: %v", e.Description)
+// GetErrorReason returns the root cause of the error.
+func (e Error) GetErrorReason() string {
+	err, _ := e.getRootError()
+	if err.originalError != nil {
+		return err.originalError.Error()
 	}
+	return fmt.Sprintf("%s", err.detail)
+}
 
-	if e.Stack != "" {
-		errStr += fmt.Sprintf(", Stack trace: %s", e.Stack)
-	}
+// GetRemiation returns the remediation of the root error.
+func (e Error) GetRemediation() string {
+	err, _ := e.getRootError()
+	return err.remediation
+}
 
-	return errStr
+func (e Error) getRootError() (err Error, details []string) {
+	err = e
+	for !err.isRootError {
+		if err.detail != nil {
+			details = append(details, fmt.Sprintf("%s", err.detail))
+		}
+		var ratifyError Error
+		if errors.As(err.originalError, &ratifyError) {
+			err = ratifyError
+		} else {
+			// break is unnecessary, but added for safety
+			break
+		}
+	}
+	return err, details
 }
 
 // WithDetail will return a new Error, based on the current one, but with
 // some Detail info added
 func (e Error) WithDetail(detail interface{}) Error {
-	e.Detail = detail
+	e.detail = detail
 	return e
 }
 
 // WithPluginName returns a new Error object with pluginName set.
 func (e Error) WithPluginName(pluginName string) Error {
-	e.PluginName = pluginName
+	e.pluginName = pluginName
 	return e
 }
 
 // WithComponentType returns a new Error object with ComponentType set.
 func (e Error) WithComponentType(componentType ComponentType) Error {
-	e.ComponentType = componentType
+	e.componentType = componentType
 	return e
 }
 
 // WithError returns a new Error object with original error.
 func (e Error) WithError(err error) Error {
-	e.OriginalError = err
+	e.originalError = err
+	e.isRootError = err == nil || !errors.As(err, &Error{})
 	return e
 }
 
-// WithLinkToDoc returns a new Error object attached with link to documentation.
-func (e Error) WithLinkToDoc(link string) Error {
-	e.LinkToDoc = link
+// WithRemediation returns a new Error object attached with remediation.
+func (e Error) WithRemediation(remediation string) Error {
+	e.remediation = remediation
 	return e
 }
 
 func (e Error) WithDescription() Error {
-	e.Description = e.Code.Description()
+	e.description = e.code.Description()
 	return e
 }
 
diff --git a/go.mod b/go.mod
index 1c03ba9..3427882 100644
--- a/go.mod
+++ b/go.mod
@@ -21,12 +21,12 @@ require (
 	github.com/dapr/go-sdk v1.8.0
 	github.com/dgraph-io/ristretto v0.1.1
 	github.com/distribution/reference v0.5.0
-	github.com/docker/cli v26.1.5+incompatible
+	github.com/docker/cli v27.1.1+incompatible
 	github.com/docker/distribution v2.8.3+incompatible
 	github.com/fsnotify/fsnotify v1.7.0
 	github.com/go-jose/go-jose/v3 v3.0.3
 	github.com/golang/protobuf v1.5.4
-	github.com/google/go-containerregistry v0.20.1
+	github.com/google/go-containerregistry v0.20.2
 	github.com/gorilla/mux v1.8.1
 	github.com/notaryproject/notation-core-go v1.0.3
 	github.com/notaryproject/notation-go v1.0.1
@@ -91,7 +91,6 @@ require (
 	github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect
 	github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect
 	github.com/dimchansky/utfbom v1.1.1 // indirect
-	github.com/docker/docker v26.1.5+incompatible // indirect
 	github.com/emicklei/go-restful/v3 v3.11.0 // indirect
 	github.com/evanphx/json-patch v4.12.0+incompatible // indirect
 	github.com/evanphx/json-patch/v5 v5.6.0 // indirect
@@ -131,7 +130,7 @@ require (
 	github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 // indirect
 	github.com/Azure/go-autorest v14.2.0+incompatible // indirect
 	github.com/Azure/go-autorest/autorest v0.11.29
-	github.com/Azure/go-autorest/autorest/adal v0.9.24
+	github.com/Azure/go-autorest/autorest/adal v0.9.24 // indirect
 	github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
 	github.com/Azure/go-autorest/logger v0.2.1 // indirect
 	github.com/Azure/go-autorest/tracing v0.6.0 // indirect
diff --git a/go.sum b/go.sum
index 320634e..d02ee02 100644
--- a/go.sum
+++ b/go.sum
@@ -238,12 +238,10 @@ github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi
 github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
 github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
 github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
-github.com/docker/cli v26.1.5+incompatible h1:NxXGSdz2N+Ibdaw330TDO3d/6/f7MvHuiMbuFaIQDTk=
-github.com/docker/cli v26.1.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
+github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE=
+github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
 github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
 github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
-github.com/docker/docker v26.1.5+incompatible h1:NEAxTwEjxV6VbBMBoGG3zPqbiJosIApZjxlbrG9q3/g=
-github.com/docker/docker v26.1.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8=
 github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@@ -375,8 +373,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/go-containerregistry v0.20.1 h1:eTgx9QNYugV4DN5mz4U8hiAGTi1ybXn0TPi4Smd8du0=
-github.com/google/go-containerregistry v0.20.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI=
+github.com/google/go-containerregistry v0.20.2 h1:B1wPJ1SN/S7pB+ZAimcciVD+r+yV/l/DSArMxlbwseo=
+github.com/google/go-containerregistry v0.20.2/go.mod h1:z38EKdKh4h7IP2gSfUUqEvalZBqs6AoLeWfUy34nQC8=
 github.com/google/go-github/v55 v55.0.0 h1:4pp/1tNMB9X/LuAhs5i0KQAE40NmiR/y6prLNb9x9cg=
 github.com/google/go-github/v55 v55.0.0/go.mod h1:JLahOTA1DnXzhxEymmFF5PP2tSS9JVNj68mSZNDwskA=
 github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
diff --git a/httpserver/Dockerfile b/httpserver/Dockerfile
index f0bf06d..48f4caf 100644
--- a/httpserver/Dockerfile
+++ b/httpserver/Dockerfile
@@ -11,7 +11,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-FROM --platform=$BUILDPLATFORM golang:1.22@sha256:86a3c48a61915a8c62c0e1d7594730399caa3feb73655dfe96c7bc17710e96cf as builder
+FROM --platform=$BUILDPLATFORM golang:1.22@sha256:2bd56f00ff47baf33e64eae7996b65846c7cb5e0a46e0a882ef179fd89654afa as builder
 
 ARG TARGETPLATFORM
 ARG TARGETOS
diff --git a/httpserver/types.go b/httpserver/types.go
index f307bc5..8e3655c 100644
--- a/httpserver/types.go
+++ b/httpserver/types.go
@@ -22,9 +22,11 @@ import (
 
 const (
 	VerificationResultVersion = "0.1.0"
+	ResultVersion0_2_0        = "0.2.0"
 	// Starting from this version, the verification result can be
 	// evaluated by Ratify embedded OPA engine.
 	ResultVersionSupportingRego = "1.0.0"
+	ResultVersion1_1_0          = "1.1.0"
 )
 
 type VerificationResponse struct {
@@ -34,9 +36,9 @@ type VerificationResponse struct {
 }
 
 func fromVerifyResult(res types.VerifyResult, policyType string) VerificationResponse {
-	version := VerificationResultVersion
+	version := ResultVersion0_2_0
 	if policyType == pt.RegoPolicy {
-		version = ResultVersionSupportingRego
+		version = ResultVersion1_1_0
 	}
 	return VerificationResponse{
 		Version:         version,
diff --git a/pkg/certificateprovider/azurekeyvault/auth.go b/pkg/certificateprovider/azurekeyvault/auth.go
index 4abbbe8..b347000 100644
--- a/pkg/certificateprovider/azurekeyvault/auth.go
+++ b/pkg/certificateprovider/azurekeyvault/auth.go
@@ -28,7 +28,6 @@ import (
 	"github.com/ratify-project/ratify/pkg/utils/azureauth"
 
 	"github.com/Azure/go-autorest/autorest"
-	"github.com/Azure/go-autorest/autorest/adal"
 )
 
 const (
@@ -37,7 +36,6 @@ const (
 	// the format for expires_on in UTC without AM/PM
 	expiresOnDateFormat = "1/2/2006 15:04:05 +00:00"
 
-	tokenTypeBearer = "Bearer"
 	// For Azure AD Workload Identity, the audience recommended for use is
 	// "api://AzureADTokenExchange"
 	DefaultTokenAudience = "api://AzureADTokenExchange" //nolint
@@ -64,13 +62,7 @@ func getAuthorizerForWorkloadIdentity(ctx context.Context, tenantID, clientID, r
 		return nil, fmt.Errorf("failed to acquire token: %w", err)
 	}
 
-	token := adal.Token{
-		AccessToken: result.AccessToken,
-		Resource:    resource,
-		Type:        tokenTypeBearer,
-	}
-	token.ExpiresOn, err = parseExpiresOn(result.ExpiresOn.UTC().Local().Format(expiresOnDateFormat))
-	if err != nil {
+	if _, err = parseExpiresOn(result.ExpiresOn.UTC().Local().Format(expiresOnDateFormat)); err != nil {
 		return nil, fmt.Errorf("failed to parse expires_on: %w", err)
 	}
 
diff --git a/pkg/executor/core/executor.go b/pkg/executor/core/executor.go
index ef9d272..2cc90b0 100644
--- a/pkg/executor/core/executor.go
+++ b/pkg/executor/core/executor.go
@@ -175,19 +175,22 @@ func (executor Executor) verifyReferenceForJSONPolicy(ctx context.Context, subje
 		if verifier.CanVerify(ctx, referenceDesc) {
 			verifierStartTime := time.Now()
 			verifyResult, err := verifier.Verify(ctx, subjectRef, referenceDesc, referrerStore)
-			verifyResult.Subject = subjectRef.String()
 			if err != nil {
 				verifyResult = vr.VerifierResult{
-					IsSuccess: false,
-					Name:      verifier.Name(),
-					Type:      verifier.Type(),
-					Message:   errors.ErrorCodeVerifyReferenceFailure.NewError(errors.Verifier, verifier.Name(), errors.EmptyLink, err, nil, errors.HideStackTrace).Error()}
+					IsSuccess:    false,
+					Name:         verifier.Name(),
+					Type:         verifier.Type(),
+					VerifierName: verifier.Name(),
+					VerifierType: verifier.Type(),
+					Message:      errors.ErrorCodeVerifyReferenceFailure.NewError(errors.Verifier, verifier.Name(), errors.EmptyLink, err, nil, errors.HideStackTrace).Error()}
 			}
 
 			if len(verifier.GetNestedReferences()) > 0 {
 				executor.addNestedVerifierResult(ctx, referenceDesc, subjectRef, &verifyResult)
 			}
 
+			verifyResult.Subject = subjectRef.String()
+			verifyResult.ReferenceDigest = referenceDesc.Digest.String()
 			verifyResult.ArtifactType = referenceDesc.ArtifactType
 			verifyResults = append(verifyResults, verifyResult)
 			isSuccess = verifyResult.IsSuccess
@@ -227,10 +230,12 @@ func (executor Executor) verifyReferenceForRegoPolicy(ctx context.Context, subje
 			verifierResult, err := verifier.Verify(errCtx, subjectRef, referenceDesc, referrerStore)
 			if err != nil {
 				verifierReport = vt.VerifierResult{
-					IsSuccess: false,
-					Name:      verifier.Name(),
-					Type:      verifier.Type(),
-					Message:   errors.ErrorCodeVerifyReferenceFailure.NewError(errors.Verifier, verifier.Name(), errors.EmptyLink, err, nil, errors.HideStackTrace).Error()}
+					IsSuccess:    false,
+					Name:         verifier.Name(), // Deprecating Name in next release, reference to VerifierName instead.
+					Type:         verifier.Type(), // Deprecating Type in next release, reference to VerifierType instead.
+					VerifierName: verifier.Name(),
+					VerifierType: verifier.Type(),
+					Message:      errors.ErrorCodeVerifyReferenceFailure.NewError(errors.Verifier, verifier.Name(), errors.EmptyLink, err, nil, errors.HideStackTrace).Error()}
 			} else {
 				verifierReport = vt.NewVerifierResult(verifierResult)
 			}
diff --git a/pkg/keymanagementprovider/azurekeyvault/auth.go b/pkg/keymanagementprovider/azurekeyvault/auth.go
index d380beb..cd4d248 100644
--- a/pkg/keymanagementprovider/azurekeyvault/auth.go
+++ b/pkg/keymanagementprovider/azurekeyvault/auth.go
@@ -28,7 +28,6 @@ import (
 	"github.com/ratify-project/ratify/pkg/utils/azureauth"
 
 	"github.com/Azure/go-autorest/autorest"
-	"github.com/Azure/go-autorest/autorest/adal"
 )
 
 const (
@@ -37,7 +36,6 @@ const (
 	// the format for expires_on in UTC without AM/PM
 	expiresOnDateFormat = "1/2/2006 15:04:05 +00:00"
 
-	tokenTypeBearer = "Bearer"
 	// For Azure AD Workload Identity, the audience recommended for use is
 	// "api://AzureADTokenExchange"
 	DefaultTokenAudience = "api://AzureADTokenExchange" //nolint
@@ -64,13 +62,7 @@ func getAuthorizerForWorkloadIdentity(ctx context.Context, tenantID, clientID, r
 		return nil, fmt.Errorf("failed to acquire token: %w", err)
 	}
 
-	token := adal.Token{
-		AccessToken: result.AccessToken,
-		Resource:    resource,
-		Type:        tokenTypeBearer,
-	}
-	token.ExpiresOn, err = parseExpiresOn(result.ExpiresOn.UTC().Local().Format(expiresOnDateFormat))
-	if err != nil {
+	if _, err = parseExpiresOn(result.ExpiresOn.UTC().Local().Format(expiresOnDateFormat)); err != nil {
 		return nil, fmt.Errorf("failed to parse expires_on: %w", err)
 	}
 
diff --git a/pkg/verifier/api.go b/pkg/verifier/api.go
index dcc5496..8f96955 100644
--- a/pkg/verifier/api.go
+++ b/pkg/verifier/api.go
@@ -25,14 +25,19 @@ import (
 
 // VerifierResult describes the result of verifying a reference manifest for a subject
 type VerifierResult struct { //nolint:revive // ignore linter to have unique type name
-	Subject       string           `json:"subject,omitempty"`
-	IsSuccess     bool             `json:"isSuccess"`
-	Name          string           `json:"name,omitempty"`
-	Type          string           `json:"type,omitempty"`
-	Message       string           `json:"message,omitempty"`
-	Extensions    interface{}      `json:"extensions,omitempty"`
-	NestedResults []VerifierResult `json:"nestedResults,omitempty"`
-	ArtifactType  string           `json:"artifactType,omitempty"`
+	Subject         string           `json:"subject,omitempty"`
+	IsSuccess       bool             `json:"isSuccess"`
+	Name            string           `json:"name,omitempty"`
+	VerifierName    string           `json:"verifierName,omitempty"`
+	Type            string           `json:"type,omitempty"`
+	VerifierType    string           `json:"verifierType,omitempty"`
+	Message         string           `json:"message,omitempty"`
+	ErrorReason     string           `json:"errorReason,omitempty"`
+	Extensions      interface{}      `json:"extensions,omitempty"`
+	NestedResults   []VerifierResult `json:"nestedResults,omitempty"`
+	ArtifactType    string           `json:"artifactType,omitempty"`
+	ReferenceDigest string           `json:"referenceDigest,omitempty"`
+	Remediation     string           `json:"remediation,omitempty"`
 }
 
 // ReferenceVerifier is an interface that defines methods to verify a reference
diff --git a/pkg/verifier/cosign/cosign.go b/pkg/verifier/cosign/cosign.go
index 40a67fb..a5a3bc4 100644
--- a/pkg/verifier/cosign/cosign.go
+++ b/pkg/verifier/cosign/cosign.go
@@ -285,11 +285,13 @@ func (v *cosignVerifier) verifyInternal(ctx context.Context, subjectReference co
 
 	if hasValidSignature {
 		return verifier.VerifierResult{
-			Name:       v.name,
-			Type:       v.verifierType,
-			IsSuccess:  true,
-			Message:    "cosign verification success. valid signatures found. please refer to extensions field for verifications performed.",
-			Extensions: Extension{SignatureExtension: sigExtensions, TrustPolicy: trustPolicy.GetName()},
+			Name:         v.name,
+			Type:         v.verifierType,
+			VerifierName: v.name,
+			VerifierType: v.verifierType,
+			IsSuccess:    true,
+			Message:      "Verification success. Valid signatures found. Please refer to extensions field for verifications performed.",
+			Extensions:   Extension{SignatureExtension: sigExtensions, TrustPolicy: trustPolicy.GetName()},
 		}, nil
 	}
 
@@ -396,11 +398,13 @@ func (v *cosignVerifier) verifyLegacy(ctx context.Context, subjectReference comm
 
 	if len(signatures) > 0 {
 		return verifier.VerifierResult{
-			Name:       v.name,
-			Type:       v.verifierType,
-			IsSuccess:  true,
-			Message:    "cosign verification success. valid signatures found",
-			Extensions: LegacyExtension{SignatureExtension: sigExtensions},
+			Name:         v.name,
+			Type:         v.verifierType,
+			VerifierName: v.name,
+			VerifierType: v.verifierType,
+			IsSuccess:    true,
+			Message:      "Verification success. Valid signatures found",
+			Extensions:   LegacyExtension{SignatureExtension: sigExtensions},
 		}, nil
 	}
 
@@ -482,10 +486,13 @@ func staticLayerOpts(desc imgspec.Descriptor) ([]static.Option, error) {
 // ErrorToVerifyResult returns a verifier result with the error message and isSuccess set to false
 func errorToVerifyResult(name string, verifierType string, err error) verifier.VerifierResult {
 	return verifier.VerifierResult{
-		IsSuccess: false,
-		Name:      name,
-		Type:      verifierType,
-		Message:   errors.Wrap(err, "cosign verification failed").Error(),
+		IsSuccess:    false,
+		Name:         name,
+		Type:         verifierType,
+		VerifierName: name,
+		VerifierType: verifierType,
+		Message:      "Verification failed",
+		ErrorReason:  err.Error(),
 	}
 }
 
diff --git a/pkg/verifier/notation/notation.go b/pkg/verifier/notation/notation.go
index 6fb0ccf..1a43493 100644
--- a/pkg/verifier/notation/notation.go
+++ b/pkg/verifier/notation/notation.go
@@ -175,11 +175,13 @@ func (v *notationPluginVerifier) Verify(ctx context.Context,
 	}
 
 	return verifier.VerifierResult{
-		Name:       v.name,
-		Type:       v.verifierType,
-		IsSuccess:  true,
-		Message:    "signature verification success",
-		Extensions: extensions,
+		Name:         v.name,
+		Type:         v.verifierType,
+		VerifierName: v.name,
+		VerifierType: v.verifierType,
+		IsSuccess:    true,
+		Message:      "Signature verification success",
+		Extensions:   extensions,
 	}, nil
 }
 
diff --git a/pkg/verifier/types/types.go b/pkg/verifier/types/types.go
index 1c12dc0..2eed0a6 100644
--- a/pkg/verifier/types/types.go
+++ b/pkg/verifier/types/types.go
@@ -47,11 +47,14 @@ const (
 
 // VerifierResult describes the verification result returned from the verifier plugin
 type VerifierResult struct {
-	IsSuccess  bool        `json:"isSuccess"`
-	Message    string      `json:"message"`
-	Name       string      `json:"name"`
-	Type       string      `json:"type,omitempty"`
-	Extensions interface{} `json:"extensions"`
+	IsSuccess    bool        `json:"isSuccess"`
+	Message      string      `json:"message"`
+	ErrorReason  string      `json:"errorReason,omitempty"`
+	Name         string      `json:"name"`
+	VerifierName string      `json:"verifierName,omitempty"`
+	Type         string      `json:"type,omitempty"`
+	VerifierType string      `json:"verifierType,omitempty"`
+	Extensions   interface{} `json:"extensions"`
 }
 
 // GetVerifierResult encodes the given JSON data into verify result object
@@ -61,11 +64,13 @@ func GetVerifierResult(result []byte) (*verifier.VerifierResult, error) {
 		return nil, err
 	}
 	return &verifier.VerifierResult{
-		IsSuccess:  vResult.IsSuccess,
-		Message:    vResult.Message,
-		Name:       vResult.Name,
-		Type:       vResult.Type,
-		Extensions: vResult.Extensions,
+		IsSuccess:    vResult.IsSuccess,
+		Message:      vResult.Message,
+		Name:         vResult.Name,
+		Type:         vResult.Type,
+		VerifierName: vResult.Name,
+		VerifierType: vResult.Type,
+		Extensions:   vResult.Extensions,
 	}, nil
 }
 
@@ -78,9 +83,12 @@ func WriteVerifyResultResult(result *verifier.VerifierResult, w io.Writer) error
 // verifier.VerifierResult.
 func NewVerifierResult(result verifier.VerifierResult) VerifierResult {
 	return VerifierResult{
-		IsSuccess:  result.IsSuccess,
-		Message:    result.Message,
-		Name:       result.Name,
-		Extensions: result.Extensions,
+		IsSuccess:    result.IsSuccess,
+		Message:      result.Message,
+		Name:         result.Name,
+		Type:         result.Type,
+		VerifierName: result.VerifierName,
+		VerifierType: result.VerifierType,
+		Extensions:   result.Extensions,
 	}
 }


Test patch:
diff --git a/.github/workflows/e2e-aks.yml b/.github/workflows/e2e-aks.yml
index 6042031..30dced1 100644
--- a/.github/workflows/e2e-aks.yml
+++ b/.github/workflows/e2e-aks.yml
@@ -28,7 +28,7 @@ jobs:
       contents: read
     steps:
       - name: Harden Runner
-        uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
+        uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
         with:
           egress-policy: audit
 
@@ -64,7 +64,7 @@ jobs:
           make e2e-aks KUBERNETES_VERSION=${{ inputs.k8s_version }} GATEKEEPER_VERSION=${{ inputs.gatekeeper_version }} TENANT_ID=${{ secrets.AZURE_TENANT_ID }} AZURE_SP_OBJECT_ID=${{ secrets.AZURE_SP_OBJECT_ID }}
 
       - name: Upload artifacts
-        uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5
+        uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
         if: ${{ always() }}
         with:
           name: e2e-logs-aks-${{ inputs.k8s_version }}-${{ inputs.gatekeeper_version }}
diff --git a/.github/workflows/e2e-cli.yml b/.github/workflows/e2e-cli.yml
index 87e9980..324de26 100644
--- a/.github/workflows/e2e-cli.yml
+++ b/.github/workflows/e2e-cli.yml
@@ -14,7 +14,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Harden Runner
-        uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
+        uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
         with:
           egress-policy: audit
 
@@ -35,7 +35,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Harden Runner
-        uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
+        uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
         with:
           egress-policy: audit
 
@@ -64,7 +64,7 @@ jobs:
       contents: read
     steps:
       - name: Harden Runner
-        uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
+        uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
         with:
           egress-policy: audit
 
@@ -92,7 +92,7 @@ jobs:
       runs-on: ubuntu-latest
       steps:
       - name: Harden Runner
-        uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
+        uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
         with:
           egress-policy: audit
 
diff --git a/.github/workflows/e2e-k8s.yml b/.github/workflows/e2e-k8s.yml
index a8f5ee4..06d97d5 100644
--- a/.github/workflows/e2e-k8s.yml
+++ b/.github/workflows/e2e-k8s.yml
@@ -26,7 +26,7 @@ jobs:
       contents: read
     steps:
       - name: Harden Runner
-        uses: step-security/harden-runner@0d381219ddf674d61a7572ddd19d7941e271515c # v2.9.0
+        uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
         with:
           egress-policy: audit
 
@@ -65,7 +65,7 @@ jobs:
           kubectl logs -n gatekeeper-system -l app=ratify --tail=-1 > logs-ratify-preinstall-${{ matrix.KUBERNETES_VERSION }}-${{ matrix.GATEKEEPER_VERSION }}-rego-policy.json
           kubectl logs -n gatekeeper-system -l app.kubernetes.io/name=ratify --tail=-1 > logs-ratify-${{ matrix.KUBERNETES_VERSION }}-${{ matrix.GATEKEEPER_VERSION }}-rego-policy.json
       - name: Upload artifacts
-        uses: actions/upload-artifact@89ef406dd8d7e03cfd12d9e0a4a378f454709029 # v4.3.5
+        uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
         if: ${{ always() }}
         with:
           name: e2e-logs-${{ inputs.k8s_version }}-${{ inputs.gatekeeper_version }}
diff --git a/cmd/ratify/cmd/cmd_test.go b/cmd/ratify/cmd/cmd_test.go
index 49149dc..9016198 100644
--- a/cmd/ratify/cmd/cmd_test.go
+++ b/cmd/ratify/cmd/cmd_test.go
@@ -50,8 +50,8 @@ func TestDiscover(t *testing.T) {
 
 	// TODO: make ratify cli more unit testable
 	// unit test should not need to resolve real image
-	if !strings.Contains(err.Error(), "referrer store failure") {
-		t.Errorf("error expected")
+	if !strings.Contains(err.Error(), "REFERRER_STORE_FAILURE") {
+		t.Errorf("expected containing: %s, but got: %s", "REFERRER_STORE_FAILURE", err.Error())
 	}
 }
 
diff --git a/errors/types_test.go b/errors/types_test.go
index 5e97400..a1f66e3 100644
--- a/errors/types_test.go
+++ b/errors/types_test.go
@@ -22,26 +22,31 @@ import (
 )
 
 const (
-	testGroup         = "test-group"
-	testValue         = "test-value"
-	testMessage       = "test-message"
-	testDescription   = "test-description"
-	testDetail        = "test-detail"
-	testComponentType = "test-component-type"
-	testLink          = "test-link"
-	testPluginName    = "test-plugin-name"
-	testErrorString   = "Original Error: (Error: , Code: UNKNOWN), Error: test-message, Code: test-value, Plugin Name: test-plugin-name, Component Type: test-component-type, Documentation: test-link, Detail: test-detail"
-	nonexistentEC     = 2000
+	testGroup          = "test-group"
+	testErrCode1       = "TEST_ERROR_CODE_1"
+	testErrCode2       = "TEST_ERROR_CODE_2"
+	testMessage        = "test-message"
+	testDescription    = "test-description"
+	testDetail1        = "test-detail-1"
+	testDetail2        = "test-detail-2"
+	testComponentType1 = "test-component-type-1"
+	testComponentType2 = "test-component-type-2"
+	testLink1          = "test-link-1"
+	testLink2          = "test-link-2"
+	testPluginName     = "test-plugin-name"
+	nonexistentEC      = 2000
 )
 
 var (
 	testEC = Register(testGroup, ErrorDescriptor{
-		Value:       testValue,
+		Value:       testErrCode1,
 		Message:     testMessage,
 		Description: testDescription,
 	})
 
-	testEC2 = Register(testGroup, ErrorDescriptor{})
+	testEC2 = Register(testGroup, ErrorDescriptor{
+		Value: testErrCode2,
+	})
 )
 
 func TestErrorCode(t *testing.T) {
@@ -52,8 +57,9 @@ func TestErrorCode(t *testing.T) {
 }
 
 func TestError(t *testing.T) {
-	if testEC.Error() != testValue {
-		t.Fatalf("expected: %s, got: %s", testValue, testEC.Error())
+	expectedStr := "test error code 1"
+	if testEC.Error() != expectedStr {
+		t.Fatalf("expected: %s, got: %s", expectedStr, testEC.Error())
 	}
 }
 
@@ -66,7 +72,7 @@ func TestDescriptor(t *testing.T) {
 		{
 			name:          "existing error code",
 			ec:            testEC,
-			expectedValue: testValue,
+			expectedValue: testErrCode1,
 		},
 		{
 			name:          "nonexistent error code",
@@ -92,9 +98,9 @@ func TestMessage(t *testing.T) {
 }
 
 func TestWithDetail(t *testing.T) {
-	err := testEC.WithDetail(testDetail)
-	if err.Detail != testDetail {
-		t.Fatalf("expected detail: %s, got: %s", testDetail, err.Detail)
+	err := testEC.WithDetail(testDetail1)
+	if err.detail != testDetail1 {
+		t.Fatalf("expected detail: %s, got: %s", testDetail1, err.detail)
 	}
 }
 
@@ -106,35 +112,35 @@ func TestWithError(t *testing.T) {
 }
 
 func TestWithComponentType(t *testing.T) {
-	err := testEC.WithComponentType(testComponentType)
-	if err.ComponentType != testComponentType {
-		t.Fatalf("expected component type: %s, got: %s", testComponentType, err.ComponentType)
+	err := testEC.WithComponentType(testComponentType1)
+	if err.componentType != testComponentType1 {
+		t.Fatalf("expected component type: %s, got: %s", testComponentType1, err.componentType)
 	}
 }
 
-func TestWithLinkToDoc(t *testing.T) {
-	err := testEC.WithLinkToDoc(testLink)
-	if err.LinkToDoc != testLink {
-		t.Fatalf("expected link to doc: %s, got: %s", testLink, err.LinkToDoc)
+func TestWithRemediation(t *testing.T) {
+	err := testEC.WithRemediation(testLink1)
+	if err.remediation != testLink1 {
+		t.Fatalf("expected remediation: %s, got: %s", testLink1, err.remediation)
 	}
 }
 
 func TestWithPluginName(t *testing.T) {
 	err := testEC.WithPluginName(testPluginName)
-	if err.PluginName != testPluginName {
-		t.Fatalf("expected plugin name: %s, got: %s", testPluginName, err.PluginName)
+	if err.pluginName != testPluginName {
+		t.Fatalf("expected plugin name: %s, got: %s", testPluginName, err.pluginName)
 	}
 }
 
 func TestWithDescription(t *testing.T) {
 	err := testEC.WithDescription()
-	if err.Description != testDescription {
-		t.Fatalf("expected description: %s, got: %s", testDescription, err.Description)
+	if err.description != testDescription {
+		t.Fatalf("expected description: %s, got: %s", testDescription, err.description)
 	}
 }
 
 func TestIs(t *testing.T) {
-	err := testEC.WithDetail(testDetail)
+	err := testEC.WithDetail(testDetail1)
 	result := err.Is(err)
 	if !result {
 		t.Fatalf("expected true, got: %v", result)
@@ -149,7 +155,7 @@ func TestIs(t *testing.T) {
 
 func TestError_ErrorCode(t *testing.T) {
 	err := Error{
-		Code: 1,
+		code: 1,
 	}
 	if err.ErrorCode() != 1 {
 		t.Fatalf("expected 1, got: %d", err.ErrorCode())
@@ -168,17 +174,64 @@ func TestIsEmpty(t *testing.T) {
 }
 
 func TestError_Error(t *testing.T) {
-	err := testEC.WithPluginName(testPluginName).WithComponentType(testComponentType).WithLinkToDoc(testLink).WithDetail(testDetail).WithError(Error{}).WithDescription()
-	result := err.Error()
-	if !strings.HasPrefix(result, testErrorString) {
-		t.Fatalf("expected string starts with: %s, but got: %s", testErrorString, result)
+	// Nested errors.
+	rootErr := testEC.NewError(testComponentType1, "", testLink1, errors.New(testMessage), testDetail1, false)
+	err := testEC2.WithPluginName(testPluginName).WithComponentType(testComponentType2).WithRemediation(testLink2).WithDetail(testDetail2).WithError(rootErr)
+
+	expectedErrStr := strings.Join([]string{testErrCode1, testDetail2, testDetail1, testMessage, testLink1}, ": ")
+	if err.Error() != expectedErrStr {
+		t.Fatalf("expected string: %s, but got: %s", expectedErrStr, err.Error())
+	}
+
+	// Single error.
+	err = testEC.WithDetail(testDetail1)
+	expectedErrStr = "TEST_ERROR_CODE_1: test-detail-1"
+	if err.Error() != expectedErrStr {
+		t.Fatalf("expected string: %s, but got: %s", expectedErrStr, err.Error())
+	}
+}
+
+func TestError_GetRootCause(t *testing.T) {
+	// rootErr contains original error.
+	rootErr := testEC.NewError(testComponentType1, "", testLink1, errors.New(testMessage), testDetail1, false)
+	err := testEC.WithPluginName(testPluginName).WithComponentType(testComponentType2).WithRemediation(testLink2).WithDetail(testDetail2).WithError(rootErr)
+
+	if err.GetErrorReason() != testMessage {
+		t.Fatalf("expected root cause: %v, but got: %v", err.GetErrorReason(), testMessage)
+	}
+
+	// rootErr does not contain original error.
+	rootErr = testEC.NewError(testComponentType1, "", testLink1, nil, testDetail1, false)
+	err = testEC.WithPluginName(testPluginName).WithComponentType(testComponentType2).WithRemediation(testLink2).WithDetail(testDetail2).WithError(rootErr)
+
+	if err.GetErrorReason() != testDetail1 {
+		t.Fatalf("expected root cause: %v, but got: %v", err.GetErrorReason(), testDetail1)
+	}
+}
+
+func TestError_GetFullDetails(t *testing.T) {
+	rootErr := testEC.NewError(testComponentType1, "", testLink1, errors.New(testMessage), testDetail1, false)
+	err := testEC.WithPluginName(testPluginName).WithComponentType(testComponentType2).WithRemediation(testLink2).WithDetail(testDetail2).WithError(rootErr)
+
+	expectedDetails := strings.Join([]string{testDetail2, testDetail1}, ": ")
+	if err.GetDetail() != expectedDetails {
+		t.Fatalf("expected full details: %v, but got: %v", expectedDetails, err.GetDetail())
+	}
+}
+
+func TestError_GetRootRemediation(t *testing.T) {
+	rootErr := testEC.NewError(testComponentType1, "", testLink1, errors.New(testMessage), testDetail1, false)
+	err := testEC.WithPluginName(testPluginName).WithComponentType(testComponentType2).WithRemediation(testLink2).WithDetail(testDetail2).WithError(rootErr)
+
+	if err.GetRemediation() != testLink1 {
+		t.Fatalf("expected root remediation: %v, but got: %v", err.GetRemediation(), testLink1)
 	}
 }
 
 func TestNewError(t *testing.T) {
-	err := testEC.NewError(testComponentType, testPluginName, testLink, Error{}, testDetail, false)
+	err := testEC.NewError(testComponentType1, testPluginName, testLink1, Error{}, testDetail1, false)
 
-	if err.ComponentType != testComponentType || err.PluginName != testPluginName || err.LinkToDoc != testLink || err.Detail != testDetail {
-		t.Fatalf("expected component type: %s, plugin name: %s, link to doc: %s, detail: %s, but got: %s, %s, %s, %s", testComponentType, testPluginName, testLink, testDetail, err.ComponentType, err.PluginName, err.LinkToDoc, err.Detail)
+	if err.componentType != testComponentType1 || err.pluginName != testPluginName || err.remediation != testLink1 || err.detail != testDetail1 {
+		t.Fatalf("expected component type: %s, plugin name: %s, link to doc: %s, detail: %s, but got: %s, %s, %s, %s", testComponentType1, testPluginName, testLink1, testDetail1, err.componentType, err.pluginName, err.remediation, err.detail)
 	}
 }
diff --git a/httpserver/types_test.go b/httpserver/types_test.go
index ce2c359..04ed6da 100644
--- a/httpserver/types_test.go
+++ b/httpserver/types_test.go
@@ -32,12 +32,12 @@ func TestFromVerifyResult(t *testing.T) {
 		{
 			name:            "Rego policy",
 			policyType:      pt.RegoPolicy,
-			expectedVersion: "1.0.0",
+			expectedVersion: "1.1.0",
 		},
 		{
 			name:            "Config policy",
 			policyType:      pt.ConfigPolicy,
-			expectedVersion: "0.1.0",
+			expectedVersion: "0.2.0",
 		},
 	}
 
diff --git a/pkg/verifier/cosign/cosign_test.go b/pkg/verifier/cosign/cosign_test.go
index 4ceb03a..fde624a 100644
--- a/pkg/verifier/cosign/cosign_test.go
+++ b/pkg/verifier/cosign/cosign_test.go
@@ -407,8 +407,8 @@ func TestErrorToVerifyResult(t *testing.T) {
 	if verifierResult.Type != "cosign" {
 		t.Errorf("errorToVerifyResult() = %v, want %v", verifierResult.Type, "cosign")
 	}
-	if verifierResult.Message != "cosign verification failed: test error" {
-		t.Errorf("errorToVerifyResult() = %v, want %v", verifierResult.Message, "cosign verification failed: test error")
+	if verifierResult.Message != "Verification failed" {
+		t.Errorf("errorToVerifyResult() = %v, want %v", verifierResult.Message, "Verification failed")
 	}
 }
 
@@ -562,6 +562,7 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry
 		cosignOpts                  cosign.CheckOpts
 		store                       *mocks.MemoryTestStore
 		expectedResultMessagePrefix string
+		expectedErrorReason         string
 		defaultCosignOpts           bool
 	}{
 		{
@@ -569,14 +570,16 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry
 			keys:                        map[PKKey]keymanagementprovider.PublicKey{},
 			getKeysError:                true,
 			store:                       &mocks.MemoryTestStore{},
-			expectedResultMessagePrefix: "cosign verification failed: error",
+			expectedResultMessagePrefix: "Verification failed",
+			expectedErrorReason:         "error",
 		},
 		{
 			name:                        "manifest fetch error",
 			keys:                        map[PKKey]keymanagementprovider.PublicKey{},
 			getKeysError:                false,
 			store:                       &mocks.MemoryTestStore{},
-			expectedResultMessagePrefix: "cosign verification failed: failed to get reference manifest",
+			expectedResultMessagePrefix: "Verification failed",
+			expectedErrorReason:         "failed to get reference manifest: manifest not found",
 		},
 		{
 			name:         "incorrect reference manifest media type error",
@@ -589,7 +592,8 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry
 					},
 				},
 			},
-			expectedResultMessagePrefix: "cosign verification failed: reference manifest is not an image",
+			expectedResultMessagePrefix: "Verification failed",
+			expectedErrorReason:         "reference manifest is not an image",
 		},
 		{
 			name:         "failed subject descriptor fetch",
@@ -602,7 +606,8 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry
 					},
 				},
 			},
-			expectedResultMessagePrefix: "cosign verification failed: failed to create subject hash",
+			expectedResultMessagePrefix: "Verification failed",
+			expectedErrorReason:         "failed to create subject hash: subject not found for sha256:5678",
 		},
 		{
 			name:         "failed to fetch blob",
@@ -628,7 +633,8 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry
 					},
 				},
 			},
-			expectedResultMessagePrefix: "cosign verification failed: failed to get blob content",
+			expectedResultMessagePrefix: "Verification failed",
+			expectedErrorReason:         "failed to get blob content: blob not found",
 		},
 		{
 			name: "invalid key type for AKV",
@@ -659,7 +665,8 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry
 					blobDigest: validSignatureBlob,
 				},
 			},
-			expectedResultMessagePrefix: "cosign verification failed: failed to verify with keys: failed to process AKV signature: unsupported public key type",
+			expectedResultMessagePrefix: "Verification failed",
+			expectedErrorReason:         "failed to verify with keys: failed to process AKV signature: unsupported public key type: *ecdh.PublicKey",
 		},
 		{
 			name: "invalid RSA key size for AKV",
@@ -690,7 +697,8 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry
 					blobDigest: validSignatureBlob,
 				},
 			},
-			expectedResultMessagePrefix: "cosign verification failed: failed to verify with keys: failed to process AKV signature: RSA key check: unsupported key size",
+			expectedResultMessagePrefix: "Verification failed",
+			expectedErrorReason:         "failed to verify with keys: failed to process AKV signature: RSA key check: unsupported key size: 128",
 		},
 		{
 			name: "invalid ECDSA curve type for AKV",
@@ -721,7 +729,8 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry
 					blobDigest: validSignatureBlob,
 				},
 			},
-			expectedResultMessagePrefix: "cosign verification failed: failed to verify with keys: failed to process AKV signature: ECDSA key check: unsupported key curve",
+			expectedResultMessagePrefix: "Verification failed",
+			expectedErrorReason:         "failed to verify with keys: failed to process AKV signature: ECDSA key check: unsupported key curve: P-224",
 		},
 		{
 			name: "valid hash: 256 keysize: 2048 RSA key AKV",
@@ -761,7 +770,7 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry
 					"sha256:6e1ffef2ba058cda5d1aa7ed792cb1e63b4207d8195a469bee1b5fc662cd9b70": []byte(`{"critical":{"identity":{"docker-reference":"artifactstest.azurecr.io/4-15-24/cosign/hello-world"},"image":{"docker-manifest-digest":"sha256:d37ada95d47ad12224c205a938129df7a3e52345828b4fa27b03a98825d1e2e7"},"type":"cosign container image signature"},"optional":null}`),
 				},
 			},
-			expectedResultMessagePrefix: "cosign verification success",
+			expectedResultMessagePrefix: "Verification success",
 		},
 		{
 			name: "valid hash: 256 keysize: 3072 RSA key",
@@ -801,7 +810,7 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry
 					"sha256:6e1ffef2ba058cda5d1aa7ed792cb1e63b4207d8195a469bee1b5fc662cd9b70": []byte(`{"critical":{"identity":{"docker-reference":"artifactstest.azurecr.io/4-15-24/cosign/hello-world"},"image":{"docker-manifest-digest":"sha256:d37ada95d47ad12224c205a938129df7a3e52345828b4fa27b03a98825d1e2e7"},"type":"cosign container image signature"},"optional":null}`),
 				},
 			},
-			expectedResultMessagePrefix: "cosign verification success",
+			expectedResultMessagePrefix: "Verification success",
 		},
 		{
 			name: "valid hash: 256 curve: P256 ECDSA key AKV",
@@ -841,7 +850,7 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry
 					"sha256:6e1ffef2ba058cda5d1aa7ed792cb1e63b4207d8195a469bee1b5fc662cd9b70": []byte(`{"critical":{"identity":{"docker-reference":"artifactstest.azurecr.io/4-15-24/cosign/hello-world"},"image":{"docker-manifest-digest":"sha256:d37ada95d47ad12224c205a938129df7a3e52345828b4fa27b03a98825d1e2e7"},"type":"cosign container image signature"},"optional":null}`),
 				},
 			},
-			expectedResultMessagePrefix: "cosign verification success",
+			expectedResultMessagePrefix: "Verification success",
 		},
 		{
 			name: "valid hash: 256 curve: P384 ECDSA key",
@@ -881,7 +890,7 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry
 					"sha256:6e1ffef2ba058cda5d1aa7ed792cb1e63b4207d8195a469bee1b5fc662cd9b70": []byte(`{"critical":{"identity":{"docker-reference":"artifactstest.azurecr.io/4-15-24/cosign/hello-world"},"image":{"docker-manifest-digest":"sha256:d37ada95d47ad12224c205a938129df7a3e52345828b4fa27b03a98825d1e2e7"},"type":"cosign container image signature"},"optional":null}`),
 				},
 			},
-			expectedResultMessagePrefix: "cosign verification success",
+			expectedResultMessagePrefix: "Verification success",
 		},
 		{
 			name:              "successful keyless verification",
@@ -917,7 +926,7 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry
 					"sha256:d1226e36bc8502978324cb2cb2116c6aa48edb2ea8f15b1c6f6f256ed43388f6": []byte(`{"critical":{"identity":{"docker-reference":"wabbitnetworks.azurecr.io/test/cosign-image"},"image":{"docker-manifest-digest":"sha256:623621b56649b5e0c2c7cf3ffd987932f8f9a5a01036e00d6f3ae9480087621c"},"type":"cosign container image signature"},"optional":null}`),
 				},
 			},
-			expectedResultMessagePrefix: "cosign verification success",
+			expectedResultMessagePrefix: "Verification success",
 		},
 		{
 			name:              "failed keyless verification",
@@ -953,7 +962,8 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry
 					"sha256:d1226e36bc8502978324cb2cb2116c6aa48edb2ea8f15b1c6f6f256ed43388f6": []byte(`{"critical":{"identity":{"docker-reference":"wabbitnetworks.azurecr.io/test/cosign-image"},"image":{"docker-manifest-digest":"sha256:623621b56649b5e0c2c7cf3ffd987932f8f9a5a01036e00d6f3ae9480087621c"},"type":"cosign container image signature"},"optional":null}`),
 				},
 			},
-			expectedResultMessagePrefix: "cosign verification failed",
+			expectedResultMessagePrefix: "Verification failed",
+			expectedErrorReason:         "failed to parse static signature opts: failed to unmarshal bundle from blob payload: illegal base64 data at input byte 91",
 		},
 	}
 
@@ -991,7 +1001,10 @@ mmBwUAwwW0Uc+Nt3bDOCiB1nUsICv1ry
 			}
 			result, _ := cosignVerifier.Verify(context.Background(), subjectRef, refDescriptor, tt.store)
 			if !strings.HasPrefix(result.Message, tt.expectedResultMessagePrefix) {
-				t.Errorf("Verify() = %v, want %v", result.Message, tt.expectedResultMessagePrefix)
+				t.Errorf("result.Message = %v, want %v", result.Message, tt.expectedResultMessagePrefix)
+			}
+			if result.ErrorReason != tt.expectedErrorReason {
+				t.Fatalf("expected error reason: %s, but got: %s", tt.expectedErrorReason, result.ErrorReason)
 			}
 		})
 	}
diff --git a/test/bats/plugin-test.bats b/test/bats/plugin-test.bats
index 3d37bbd..0cc9dea 100644
--- a/test/bats/plugin-test.bats
+++ b/test/bats/plugin-test.bats
@@ -312,7 +312,7 @@ RATIFY_NAMESPACE=gatekeeper-system
     sed 's/licensechecker/invalidlicensechecker/' ./config/samples/clustered/verifier/config_v1beta1_verifier_complete_licensechecker.yaml >invalidVerifier.yaml
     run kubectl apply -f invalidVerifier.yaml
     assert_success
-    run bash -c "kubectl describe verifiers.config.ratify.deislabs.io/verifier-license-checker -n ${RATIFY_NAMESPACE} | grep 'Brieferror:  Original Error:'"
+    run bash -c "kubectl describe verifiers.config.ratify.deislabs.io/verifier-license-checker -n ${RATIFY_NAMESPACE} | grep 'Brieferror:  PLUGIN_NOT_FOUND:'"
     assert_success
 
     # apply a valid verifier, validate status property shows success