Outputs
Each policy can have multiple outputs controlling the format, destination, and presentation of results.
Configuration
outputs:
- format: markdown
destination: git:pr
template: table
fields: [VulnerabilityID, PkgName, Severity, FixedVersion]
changes: [new, changed]
group_by: [Severity]
combined: false
collapse: true
title: Security Issues
comment: Review required before merging
| Field | Required | Description |
|---|---|---|
format | Yes | markdown, html, json, or sarif |
destination | Yes | Where to send output |
template | No | table, text, or combined (not for JSON) |
fields | No | Fields to include (defaults per policy type) |
changes | No | Filter by change category: new, changed, removed, existing |
group_by | No | Group results by fields |
combined | No | Merge with other outputs at same destination |
collapse | No | Collapsible section (markdown/html <details> tags) |
title | No | Section title |
comment | No | Text below title |
webhook | No | Custom webhook config (only for url: destinations) |
Destinations
| Destination | Description |
|---|---|
git:pr / github:pr | Post as PR comment |
git:issue / github:issue | Create/update GitHub issue |
github:code-scanning | Upload SARIF to GitHub Code Scanning (Security tab) |
file:/path | Write to file |
log:stdout / log:stderr | Print to console |
url:https://... | HTTP request to webhook |
git:pr
Posts or updates a PR comment. Requires CODEWARD_MODE=diff and GitHub env vars.
Behavior: If body is empty, existing comment with matching title is deleted. If non-empty, replaces existing comment.
git:issue
Creates or updates a GitHub issue. Searches all states to avoid duplicates. Closed issues are reopened when content reappears.
file:
destination: "file:/results/vulnerabilities.json"
url:
HTTP request to a webhook endpoint. Content-Type auto-detected.
Retry: 3 attempts, 1s backoff. Retries on network errors and 5xx. No retry on 4xx.
Formats
| Format | Template Required |
|---|---|
markdown / md | Yes: table, text, or combined |
html | Yes: table, text, or combined |
json | No (template must be empty) |
sarif | No (template must be empty) |
Default Fields
When fields is omitted, defaults are applied per policy type:
| Policy Type | Default Fields |
|---|---|
| vulnerability | VulnerabilityID, Severity, PkgName, InstalledVersion, FixedVersion |
| license | Name, Severity, Category, PkgName |
| package | Name, Version, Relationship |
| file | Key, Value, Reason |
| pr | Key, Value, Reason |
Combining Outputs
Use combined: true to merge multiple policies into a single output:
vulnerability:
- name: critical
outputs:
- { format: markdown, destination: git:pr, combined: true }
license:
- name: gpl
outputs:
- { format: markdown, destination: git:pr, combined: true }
Produces a single PR comment with both sections.
| Mix | Allowed |
|---|---|
| Markdown + Markdown | ✅ |
| HTML + HTML | ✅ |
| JSON + JSON | ✅ |
| Markdown + HTML | ✅ (separate groups) |
| JSON + Markdown | ❌ Error |
Empty combined groups are suppressed entirely.
SARIF Output
Support for SARIF 2.1.0 (Static Analysis Results Interchange Format), enabling native integration with GitHub Security tab, VS Code Problems panel, and Azure DevOps.
Per-Policy SARIF Output
Write individual policy findings as SARIF:
vulnerability:
- name: all-vulns
outputs:
- destination: "file:results.sarif"
format: "sarif"
GitHub Code Scanning Upload
Upload SARIF directly to GitHub's Security tab:
vulnerability:
- name: all-vulns
outputs:
- destination: "github:code-scanning"
format: "sarif"
Findings appear as inline PR annotations and in the Security tab → Code scanning alerts.
Environment Variable Shortcuts
# Write all findings as SARIF to a file
CODEWARD_SARIF_OUTPUT=results.sarif
# Upload all findings to GitHub Code Scanning
CODEWARD_SARIF_UPLOAD=true
SARIF Field Mapping
| Scanner concept | SARIF field |
|---|---|
VulnerabilityID (e.g. CVE-2024-1234) | result.ruleId and rules[].id |
Policy action (block/warn/info) | result.level (error/warning/note) |
Sources[] (lockfile paths) | locations[].physicalLocation.artifactLocation.uri |
PrimaryURL | rules[].helpUri |
Severity | rules[].properties.severity |
Title + fix info | result.message.text |
License PkgName:LicenseName | result.ruleId (prefixed LICENSE-) |
License Category + Severity | rules[].properties |
A single SARIF file can contain both vulnerability and license findings. When triggered by CODEWARD_SARIF_OUTPUT, all finding types are merged into one run.
SBOM Export
Export the scanner's package inventory as a CycloneDX 1.6 JSON Software Bill of Materials (SBOM).
Config-Driven SBOM
# .codeward.yaml — top-level key
sbom:
destination: "file:sbom.cdx.json"
format: "cyclonedx" # cyclonedx (default)
version: "1.6" # CycloneDX spec version (default)
include_dev: false # Exclude dev dependencies
Environment Variable Shortcut
CODEWARD_SBOM_OUTPUT=sbom.cdx.json
CycloneDX Field Mapping
| Scanner data | CycloneDX field |
|---|---|
Package.Name | component.name |
Package.Version | component.version |
Package.Identifier.PURL | component.purl and bom-ref |
Package.Relationship (direct/indirect) | component.scope (required/optional) |
License.Name (SPDX ID) | component.licenses[].license.id |
Package.Children | dependencies[].dependsOn |
| Scan timestamp | metadata.timestamp |
| Repository name | metadata.component.name (type: application) |
Features
- De-duplication: Packages appearing in multiple lockfiles produce a single component
- PURL-based references: Uses Package URL for component identification
- License attachment: License scan results are mapped to their parent components
- Dependency graph: Parent/child relationships exported as CycloneDX dependencies
- Dev dependency filtering: Optional exclusion of development dependencies
- No external dependencies: Pure JSON construction, no CycloneDX library needed
Custom Webhooks
The webhook field on url: outputs gives full control over the HTTP request:
outputs:
- destination: "url:https://hooks.slack.com/services/xxx"
format: markdown
template: text
webhook:
method: POST
headers:
Content-Type: application/json
Authorization: "Bearer ${SLACK_TOKEN}"
body:
text: "${result}"
channel: "#security-alerts"
Webhook Fields
| Field | Type | Default | Description |
|---|---|---|---|
method | string | POST | HTTP method: GET, POST, PUT, PATCH, DELETE |
headers | map | — | Custom HTTP headers |
body | map | — | Custom JSON body (supports nesting) |
Template Variables
Headers and body values support ${variable} placeholders:
| Variable | Source |
|---|---|
${result} | Rendered template output |
${title} | Output title |
${repository} | CODEWARD_GITHUB_REPOSITORY |
${branch} | CODEWARD_GITHUB_BRANCH |
${pr_number} | CODEWARD_GITHUB_PR_NUMBER |
${owner} | CODEWARD_GITHUB_OWNER |
${mode} | CODEWARD_MODE |
${timestamp} | ISO 8601 UTC |
Unknown variables fall back to os.Getenv(), enabling secrets passthrough.
Secrets Passthrough
Never hardcode tokens in config files. Use CODEWARD_WEBHOOK_SECRETS:
# GitHub Actions workflow
- uses: codeward-io/[email protected]
with:
webhook_secrets: |
SLACK_TOKEN=${{ secrets.SLACK_TOKEN }}
JIRA_API_KEY=${{ secrets.JIRA_API_KEY }}
The entrypoint exports each KEY=VALUE pair, then unsets CODEWARD_WEBHOOK_SECRETS.
Integration Examples
Slack:
webhook:
method: POST
headers:
Authorization: "Bearer ${SLACK_TOKEN}"
body:
text: "${result}"
channel: "#security"
JIRA:
webhook:
method: POST
headers:
Authorization: "Basic ${JIRA_API_KEY}"
Content-Type: application/json
body:
fields:
project: { key: SEC }
summary: "Security Alert: ${title}"
description: "${result}"
issuetype: { name: Bug }
PagerDuty:
webhook:
method: POST
headers:
Authorization: "Token token=${PAGERDUTY_TOKEN}"
body:
routing_key: "${PAGERDUTY_ROUTING_KEY}"
event_action: trigger
payload:
summary: "${title}"
source: "${repository}"
severity: critical
Webhook Validation
Config validation checks:
webhookonly valid onurl:destinationsmethodmust be GET, POST, PUT, PATCH, or DELETE- Unclosed
${variables are flagged as errors - Hardcoded secrets in headers produce warnings
Custom Templates
Load custom output templates from the filesystem:
export CODEWARD_TEMPLATES_PATH=/path/to/templates/
Template files: table.markdown.tmpl, text.markdown.tmpl, combined.markdown.tmpl (and .html.tmpl variants).