Skip to content

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.

Checked against config-schemas@v1.15.0, terraform-provider-resolve@82cb23c, cli@v4.9.1

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 with vulnetix config plan: it shows each object as create, update, delete or no change, with a planHash that apply then holds to. Apply writes in one transaction.
  • Manager. Each object records the manager that applied it (terraform, console or gitops:<repo>). --prune deletes only objects its own manager owns. See The manager and the config engine.
  • Order. Applied after MessagingConnection and MessagingTemplate, so a document that refers to one by metadata.name can 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.

Edit this pageSources: config-schemas/schemas/MessagingRoute.schema.json, config-schemas/schemas/common.schema.json, config-schemas/examples/MessagingRoute/minimal.yaml, config-schemas/examples/MessagingRoute/advanced.yaml, terraform-provider-resolve/examples/resources/vulnetix_messaging_route/minimal.tf, terraform-provider-resolve/examples/resources/vulnetix_messaging_route/advanced.tf, vdb-site/docs/platform-iac-engine.md, vdb-site/api/internal/handler/config_engine_kinds.go, vdb-site/api/internal/config/messagingroute.go
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.