Skip to content

Commit 85a9661

Browse files
committed
Add notification filter docs and update notification proto
Signed-off-by: nscuro <nscuro@protonmail.com>
1 parent effc780 commit 85a9661

File tree

6 files changed

+310
-3
lines changed

6 files changed

+310
-3
lines changed
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
## Introduction
2+
3+
Alerts can include a filter expression to control which notifications are dispatched
4+
based on their content. Filter expressions are written in [CEL] (Common Expression Language),
5+
the same language used for [policy compliance expressions](../policy-compliance/expressions.md).
6+
7+
Without a filter expression, an alert matches all notifications that satisfy its scope, group,
8+
level, and project or tag restrictions. A filter expression adds a further condition: the
9+
notification is only dispatched when the expression evaluates to `true`.
10+
11+
![Filter expression field in the alert editor](./images/filter-expression-editor.png)
12+
13+
## Syntax
14+
15+
Filter expressions use the same [CEL syntax](../policy-compliance/expressions.md#syntax) as
16+
policy compliance expressions. CEL is not [Turing-complete] and does not support constructs
17+
like `if` statements or loops. It compensates for this with [macros] like `all`, `exists`,
18+
`exists_one`, `map`, and `filter`.
19+
20+
Refer to the official [language definition] for a thorough description of the syntax.
21+
22+
## Evaluation Context
23+
24+
The context in which filter expressions are evaluated contains the following variables:
25+
26+
| Variable | Type | Description |
27+
|:------------|:-------------------------------------------------------------|:---------------------------------------------------------------------------------------|
28+
| `level` | [`Level`](../../reference/schemas/notification.md#level) | The notification level, as an integer enum value. Use named constants (see below). |
29+
| `scope` | [`Scope`](../../reference/schemas/notification.md#scope) | The notification scope, as an integer enum value. Use named constants (see below). |
30+
| `group` | [`Group`](../../reference/schemas/notification.md#group) | The notification group, as an integer enum value. Use named constants (see below). |
31+
| `title` | `string` | The notification title. |
32+
| `content` | `string` | The notification content. |
33+
| `timestamp` | [`google.protobuf.Timestamp`][protobuf-ts-docs] | The time at which the notification was created. |
34+
| `subject` | dynamic | The notification subject, typed according to the notification group (see below). |
35+
36+
### Enum Constants
37+
38+
The `level`, `scope`, and `group` variables hold integer values. To compare them in a readable way,
39+
use the named constants from the [notification schema](../../reference/schemas/notification.md#enums):
40+
41+
```js
42+
level == Level.LEVEL_INFORMATIONAL
43+
```
44+
45+
```js
46+
group == Group.GROUP_NEW_VULNERABILITY
47+
```
48+
49+
```js
50+
scope == Scope.SCOPE_PORTFOLIO
51+
```
52+
53+
### Subject Types
54+
55+
The `subject` variable holds the notification's subject, which varies depending on the notification
56+
group. Refer to the [notification schema reference](../../reference/schemas/notification.md#subjects)
57+
for full details of each subject type and its fields.
58+
59+
| Group | Subject Type |
60+
|:--------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------|
61+
| `BOM_CONSUMED`, `BOM_PROCESSED` | [BomConsumedOrProcessedSubject](../../reference/schemas/notification.md#bomconsumedorprocessedsubject) |
62+
| `BOM_PROCESSING_FAILED` | [BomProcessingFailedSubject](../../reference/schemas/notification.md#bomprocessingfailedsubject) |
63+
| `BOM_VALIDATION_FAILED` | [BomValidationFailedSubject](../../reference/schemas/notification.md#bomvalidationfailedsubject) |
64+
| `NEW_VULNERABILITY` | [NewVulnerabilitySubject](../../reference/schemas/notification.md#newvulnerabilitysubject) |
65+
| `NEW_VULNERABLE_DEPENDENCY` | [NewVulnerableDependencySubject](../../reference/schemas/notification.md#newvulnerabledependencysubject) |
66+
| `POLICY_VIOLATION` | [PolicyViolationSubject](../../reference/schemas/notification.md#policyviolationsubject) |
67+
| `PROJECT_AUDIT_CHANGE` | [VulnerabilityAnalysisDecisionChangeSubject](../../reference/schemas/notification.md#vulnerabilityanalysisdecisionchangesubject) or [PolicyViolationAnalysisDecisionChangeSubject](../../reference/schemas/notification.md#policyviolationanalysisdecisionchangesubject) |
68+
| `PROJECT_VULN_ANALYSIS_COMPLETE` | [ProjectVulnAnalysisCompleteSubject](../../reference/schemas/notification.md#projectvulnanalysiscompletesubject) |
69+
| `VEX_CONSUMED`, `VEX_PROCESSED` | [VexConsumedOrProcessedSubject](../../reference/schemas/notification.md#vexconsumedorprocessedsubject) |
70+
| `USER_CREATED`, `USER_DELETED` | [UserSubject](../../reference/schemas/notification.md#usersubject) |
71+
72+
## Validation
73+
74+
Filter expressions are validated when an alert is saved. If the expression contains syntax errors
75+
or references types that do not exist, the save operation will fail and the errors will be reported
76+
with their exact location (line and column).
77+
78+
![Validation error for an invalid filter expression](./images/filter-expression-editor-error.png)
79+
80+
The maximum length of a filter expression is 2048 characters.
81+
82+
## Failure Behaviour
83+
84+
If a filter expression fails to evaluate at dispatch time (for example due to accessing a field
85+
that does not exist on the subject), the alert matches the notification regardless.
86+
This "fail-open" strategy ensures that a broken expression causes over-notification rather than
87+
silently suppressing notifications. Evaluation failures are logged as warnings.
88+
89+
!!! warning
90+
An alert with an expression that consistently fails will behave as though
91+
it has no filter expression at all. Check the application logs for evaluation warnings
92+
if an alert appears to match more broadly than expected.
93+
94+
## Filtering Order
95+
96+
When a notification is dispatched, Dependency-Track evaluates the following filters in order:
97+
98+
1. **Scope, group, and level** matching (configured on the alert).
99+
2. **Project and tag restrictions** (if the alert is limited to specific projects or tags).
100+
3. **Filter expression** (if the alert has one).
101+
102+
The filter expression is only evaluated when the notification has already passed the preceding
103+
checks. This means that project and tag restrictions are always enforced, regardless of what the
104+
expression contains.
105+
106+
## Examples
107+
108+
### Only critical and high severity vulnerabilities
109+
110+
The following expression matches `NEW_VULNERABILITY` notifications where the vulnerability
111+
severity is `CRITICAL` or `HIGH`:
112+
113+
```js linenums="1"
114+
subject.vulnerability.severity in ["CRITICAL", "HIGH"]
115+
```
116+
117+
### Vulnerabilities with a CVSS v3 score above a threshold
118+
119+
```js linenums="1"
120+
subject.vulnerability.cvss_v3 >= 7.0
121+
```
122+
123+
### Notifications for projects matching a name prefix
124+
125+
The following expression matches notifications whose subject contains a project with a name
126+
starting with `acme-`:
127+
128+
```js linenums="1"
129+
subject.project.name.startsWith("acme-")
130+
```
131+
132+
!!! tip
133+
For simple project-based filtering, consider using project and tag restrictions on the
134+
alert instead. Filter expressions are more useful for content-based conditions
135+
that cannot be expressed through project or tag restrictions alone.
136+
137+
### Vulnerabilities with a specific CWE
138+
139+
The following expression matches `NEW_VULNERABILITY` notifications where the vulnerability
140+
has CWE-79 (Cross-site Scripting) among its CWEs:
141+
142+
```js linenums="1"
143+
subject.vulnerability.cwes.exists(cwe, cwe.cwe_id == 79)
144+
```
145+
146+
### Combining multiple conditions
147+
148+
The following expression matches `NEW_VULNERABILITY` notifications for `CRITICAL` vulnerabilities
149+
with a network attack vector in CVSSv3:
150+
151+
```js linenums="1"
152+
subject.vulnerability.severity == "CRITICAL"
153+
&& subject.vulnerability.cvss_v3_vector.matches(".*/AV:N/.*")
154+
```
155+
156+
### Scheduled alerts: only when critical vulnerabilities were found
157+
158+
For scheduled alerts that produce vulnerability summaries, the subject contains
159+
an overview with vulnerability counts grouped by severity. The following expression matches
160+
only when at least one `CRITICAL` vulnerability was found in the reporting period:
161+
162+
```js linenums="1"
163+
"CRITICAL" in subject.overview.new_vulnerabilities_count_by_severity
164+
```
165+
166+
### Optional field checking
167+
168+
CEL does not have a concept of `null`. Accessing a field that is not set returns its default
169+
value (e.g. `""` for strings, `0` for numbers), which can lead to misleading matches.
170+
Use the `has()` macro to check for field presence before accessing it:
171+
172+
```js linenums="1"
173+
has(subject.vulnerability.cvss_v3_vector)
174+
&& subject.vulnerability.cvss_v3_vector.matches(".*/AV:N/.*")
175+
```
176+
177+
[CEL]: https://cel.dev/
178+
[Turing-complete]: https://en.wikipedia.org/wiki/Turing_completeness
179+
[language definition]: https://github.com/google/cel-spec/blob/v0.13.0/doc/langdef.md#language-definition
180+
[macros]: https://github.com/google/cel-spec/blob/v0.13.0/doc/langdef.md#macros
181+
[protobuf-ts-docs]: https://protobuf.dev/reference/protobuf/google.protobuf/#timestamp
29.8 KB
Loading
25.3 KB
Loading

docs/usage/notifications/overview.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,14 @@ in the platform.
88

99
### Alerts
1010

11-
Alerts, a.k.a. *notification rules*, are configurations that specify
11+
Alerts, a.k.a. *notification rules*, are configurations that specify which notifications
12+
are sent to which destinations. An alert defines the scope, groups, and level of notifications
13+
it is interested in, and optionally restricts matching to specific projects or tags.
14+
15+
Alerts can further be refined with a *filter expression*, written in [CEL], that evaluates
16+
against the content of each notification. This allows filtering by properties such as
17+
vulnerability severity, CVSS score, or component name, without requiring dedicated UI controls
18+
for each filter criterion. Refer to [Filter Expressions](filter-expressions.md) for details.
1219

1320
### Publishers
1421

@@ -69,3 +76,5 @@ A group is a granular classification of notification subjects within a [scope](#
6976
| PORTFOLIO | BOM_PROCESSING_FAILED | Error | Notifications generated whenever a BOM upload process fails |
7077
| PORTFOLIO | BOM_VALIDATION_FAILED | Error | Notifications generated whenever an invalid BOM is uploaded |
7178
| PORTFOLIO | POLICY_VIOLATION | Informational | Notifications generated whenever a policy violation is identified |
79+
80+
[CEL]: https://cel.dev/

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ nav:
9393
- Notifications:
9494
- Overview: usage/notifications/overview.md
9595
- Publishers: usage/notifications/publishers.md
96+
- Filter Expressions: usage/notifications/filter-expressions.md
9697
- Templating: usage/notifications/templating.md
9798
- Architecture:
9899
- Overview: architecture/index.md

proto/src/main/proto/org/dependencytrack/notification/v1/notification.proto

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import "google/protobuf/any.proto";
66
import "google/protobuf/timestamp.proto";
77

88
option java_multiple_files = true;
9-
option java_package = "org.dependencytrack.proto.notification.v1";
9+
option java_package = "org.dependencytrack.notification.proto.v1";
1010

1111
message Notification {
1212
Level level = 1;
@@ -57,6 +57,16 @@ enum Group {
5757
GROUP_USER_DELETED = 20;
5858
GROUP_BOM_VALIDATION_FAILED = 21;
5959

60+
// A previously identified vulnerability is no longer applicable,
61+
// e.g. due to upstream sources correcting their data.
62+
GROUP_VULNERABILITY_RETRACTED = 22;
63+
64+
// Scheduled summary of new vulnerabilities across projects.
65+
GROUP_NEW_VULNERABILITIES_SUMMARY = 23;
66+
67+
// Scheduled summary of new policy violations across projects.
68+
GROUP_NEW_POLICY_VIOLATIONS_SUMMARY = 24;
69+
6070
// Indexing service has been removed as of
6171
// https://github.com/DependencyTrack/hyades/issues/661
6272
reserved 5;
@@ -101,13 +111,16 @@ message NewVulnerabilitySubject {
101111
Project project = 2;
102112
Vulnerability vulnerability = 3;
103113
BackReference affected_projects_reference = 4;
104-
optional string vulnerability_analysis_level = 5;
114+
optional string vulnerability_analysis_level = 5 [deprecated = true];
105115
// List of projects affected by the vulnerability.
106116
// DEPRECATED: This list only holds one item, and it is identical to the one in the project field.
107117
// The field is kept for backward compatibility of JSON notifications, but consumers should not expect multiple projects here.
108118
// Transmitting all affected projects in one notification is not feasible for large portfolios,
109119
// see https://github.com/DependencyTrack/hyades/issues/467 for details.
110120
repeated Project affected_projects = 6 [deprecated = true];
121+
122+
// The trigger of the analysis that identified the vulnerability.
123+
AnalysisTrigger analysis_trigger = 7;
111124
}
112125

113126
message NewVulnerableDependencySubject {
@@ -257,3 +270,106 @@ message PolicyViolationAnalysis {
257270
optional string state = 4;
258271
optional bool suppressed = 5;
259272
}
273+
274+
enum AnalysisTrigger {
275+
// No trigger specified.
276+
ANALYSIS_TRIGGER_UNSPECIFIED = 0;
277+
278+
// The analysis was triggered by a BOM upload.
279+
ANALYSIS_TRIGGER_BOM_UPLOAD = 1;
280+
281+
// The analysis was triggered by a schedule.
282+
ANALYSIS_TRIGGER_SCHEDULE = 2;
283+
284+
// The analysis was triggered manually.
285+
ANALYSIS_TRIGGER_MANUAL = 3;
286+
}
287+
288+
message VulnerabilityRetractedSubject {
289+
// The component for which the vulnerability was previously reported.
290+
Component component = 1;
291+
292+
// The project for which the vulnerability was previously reported.
293+
Project project = 2;
294+
295+
// The previously reported vulnerability.
296+
Vulnerability vulnerability = 3;
297+
}
298+
299+
// Subject for GROUP_NEW_VULNERABILITIES_SUMMARY notifications.
300+
message NewVulnerabilitiesSummarySubject {
301+
Overview overview = 1;
302+
repeated ProjectSummaryEntry project_summaries = 2;
303+
repeated ProjectFindingsEntry findings_by_project = 3;
304+
google.protobuf.Timestamp since = 4;
305+
306+
message Overview {
307+
int32 affected_projects_count = 1;
308+
int32 affected_components_count = 2;
309+
int32 new_vulnerabilities_count = 3;
310+
map<string, int32> new_vulnerabilities_count_by_severity = 4;
311+
int32 suppressed_new_vulnerabilities_count = 5;
312+
int32 total_new_vulnerabilities_count = 6;
313+
}
314+
315+
message ProjectSummaryEntry {
316+
Project project = 1;
317+
map<string, int32> new_vulnerabilities_count_by_severity = 2;
318+
map<string, int32> suppressed_new_vulnerabilities_count_by_severity = 3;
319+
map<string, int32> total_new_vulnerabilities_count_by_severity = 4;
320+
}
321+
322+
message ProjectFindingsEntry {
323+
Project project = 1;
324+
repeated Finding findings = 2;
325+
}
326+
327+
message Finding {
328+
Component component = 1;
329+
Vulnerability vulnerability = 2;
330+
optional string analyzer_identity = 3;
331+
google.protobuf.Timestamp attributed_on = 4;
332+
optional string reference_url = 5;
333+
optional string analysis_state = 6;
334+
bool suppressed = 7;
335+
}
336+
}
337+
338+
// Subject for GROUP_NEW_POLICY_VIOLATIONS_SUMMARY notifications.
339+
message NewPolicyViolationsSummarySubject {
340+
Overview overview = 1;
341+
repeated ProjectSummaryEntry project_summaries = 2;
342+
repeated ProjectViolationsEntry violations_by_project = 3;
343+
google.protobuf.Timestamp since = 4;
344+
345+
message Overview {
346+
int32 affected_projects_count = 1;
347+
int32 affected_components_count = 2;
348+
int32 new_violations_count = 3;
349+
map<string, int32> new_violations_count_by_type = 4;
350+
int32 suppressed_new_violations_count = 5;
351+
int32 total_new_violations_count = 6;
352+
}
353+
354+
message ProjectSummaryEntry {
355+
Project project = 1;
356+
map<string, int32> new_violations_count_by_type = 2;
357+
map<string, int32> suppressed_new_violations_count_by_type = 3;
358+
map<string, int32> total_new_violations_count_by_type = 4;
359+
}
360+
361+
message ProjectViolationsEntry {
362+
Project project = 1;
363+
repeated Violation violations = 2;
364+
}
365+
366+
message Violation {
367+
string uuid = 1;
368+
Component component = 2;
369+
PolicyCondition policy_condition = 3;
370+
string type = 4;
371+
google.protobuf.Timestamp timestamp = 5;
372+
optional string analysis_state = 6;
373+
bool suppressed = 7;
374+
}
375+
}

0 commit comments

Comments
 (0)