MessagingRoute
A messaging workflow: which events go to which messaging connections, filtered how, rendered with which template, and how often. Fields, YAML, HCL and CLI samples, and how apply treats it.
A messaging workflow: which events go to which messaging connections, filtered how, rendered with which template, and how often. Every event the organisation’s producers emit is matched against its routes; a route that takes it renders its template once per target and records each delivery in the console’s delivery log. A route whose event types reach a sensitive type (security., vault., or a sensitive Pix event) is marked sensitive: it may then send to a signed webhook only with allowSensitiveWebhook, and to a GitHub repository only when the repository is private. Each change is recorded as a messaging.route.changed event, as a console edit is. Deleting it stops its deliveries; the delivery log is kept.
apiVersion |
vulnetix.com/v1 |
kind |
MessagingRoute |
| Group | Integrations |
| Status | Served. The config engine plans and applies it. |
| Objects per tenant | many, keyed by metadata.name |
| Token and role | An owner or admin session, or a vmt_ config management token with the owner or admin role that lists MessagingRoute in its kinds. See Management tokens. |
| Applied after | MessagingConnection, MessagingTemplate (deleted before them) |
| Terraform resource | vulnetix_messaging_route, imported by name (see Terraform and OpenTofu) |
| CLI | vulnetix config apply, CLI v3.168.0 and later. |
| Pinned schema | v1.15.0/MessagingRoute.schema.json |
| Newest schema | latest/MessagingRoute.schema.json |
| First released in | v1.5.0 |
| Print the built-in copy | vulnetix config schema MessagingRoute |
Minimal
Only the fields the kind requires. From examples/MessagingRoute/minimal.yaml.
YAML
# yaml-language-server: $schema=https://github.com/Vulnetix/config-schemas/releases/latest/download/MessagingRoute.schema.json
apiVersion: vulnetix.com/v1
kind: MessagingRoute
metadata:
name: broken-builds
spec:
eventTypes:
- scan.build_broken
HCL (resource vulnetix_messaging_route)
# Only the arguments the kind requires. From config-schemas examples/MessagingRoute/minimal.yaml.
resource "vulnetix_messaging_route" "broken_builds" {
name = "broken-builds"
event_types = ["scan.build_broken"]
}
CLI (applies it)
export VULNETIX_CONFIG_TOKEN=vmt_... # a config management token
vulnetix config apply -f - --manager gitops:acme/platform --auto-approve <<'EOF'
# yaml-language-server: $schema=https://github.com/Vulnetix/config-schemas/releases/latest/download/MessagingRoute.schema.json
apiVersion: vulnetix.com/v1
kind: MessagingRoute
metadata:
name: broken-builds
spec:
eventTypes:
- scan.build_broken
EOF
Advanced
Every field the kind supports, with a when expression where it takes one. From examples/MessagingRoute/advanced.yaml.
YAML
# yaml-language-server: $schema=https://github.com/Vulnetix/config-schemas/releases/latest/download/MessagingRoute.schema.json
# The connections and the template are declared in MessagingConnection and
# MessagingTemplate documents of the same apply.
apiVersion: vulnetix.com/v1
kind: MessagingRoute
metadata:
name: critical-findings
labels:
team: security
annotations:
runbook: https://wiki.example.com/security/alerts
spec:
displayName: Critical findings to security
enabled: true
eventTypes:
- scan.completed
- scan.build_broken
minSeverity: warning
filters:
scope:
appName: payments-api
appVersion: 2.4.0
uploadUuid: 0b6f6c1e-8f4a-4c55-9a0e-3c2d1b7a9e10
repo: acme/payments-api
findings:
cvssThreshold: 7
cvssSeverities: [critical, high]
epssThreshold: 0.1
cessThreshold: 5
isMalware: false
isExploitable: true
hasExploits: [functional, weaponized, in_the_wild]
exploitThreshold: 1
isEol: false
eolDays: 90
isReachable: true
ignoreTest: true
onlyProduction: true
onlyDirect: false
newOnly: true
actorFilter:
mode: exclude
principals:
- 7d3f2a10-4b1c-4e8a-9f20-6a5b4c3d2e1f
kinds:
- service_account
mentionPrincipals:
- 2c9e8d7f-1a2b-4c3d-8e4f-5a6b7c8d9e0f
mentionStatic:
slack: [U0123ABCD]
discord: ["123456789012345678"]
google_chat: ["112233445566"]
teams: [secops@acme.example]
jira: ["5b10ac8d82e05b22cc7d4ef5"]
github: [octocat]
template: security-findings
templateVersion: 1
digest:
mode: immediate
minutes: 15
tiers:
needs_you: instant
error: instant
warning: digest
info: "off"
maxPerHour: 120
includePII: false
allowSensitiveWebhook: false
mode: live
targets:
- connection: security-slack
enabled: true
options:
channelId: C0123456789
- connection: appsec-jira
options:
projectKey: SEC
issueType: Bug
labels: [vulnetix, critical]
- connection: github-acme
options:
owner: acme
repo: security-alerts
labels: [vulnetix]
assignMentions: true
---
apiVersion: vulnetix.com/v1
kind: MessagingRoute
metadata:
name: nightly-digest
spec:
eventTypes: ["pix.*"]
systemTemplate: generic
digest:
mode: daily
hour: 8
tz: Australia/Sydney
mode: shadow
HCL (resource vulnetix_messaging_route)
# Every argument the kind supports. From config-schemas examples/MessagingRoute/advanced.yaml.
resource "vulnetix_messaging_route" "critical_findings" {
name = "critical-findings"
actor_filter = {
kinds = ["service_account"]
mode = "exclude"
principals = ["7d3f2a10-4b1c-4e8a-9f20-6a5b4c3d2e1f"]
}
allow_sensitive_webhook = false
digest = {
minutes = 15
mode = "immediate"
tiers = {
error = "instant"
info = "off"
needs_you = "instant"
warning = "digest"
}
}
display_name = "Critical findings to security"
enabled = true
event_types = ["scan.completed", "scan.build_broken"]
filters = {
findings = {
cess_threshold = 5
cvss_severities = ["critical", "high"]
cvss_threshold = 7
eol_days = 90
epss_threshold = 0.1
exploit_threshold = 1
has_exploits = ["functional", "weaponized", "in_the_wild"]
ignore_test = true
is_eol = false
is_exploitable = true
is_malware = false
is_reachable = true
new_only = true
only_direct = false
only_production = true
}
scope = {
app_name = "payments-api"
app_version = "2.4.0"
repo = "acme/payments-api"
upload_uuid = "0b6f6c1e-8f4a-4c55-9a0e-3c2d1b7a9e10"
}
}
include_pii = false
max_per_hour = 120
mention_principals = ["2c9e8d7f-1a2b-4c3d-8e4f-5a6b7c8d9e0f"]
mention_static = {
discord = ["123456789012345678"]
github = ["octocat"]
google_chat = ["112233445566"]
jira = ["5b10ac8d82e05b22cc7d4ef5"]
slack = ["U0123ABCD"]
teams = ["secops@acme.example"]
}
min_severity = "warning"
mode = "live"
targets = [
{
connection = "security-slack"
enabled = true
options = {
channel_id = "C0123456789"
}
},
{
connection = "appsec-jira"
options = {
issue_type = "Bug"
labels = ["vulnetix", "critical"]
project_key = "SEC"
}
},
{
connection = "github-acme"
options = {
assign_mentions = true
labels = ["vulnetix"]
owner = "acme"
repo = "security-alerts"
}
},
]
template = "security-findings"
template_version = 1
labels = {
team = "security"
}
annotations = {
runbook = "https://wiki.example.com/security/alerts"
}
}
resource "vulnetix_messaging_route" "nightly_digest" {
name = "nightly-digest"
digest = {
hour = 8
mode = "daily"
tz = "Australia/Sydney"
}
event_types = ["pix.*"]
mode = "shadow"
system_template = "generic"
}
CLI (applies it)
export VULNETIX_CONFIG_TOKEN=vmt_... # a config management token
vulnetix config apply -f - --manager gitops:acme/platform --auto-approve <<'EOF'
# yaml-language-server: $schema=https://github.com/Vulnetix/config-schemas/releases/latest/download/MessagingRoute.schema.json
# The connections and the template are declared in MessagingConnection and
# MessagingTemplate documents of the same apply.
apiVersion: vulnetix.com/v1
kind: MessagingRoute
metadata:
name: critical-findings
labels:
team: security
annotations:
runbook: https://wiki.example.com/security/alerts
spec:
displayName: Critical findings to security
enabled: true
eventTypes:
- scan.completed
- scan.build_broken
minSeverity: warning
filters:
scope:
appName: payments-api
appVersion: 2.4.0
uploadUuid: 0b6f6c1e-8f4a-4c55-9a0e-3c2d1b7a9e10
repo: acme/payments-api
findings:
cvssThreshold: 7
cvssSeverities: [critical, high]
epssThreshold: 0.1
cessThreshold: 5
isMalware: false
isExploitable: true
hasExploits: [functional, weaponized, in_the_wild]
exploitThreshold: 1
isEol: false
eolDays: 90
isReachable: true
ignoreTest: true
onlyProduction: true
onlyDirect: false
newOnly: true
actorFilter:
mode: exclude
principals:
- 7d3f2a10-4b1c-4e8a-9f20-6a5b4c3d2e1f
kinds:
- service_account
mentionPrincipals:
- 2c9e8d7f-1a2b-4c3d-8e4f-5a6b7c8d9e0f
mentionStatic:
slack: [U0123ABCD]
discord: ["123456789012345678"]
google_chat: ["112233445566"]
teams: [secops@acme.example]
jira: ["5b10ac8d82e05b22cc7d4ef5"]
github: [octocat]
template: security-findings
templateVersion: 1
digest:
mode: immediate
minutes: 15
tiers:
needs_you: instant
error: instant
warning: digest
info: "off"
maxPerHour: 120
includePII: false
allowSensitiveWebhook: false
mode: live
targets:
- connection: security-slack
enabled: true
options:
channelId: C0123456789
- connection: appsec-jira
options:
projectKey: SEC
issueType: Bug
labels: [vulnetix, critical]
- connection: github-acme
options:
owner: acme
repo: security-alerts
labels: [vulnetix]
assignMentions: true
---
apiVersion: vulnetix.com/v1
kind: MessagingRoute
metadata:
name: nightly-digest
spec:
eventTypes: ["pix.*"]
systemTemplate: generic
digest:
mode: daily
hour: 8
tz: Australia/Sydney
mode: shadow
EOF
Fields
Paths use dots for nesting and [] for the items of a list. Required means required whenever the parent object is present. A default is what the server uses when the field is left out.
| Field | Type | Required | Default | Allowed values | Description |
|---|---|---|---|---|---|
apiVersion |
string | yes | exactly vulnetix.com/v1 |
The document format version. Every kind in this release is vulnetix.com/v1. | |
kind |
string | yes | exactly MessagingRoute |
Always MessagingRoute. | |
metadata |
object | yes | no other keys | ||
metadata.name |
string | yes | pattern ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$ |
The object’s name, unique per kind within a tenant. It is the natural key: plan, apply, export and the Terraform provider all address an object by kind and name. | |
metadata.org |
string | no | format uuid |
The tenant (organization UUID) the document belongs to. When set, plan and apply refuse to run against any other tenant. | |
metadata.labels |
object | no | map of string to string (length 0 to 256); keys match ^[a-z0-9]([a-z0-9._/-]{0,61}[a-z0-9])?$ |
Free key/value labels. Stored with the object, never interpreted by the server. | |
metadata.annotations |
object | no | map of string to string (length 0 to 4096) | Free key/value notes for people and tools, such as a ticket link. | |
spec |
object | yes | no other keys | ||
spec.displayName |
string | no | length 1 to 120 | The name the console shows. Defaults to metadata.name. | |
spec.enabled |
boolean | no | true |
A disabled route matches nothing. | |
spec.eventTypes |
array of string | yes | 1 to 50 items; items unique; each item: pattern ^(\*|[a-z0-9_]+(\.[a-z0-9_]+)*(\.\*)?)$; length 0 to 128 |
The event types the route takes: a type (scan.completed), a family (pix.*), or * for every event. A type that was renamed is stored as its new name. | |
spec.minSeverity |
string | no | info |
info, notice, warning, critical |
The quietest event severity the route takes. |
spec.filters |
object | no | no other keys | Narrows the route to one scope, and for scan events to the findings that pass the thresholds. An unset condition lets everything through. | |
spec.filters.scope |
object | no | no other keys | Only events about this application, version, upload or repository. Each field that is set must match the event. | |
spec.filters.scope.appName |
string | no | length 1 to 256 | The application name. | |
spec.filters.scope.appVersion |
string | no | length 1 to 256 | The application version. | |
spec.filters.scope.uploadUuid |
string | no | format uuid |
One upload. | |
spec.filters.scope.repo |
string | no | pattern ^[^\s/]+(/[^\s/]+)+$ |
One repository, owner/name. | |
spec.filters.findings |
object | no | no other keys | Thresholds a scan event’s findings must pass. The event is sent with the findings that pass, and not at all when none do. | |
spec.filters.findings.cvssThreshold |
number | no | 0 to 10 | The lowest CVSS base score. | |
spec.filters.findings.cvssSeverities |
array of string | no | items unique; each item: critical, high, medium, low, none |
Only these CVSS severities. | |
spec.filters.findings.epssThreshold |
number | no | 0 to 1 | The lowest EPSS probability. | |
spec.filters.findings.cessThreshold |
number | no | 0 to 10 | The lowest CESS score. | |
spec.filters.findings.isMalware |
boolean | no | Only malware (true) or only not malware (false). | ||
spec.filters.findings.isExploitable |
boolean | no | Only exploitable (true) or only not exploitable (false). | ||
spec.filters.findings.hasExploits |
array of string | no | items unique; each item: poc, functional, weaponized, in_the_wild |
Only findings whose exploit maturity is one of these. | |
spec.filters.findings.exploitThreshold |
integer | no | >= 0 | The fewest known exploits. | |
spec.filters.findings.isEol |
boolean | no | Only end-of-life packages (true) or only supported ones (false). | ||
spec.filters.findings.eolDays |
integer | no | >= 0 | Only packages that reach end of life within this many days. | |
spec.filters.findings.isReachable |
boolean | no | Only reachable (true) or only unreachable (false) code. | ||
spec.filters.findings.ignoreTest |
boolean | no | false |
Leave out test dependencies. | |
spec.filters.findings.onlyProduction |
boolean | no | false |
Only production dependencies. | |
spec.filters.findings.onlyDirect |
boolean | no | false |
Only direct dependencies. | |
spec.filters.findings.newOnly |
boolean | no | false |
Only findings new in this scan. | |
spec.actorFilter |
object | no | no other keys | Narrows the route by who acted. | |
spec.actorFilter.mode |
string | no | any |
any, include, exclude |
any: everyone. include: only the principals and actor kinds listed. exclude: everyone but them. |
spec.actorFilter.principals |
array of string | no | 0 to 100 items; items unique; each item: length 1 to 128 | Principal uuids. At most 100. | |
spec.actorFilter.kinds |
array of string | no | 0 to 10 items; items unique; each item: length 1 to 64 | Actor kinds, such as principal, service_account, agent or system. At most 10. | |
spec.mentionPrincipals |
array of string | no | 0 to 25 items; items unique; each item: format uuid |
Members of the organisation, by principal uuid, to @-mention on every target that supports mentions. Each is mentioned by the identity the messaging centre holds for them on that provider. At most 25. | |
spec.mentionStatic |
object | no | no other keys | Provider user ids to @-mention that are not members, keyed by provider: a Slack user id (U0123ABCD), a Discord user id, a Google Chat user id, a Teams user id or UPN, a Jira account id, or a GitHub login. | |
spec.mentionStatic.slack |
array of string | no | 0 to 25 items; items unique; each item: length 1 to 128 | ||
spec.mentionStatic.discord |
array of string | no | 0 to 25 items; items unique; each item: length 1 to 128 | ||
spec.mentionStatic.google_chat |
array of string | no | 0 to 25 items; items unique; each item: length 1 to 128 | ||
spec.mentionStatic.teams |
array of string | no | 0 to 25 items; items unique; each item: length 1 to 128 | ||
spec.mentionStatic.jira |
array of string | no | 0 to 25 items; items unique; each item: length 1 to 128 | ||
spec.mentionStatic.github |
array of string | no | 0 to 25 items; items unique; each item: length 1 to 128 | ||
spec.template |
string | no | pattern ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$ |
The metadata.name of a MessagingTemplate to render with. Leave it and systemTemplate out to use the system template for each event type. | |
spec.templateVersion |
integer | no | >= 1 | Pin the template to one of its versions. Left out, the route follows the template’s latest version. Only with template. | |
spec.systemTemplate |
string | no | pattern ^[a-z0-9_.]{1,64}$ |
The key of a system template to render every event with (generic, notice, findings.report and so on), instead of the one each event type has. Not with template. | |
spec.digest |
object | no | no other keys | How events are batched. immediate sends each event as it comes; window sends one digest every 15 or 60 minutes; daily sends one digest a day at hour in tz. tiers overrides that per delivery tier. | |
spec.digest.mode |
string | no | immediate |
immediate, window, daily |
|
spec.digest.minutes |
integer | no | 15, 60 |
The window, for mode window, or for an immediate route with a tier set to digest (60 when left out). | |
spec.digest.hour |
integer | no | 0 to 23 | The hour of a daily digest, 0 to 23. | |
spec.digest.tz |
string | no | length 1 to 64 | The IANA time zone of a daily digest, such as Australia/Sydney. | |
spec.digest.tiers |
object | no | no other keys | Per delivery tier: instant, digest or off. A tier left out follows mode. | |
spec.digest.tiers.needs_you |
string | no | instant, digest, off |
||
spec.digest.tiers.error |
string | no | instant, digest, off |
||
spec.digest.tiers.warning |
string | no | instant, digest, off |
||
spec.digest.tiers.info |
string | no | instant, digest, off |
||
spec.maxPerHour |
integer | no | 60 |
1 to 10000 | At most this many messages an hour per target; the rest are suppressed and logged. |
spec.includePII |
boolean | no | false |
Let personal details (a member’s email address) into messages. Off, they are masked. | |
spec.allowSensitiveWebhook |
boolean | no | false |
Let a sensitive route send to a signed webhook. | |
spec.mode |
string | no | live |
live, shadow |
live sends. shadow renders and logs every delivery without sending, to try a route out. |
spec.targets |
array of object | no | 0 to 20 items | Where the route sends: one entry per connection, each connection at most once. At most 20. | |
spec.targets[].connection |
string | yes | pattern ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$ |
The metadata.name of the MessagingConnection to send to. A GitHub Issues connection, which the GitHub App installation makes, is named as export names it. | |
spec.targets[].enabled |
boolean | no | true |
A disabled target is skipped. | |
spec.targets[].options |
object | no | no other keys | Per provider. slack: channelId overrides the connection’s channel. jira: projectKey and issueType override the connection’s defaults, labels are added. GitHub Issues: owner and repo (required), labels, assignMentions. Other providers take none. | |
spec.targets[].options.channelId |
string | no | length 0 to 200 | A Slack channel id. | |
spec.targets[].options.projectKey |
string | no | length 0 to 200 | A Jira project key. | |
spec.targets[].options.issueType |
string | no | length 0 to 200 | A Jira issue type. | |
spec.targets[].options.owner |
string | no | length 0 to 200 | The GitHub repository owner. | |
spec.targets[].options.repo |
string | no | length 0 to 200 | The GitHub repository name. | |
spec.targets[].options.labels |
array of string | no | 0 to 10 items; each item: length 1 to 100 | Labels to add to the issue. At most 10. | |
spec.targets[].options.assignMentions |
boolean | no | Assign the GitHub issue to the mentioned people. |
Rules
Conditions the schema enforces beyond each field’s own type. vulnetix config validate and the engine’s plan report a document that breaks one.
| On | Rule |
|---|---|
spec.digest |
When mode is window: minutes is required. |
spec.digest |
When mode is daily: hour is required and tz is required. |
Applying this kind
- Command.
vulnetix config apply -f <file>; the CLI line above says from which release. Plan first withvulnetix config plan: it shows each object as create, update, delete or no change, with aplanHashthat apply then holds to. Apply writes in one transaction. - Manager. Each object records the manager that applied it (
terraform,consoleorgitops:<repo>).--prunedeletes only objects its own manager owns. See The manager and the config engine. - Order. Applied after
MessagingConnectionandMessagingTemplate, so a document that refers to one bymetadata.namecan be in the same apply. Deleted before them. - Status. Applied, Unchanged or Deleted, with no other status fields. The phases the engine reports are explained under status phases.
How apply treats it
Connections are referenced by the metadata.name of a MessagingConnection. Plan blocks a connection the tenant does not have.
Terraform and OpenTofu
The resource is vulnetix_messaging_route in the vulnetix/resolve provider. Its arguments are the spec in snake case, beside name, labels and annotations from metadata. The Minimal and Advanced HCL are above. The provider’s own reference is vulnetix_messaging_route.
Import an object that already exists by its name:
# Import an existing MessagingRoute by its name (metadata.name).
terraform import vulnetix_messaging_route.broken_builds broken-builds
Install, authentication and how state is read: Terraform and OpenTofu.
Check a file with vulnetix config validate -f <file>. See Validate documents. In an editor, start the file with the schema comment.