Skip to main content

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:

  1. Path specified by CONFIG_PATH environment variable
  2. .codeward.json in repository root
  3. 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

SettingTypeDefaultDescription
dependency_treebooleanfalseEnable dependency graph analysis. Adds Relationship, Parents, Children, Sources fields. Slight performance cost.
modestring-Execution mode: diff or main. See Execution Mode below.
loggingobjectSee belowLogging configuration. See Logging.
ignorearray[]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

ModeWhen to UseWhat It Does
diffPull requests, branch comparisonsCompares 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.
mainPush to main, scheduled scans, initial baselineScans 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:

  1. Scan both branches - Main (/main) and feature (/branch) directories
  2. Compare results - Identify what changed between branches
  3. 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
  4. Enable PR features - git:pr destination, 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:

  1. Scan only main branch - Only /main directory is scanned
  2. Mark all findings as existing - No diff comparison
  3. Enable issue creation - git:issue destination 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:

  1. Environment Variables - Set directly in the shell or CI/CD system (highest priority)
  2. Configuration File - Override defaults in .codeward.json via global section (only for non-sensitive variables)

Available Variables

Core Configuration:

  • CODEWARD_CONFIG_PATH - Path to .codeward.json config file
  • CODEWARD_PRIVATE_CONFIG_PATH - Path to private config file
  • CODEWARD_MODE - Execution mode: diff or main. Defaults to main. 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/organization
  • CODEWARD_GITHUB_BRANCH - Branch name (e.g., main)
  • CODEWARD_GITHUB_PR_NUMBER - Pull request number (required for diff mode with git:pr destination)

Logging (see Logging section):

  • CODEWARD_LOG_LEVEL - Global log level
  • CODEWARD_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):

  1. Environment variables - Always take precedence
  2. Config file (global section) - Applied when env var not set
  3. 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:

  1. Configuration File - global.logging section in .codeward.json
  2. 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

OptionTypeValuesDefaultDescription
levelstringsilent, error, warn, info, debug, traceinfoGlobal log level
formatstringtext, jsontextLog output format
show_timestampbooleantrue, falsefalseDisplay ISO 8601 timestamps
summarystringminimal, standard, detailedstandardExecution summary verbosity

Environment Variables

Environment variables override config file settings:

  • CODEWARD_LOG_LEVEL - Global log level
  • CODEWARD_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 only
  • standard - 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/display
  • description (optional) - Explanation
  • paths (optional) - Array of glob patterns for file paths
  • targets (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/directories
  • test/** - all files under test directory
  • **/test/** - test directory at any depth
  • **/*_test.go - all Go test files
  • vendor/* - 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 paths and targets: 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 rule
  • description (optional) - Explanation
  • author (optional) - Contact or creator
  • expires (optional) - Expiration date (YYYY-MM-DD). The ignore rule is skipped after this date.
  • operator (optional) - AND or OR (default: OR)
  • rules (required) - Array of rule objects

Operators:

  • OR (default) - Item ignored if any rule matches
  • AND - Item ignored only if all rules match

Processing Order

  1. Policy rules filter items (include matching)
  2. Global ignores applied (path/target filtering)
  3. Policy-level ignores applied (content filtering)
  4. 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

FieldRequiredDescription
nameYesUnique identifier for the policy
disabledNoSet true to skip this policy
actionsYesWhat to do for each change category
operatorNoOR (default), AND, or implies for rule matching
rulesYesArray of filter rules (can be empty for "match all")
outputsYesWhere 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):

  1. The invalid policy is skipped.
  2. A warning is logged detailing the error.
  3. 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:

CategoryMeaningTypical Action
newOnly in feature branch (introduced)block or warn
changedIn both, but attributes differwarn
removedOnly in main branch (deleted)info
existingIn both, unchangedinfo or warn

Key insight: Only findings in new category should typically block PRs. Existing issues are technical debt to address separately.

Actions

ActionEffect
infoLog only, no impact on CI
warnVisible in output, but CI passes
blockCI 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):

  • eq reports when value equals target
  • ne reports when value does not equal target
  • gt reports when value is greater than target
  • contains reports when value contains substring
  • not_contains reports when value does not contain substring
  • exists reports when key exists
  • not_exists reports 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

OperatorDescriptionReports When
eqEquals (exact match)Value equals target
neNot equalsValue does not equal target
ltLess than (semantic version aware)Value is less than target
gtGreater than (semantic version aware)Value is greater than target
leLess than or equal (semantic version aware)Value is less than or equal to target
geGreater than or equal (semantic version aware)Value is greater than or equal to target
containsString contains substringValue contains substring
not_containsString does not contain substringValue does not contain substring
hasPrefixString starts withValue starts with string
hasSuffixString ends withValue ends with string
regexRegular expression matchValue matches regex pattern
existsKey/file existsKey or file exists
not_existsKey/file does not existKey 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"
}
FieldRequiredDescription
formatYesmarkdown, html, or json
destinationYesWhere to send output (see below)
templateNotable or text (ignored for JSON)
fieldsNoWhich fields to include
changesNoFilter to specific change categories
group_byNoGroup results by these fields
combinedNoMerge with other outputs at same destination
collapseNoMake section collapsible (markdown/html)
titleNoCustom section title
commentNoText 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 CRITICAL and HIGH vulnerabilities. Posts to PR and creates issues for existing.
  • Licenses: Warns on new/existing/changed Forbidden and Restricted categories or UNKNOWN severity. 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:

PhaseApproach
1. ObserveAll actions set to info — see what would be flagged
2. WarnChange new to warn — visible but non-blocking
3. Block CriticalSet new: block for CRITICAL only
4. ExpandAdd HIGH to blocking, then more categories
5. Steady StateBlock what matters, warn on the rest

Tip: Use separate policies for different severity levels so you can promote them independently.

Common Mistakes

ProblemCauseFix
Rules ignoredUsed nested object formatUse array: "rules": [{ "field": "...", "type": "...", "value": "..." }]
Everything shows as "new"Missing baseline scanMount /main and set CODEWARD_MODE=diff
Template error with JSONSet template for JSON formatRemove template — JSON doesn't use templates
Mixed formats in combinedCombined outputs mix JSON with markdownUse same format for all combined outputs
Invalid field errorField not available for policy typeCheck allowed fields for each policy type

See Policies for allowed fields per policy type.