Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- 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.
240 lines
12 KiB
Markdown
240 lines
12 KiB
Markdown
# 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`](../modules/notify/architecture.md#5-channels--connectors-plug-ins) | 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)
|
|
|
|
```hbs
|
|
{{#*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)
|
|
|
|
```hbs
|
|
<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)
|
|
|
|
```hbs
|
|
: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)
|
|
|
|
```hbs
|
|
<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)
|
|
|
|
```json
|
|
{
|
|
"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)**
|
|
```hbs
|
|
: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)**
|
|
```hbs
|
|
<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.
|