Files
git.stella-ops.org/docs/notifications/templates.md
StellaOps Bot f43e828b4e
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
feat: Implement MongoDB orchestrator storage with registry, commands, and heartbeats
- Added NullAdvisoryObservationEventTransport for handling advisory observation events.
- Created IOrchestratorRegistryStore interface for orchestrator registry operations.
- Implemented MongoOrchestratorRegistryStore for MongoDB interactions with orchestrator data.
- Defined OrchestratorCommandDocument and OrchestratorCommandRecord for command handling.
- Added OrchestratorHeartbeatDocument and OrchestratorHeartbeatRecord for heartbeat tracking.
- Created OrchestratorRegistryDocument and OrchestratorRegistryRecord for registry management.
- Developed tests for orchestrator collections migration and MongoOrchestratorRegistryStore functionality.
- Introduced AirgapImportRequest and AirgapImportValidator for air-gapped VEX bundle imports.
- Added incident mode rules sample JSON for notifier configuration.
2025-11-22 12:35:38 +02:00

12 KiB

Notifications Templates

Imposed rule: Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.

Templates shape the payload rendered for each channel when a rule action fires. They are deterministic, locale-aware artefacts stored alongside rules so Notify.Worker replicas can render identical messages regardless of environment.


1. Template lifecycle

  1. Authoring. Operators create templates via the API (POST /templates) or UI. Each template binds to a channel type (Slack, Teams, Email, Webhook, Custom) and a locale.
  2. Reference. Rule actions opt in by referencing the template key (actions[].template). Channel defaults apply when no template is specified.
  3. Rendering. During delivery, the worker resolves the template (locale fallbacks included), executes it using the safe Handlebars-style engine, and passes the rendered payload plus metadata to the connector.
  4. Audit. Rendered payloads stored in the delivery ledger include the templateId so operators can trace which text was used.

2. Template schema reference

Field Type Notes
templateId string Stable identifier (UUID/slug).
tenantId string Must match the tenant header in API calls.
channelType NotifyChannelType Determines connector payload envelope.
key string Human-readable key referenced by rules (tmpl-critical).
locale string BCP-47 tag, stored lower-case (en-us, bg-bg).
body string Template body; rendered strictly without executing arbitrary code.
renderMode enum Markdown, Html, AdaptiveCard, PlainText, or Json. Guides connector sanitisation.
format enum Slack, Teams, Email, Webhook, or Json. Signals delivery payload structure.
description string? Optional operator note.
metadata map<string,string> Sorted map for automation (layout hints, fallback text).
createdBy/createdAt string?, instant Auto-populated.
updatedBy/updatedAt string?, instant Auto-populated.
schemaVersion string Auto-upgraded on persistence.

Templates are normalised: string fields trimmed, locale lower-cased, metadata sorted to preserve determinism.


3. Variables, helpers, and context

Templates receive a structured context derived from the Notify event, rule match, and rendering metadata.

Path Description
event.* Canonical event envelope (kind, tenant, ts, actor).
event.scope.* Namespace, repository, digest, image, component identifiers, labels, attributes.
payload.* Raw event payload (e.g., payload.verdict, payload.delta.*, payload.links.*).
rule.* Rule descriptor (ruleId, name, labels, metadata).
action.* Action descriptor (actionId, channel, digest, throttle, metadata).
policy.* Policy metadata when supplied (revisionId, name).
topFindings[] Top-N findings summarised for convenience (vulnerability ID, severity, reachability).
digest.* When rendering digest flushes: window, openedAt, itemCount.

Built-in helpers mirror the architecture dossier:

Helper Usage
severity_icon severity Returns emoji/text badge representing severity.
link text url Produces channel-safe hyperlink.
pluralize count "finding" Adds plural suffix when count != 1.
truncate text maxLength Cuts strings while preserving determinism.
code text Formats inline code (Markdown/HTML aware).

Connectors may expose additional helpers via partials, but must remain deterministic and side-effect free.


4. Sample templates

4.1 Slack (Markdown + block kit)

{{#*inline "findingLine"}}
- {{severity_icon severity}} {{vulnId}} ({{severity}}) in `{{component}}`
{{/inline}}

*:rotating_light: {{payload.summary.total}} findings {{#if payload.delta.newCritical}}(new critical: {{payload.delta.newCritical}}){{/if}}*

{{#if topFindings.length}}
Top findings:
{{#each topFindings}}{{> findingLine}}{{/each}}
{{/if}}

{{link "Open report in Console" payload.links.ui}}

4.2 Email (HTML + text alternative)

<h2>{{payload.verdict}} for {{event.scope.repo}}</h2>
<p>{{payload.summary.total}} findings ({{payload.summary.blocked}} blocked, {{payload.summary.warned}} warned)</p>
<table>
  <thead><tr><th>Finding</th><th>Severity</th><th>Package</th></tr></thead>
  <tbody>
    {{#each topFindings}}
    <tr>
      <td>{{this.vulnId}}</td>
      <td>{{this.severity}}</td>
      <td>{{this.component}}</td>
    </tr>
    {{/each}}
  </tbody>
</table>
<p>{{link "View full analysis" payload.links.ui}}</p>

When delivering via email, connectors automatically attach a plain-text alternative derived from the rendered content to preserve accessibility.


5. Preview and validation

  • POST /channels/{id}/test accepts an optional templateId and sample payload to produce a rendered preview without dispatching the event. Results include channel type, target, title/summary, locale, body hash, and connector metadata.
  • UI previews rely on the same API and highlight connector fallbacks (e.g., Teams adaptive card vs. text fallback).
  • Offline Kit scenarios can call /internal/notify/templates/normalize to ensure bundled templates match the canonical schema before packaging.

6. Best practices

  • Keep channel-specific limits in mind (Slack block/character quotas, Teams adaptive card size, email line length). Lean on digests to summarise long lists.
  • Provide locale-specific versions for high-volume tenants; Notify selects the closest locale, falling back to en-us.
  • Store connector-specific hints (metadata.layout, metadata.emoji) in template metadata rather than rules when they affect rendering.
  • Version template bodies through metadata (e.g., metadata.revision: "2025-10-28") so tenants can track changes over time.
  • Run test previews whenever introducing new helpers to confirm body hashes remain stable across environments.

7. Attestation & signing lifecycle templates (NOTIFY-ATTEST-74-001)

Attestation lifecycle events (verification failures, expiring attestations, key revocations, transparency anomalies) reuse the same structural context so operators can differentiate urgency while reusing channels. Every template must surface:

  • Subject (payload.subject.digest, payload.subject.repository, payload.subject.tag).
  • Attestation metadata (payload.attestation.kind, payload.attestation.id, payload.attestation.issuedAt, payload.attestation.expiresAt).
  • Signer/Key fingerprint (payload.signer.kid, payload.signer.algorithm, payload.signer.rotationId).
  • Traceability (payload.links.console, payload.links.rekor, payload.links.docs).

7.1 Template keys & channels

Event Template key Required channels Optional channels Notes
Verification failure (attestor.verification.failed) tmpl-attest-verify-fail Slack sec-alerts, Email supply-chain@, Webhook (Pager/SOC) Teams risk-war-room, Custom SIEM feed Include failure code, Rekor UUID, last-known good attestation link.
Expiring attestation (attestor.attestation.expiring) tmpl-attest-expiry-warning Email summary, Slack reminder Digest window (daily) Provide expiration window, renewal instructions, expiresIn helper.
Key revocation/rotation (authority.keys.revoked, authority.keys.rotated) tmpl-attest-key-rotation Email + Webhook Slack (if SOC watches channel) Add rotation batch ID, impacted tenants/services, remediation steps.
Transparency anomaly (attestor.transparency.anomaly) tmpl-attest-transparency-anomaly Slack high-priority, Webhook, PagerDuty Email follow-up Show Rekor index delta, witness ID, anomaly classification, recommended actions.

Assign these keys when creating templates so rule actions can reference them deterministically (actions[].template: "tmpl-attest-verify-fail").

7.2 Context helpers

  • attestation_status_badge status: renders /⚠️/ depending on verdict (valid, expiring, failed).
  • expires_in expiresAt now: returns human-readable duration, constrained to deterministic units (h/d).
  • fingerprint key: shortens long key IDs/pems, exposing the last 10 characters.

7.3 Slack sample (verification failure)

:rotating_light: {{attestation_status_badge payload.failure.status}} verification failed for `{{payload.subject.digest}}`
Signer: `{{fingerprint payload.signer.kid}}` ({{payload.signer.algorithm}})
Reason: `{{payload.failure.reasonCode}}` — {{payload.failure.reason}}
Last valid attestation: {{link "Console report" payload.links.console}}
Rekor entry: {{link "Transparency log" payload.links.rekor}}

7.4 Email sample (expiring attestation)

<h2>Attestation expiry notice</h2>
<p>The attestation for <code>{{payload.subject.repository}}</code> (digest {{payload.subject.digest}}) expires on <strong>{{payload.attestation.expiresAt}}</strong>.</p>
<ul>
  <li>Issued: {{payload.attestation.issuedAt}}</li>
  <li>Signer: {{payload.signer.kid}} ({{payload.signer.algorithm}})</li>
  <li>Time remaining: {{expires_in payload.attestation.expiresAt event.ts}}</li>
</ul>
<p>Please rotate the attestation before expiry. Reference <a href="{{payload.links.docs}}">renewal steps</a>.</p>

7.5 Webhook sample (transparency anomaly)

{
  "event": "attestor.transparency.anomaly",
  "tenantId": "{{event.tenant}}",
  "subjectDigest": "{{payload.subject.digest}}",
  "rekorIndex": "{{payload.transparency.rekorIndex}}",
  "witnessId": "{{payload.transparency.witnessId}}",
  "anomaly": "{{payload.transparency.classification}}",
  "detailsUrl": "{{payload.links.console}}",
  "recommendation": "{{payload.recommendation}}"
}

7.6 Offline kit guidance

  • Bundle these templates (JSON export) under offline/notifier/templates/attestation/.
  • Baseline English templates for Slack, Email, and Webhook ship in the repository at offline/notifier/templates/attestation/*.template.json; copy and localise them per tenant as needed.
  • Provide localized variants for en-us and de-de at minimum; additional locales can be appended per customer.
  • Include preview fixtures in Offline Kit smoke tests to guarantee channel render parity when air-gapped.

Imposed rule reminder: Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.


8. Incident mode templates (NOTIFY-OBS-55-001)

Incident toggles are high-noise events that must pierce quiet hours and include audit-ready context. Use dedicated templates so downstream tooling can distinguish activation vs. recovery and surface the required evidence.

Required context keys

  • payload.incidentId, payload.reason, payload.startedAt / payload.stoppedAt.
  • payload.links.trace (root cause trace/span), payload.links.evidence (timeline/export bundle), payload.links.timeline.
  • payload.retentionDays (active) and payload.retentionBaselineDays (post-incident).
  • payload.quietHoursOverride (boolean) to justify bypassing quiet hours.
  • payload.legal.jurisdiction, payload.legal.ticket, payload.legal.logPath for compliance logging.

Template keys

  • tmpl-incident-start — activation notice.
  • tmpl-incident-stop — recovery/cleanup notice.

Slack sample (start)

:rotating_light: Incident mode activated for {{payload.incidentId}}
Reason: {{payload.reason}}
Trace: {{link "root span" payload.links.trace}} · Evidence: {{link "bundle" payload.links.evidence}}
Retention extended to {{payload.retentionDays}} days (baseline {{payload.retentionBaselineDays}})
Quiet hours overridden: {{payload.quietHoursOverride}}
Legal: {{payload.legal.jurisdiction}} (ticket {{payload.legal.ticket}})

Email sample (stop)

<h2>Incident mode cleared: {{payload.incidentId}}</h2>
<p>Stopped at {{payload.stoppedAt}} — retention reset to {{payload.retentionBaselineDays}} days.</p>
<p>Timeline: {{link "view timeline" payload.links.timeline}} · Audit log: {{payload.legal.logPath}}</p>

See src/Notifier/StellaOps.Notifier/docs/incident-mode-rules.sample.json for ready-to-import rules referencing these templates with quiet-hour overrides and legal logging metadata.