Outputs
Each policy can have multiple outputs. Outputs control the format, destination, and presentation of results.
Output Configuration
{
"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",
"display_ignores": true
}
Output Configuration Options
| Field | Required | Description |
|---|---|---|
format | Yes | Output format: markdown, html, or json |
destination | Yes | Where to send output (see Destinations) |
template | No | Template style: table or text (ignored for JSON) |
fields | No | Which fields to include in output |
changes | No | Filter to specific change categories: new, changed, removed, existing |
group_by | No | Group results by these fields |
combined | No | Merge with other outputs at same destination |
collapse | No | Make section collapsible (markdown/html) |
title | No | Custom section title |
comment | No | Text shown below title |
output_reason | No | Custom failure message (overrides technical reason) |
display_ignores | No | Show ignored items in output (default: true) |
Formats
| Format | Description | Template Required |
|---|---|---|
markdown | GitHub-flavored markdown tables or text | Yes: table or text |
html | HTML formatted output | Yes: table or text |
json | Structured JSON array | No (template must be empty) |
Markdown/HTML Templates
table(default): Compact table layout, one row per findingtext: Narrative format with severity icons and structured sections
JSON Output
JSON outputs are always arrays. Each policy produces an array of finding objects:
[
{
"policy": "block-critical",
"title": "Vulnerability Policy: block-critical",
"fields": ["VulnerabilityID", "PkgName", "Severity", "FixedVersion"],
"sources": {
"vulnerabilities": {
"new": {
"action": "block",
"change": "new",
"header": "Detection: new - Action: block",
"items": [
{
"VulnerabilityID": "CVE-2024-0001",
"PkgName": "lodash",
"Severity": "CRITICAL",
"FixedVersion": "4.17.21"
}
]
}
}
}
}
]
Important: When using format: json, do not set template. It will cause a validation error.
Destinations
| Destination | Description |
|---|---|
git:pr | Post as PR comment (requires PR context) |
git:issue | Create/update GitHub issue |
file:/path | Write to file (absolute or relative) |
log:stdout | Print to standard output |
log:stderr | Print to standard error |
url:https://... | HTTP POST to webhook endpoint |
git:pr
Posts or updates a comment on the pull request. Requires:
CODEWARD_MODE=diffCODEWARD_GITHUB_TOKEN,CODEWARD_GITHUB_OWNER,CODEWARD_GITHUB_REPOSITORY,CODEWARD_GITHUB_PR_NUMBER
{ "format": "markdown", "destination": "git:pr" }
git:issue
Creates or updates a GitHub issue. Typically used for tracking existing backlog. Requires:
CODEWARD_MODE=main(default)CODEWARD_GITHUB_TOKEN,CODEWARD_GITHUB_OWNER,CODEWARD_GITHUB_REPOSITORY
{ "format": "markdown", "destination": "git:issue", "changes": ["existing"] }
Note: Issue titles are suppressed (handled by GitHub).
file:
Write to a file. Supports both absolute and relative paths:
file:/path/to/file-> Absolute pathfile:path/to/file-> Relative path (from where scanner is run)file:./path/to/file-> Relative path
{ "format": "json", "destination": "file:vulnerabilities.json" }
log:stdout / log:stderr
Print to console (useful for debugging or simple CI logs):
{ "format": "markdown", "destination": "log:stdout" }
url:
HTTP POST to a webhook. Body is the rendered output. Content-Type is auto-detected (application/json for JSON outputs):
{ "format": "json", "destination": "url:https://hooks.example.com/security" }
The X-Report-Title header is included when a title is available.
Retry Logic: Codeward automatically retries failed webhook requests (network errors, 5xx server errors):
- Attempts: 3 retries maximum
- Backoff: 1 second wait between attempts
- No retry: Client errors (4xx) are not retried
Notes:
- Non-2xx responses are logged but don't affect exit code
- Empty results are skipped (no POST sent)
- Only POST is implemented (idempotency left to receiver)
Filtering by Change Category
Use changes to limit which findings appear:
{ "changes": ["new", "changed"] }
| Category | When to Use |
|---|---|
new | PR comments — show introduced issues |
changed | When attributes shifted (version, severity) |
removed | Track remediation progress |
existing | Backlog tracking (issues, JSON exports) |
Common pattern:
- PR comment:
["new", "changed"]— actionable delta - Issue:
["existing"]— backlog tracking - JSON export: all categories for full audit
Field Selection
Limit which fields appear in output:
{ "fields": ["VulnerabilityID", "PkgName", "Severity", "FixedVersion"] }
If omitted, a default set of fields is included. See Policies for allowed fields per policy type.
Grouping
Group findings by field values:
{ "group_by": ["Severity"] }
Creates sections for each severity level. Multiple fields create nested grouping.
Severity aggregation: When grouping omits Severity, the highest severity in each group is shown in the header.
Note: Grouping doesn't affect JSON output (remains flat array). Process JSON client-side if needed.
Combining Outputs
Multiple policies can write to the same destination. Use combined: true to merge them:
{
"vulnerability": [{
"name": "critical",
"outputs": [{
"format": "markdown",
"destination": "git:pr",
"combined": true
}]
}],
"license": [{
"name": "gpl",
"outputs": [{
"format": "markdown",
"destination": "git:pr",
"combined": true
}]
}]
}
This produces a single PR comment with both vulnerability and license sections.
Combining Rules
| Mix | Allowed |
|---|---|
| Markdown + Markdown | ✅ Merged into one document |
| HTML + HTML | ✅ Merged into one document |
| JSON + JSON | ✅ Single concatenated array |
| Markdown + HTML | ✅ Treated as separate groups |
| JSON + Markdown | ❌ Error: formats must match |
JSON combining: Produces a single array containing all policies' results. No wrapper object.
Empty Combined Outputs
If all policies in a combined group produce zero results (after filtering), the entire output is suppressed.
Collapsible Sections
For long reports, use collapse: true:
{ "collapse": true }
Renders markdown/HTML sections as collapsible (using <details> tags). Ignored for JSON.
Title and Comment
Customize the section header:
{
"title": "Critical Vulnerabilities",
"comment": "These must be fixed before merging"
}
title: Section headingcomment: Explanatory text below the title
Examples
PR Comment with New Issues Only
{
"format": "markdown",
"destination": "git:pr",
"template": "table",
"fields": ["VulnerabilityID", "PkgName", "Severity", "FixedVersion"],
"changes": ["new"],
"collapse": true,
"title": "New Vulnerabilities"
}
JSON Export for Automation
{
"format": "json",
"destination": "file:/results/security-report.json",
"changes": ["new", "changed", "removed"],
"combined": true
}
Backlog Issue
{
"format": "markdown",
"destination": "git:issue",
"template": "table",
"fields": ["VulnerabilityID", "PkgName", "Severity"],
"changes": ["existing"],
"title": "Security Backlog"
}
Webhook Notification
{
"format": "json",
"destination": "url:https://hooks.slack.com/services/...",
"changes": ["new"],
"title": "New Security Issues"
}
Combined PR Comment (Multiple Policies)
{
"vulnerability": [{
"name": "vulns",
"outputs": [{
"format": "markdown",
"destination": "git:pr",
"combined": true,
"title": "Vulnerabilities"
}]
}],
"license": [{
"name": "licenses",
"outputs": [{
"format": "markdown",
"destination": "git:pr",
"combined": true,
"title": "License Issues"
}]
}]
}
Common Mistakes
| Problem | Cause | Fix |
|---|---|---|
| Empty PR comment | Only existing in changes | Include new or changed |
| Template error with JSON | Set template for JSON format | Remove template field |
| Mixed format error | combined: true with JSON + markdown | Use same format for all combined outputs |
| File not found (Docker) | Path not in a mounted volume | Write to a mounted path (e.g. file:/results/out.json) |
| No PR comment | Missing GitHub env vars | Set CODEWARD_GITHUB_TOKEN, CODEWARD_GITHUB_PR_NUMBER, etc. |
| Invalid field error | Field not allowed for policy type | Check allowed fields in Policies |
Customizing Failure Messages
By default, Codeward outputs the technical reason for a policy violation (e.g., "Field 'scripts.test' missing"). You can override this with a user-friendly message using output_reason.
This is especially useful for Validation policies:
{
"rules": [
{
"key": "scripts.test",
"type": "not_exists",
"output_reason": "Missing required test script. Run 'npm pkg set scripts.test=\"vitest\"' to fix."
}
]
}
The output will use this text instead of the default error message.