Configuration
Codeward is configured with a single JSON file (.codeward.json) in your repository root. This file defines what to scan, what rules to apply, and where to send results.
Config File Location
Codeward looks for configuration in this order:
- Path specified by
CONFIG_PATHenvironment variable .codeward.jsonin repository root- Built-in defaults (if no config found)
Top-Level Structure
{
"global": {
"dependency_tree": false
},
"vulnerability": [ /* vulnerability policies */ ],
"license": [ /* license policies */ ],
"package": [ /* package policies */ ],
"file": [ /* file validation policies */ ],
"pr": [ /* PR validation policies */ ]
}
All policy arrays are optional. Omit any you don't need.
Global Settings
| Setting | Type | Default | Description |
|---|---|---|---|
dependency_tree | boolean | false | Enable dependency graph analysis. Adds Relationship, Parents, Children, Sources fields. Slight performance cost. |
mode | string | - | Execution mode: diff or main. See Execution Mode below. |
logging | object | See below | Logging configuration. See Logging. |
ignore | array | [] | Global ignore rules. See Ignore Rules. |
Execution Mode
The CODEWARD_MODE environment variable (or global.mode in config) controls how Codeward scans and categorizes findings. This is the most important setting for controlling Codeward's behavior.
Mode Values
| Mode | When to Use | What It Does |
|---|---|---|
diff | Pull requests, branch comparisons | Compares feature branch against main branch. Categorizes findings as new, changed, removed, or existing. Enables PR-specific features like git:pr destinations and PR policy validation. |
main | Push to main, scheduled scans, initial baseline | Scans only the main branch. All findings are categorized as existing. Enables issue creation via git:issue destinations. |
Diff Mode (CODEWARD_MODE=diff)
Use diff mode for pull request workflows. Codeward will:
- Scan both branches - Main (
/main) and feature (/branch) directories - Compare results - Identify what changed between branches
- Categorize findings:
new- Only in feature branch (introduced by this PR)changed- In both, but attributes differ (e.g., version changed)removed- Only in main branch (fixed by this PR)existing- In both, unchanged
- Enable PR features -
git:prdestination, PR metadata access, PR policies
Key insight: Only new findings should typically block PRs. Existing technical debt doesn't slow you down.
# Docker example for PR scanning
docker run --rm \
-v /path/to/main-branch:/main:rw \
-v /path/to/feature-branch:/branch:rw \
-e CODEWARD_MODE=diff \
-e CODEWARD_GITHUB_TOKEN=$GITHUB_TOKEN \
-e CODEWARD_GITHUB_PR_NUMBER=123 \
ghcr.io/codeward-io/scan:latest
Main Mode (Default)
Use main mode for scheduled scans or baseline tracking. Codeward will:
- Scan only main branch - Only
/maindirectory is scanned - Mark all findings as existing - No diff comparison
- Enable issue creation -
git:issuedestination for backlog tracking
# Docker example for main branch scanning
docker run --rm \
-v /path/to/repo:/main:rw \
-e CODEWARD_GITHUB_TOKEN=$GITHUB_TOKEN \
ghcr.io/codeward-io/scan:latest
Setting the Mode
Via environment variable (recommended for CI):
export CODEWARD_MODE=diff
Via config file:
{
"global": {
"mode": "diff"
}
}
Environment variables always take precedence over config file settings.
Environment Variables
Codeward supports centralized environment variable management with config file overrides for non-sensitive variables. All CODEWARD_* prefixed environment variables are automatically loaded at startup.
Configuration Methods
Environment variables can be configured through two methods:
- Environment Variables - Set directly in the shell or CI/CD system (highest priority)
- Configuration File - Override defaults in
.codeward.jsonviaglobalsection (only for non-sensitive variables)
Available Variables
Core Configuration:
CODEWARD_CONFIG_PATH- Path to.codeward.jsonconfig fileCODEWARD_PRIVATE_CONFIG_PATH- Path to private config fileCODEWARD_MODE- Execution mode:difformain. Defaults tomain. See Execution Mode for details.
GitHub Integration:
CODEWARD_GITHUB_TOKEN- GitHub API token (sensitive - environment only)CODEWARD_GITHUB_REPOSITORY- Repository name (e.g.,my-repo)CODEWARD_GITHUB_OWNER- Repository owner/organizationCODEWARD_GITHUB_BRANCH- Branch name (e.g.,main)CODEWARD_GITHUB_PR_NUMBER- Pull request number (required fordiffmode withgit:prdestination)
Logging (see Logging section):
CODEWARD_LOG_LEVEL- Global log levelCODEWARD_LOG_FORMAT- Output format (text/json)CODEWARD_LOG_TIMESTAMP- Show timestamps (true/false)CODEWARD_LOG_SUMMARY- Summary verbosity (minimal/standard/detailed)
Configuration Precedence
Environment variables follow this priority (highest to lowest):
- Environment variables - Always take precedence
- Config file (
globalsection) - Applied when env var not set - Defaults - Built-in defaults
Config File Example
{
"global": {
"mode": "diff",
"logging": {
"level": "info",
"format": "text"
},
"repo": {
"github_repository": "my-repo"
}
}
}
Security Notes:
- Sensitive variables (tokens) are never loaded from config files
- Sensitive variables:
CODEWARD_GITHUB_TOKEN - Config file overrides only apply when the environment variable is not set
Usage Examples
# Basic usage with environment variables
CODEWARD_GITHUB_TOKEN=def456 codeward-scan
# Override mode via config file
echo '{"global":{"mode":"diff"}}' > .codeward.json
# Environment variables override config file
CODEWARD_MODE=main codeward-scan # Overrides config setting
Logging
Codeward uses a structured logging system with component-based output. Configure logging to control verbosity and format.
Configuration Methods
Logging can be configured through two methods:
- Configuration File -
global.loggingsection in.codeward.json - Environment Variables - Override config file settings (take precedence)
Configuration File
Add a logging section to the global object:
{
"global": {
"logging": {
"level": "info",
"format": "text",
"show_timestamp": false,
"summary": "standard"
}
}
}
Configuration Options
| Option | Type | Values | Default | Description |
|---|---|---|---|---|
level | string | silent, error, warn, info, debug, trace | info | Global log level |
format | string | text, json | text | Log output format |
show_timestamp | boolean | true, false | false | Display ISO 8601 timestamps |
summary | string | minimal, standard, detailed | standard | Execution summary verbosity |
Environment Variables
Environment variables override config file settings:
CODEWARD_LOG_LEVEL- Global log levelCODEWARD_LOG_FORMAT- Output format (text/json)CODEWARD_LOG_TIMESTAMP- Show timestamps (true/false)CODEWARD_LOG_SUMMARY- Summary verbosity
Examples:
# Debug level with JSON format
CODEWARD_LOG_LEVEL=debug CODEWARD_LOG_FORMAT=json codeward-scan
# Detailed summary
CODEWARD_LOG_SUMMARY=detailed codeward-scan
# Silent mode (suppress all logs)
CODEWARD_LOG_LEVEL=silent codeward-scan
Log Formats
Text Format (human-readable):
INFO [config] Using default private config for local development
WARN [filter] No PR files available policy="PR Title Convention"
ERROR [trivy] Failed to get cache dir error=<details>
JSON Format (structured):
{"level":"INFO","component":"config","message":"Using default private config"}
{"level":"WARN","component":"filter","message":"No PR files available","context":{"policy":"PR Title Convention"}}
{"level":"ERROR","component":"trivy","message":"Failed to get cache dir","context":{"error":"<details>"}}
Execution Summary
At the end of each scan, Codeward displays a formatted execution summary:
Summary Levels:
minimal- Basic stats onlystandard- Component runtimes and severity breakdown (default)detailed- Everything including individual blocking actions
Example Output:
================================================================================
CODEWARD SCAN SUMMARY
================================================================================
Environment
• Mode: local
• Repository: owner/repo
Execution Time
• Total Runtime: 22.2s
• scanner.main: 22.2s
Scan Results
• Vulnerabilities: 3 new, 10 existing, 2 removed
• Licenses: 1 new, 5 existing, 0 removed
• Packages: 2 new, 20 existing, 1 removed
Policy Execution
• Total Policies: 4 evaluated
• Blocking Actions: 0
Exit Status
• Result: PASSED
• Exit Code: 0
================================================================================
Ignore Rules
Ignore rules allow you to exclude specific items from policy processing. They can be defined at the global level (WHERE findings come from) or at the policy level (WHAT to filter).
Global Ignore Rules
Global ignores are defined in global.ignore and filter findings based on file paths and targets. They apply across all policies.
{
"global": {
"ignore": [
{
"name": "Ignore test directories",
"description": "Skip all findings from test directories",
"paths": ["test/**", "**/test/**", "**/*_test.go"]
},
{
"name": "Ignore vendor with specific CVEs",
"description": "Known false positives in vendored dependencies",
"paths": ["vendor/**"],
"targets": ["CVE-2024-0001", "lodash"]
},
{
"name": "Ignore specific vulnerabilities everywhere",
"description": "False positives across all files",
"targets": ["CVE-2023-1234", "CVE-2023-5678"]
}
]
}
}
Rules:
name(required) - Name for logging/displaydescription(optional) - Explanationpaths(optional) - Array of glob patterns for file pathstargets(optional) - Array of items to ignore (CVE IDs, package names, license names)expires(optional) - Expiration date (YYYY-MM-DD). The rule is ignored after this date.
Path Glob Patterns:
*matches single path segment**matches multiple segments/directoriestest/**- all files under test directory**/test/**- test directory at any depth**/*_test.go- all Go test filesvendor/*- only direct children of vendor
Matching Logic:
- If only
paths: ignores all findings from matching paths - If only
targets: ignores matching targets from all paths - If both
pathsandtargets: finding must match BOTH to be ignored
Policy-Level Ignore Rules
Policy-level ignores are content-based - they filter WHAT to exclude using field values. Define them in the ignores array within each policy.
{
"vulnerability": [{
"name": "Critical vulnerabilities",
"actions": {"new": "block"},
"rules": [
{"field": "Severity", "type": "eq", "value": "CRITICAL"}
],
"ignores": [
{
"name": "Accepted risk - CVE-2024-0001",
"expires": "2026-06-30",
"description": "This CVE only affects Windows, we run Linux",
"author": "[email protected]",
"operator": "AND",
"rules": [
{"field": "VulnerabilityID", "type": "eq", "value": "CVE-2024-0001"},
{"field": "PkgName", "type": "eq", "value": "openssl"}
]
},
{
"name": "False positive - test dependencies",
"description": "Test-only dependencies are not deployed",
"author": "[email protected]",
"operator": "OR",
"rules": [
{"field": "PkgName", "type": "contains", "value": "-test"},
{"field": "PkgName", "type": "hasSuffix", "value": "-mock"}
]
}
],
"outputs": [...]
}]
}
Structure:
name(required) - Name of the ignore ruledescription(optional) - Explanationauthor(optional) - Contact or creatorexpires(optional) - Expiration date (YYYY-MM-DD). The ignore rule is skipped after this date.operator(optional) -ANDorOR(default:OR)rules(required) - Array of rule objects
Operators:
OR(default) - Item ignored if any rule matchesAND- Item ignored only if all rules match
Processing Order
- Policy rules filter items (include matching)
- Global ignores applied (path/target filtering)
- Policy-level ignores applied (content filtering)
- Remaining items processed for output
Common Use Cases
Ignore test directories:
{"paths": ["test/**", "**/test/**", "**/*_test.go"]}
Ignore vendor dependencies:
{"paths": ["vendor/**", "**/node_modules/**"]}
Ignore specific CVEs:
{"targets": ["CVE-2023-1234", "CVE-2024-5678"]}
Ignore CVE in specific package:
{
"operator": "AND",
"rules": [
{"field": "VulnerabilityID", "type": "eq", "value": "CVE-2024-0001"},
{"field": "PkgName", "type": "eq", "value": "openssl"}
]
}
Policy Structure
Every policy (except file and pr types) follows this structure:
{
"name": "policy-name",
"disabled": false,
"actions": {
"new": "block",
"existing": "warn",
"removed": "info",
"changed": "warn"
},
"operator": "OR",
"rules": [
{ "field": "Severity", "type": "eq", "value": "CRITICAL" }
],
"outputs": [
{ /* output config */ }
]
}
Policy Fields
| Field | Required | Description |
|---|---|---|
name | Yes | Unique identifier for the policy |
disabled | No | Set true to skip this policy |
actions | Yes | What to do for each change category |
operator | No | OR (default), AND, or implies for rule matching |
rules | Yes | Array of filter rules (can be empty for "match all") |
outputs | Yes | Where and how to report results |
Policy Validation & Error Handling
Codeward performs strict validation on your configuration. If a policy contains errors (e.g., invalid rule type, unknown field):
- The invalid policy is skipped.
- A warning is logged detailing the error.
- The scan continues with the remaining valid policies.
This ensures that a typo in one policy doesn't break your entire CI pipeline. The scan will only fail if all policies are invalid.
Change Categories
Codeward classifies every finding based on the diff between branches:
| Category | Meaning | Typical Action |
|---|---|---|
new | Only in feature branch (introduced) | block or warn |
changed | In both, but attributes differ | warn |
removed | Only in main branch (deleted) | info |
existing | In both, unchanged | info or warn |
Key insight: Only findings in new category should typically block PRs. Existing issues are technical debt to address separately.
Actions
| Action | Effect |
|---|---|
info | Log only, no impact on CI |
warn | Visible in output, but CI passes |
block | CI fails (non-zero exit code) |
You only need to define actions for categories you care about. Omitted categories default to no action.
Rules
Rules filter which findings the policy applies to.
Understanding Rule Logic
Important: Rules define what to search for, not requirements. When a rule matches, it creates a finding that triggers the configured action.
Think of rules as search queries:
- ✅ "Find vulnerabilities with CRITICAL severity"
- ✅ "Find PRs with more than 30 files changed"
- ✅ "Find files that don't contain USER instruction"
All operators report when the condition is true (matches):
eqreports when value equals targetnereports when value does not equal targetgtreports when value is greater than targetcontainsreports when value contains substringnot_containsreports when value does not contain substringexistsreports when key existsnot_existsreports when key does not exist
Example:
{
"rules": [
{ "field": "Severity", "type": "eq", "value": "CRITICAL" }
],
"actions": { "new": "block" }
}
This searches for vulnerabilities where severity equals CRITICAL. When found, it blocks the PR.
Rule Structure
{ "field": "Severity", "type": "eq", "value": "CRITICAL" }
Rule Operators
| Operator | Description | Reports When |
|---|---|---|
eq | Equals (exact match) | Value equals target |
ne | Not equals | Value does not equal target |
lt | Less than (semantic version aware) | Value is less than target |
gt | Greater than (semantic version aware) | Value is greater than target |
le | Less than or equal (semantic version aware) | Value is less than or equal to target |
ge | Greater than or equal (semantic version aware) | Value is greater than or equal to target |
contains | String contains substring | Value contains substring |
not_contains | String does not contain substring | Value does not contain substring |
hasPrefix | String starts with | Value starts with string |
hasSuffix | String ends with | Value ends with string |
regex | Regular expression match | Value matches regex pattern |
exists | Key/file exists | Key or file exists |
not_exists | Key/file does not exist | Key or file does not exist |
Field names are case-insensitive: Severity, severity, and SEVERITY all work.
Common Rule Patterns
Find problems to block:
{
"name": "find-critical-vulns",
"rules": [
{ "field": "Severity", "type": "eq", "value": "CRITICAL" }
],
"actions": { "new": "block" }
}
When a CRITICAL vulnerability is found, block the PR.
Find missing requirements:
{
"name": "find-missing-tests",
"type": "json",
"path": "package.json",
"rules": [
{ "key": "scripts.test", "type": "not_exists" }
],
"actions": { "new": "warn" }
}
When test script does not exist, warn.
Find policy violations:
{
"name": "find-large-prs",
"rules": [
{ "key": "changed_files", "type": "gt", "value": "30" }
],
"actions": { "new": "warn" }
}
When PR has more than 30 changed files, warn.
Find unwanted content:
{
"name": "find-wip-prs",
"rules": [
{ "key": "title", "type": "contains", "value": "WIP" }
],
"actions": { "new": "block" }
}
When PR title contains "WIP", block.
Policy Operators
Codeward supports AND, OR, and implies logic at the policy level. This controls how the rules in the rules array are combined.
AND vs OR
{
"operator": "OR",
"rules": [
{ "field": "Severity", "type": "eq", "value": "CRITICAL" },
{ "field": "Severity", "type": "eq", "value": "HIGH" }
]
}
Matches CRITICAL or HIGH severity.
{
"operator": "AND",
"rules": [
{ "field": "Severity", "type": "eq", "value": "CRITICAL" },
{ "field": "PkgName", "type": "eq", "value": "lodash" }
]
}
Matches only CRITICAL severity and package name is lodash.
Conditional Logic (implies)
The implies operator enables "If X then Y" logic.
- The first rule is the "trigger".
- If the trigger passes, subsequent rules are evaluated (actions apply).
- If the trigger fails, the policy passes silently (no action).
Example: "If more than 10 files changed, warn if documentation is not updated":
{
"name": "find-undocumented-large-changes",
"operator": "implies",
"rules": [
{ "key": "changed_files", "type": "gt", "value": 10 },
{ "key": "files.filename", "file_pattern": "docs/**", "type": "not_exists" }
]
}
Output Configuration
Each policy can have multiple 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": "Please fix before merging"
}
| Field | Required | Description |
|---|---|---|
format | Yes | markdown, html, or json |
destination | Yes | Where to send output (see below) |
template | No | table or text (ignored for JSON) |
fields | No | Which fields to include |
changes | No | Filter to specific change categories |
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 |
See Outputs for full details on formats, destinations, and combining.
Default Configuration
When no .codeward.json exists, Codeward uses these defaults:
{
"global": {
"ignore": [],
"dependency": {
"install": false,
"install_dev": false
},
"dependency_tree": false
},
"vulnerability": [
{
"name": "Vulnerability issues",
"actions": {
"new": "warn",
"existing": "warn",
"removed": "info",
"changed": "warn"
},
"operator": "OR",
"rules": [
{
"field": "Severity",
"type": "eq",
"value": "CRITICAL"
},
{
"field": "Severity",
"type": "eq",
"value": "HIGH"
}
],
"outputs": [
{
"collapse": true,
"format": "markdown",
"fields": [
"PkgName",
"Severity",
"VulnerabilityID",
"InstalledVersion",
"FixedVersion"
],
"destination": "git:pr",
"title": "[codeward.io] Vulnerability Issues",
"comment": "Please contact the security team to review the vulnerability issues.",
"changes": [
"new",
"removed",
"changed"
],
"template": "table"
},
{
"collapse": true,
"format": "markdown",
"fields": [
"PkgName",
"Severity",
"VulnerabilityID",
"InstalledVersion",
"FixedVersion"
],
"destination": "git:issue",
"title": "[codeward.io] Vulnerability Issues",
"comment": "Please contact the security team to review the vulnerability issues.",
"changes": [
"existing"
],
"template": "table"
}
],
"dependency_tree": false
}
],
"license": [
{
"name": "License issues",
"actions": {
"new": "warn",
"existing": "warn",
"removed": "info",
"changed": "warn"
},
"operator": "OR",
"rules": [
{
"field": "Category",
"type": "eq",
"value": "Forbidden"
},
{
"field": "Category",
"type": "eq",
"value": "Restricted"
},
{
"field": "Severity",
"type": "eq",
"value": "UNKNOWN"
}
],
"outputs": [
{
"format": "markdown",
"collapse": true,
"fields": [
"Name",
"Severity",
"Category",
"PkgName"
],
"destination": "git:pr",
"title": "[codeward.io] License Issues",
"changes": [
"new",
"removed",
"changed"
],
"comment": "Please contact the legal team to review the license issues.",
"template": "table"
},
{
"format": "markdown",
"collapse": true,
"fields": [
"Name",
"Severity",
"Category",
"PkgName"
],
"destination": "git:issue",
"title": "[codeward.io] License Issues",
"changes": [
"existing"
],
"comment": "Please contact the legal team to review the license issues.",
"template": "table"
}
]
}
],
"package": [
{
"name": "Package changes",
"actions": {
"new": "info",
"removed": "info",
"changed": "info"
},
"operator": "OR",
"rules": [
{
"field": "Name",
"type": "regex",
"value": ".*"
}
],
"outputs": [
{
"format": "markdown",
"collapse": true,
"fields": [
"Name",
"Version"
],
"destination": "git:pr",
"title": "[codeward.io] Package Changes",
"changes": [
"new",
"removed",
"changed"
],
"comment": "The following package changes were detected.",
"template": "table"
}
]
}
],
"file": [],
"pr": []
}
Default behavior summary:
- Vulnerabilities: Warns on new/existing/changed
CRITICALandHIGHvulnerabilities. Posts to PR and creates issues for existing. - Licenses: Warns on new/existing/changed
ForbiddenandRestrictedcategories orUNKNOWNseverity. Posts to PR and creates issues for existing. - Packages: Logs info for all package changes (
new,removed,changed). - File/PR Validation: Not enabled by default.
Starter Configurations
Minimal: Block Critical Only
{
"vulnerability": [{
"name": "block-critical",
"actions": { "new": "block" },
"rules": [
{ "field": "Severity", "type": "eq", "value": "CRITICAL" }
],
"outputs": [{
"format": "markdown",
"destination": "git:pr",
"fields": ["VulnerabilityID", "PkgName", "Severity", "FixedVersion"],
"changes": ["new"]
}]
}]
}
Progressive: Observe First, Then Block
{
"vulnerability": [
{
"name": "critical-block",
"actions": { "new": "block", "existing": "warn" },
"rules": [{ "field": "Severity", "type": "eq", "value": "CRITICAL" }],
"outputs": [{
"format": "markdown",
"destination": "git:pr",
"fields": ["VulnerabilityID", "PkgName", "Severity", "FixedVersion"],
"changes": ["new"],
"collapse": true
}]
},
{
"name": "high-observe",
"actions": { "new": "warn" },
"rules": [{ "field": "Severity", "type": "eq", "value": "HIGH" }],
"outputs": [{
"format": "markdown",
"destination": "git:pr",
"fields": ["VulnerabilityID", "PkgName", "Severity"],
"changes": ["new"],
"collapse": true
}]
}
]
}
Full: Vulnerabilities + Licenses + Packages
{
"vulnerability": [{
"name": "crit-high-block",
"actions": { "new": "block", "existing": "warn" },
"rules": [
{ "field": "Severity", "type": "eq", "value": "CRITICAL" },
{ "field": "Severity", "type": "eq", "value": "HIGH" }
],
"outputs": [{
"format": "markdown",
"destination": "git:pr",
"fields": ["VulnerabilityID", "PkgName", "Severity", "FixedVersion"],
"changes": ["new"]
}]
}],
"license": [{
"name": "gpl-block",
"actions": { "new": "block" },
"rules": [{ "field": "Category", "type": "eq", "value": "Copyleft" }],
"outputs": [{
"format": "markdown",
"destination": "git:pr",
"fields": ["Name", "Category", "Severity", "PkgName"],
"changes": ["new"]
}]
}],
"package": [{
"name": "new-packages",
"actions": { "new": "info" },
"rules": [],
"outputs": [{
"format": "markdown",
"destination": "git:pr",
"fields": ["Name", "Version"],
"changes": ["new"]
}]
}]
}
Progressive Enforcement
Start with observation, then gradually tighten:
| Phase | Approach |
|---|---|
| 1. Observe | All actions set to info — see what would be flagged |
| 2. Warn | Change new to warn — visible but non-blocking |
| 3. Block Critical | Set new: block for CRITICAL only |
| 4. Expand | Add HIGH to blocking, then more categories |
| 5. Steady State | Block what matters, warn on the rest |
Tip: Use separate policies for different severity levels so you can promote them independently.
Common Mistakes
| Problem | Cause | Fix |
|---|---|---|
| Rules ignored | Used nested object format | Use array: "rules": [{ "field": "...", "type": "...", "value": "..." }] |
| Everything shows as "new" | Missing baseline scan | Mount /main and set CODEWARD_MODE=diff |
| Template error with JSON | Set template for JSON format | Remove template — JSON doesn't use templates |
| Mixed formats in combined | Combined outputs mix JSON with markdown | Use same format for all combined outputs |
| Invalid field error | Field not available for policy type | Check allowed fields for each policy type |
See Policies for allowed fields per policy type.