diff --git a/src/app/(loading-group)/[organizationSlug]/projects/[projectSlug]/assets/[assetSlug]/refs/[assetVersionSlug]/dependency-risks/[vulnId]/page.tsx b/src/app/(loading-group)/[organizationSlug]/projects/[projectSlug]/assets/[assetSlug]/refs/[assetVersionSlug]/dependency-risks/[vulnId]/page.tsx
index 23de5e62..81cc3d1c 100644
--- a/src/app/(loading-group)/[organizationSlug]/projects/[projectSlug]/assets/[assetSlug]/refs/[assetVersionSlug]/dependency-risks/[vulnId]/page.tsx
+++ b/src/app/(loading-group)/[organizationSlug]/projects/[projectSlug]/assets/[assetSlug]/refs/[assetVersionSlug]/dependency-risks/[vulnId]/page.tsx
@@ -45,6 +45,7 @@ import type {
VulnEventDTO,
} from "@/types/api/api";
import { RequirementsLevel } from "@/types/api/api";
+import { getVexRulesSectionLabel } from "@/utils/vexRuleHelpers";
import { getIntegrationNameFromRepositoryIdOrExternalProviderId } from "@/utils/view";
import {
InformationCircleIcon,
@@ -919,7 +920,9 @@ const Index: FunctionComponent = () => {
)}
- Applied Rules ({vexRulesData?.length || 0})
+ {vuln
+ ? `${getVexRulesSectionLabel(vexRulesData ?? [], vuln)} (${vexRulesData?.length ?? 0})`
+ : `Matching Rules (${vexRulesData?.length ?? 0})`}
{vexRulesData && vexRulesData.length > 0 && (
diff --git a/src/utils/vexRuleHelpers.test.ts b/src/utils/vexRuleHelpers.test.ts
new file mode 100644
index 00000000..8a66ea3b
--- /dev/null
+++ b/src/utils/vexRuleHelpers.test.ts
@@ -0,0 +1,81 @@
+import type { DetailedDependencyVulnDTO, VexRule } from "@/types/api/api";
+import {
+ getVexRulesSectionLabel,
+ isVexRuleAppliedToVuln,
+} from "./vexRuleHelpers";
+
+const baseVuln = {
+ state: "open",
+} as DetailedDependencyVulnDTO;
+
+const falsePositiveRule = {
+ id: "rule-1",
+ eventType: "falsePositive",
+} as VexRule;
+
+const acceptedRule = {
+ id: "rule-2",
+ eventType: "accepted",
+} as VexRule;
+
+describe("isVexRuleAppliedToVuln", () => {
+ it("returns true when false positive rule matches vuln state", () => {
+ expect(
+ isVexRuleAppliedToVuln(falsePositiveRule, {
+ ...baseVuln,
+ state: "falsePositive",
+ }),
+ ).toBe(true);
+ });
+
+ it("returns false when vuln was reopened (state is open)", () => {
+ expect(
+ isVexRuleAppliedToVuln(falsePositiveRule, {
+ ...baseVuln,
+ state: "open",
+ }),
+ ).toBe(false);
+ });
+
+ it("returns true when accepted rule matches vuln state", () => {
+ expect(
+ isVexRuleAppliedToVuln(acceptedRule, {
+ ...baseVuln,
+ state: "accepted",
+ }),
+ ).toBe(true);
+ });
+});
+
+describe("getVexRulesSectionLabel", () => {
+ it('shows "Matching Rules" when rule is not currently applied (reopened vuln)', () => {
+ expect(
+ getVexRulesSectionLabel([falsePositiveRule], {
+ ...baseVuln,
+ state: "open",
+ }),
+ ).toBe("Matching Rules");
+ });
+
+ it('shows "Applied Rules" when false positive rule is applied', () => {
+ expect(
+ getVexRulesSectionLabel([falsePositiveRule], {
+ ...baseVuln,
+ state: "falsePositive",
+ }),
+ ).toBe("Applied Rules");
+ });
+
+ it('shows "Matching Rules" when only some rules are applied', () => {
+ expect(
+ getVexRulesSectionLabel([falsePositiveRule, acceptedRule], {
+ ...baseVuln,
+ state: "falsePositive",
+ }),
+ ).toBe("Matching Rules");
+ });
+
+ it('shows "Matching Rules" when there are no rules', () => {
+ expect(getVexRulesSectionLabel([], baseVuln)).toBe("Matching Rules");
+ });
+});
diff --git a/src/utils/vexRuleHelpers.ts b/src/utils/vexRuleHelpers.ts
new file mode 100644
index 00000000..c2145383
--- /dev/null
+++ b/src/utils/vexRuleHelpers.ts
@@ -0,0 +1,27 @@
+import type { DetailedDependencyVulnDTO, VexRule } from "@/types/api/api";
+
+export function isVexRuleAppliedToVuln(
+ rule: VexRule,
+ vuln: DetailedDependencyVulnDTO,
+): boolean {
+ if (rule.eventType === "falsePositive") {
+ return vuln.state === "falsePositive";
+ }
+ if (rule.eventType === "accepted") {
+ return vuln.state === "accepted";
+ }
+ return false;
+}
+
+export function getVexRulesSectionLabel(
+ rules: VexRule[],
+ vuln: DetailedDependencyVulnDTO,
+): string {
+ if (rules.length === 0) {
+ return "Matching Rules";
+ }
+ const allApplied = rules.every((rule) =>
+ isVexRuleAppliedToVuln(rule, vuln),
+ );
+ return allApplied ? "Applied Rules" : "Matching Rules";
+}