Configuration
Codeward is configured with a single file in your repository root. Both JSON and YAML formats are fully supported.
Don't want to write configs from scratch? Browse the Codeward Registry for ready-to-use policy profiles covering security, infrastructure, language-specific checks, compliance, and more.
Config File Discovery
Codeward looks for configuration in this order (first found wins):
- Path specified by
CODEWARD_CONFIG_PATH(or--configCLI flag) .codeward.json.codeward.yaml.codeward.yml- Built-in defaults (if no config found)
Top-Level Structure
# .codeward.yaml
global:
dependency_tree: false
vulnerability: # vulnerability policies
license: # license policies
package: # package policies
file: # file validation policies
pr: # PR validation policies
sbom: # SBOM export settings (optional)
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. |
mode | string | - | Execution mode: diff or main. See Execution Mode. |
logging | object | See below | Logging configuration. |
ignore | array | [] | Global ignore rules. See Ignore Rules. |
Execution Mode
The CODEWARD_MODE environment variable (or global.mode in config) controls how Codeward scans.
| Mode | When to Use | Behavior |
|---|---|---|
diff | Pull requests | Compares feature vs main branch. Categorizes as new, changed, removed, existing. Enables git:pr. |
main | Push to main, scheduled scans | Scans main branch only. All findings are existing. Enables git:issue. |
Key insight: Only new findings should typically block PRs. Existing technical debt doesn't slow you down.
Setting the Mode
# Via environment variable (recommended for CI)
export CODEWARD_MODE=diff
# Via CLI flag
codeward-scan --mode diff
# Via config file
global:
mode: diff
CLI flags > environment variables > config file.
Policy Structure
Every policy follows this structure:
name: policy-name
disabled: false
actions:
new: block
existing: warn
removed: info
changed: warn
operator: or # or, and, implies
rules:
- { field: Severity, type: eq, value: CRITICAL }
outputs:
- format: markdown
destination: git:pr
| 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
Codeward validates your configuration. If a policy has errors:
- The invalid policy is skipped with a warning
- Valid policies continue executing
- Scan only fails if all policies are invalid
This ensures a typo in one policy doesn't break your entire CI pipeline.
Actions
| Action | Effect | Exit Code |
|---|---|---|
info | Log only, no impact on CI | 0 |
warn | Visible in output, CI passes | 0 |
block | CI fails (non-zero exit code) | 1 |
ignore | Suppress from output entirely | 0 |
File and PR policies use only the existing action since they don't track changes over time. Config validation will warn if new, removed, or changed actions are specified.
Change Categories
| 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 |
Rules
Rules define what to search for. When a rule matches, it creates a finding that triggers the configured action.
Rule Types
| Type | Reports When | Example |
|---|---|---|
eq | Value equals target | { field: Severity, type: eq, value: CRITICAL } |
ne | Value does not equal target | { field: Severity, type: ne, value: LOW } |
lt / gt | Less / greater than (semver-aware) | { field: Score, type: gt, value: "7.0" } |
le / ge | Less/greater or equal (semver-aware) | { key: changed_files, type: ge, value: "20" } |
contains | Value contains substring | { field: Title, type: contains, value: injection } |
not_contains | Value does not contain substring | { field: PkgName, type: not_contains, value: test } |
hasPrefix | Value starts with string | { field: VulnerabilityID, type: hasPrefix, value: CVE-2024 } |
hasSuffix | Value ends with string | { field: PkgName, type: hasSuffix, value: -dev } |
regex | Value matches regex | { field: Description, type: regex, value: "remote.*code" } |
not_regex | Value does not match regex | { key: title, type: not_regex, value: "^(feat|fix|docs):" } |
in | Value is in comma-separated set | { field: Severity, type: in, value: "CRITICAL,HIGH" } |
not_in | Value is not in set | { field: Category, type: not_in, value: "test,mock" } |
exists | Key or file exists | { type: exists, key: scripts.test } |
not_exists | Key or file does not exist | { type: not_exists, path: ".env.local" } |
last_match | Last line matching filter matches value | See Last Match Rules |
not_last_match | Last line matching filter does NOT match value | See Last Match Rules |
Go's regexp uses RE2 which does not support lookahead/lookbehind. Use not_regex instead of (?!...). See RE2 Reference.
Rule Field Naming
| Policy Type | Property | Description |
|---|---|---|
| vulnerability / license / package | field | Entity field name (e.g., Severity, PkgName) |
| file / pr | key (or field alias) | Key path (e.g., version, dependencies.axios) |
Field names are case-insensitive for PR policies.
Policy Operators
OR (default) — match if any rule matches:
operator: or
rules:
- { field: Severity, type: eq, value: CRITICAL }
- { field: Severity, type: eq, value: HIGH }
AND — match only if all rules match:
operator: and
rules:
- { field: Severity, type: eq, value: CRITICAL }
- { field: PkgName, type: eq, value: lodash }
implies — conditional "If X then Y" logic:
operator: implies
rules:
- { key: changed_files, type: gt, value: "10" } # trigger
- { key: "files.filename", file_pattern: "docs/**", type: not_exists } # condition
- First rule is the trigger — if it doesn't match, policy passes silently
- Remaining rules are conditions — evaluated only if trigger matches
- Use
RuleRolefield in outputs to show trigger/condition labels
Output 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: Please fix before merging
| Field | Required | Description |
|---|---|---|
format | Yes | markdown, html, json, or sarif |
destination | Yes | Where to send output (see Outputs) |
template | No | table, text, or combined (not for JSON) |
fields | No | Which fields to include (defaults per policy type) |
changes | No | Filter to 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 |
webhook | No | Custom webhook config (only for url: destinations) |
See Outputs for full details.
Ignore Rules
Global Ignores
Filter findings based on file paths and targets across all policies:
global:
ignore:
- name: Ignore test directories
paths: ["test/**", "**/test/**", "**/*_test.go"]
- name: Ignore specific CVEs
targets: [CVE-2023-1234, CVE-2023-5678]
- name: Ignore vendor CVEs (with expiry)
paths: ["vendor/**"]
targets: [CVE-2024-0001]
expires: "2026-06-30"
| Field | Description |
|---|---|
name | Name for logging (required) |
paths | Glob patterns for file paths |
targets | Items to ignore (CVE IDs, package names, etc.) |
expires | Expiration date YYYY-MM-DD (rule ignored after this date) |
If both paths and targets are specified, finding must match both to be ignored.
Policy-Level Ignores
Content-based ignores within a specific policy:
vulnerability:
- name: Critical vulnerabilities
actions: { new: block }
rules:
- { field: Severity, type: eq, value: CRITICAL }
ignores:
- name: Windows-only CVE
expires: "2026-06-30"
author: security-[email protected]
operator: and
rules:
- { field: VulnerabilityID, type: eq, value: CVE-2024-0001 }
- { field: PkgName, type: eq, value: openssl }
| Field | Description |
|---|---|
operator | or (default) or and |
expires | Expiration date YYYY-MM-DD |
rules | Filter rules using same syntax as policy rules |
Logging
global:
logging:
level: info # silent, error, warn, info, debug, trace
format: text # text or json
show_timestamp: false
summary: standard # none, minimal, standard, detailed
Environment variables override config: CODEWARD_LOG_LEVEL, CODEWARD_LOG_FORMAT, CODEWARD_LOG_TIMESTAMP, CODEWARD_LOG_SUMMARY.
Default Configuration
When no config file exists, Codeward uses these defaults:
- Vulnerabilities: Warns on new/existing CRITICAL and HIGH. Posts to PR + creates issues.
- Licenses: Warns on new/existing Forbidden, Restricted categories and UNKNOWN severity. Posts to PR + creates issues.
- Packages: Info for all changes. Posts to PR.
- File/PR Validation: Not enabled by default.
Starter Configurations
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]
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
| Phase | Approach |
|---|---|
| 1. Observe | All actions set to info |
| 2. Warn | Change new to warn |
| 3. Block Critical | Set new: block for CRITICAL only |
| 4. Expand | Add HIGH to blocking |
| 5. Steady State | Block what matters, warn on the rest |
Tip: Use separate policies for different severity levels so you can promote them independently.