Skip to content

Commit 0c46b1b

Browse files
committed
Add ADR-017: Notification Filter Expressions
Signed-off-by: nscuro <nscuro@protonmail.com>
1 parent fc94153 commit 0c46b1b

File tree

2 files changed

+94
-0
lines changed

2 files changed

+94
-0
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
| Status | Date | Author(s) |
2+
|:---------|:-----------|:-------------------------------------|
3+
| Accepted | 2026-04-17 | [@nscuro](https://github.com/nscuro) |
4+
5+
## Context
6+
7+
Users have long requested the ability to filter notifications based on their content. For example, an organization
8+
may only want to receive notifications for newly discovered vulnerabilities of `CRITICAL` or `HIGH` severity,
9+
or only for components matching a certain name pattern. The existing notification rule model supports filtering
10+
by project and tags, but has no mechanism for filtering by the properties of the notification itself.
11+
12+
One approach would be to add dedicated filter fields to notification rules, such as a severity threshold dropdown
13+
or a component name input. This does not scale. Every new filter would require a schema migration, API changes,
14+
and corresponding UI work. As the number of filterable properties grows, the rule model and UI become increasingly
15+
complex and rigid.
16+
17+
Another approach would be to extend the existing project and tag filters to cover content-level properties.
18+
This would conflate two fundamentally different concerns. Project and tag filters control which projects a rule applies
19+
to. Content filters control which notifications within those projects are relevant. Mixing the two makes the
20+
filtering logic harder to reason about and harder to extend.
21+
22+
What is needed is a mechanism that is expressive enough to cover arbitrary notification properties, does not
23+
require code changes when new properties are added, and is safe to evaluate in a high-throughput notification
24+
pipeline.
25+
26+
## Decision
27+
28+
We will allow users to attach a [CEL] (Common Expression Language) filter expression to notification rules.
29+
When a notification matches a rule, the filter expression is evaluated against the notification.
30+
If it evaluates to `true`, the notification is dispatched. If it evaluates to `false`, it is suppressed.
31+
32+
CEL is a natural fit for this use case. Dependency-Track's notifications are defined as [Protocol Buffer] messages,
33+
and CEL has native support for Protobuf types. This means that all fields of a notification and its subject are
34+
automatically available in expressions without any additional mapping or configuration. When new fields are added
35+
to the notification schema, they become available in filter expressions immediately. CEL is already used in the
36+
project for vulnerability policies, so it is a known quantity in terms of integration and operational behavior.
37+
The language is designed to be fast, safe (no side effects, no I/O, no unbounded loops), and well-specified.
38+
39+
The following variables are available in filter expressions:
40+
41+
- `level`, `scope`, `group` for the notification's level, scope, and group (as integer enum values, with named constants like `Level.LEVEL_INFORMATIONAL`)
42+
- `title` and `content` for the notification's title and content strings
43+
- `timestamp` for the notification's timestamp
44+
- `subject` for the notification's subject, typed according to the notification group (e.g. `NewVulnerabilitySubject` for `GROUP_NEW_VULNERABILITY`)
45+
46+
Example expressions:
47+
48+
- `subject.vulnerability.severity == "CRITICAL"` to only receive critical vulnerabilities.
49+
- `subject.project.name.startsWith("acme-")` to filter by project name prefix.
50+
- `"CRITICAL" in subject.overview.new_vulnerabilities_count_by_severity` to filter scheduled vulnerability summaries.
51+
52+
Filter expressions are optional. Rules without a filter expression behave exactly as before, preserving full
53+
backward compatibility.
54+
55+
Expressions are validated at save time. When a user creates or updates a notification rule with a filter
56+
expression, the expression is compiled and checked against the CEL type environment. If the expression is
57+
invalid, the API returns an [RFC 9457] problem details response with precise error locations (line, column,
58+
message). This fail-fast approach prevents users from saving expressions that would never evaluate successfully.
59+
60+
At dispatch time, if a filter expression fails to evaluate due to a runtime error, the rule matches the
61+
notification. This fail-open strategy ensures that a misconfigured expression causes over-notification rather
62+
than silent suppression. For a security system, failing to deliver a notification is worse than delivering
63+
one too many.
64+
65+
Project and tag filtering is applied before filter expression evaluation. This ordering ensures that the cheaper
66+
filter is executed first, and that project-level access restrictions are enforced regardless of what the
67+
expression says.
68+
69+
Compiled CEL programs are cached in memory (up to 256 entries with a TTL of one hour) to avoid repeated compilation
70+
of the same expression across notification dispatches. The cache is keyed by expression text, so updating an
71+
expression naturally invalidates the previous cache entry.
72+
73+
## Consequences
74+
75+
Users can now filter notifications by any property of the notification or its subject without requiring code
76+
changes, schema migrations, or UI updates. This covers the use cases described in
77+
[DependencyTrack/dependency-track#3767](https://github.com/DependencyTrack/dependency-track/issues/3767)
78+
and similar requests.
79+
80+
Filter expressions are a power-user feature. Users need to understand CEL syntax and the structure of
81+
notification Protobuf messages to write effective expressions. Documentation and example expressions will be
82+
important for adoption.
83+
84+
The fail-open strategy means that broken expressions will not silently suppress notifications. Operators will
85+
see warnings in logs when expressions fail to evaluate, giving them the opportunity to fix the expression
86+
without missing notifications in the meantime.
87+
88+
The expression length is capped at 2048 characters to prevent abuse, which is sufficient for any reasonable
89+
filter condition.
90+
91+
[CEL]: https://cel.dev
92+
[Protocol Buffer]: https://protobuf.dev
93+
[RFC 9457]: https://www.rfc-editor.org/rfc/rfc9457

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ nav:
113113
- "ADR-014: New Alias Schema": architecture/decisions/014-new-alias-schema.md
114114
- "ADR-015: Package Metadata": architecture/decisions/015-package-metadata.md
115115
- "ADR-016: User-Managed Vulnerability Policies": architecture/decisions/016-user-managed-vuln-policies.md
116+
- "ADR-017: Notification Filter Expressions": architecture/decisions/017-notification-filter-expressions.md
116117
- Design:
117118
- Durable Execution: architecture/design/durable-execution.md
118119
- Notifications: architecture/design/notifications.md

0 commit comments

Comments
 (0)