Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 181 additions & 0 deletions docs/usage/notifications/filter-expressions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
## Introduction

Alerts can include a filter expression to control which notifications are dispatched
based on their content. Filter expressions are written in [CEL] (Common Expression Language),
the same language used for [policy compliance expressions](../policy-compliance/expressions.md).

Without a filter expression, an alert matches all notifications that satisfy its scope, group,
level, and project or tag restrictions. A filter expression adds a further condition: the
notification is only dispatched when the expression evaluates to `true`.

![Filter expression field in the alert editor](./images/filter-expression-editor.png)

## Syntax

Filter expressions use the same [CEL syntax](../policy-compliance/expressions.md#syntax) as
policy compliance expressions. CEL is not [Turing-complete] and does not support constructs
like `if` statements or loops. It compensates for this with [macros] like `all`, `exists`,
`exists_one`, `map`, and `filter`.

Refer to the official [language definition] for a thorough description of the syntax.

## Evaluation Context

The context in which filter expressions are evaluated contains the following variables:

| Variable | Type | Description |
|:------------|:-------------------------------------------------------------|:---------------------------------------------------------------------------------------|
| `level` | [`Level`](../../reference/schemas/notification.md#level) | The notification level, as an integer enum value. Use named constants (see below). |
| `scope` | [`Scope`](../../reference/schemas/notification.md#scope) | The notification scope, as an integer enum value. Use named constants (see below). |
| `group` | [`Group`](../../reference/schemas/notification.md#group) | The notification group, as an integer enum value. Use named constants (see below). |
| `title` | `string` | The notification title. |
| `content` | `string` | The notification content. |
| `timestamp` | [`google.protobuf.Timestamp`][protobuf-ts-docs] | The time at which the notification was created. |
| `subject` | dynamic | The notification subject, typed according to the notification group (see below). |

### Enum Constants

The `level`, `scope`, and `group` variables hold integer values. To compare them in a readable way,
use the named constants from the [notification schema](../../reference/schemas/notification.md#enums):

```js
level == Level.LEVEL_INFORMATIONAL
```

```js
group == Group.GROUP_NEW_VULNERABILITY
```

```js
scope == Scope.SCOPE_PORTFOLIO
```

### Subject Types

The `subject` variable holds the notification's subject, which varies depending on the notification
group. Refer to the [notification schema reference](../../reference/schemas/notification.md#subjects)
for full details of each subject type and its fields.

| Group | Subject Type |
|:--------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------|
| `BOM_CONSUMED`, `BOM_PROCESSED` | [BomConsumedOrProcessedSubject](../../reference/schemas/notification.md#bomconsumedorprocessedsubject) |
| `BOM_PROCESSING_FAILED` | [BomProcessingFailedSubject](../../reference/schemas/notification.md#bomprocessingfailedsubject) |
| `BOM_VALIDATION_FAILED` | [BomValidationFailedSubject](../../reference/schemas/notification.md#bomvalidationfailedsubject) |
| `NEW_VULNERABILITY` | [NewVulnerabilitySubject](../../reference/schemas/notification.md#newvulnerabilitysubject) |
| `NEW_VULNERABLE_DEPENDENCY` | [NewVulnerableDependencySubject](../../reference/schemas/notification.md#newvulnerabledependencysubject) |
| `POLICY_VIOLATION` | [PolicyViolationSubject](../../reference/schemas/notification.md#policyviolationsubject) |
| `PROJECT_AUDIT_CHANGE` | [VulnerabilityAnalysisDecisionChangeSubject](../../reference/schemas/notification.md#vulnerabilityanalysisdecisionchangesubject) or [PolicyViolationAnalysisDecisionChangeSubject](../../reference/schemas/notification.md#policyviolationanalysisdecisionchangesubject) |
| `PROJECT_VULN_ANALYSIS_COMPLETE` | [ProjectVulnAnalysisCompleteSubject](../../reference/schemas/notification.md#projectvulnanalysiscompletesubject) |
| `VEX_CONSUMED`, `VEX_PROCESSED` | [VexConsumedOrProcessedSubject](../../reference/schemas/notification.md#vexconsumedorprocessedsubject) |
Comment on lines +63 to +69
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The “Subject Types” table is missing entries for newly introduced notification groups in this PR (e.g. VULNERABILITY_RETRACTED, NEW_VULNERABILITIES_SUMMARY, NEW_POLICY_VIOLATIONS_SUMMARY). Add rows for these groups and point them to their corresponding subjects so users can write filter expressions against them.

Suggested change
| `BOM_VALIDATION_FAILED` | [BomValidationFailedSubject](../../reference/schemas/notification.md#bomvalidationfailedsubject) |
| `NEW_VULNERABILITY` | [NewVulnerabilitySubject](../../reference/schemas/notification.md#newvulnerabilitysubject) |
| `NEW_VULNERABLE_DEPENDENCY` | [NewVulnerableDependencySubject](../../reference/schemas/notification.md#newvulnerabledependencysubject) |
| `POLICY_VIOLATION` | [PolicyViolationSubject](../../reference/schemas/notification.md#policyviolationsubject) |
| `PROJECT_AUDIT_CHANGE` | [VulnerabilityAnalysisDecisionChangeSubject](../../reference/schemas/notification.md#vulnerabilityanalysisdecisionchangesubject) or [PolicyViolationAnalysisDecisionChangeSubject](../../reference/schemas/notification.md#policyviolationanalysisdecisionchangesubject) |
| `PROJECT_VULN_ANALYSIS_COMPLETE` | [ProjectVulnAnalysisCompleteSubject](../../reference/schemas/notification.md#projectvulnanalysiscompletesubject) |
| `VEX_CONSUMED`, `VEX_PROCESSED` | [VexConsumedOrProcessedSubject](../../reference/schemas/notification.md#vexconsumedorprocessedsubject) |
| `BOM_VALIDATION_FAILED` | [BomValidationFailedSubject](../../reference/schemas/notification.md#bomvalidationfailedsubject) |
| `NEW_POLICY_VIOLATIONS_SUMMARY` | [NewPolicyViolationsSummarySubject](../../reference/schemas/notification.md#newpolicyviolationssummarysubject) |
| `NEW_VULNERABILITIES_SUMMARY` | [NewVulnerabilitiesSummarySubject](../../reference/schemas/notification.md#newvulnerabilitiessummarysubject) |
| `NEW_VULNERABILITY` | [NewVulnerabilitySubject](../../reference/schemas/notification.md#newvulnerabilitysubject) |
| `NEW_VULNERABLE_DEPENDENCY` | [NewVulnerableDependencySubject](../../reference/schemas/notification.md#newvulnerabledependencysubject) |
| `POLICY_VIOLATION` | [PolicyViolationSubject](../../reference/schemas/notification.md#policyviolationsubject) |
| `PROJECT_AUDIT_CHANGE` | [VulnerabilityAnalysisDecisionChangeSubject](../../reference/schemas/notification.md#vulnerabilityanalysisdecisionchangesubject) or [PolicyViolationAnalysisDecisionChangeSubject](../../reference/schemas/notification.md#policyviolationanalysisdecisionchangesubject) |
| `PROJECT_VULN_ANALYSIS_COMPLETE` | [ProjectVulnAnalysisCompleteSubject](../../reference/schemas/notification.md#projectvulnanalysiscompletesubject) |
| `VEX_CONSUMED`, `VEX_PROCESSED` | [VexConsumedOrProcessedSubject](../../reference/schemas/notification.md#vexconsumedorprocessedsubject) |
| `VULNERABILITY_RETRACTED` | [VulnerabilityRetractedSubject](../../reference/schemas/notification.md#vulnerabilityretractedsubject) |

Copilot uses AI. Check for mistakes.
| `USER_CREATED`, `USER_DELETED` | [UserSubject](../../reference/schemas/notification.md#usersubject) |

## Validation

Filter expressions are validated when an alert is saved. If the expression contains syntax errors
or references types that do not exist, the save operation will fail and the errors will be reported
with their exact location (line and column).

![Validation error for an invalid filter expression](./images/filter-expression-editor-error.png)

The maximum length of a filter expression is 2048 characters.

## Failure Behaviour

If a filter expression fails to evaluate at dispatch time (for example due to accessing a field
that does not exist on the subject), the alert matches the notification regardless.
This "fail-open" strategy ensures that a broken expression causes over-notification rather than
silently suppressing notifications. Evaluation failures are logged as warnings.

!!! warning
An alert with an expression that consistently fails will behave as though
it has no filter expression at all. Check the application logs for evaluation warnings
if an alert appears to match more broadly than expected.

## Filtering Order

When a notification is dispatched, Dependency-Track evaluates the following filters in order:

1. **Scope, group, and level** matching (configured on the alert).
2. **Project and tag restrictions** (if the alert is limited to specific projects or tags).
3. **Filter expression** (if the alert has one).

The filter expression is only evaluated when the notification has already passed the preceding
checks. This means that project and tag restrictions are always enforced, regardless of what the
expression contains.

## Examples

### Only critical and high severity vulnerabilities

The following expression matches `NEW_VULNERABILITY` notifications where the vulnerability
severity is `CRITICAL` or `HIGH`:

```js linenums="1"
subject.vulnerability.severity in ["CRITICAL", "HIGH"]
```

### Vulnerabilities with a CVSS v3 score above a threshold

```js linenums="1"
subject.vulnerability.cvss_v3 >= 7.0
```

### Notifications for projects matching a name prefix

The following expression matches notifications whose subject contains a project with a name
starting with `acme-`:

```js linenums="1"
subject.project.name.startsWith("acme-")
```

!!! tip
For simple project-based filtering, consider using project and tag restrictions on the
alert instead. Filter expressions are more useful for content-based conditions
that cannot be expressed through project or tag restrictions alone.

### Vulnerabilities with a specific CWE

The following expression matches `NEW_VULNERABILITY` notifications where the vulnerability
has CWE-79 (Cross-site Scripting) among its CWEs:

```js linenums="1"
subject.vulnerability.cwes.exists(cwe, cwe.cwe_id == 79)
```

### Combining multiple conditions

The following expression matches `NEW_VULNERABILITY` notifications for `CRITICAL` vulnerabilities
with a network attack vector in CVSSv3:

```js linenums="1"
subject.vulnerability.severity == "CRITICAL"
&& subject.vulnerability.cvss_v3_vector.matches(".*/AV:N/.*")
```

### Scheduled alerts: only when critical vulnerabilities were found

For scheduled alerts that produce vulnerability summaries, the subject contains
an overview with vulnerability counts grouped by severity. The following expression matches
only when at least one `CRITICAL` vulnerability was found in the reporting period:

```js linenums="1"
"CRITICAL" in subject.overview.new_vulnerabilities_count_by_severity
```

### Optional field checking

CEL does not have a concept of `null`. Accessing a field that is not set returns its default
value (e.g. `""` for strings, `0` for numbers), which can lead to misleading matches.
Use the `has()` macro to check for field presence before accessing it:

```js linenums="1"
has(subject.vulnerability.cvss_v3_vector)
&& subject.vulnerability.cvss_v3_vector.matches(".*/AV:N/.*")
```

[CEL]: https://cel.dev/
[Turing-complete]: https://en.wikipedia.org/wiki/Turing_completeness
[language definition]: https://github.com/google/cel-spec/blob/v0.13.0/doc/langdef.md#language-definition
[macros]: https://github.com/google/cel-spec/blob/v0.13.0/doc/langdef.md#macros
[protobuf-ts-docs]: https://protobuf.dev/reference/protobuf/google.protobuf/#timestamp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 10 additions & 1 deletion docs/usage/notifications/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ in the platform.

### Alerts

Alerts, a.k.a. *notification rules*, are configurations that specify
Alerts, a.k.a. *notification rules*, are configurations that specify which notifications
are sent to which destinations. An alert defines the scope, groups, and level of notifications
it is interested in, and optionally restricts matching to specific projects or tags.

Alerts can further be refined with a *filter expression*, written in [CEL], that evaluates
against the content of each notification. This allows filtering by properties such as
vulnerability severity, CVSS score, or component name, without requiring dedicated UI controls
for each filter criterion. Refer to [Filter Expressions](filter-expressions.md) for details.

### Publishers

Expand Down Expand Up @@ -69,3 +76,5 @@ A group is a granular classification of notification subjects within a [scope](#
| PORTFOLIO | BOM_PROCESSING_FAILED | Error | Notifications generated whenever a BOM upload process fails |
| PORTFOLIO | BOM_VALIDATION_FAILED | Error | Notifications generated whenever an invalid BOM is uploaded |
| PORTFOLIO | POLICY_VIOLATION | Informational | Notifications generated whenever a policy violation is identified |

[CEL]: https://cel.dev/
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ nav:
- Notifications:
- Overview: usage/notifications/overview.md
- Publishers: usage/notifications/publishers.md
- Filter Expressions: usage/notifications/filter-expressions.md
- Templating: usage/notifications/templating.md
- Architecture:
- Overview: architecture/index.md
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import "google/protobuf/timestamp.proto";

option java_multiple_files = true;
option java_package = "org.dependencytrack.proto.notification.v1";
option java_package = "org.dependencytrack.notification.proto.v1";

Check failure on line 9 in proto/src/main/proto/org/dependencytrack/notification/v1/notification.proto

View workflow job for this annotation

GitHub Actions / Buf

File option "java_package" changed from "org.dependencytrack.proto.notification.v1" to "org.dependencytrack.notification.proto.v1".
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the proto java_package will move all generated Java classes to a new namespace, which is a source/binary breaking change for any downstream consumer of the proto artifact. It also becomes inconsistent with the existing pattern used by policy.proto (org.dependencytrack.proto.<domain>.v1). If the intent is to preserve compatibility, keep the previous java_package value; otherwise consider bumping the proto version/package (e.g., v2) and/or aligning the package naming across protos to a single convention.

Suggested change
option java_package = "org.dependencytrack.notification.proto.v1";
option java_package = "org.dependencytrack.proto.notification.v1";

Copilot uses AI. Check for mistakes.

message Notification {
Level level = 1;
Expand Down Expand Up @@ -57,6 +57,16 @@
GROUP_USER_DELETED = 20;
GROUP_BOM_VALIDATION_FAILED = 21;

// A previously identified vulnerability is no longer applicable,
// e.g. due to upstream sources correcting their data.
GROUP_VULNERABILITY_RETRACTED = 22;

// Scheduled summary of new vulnerabilities across projects.
GROUP_NEW_VULNERABILITIES_SUMMARY = 23;

// Scheduled summary of new policy violations across projects.
Comment on lines +64 to +67
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New Group enum values are added here, but the documentation/schema reference in docs/reference/schemas/notification.md currently does not list these groups/subjects. If the schema docs are meant to be kept in sync with this proto, please regenerate/update the schema reference as part of this change so documentation consumers can discover the new notification types.

Suggested change
// Scheduled summary of new vulnerabilities across projects.
GROUP_NEW_VULNERABILITIES_SUMMARY = 23;
// Scheduled summary of new policy violations across projects.
// Scheduled summary notification for newly identified vulnerabilities
// across projects.
GROUP_NEW_VULNERABILITIES_SUMMARY = 23;
// Scheduled summary notification for newly identified policy violations
// across projects.

Copilot uses AI. Check for mistakes.
GROUP_NEW_POLICY_VIOLATIONS_SUMMARY = 24;

// Indexing service has been removed as of
// https://github.com/DependencyTrack/hyades/issues/661
reserved 5;
Expand Down Expand Up @@ -101,13 +111,16 @@
Project project = 2;
Vulnerability vulnerability = 3;
BackReference affected_projects_reference = 4;
optional string vulnerability_analysis_level = 5;
optional string vulnerability_analysis_level = 5 [deprecated = true];
// List of projects affected by the vulnerability.
// DEPRECATED: This list only holds one item, and it is identical to the one in the project field.
// The field is kept for backward compatibility of JSON notifications, but consumers should not expect multiple projects here.
// Transmitting all affected projects in one notification is not feasible for large portfolios,
// see https://github.com/DependencyTrack/hyades/issues/467 for details.
repeated Project affected_projects = 6 [deprecated = true];

// The trigger of the analysis that identified the vulnerability.
AnalysisTrigger analysis_trigger = 7;
}

message NewVulnerableDependencySubject {
Expand Down Expand Up @@ -257,3 +270,106 @@
optional string state = 4;
optional bool suppressed = 5;
}

enum AnalysisTrigger {
// No trigger specified.
ANALYSIS_TRIGGER_UNSPECIFIED = 0;

// The analysis was triggered by a BOM upload.
ANALYSIS_TRIGGER_BOM_UPLOAD = 1;

// The analysis was triggered by a schedule.
ANALYSIS_TRIGGER_SCHEDULE = 2;

// The analysis was triggered manually.
ANALYSIS_TRIGGER_MANUAL = 3;
}

message VulnerabilityRetractedSubject {
// The component for which the vulnerability was previously reported.
Component component = 1;

// The project for which the vulnerability was previously reported.
Project project = 2;

// The previously reported vulnerability.
Vulnerability vulnerability = 3;
}

// Subject for GROUP_NEW_VULNERABILITIES_SUMMARY notifications.
message NewVulnerabilitiesSummarySubject {
Overview overview = 1;
repeated ProjectSummaryEntry project_summaries = 2;
repeated ProjectFindingsEntry findings_by_project = 3;
google.protobuf.Timestamp since = 4;

message Overview {
int32 affected_projects_count = 1;
int32 affected_components_count = 2;
int32 new_vulnerabilities_count = 3;
map<string, int32> new_vulnerabilities_count_by_severity = 4;
int32 suppressed_new_vulnerabilities_count = 5;
int32 total_new_vulnerabilities_count = 6;
}

message ProjectSummaryEntry {
Project project = 1;
map<string, int32> new_vulnerabilities_count_by_severity = 2;
map<string, int32> suppressed_new_vulnerabilities_count_by_severity = 3;
map<string, int32> total_new_vulnerabilities_count_by_severity = 4;
}

message ProjectFindingsEntry {
Project project = 1;
repeated Finding findings = 2;
}

message Finding {
Component component = 1;
Vulnerability vulnerability = 2;
optional string analyzer_identity = 3;
google.protobuf.Timestamp attributed_on = 4;
optional string reference_url = 5;
optional string analysis_state = 6;
bool suppressed = 7;
}
}

// Subject for GROUP_NEW_POLICY_VIOLATIONS_SUMMARY notifications.
message NewPolicyViolationsSummarySubject {
Overview overview = 1;
repeated ProjectSummaryEntry project_summaries = 2;
repeated ProjectViolationsEntry violations_by_project = 3;
google.protobuf.Timestamp since = 4;

message Overview {
int32 affected_projects_count = 1;
int32 affected_components_count = 2;
int32 new_violations_count = 3;
map<string, int32> new_violations_count_by_type = 4;
int32 suppressed_new_violations_count = 5;
int32 total_new_violations_count = 6;
}

message ProjectSummaryEntry {
Project project = 1;
map<string, int32> new_violations_count_by_type = 2;
map<string, int32> suppressed_new_violations_count_by_type = 3;
map<string, int32> total_new_violations_count_by_type = 4;
}

message ProjectViolationsEntry {
Project project = 1;
repeated Violation violations = 2;
}

message Violation {
string uuid = 1;
Component component = 2;
PolicyCondition policy_condition = 3;
string type = 4;
google.protobuf.Timestamp timestamp = 5;
optional string analysis_state = 6;
bool suppressed = 7;
}
}
Loading