Policies
Codeward supports five policy types. Each scans for different concerns but shares the same configuration pattern for actions, rules, and outputs.
Understanding Rule Logic
Critical concept: Codeward rules define what to search for, not requirements to enforce. When a rule matches, it creates a finding that triggers the configured action.
Search-Based Paradigm
Think of rules as search queries:
- ✅ "Find vulnerabilities with CRITICAL severity"
- ✅ "Find files that don't contain USER instruction"
- ✅ "Find PRs larger than 30 files"
Not as requirements:
- ❌ "Severity must not be CRITICAL"
- ❌ "File must contain USER instruction"
- ❌ "PR must be smaller than 30 files"
How Rules Work
- Rule evaluates → Check if condition matches
- Match found (
Passed = true) → Create a finding - Finding triggers action →
info,warn, orblock - Output generated → Send to configured destination
Example:
{
"rules": [
{ "field": "Severity", "type": "eq", "value": "CRITICAL" }
],
"actions": { "new": "block" }
}
This rule:
- Searches for vulnerabilities where Severity equals CRITICAL
- When found → Creates a finding
- Then blocks the PR (because action is "block")
Operator Semantics
All operators report when the condition is true (matches):
| Operator | Reports When | Example |
|---|---|---|
eq | Value equals target | Severity equals CRITICAL |
ne | Value does not equal target | Severity does not equal LOW |
gt | Value is greater than target | changed_files is greater than 30 |
lt | Value is less than target | changed_files is less than 5 |
contains | Value contains substring | title contains WIP |
not_contains | Value does not contain substring | Dockerfile does not contain USER |
regex | Value matches pattern | title matches ^feat: |
exists | Key or file exists | scripts.test exists |
not_exists | Key or file does not exist | SECURITY.md does not exist |
Common Patterns
Find problems (what you want to block):
{
"name": "find-critical-vulns",
"rules": [
{ "field": "Severity", "type": "eq", "value": "CRITICAL" }
]
}
Find missing requirements:
{
"name": "find-missing-tests",
"rules": [
{ "key": "scripts.test", "type": "not_exists" }
]
}
Find policy violations:
{
"name": "find-large-prs",
"rules": [
{ "key": "changed_files", "type": "gt", "value": "30" }
]
}
Find unwanted content:
{
"name": "find-wip-prs",
"rules": [
{ "key": "title", "type": "contains", "value": "WIP" }
]
}
Multiple Rules (OR Logic)
By default, multiple rules use OR logic — a finding is created if any rule matches:
{
"operator": "OR",
"rules": [
{ "field": "Severity", "type": "eq", "value": "CRITICAL" },
{ "field": "Severity", "type": "eq", "value": "HIGH" }
]
}
This finds vulnerabilities that are either CRITICAL or HIGH.
Multiple Rules (AND Logic)
Use "operator": "AND" to require all rules to match:
{
"operator": "AND",
"rules": [
{ "field": "Severity", "type": "eq", "value": "CRITICAL" },
{ "field": "Relationship", "type": "eq", "value": "direct" }
]
}
This finds vulnerabilities that are both CRITICAL and direct dependencies.
Conditional Logic (Implies)
The implies operator enables "If X then Y" logic:
{
"operator": "implies",
"rules": [
{ "key": "changed_files", "type": "gt", "value": "10" },
{ "key": "files.filename", "file_pattern": "docs/**", "type": "not_exists" }
]
}
This means: "If more than 10 files changed, then report if docs were not updated."
- First rule is the trigger (condition)
- If trigger matches → evaluate remaining rules
- If trigger doesn't match → policy passes silently
Policy Types Overview
| Type | What It Scans | Common Use Cases |
|---|---|---|
| vulnerability | CVEs in dependencies | Block critical/high severity, track fixes |
| license | License names and categories | Block copyleft, flag unknown licenses |
| package | Dependency additions/removals | Review new packages, track version changes |
| file | File contents and filesystem | Enforce config standards, require files |
| pr | PR metadata and file changes | Limit PR size, enforce naming conventions |
Vulnerability Policies
Detect security vulnerabilities (CVEs) in your dependencies using embedded Trivy scanner.
Allowed Fields
Display Fields (for fields and group_by in outputs):
| Field | Description |
|---|---|
VulnerabilityID | CVE identifier (e.g., CVE-2024-1234) |
PkgID | Package identifier |
PkgName | Package name |
InstalledVersion | Currently installed version |
FixedVersion | Version that fixes the vulnerability |
Status | Fix status (e.g., "fixed", "affected") |
Severity | CRITICAL, HIGH, MEDIUM, LOW, UNKNOWN |
Relationship | direct or indirect (requires dependency_tree: true) |
Children | Child packages (requires dependency_tree: true) |
Parents | Parent packages (requires dependency_tree: true) |
Sources | Affected sources (requires dependency_tree: true) |
Filterable Fields (for policy rules — all 25 fields):
| Field | Description |
|---|---|
VulnerabilityID | CVE identifier (e.g., CVE-2024-1234) |
PkgID | Package identifier |
PkgName | Package name |
InstalledVersion | Currently installed version |
FixedVersion | Version that fixes the vulnerability |
Status | Fix status (e.g., "fixed", "affected") |
Severity | CRITICAL, HIGH, MEDIUM, LOW, UNKNOWN |
Title | Vulnerability title |
Description | Detailed description |
PublishedDate | When vulnerability was published |
LastModifiedDate | When vulnerability was last modified |
ClosestFixedVersion | Closest relevant fix version (computed) |
SeveritySource | Source of severity rating |
PrimaryURL | Primary reference URL |
PkgIdentifierPURL | Package URL format identifier |
PkgIdentifierUID | Package unique identifier |
DataSourceID | Vulnerability database source ID |
DataSourceName | Vulnerability database source name |
DataSourceURL | Vulnerability database source URL |
CweIDs | CWE identifiers (array — matches if any element matches) |
References | Reference URLs (array — matches if any element matches) |
Relationship | direct or indirect (requires dependency_tree: true) |
Children | Child packages (array, requires dependency_tree: true) |
Parents | Parent packages (array, requires dependency_tree: true) |
Sources | Affected sources (array, requires dependency_tree: true) |
Examples
Block new critical vulnerabilities:
{
"vulnerability": [{
"name": "block-critical",
"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"]
}]
}]
}
Block critical and high:
{
"rules": [
{ "field": "Severity", "type": "eq", "value": "CRITICAL" },
{ "field": "Severity", "type": "eq", "value": "HIGH" }
]
}
Only direct dependencies (block transitive noise):
{
"global": { "dependency_tree": true },
"vulnerability": [{
"name": "direct-only",
"actions": { "new": "block" },
"operator": "AND",
"rules": [
{ "field": "Severity", "type": "eq", "value": "CRITICAL" },
{ "field": "Relationship", "type": "eq", "value": "direct" }
],
"outputs": [{ "format": "markdown", "destination": "git:pr" }]
}]
}
Exclude a specific CVE:
{
"rules": [
{ "field": "Severity", "type": "eq", "value": "CRITICAL" },
{ "field": "VulnerabilityID", "type": "ne", "value": "CVE-2024-ACCEPTED" }
]
}
Filter by CWE category:
{
"rules": [
{ "field": "CweIDs", "type": "eq", "value": "CWE-89" }
]
}
Filter by vulnerability title:
{
"rules": [
{ "field": "Title", "type": "contains", "value": "Injection" }
]
}
Filter by data source:
{
"rules": [
{ "field": "DataSourceName", "type": "contains", "value": "National Vulnerability Database" }
]
}
Filter by publication date (2024 vulnerabilities):
{
"rules": [
{ "field": "PublishedDate", "type": "regex", "value": "^2024-" }
]
}
Filter by Package URL (npm packages):
{
"rules": [
{ "field": "PkgIdentifierPURL", "type": "regex", "value": "pkg:npm/.*" }
]
}
Complex multi-field filter (AND logic):
{
"operator": "AND",
"rules": [
{ "field": "Severity", "type": "eq", "value": "CRITICAL" },
{ "field": "CweIDs", "type": "eq", "value": "CWE-89" },
{ "field": "Title", "type": "regex", "value": "(?i)sql.*injection" },
{ "field": "PublishedDate", "type": "regex", "value": "^2024-" }
]
}
License Policies
Detect license issues for compliance. Trivy categorizes licenses by risk level.
Allowed Fields
Display Fields (for fields and group_by in outputs):
| Field | Description |
|---|---|
Severity | License risk severity |
Category | License category (e.g., Permissive, Copyleft, Forbidden) |
PkgName | Package using this license |
Name | License name (e.g., MIT, GPL-3.0) |
Relationship | direct or indirect (requires dependency_tree: true) |
Children | Child packages (requires dependency_tree: true) |
Parents | Parent packages (requires dependency_tree: true) |
Sources | Affected sources (requires dependency_tree: true) |
Filterable Fields (for policy rules — all 9 fields):
| Field | Description |
|---|---|
Severity | License risk severity: CRITICAL, HIGH, MEDIUM, LOW, UNKNOWN |
Category | License category (e.g., Permissive, Copyleft, Forbidden) |
PkgName | Package using this license |
Name | License name (e.g., MIT, GPL-3.0) |
Relationship | direct or indirect (requires dependency_tree: true) |
Children | Child packages (array, requires dependency_tree: true) |
Parents | Parent packages (array, requires dependency_tree: true) |
Sources | Affected sources (array, requires dependency_tree: true) |
DependencyTree | Full dependency tree (array, requires dependency_tree: true) |
Examples
Block copyleft licenses:
{
"license": [{
"name": "no-copyleft",
"actions": { "new": "block", "existing": "warn" },
"rules": [
{ "field": "Category", "type": "eq", "value": "Copyleft" }
],
"outputs": [{
"format": "markdown",
"destination": "git:pr",
"fields": ["Name", "Category", "PkgName"],
"changes": ["new"]
}]
}]
}
Block GPL by name:
{
"rules": [
{ "field": "Name", "type": "contains", "value": "GPL" }
]
}
Flag unknown licenses:
{
"rules": [
{ "field": "Severity", "type": "eq", "value": "UNKNOWN" }
]
}
Package Policies
Track dependency changes: additions, removals, and version updates.
Allowed Fields
Display Fields (for fields and group_by in outputs):
| Field | Description |
|---|---|
ID | Package identifier |
Name | Package name |
Version | Package version |
Relationship | direct or indirect (requires dependency_tree: true) |
Children | Child packages (requires dependency_tree: true) |
Parents | Parent packages (requires dependency_tree: true) |
Sources | Affected sources (requires dependency_tree: true) |
Filterable Fields (for policy rules — all 9 fields):
| Field | Description |
|---|---|
ID | Package identifier |
Name | Package name |
Version | Package version |
Relationship | direct or indirect (requires dependency_tree: true) |
IdentifierPURL | Package URL format identifier |
IdentifierUID | Package unique identifier |
Children | Child packages (array, requires dependency_tree: true) |
Parents | Parent packages (array, requires dependency_tree: true) |
Sources | Affected sources (array, requires dependency_tree: true) |
Examples
Track new packages:
{
"package": [{
"name": "new-packages",
"actions": { "new": "warn" },
"rules": [],
"outputs": [{
"format": "markdown",
"destination": "git:pr",
"fields": ["Name", "Version"],
"changes": ["new"]
}]
}]
}
Only direct dependencies:
{
"global": { "dependency_tree": true },
"package": [{
"name": "new-direct",
"actions": { "new": "warn" },
"rules": [
{ "field": "Relationship", "type": "eq", "value": "direct" }
],
"outputs": [{
"format": "markdown",
"destination": "git:pr",
"fields": ["Name", "Version", "Relationship"],
"changes": ["new"]
}]
}]
}
Export full inventory:
{
"package": [{
"name": "inventory",
"actions": { "new": "info", "existing": "info", "changed": "info", "removed": "info" },
"rules": [],
"outputs": [{
"format": "json",
"destination": "file:/results/packages.json"
}]
}]
}
File Policies
Assert rules against file contents or filesystem structure. Great for enforcing standards in Dockerfiles, CI configs, package.json, etc.
Additional Required Fields
File policies require extra fields:
| Field | Required | Description |
|---|---|---|
type | Yes | text, json, yaml, yml, or filesystem |
path | Yes | File path or glob pattern to check |
Validation Types and Rule Keys
| Type | Rule Keys | Available Operators |
|---|---|---|
text | type, value | regex, contains, not_contains, hasPrefix, hasSuffix, eq, ne |
json | type, key, value | eq, ne, lt, gt, le, ge, contains, not_contains, regex, exists, not_exists |
yaml / yml | type, key, value | Same as json |
filesystem | type, path | exists, not_exists |
Array Wildcards & Matching
For JSON and YAML policies, you can inspect arrays using the * wildcard in key paths. When a path returns multiple values (e.g., spec.containers.*.image), you can control how the rule is evaluated using the match parameter.
| Match Value | Description | Use Case |
|---|---|---|
all (default) | All values must match | Enforce valid image registries |
any | At least one value must match | Check if a sidecar exists |
none | No values should match | Forbid latest tags or privileged mode |
Examples:
-
Verify all container images use trusted registry (match:
all):{
"file": [{
"name": "trusted-registry",
"type": "yaml",
"path": "k8s/*.yaml",
"rules": [
{
"key": "spec.containers.*.image",
"type": "hasPrefix",
"value": "my-registry.io/",
"match": "all"
}
]
}]
} -
Ensure no container is privileged (match:
none):{
"file": [{
"name": "no-privileged",
"type": "yaml",
"path": "k8s/*.yaml",
"rules": [
{
"key": "spec.containers.*.securityContext.privileged",
"type": "eq",
"value": true,
"match": "none"
}
]
}]
}
Output Fields
File policies use different output fields:
| Field | Description |
|---|---|
Key | The rule key checked |
Type | The rule type used |
Value | The expected value |
Reason | Explanation of pass/fail |
Passing | Boolean result |
Path | File path checked |
Custom Output Reasons
File and PR policies allow you to customize the failure message using the output_reason field. This replaces the generic technical error with actionable instructions for developers.
Example:
{
"rules": [
{
"key": "scripts.test",
"type": "not_exists",
"output_reason": "All packages must have a test script. Please add 'test': 'vitest' to package.json."
}
]
}
Examples
Require SECURITY.md file:
{
"file": [{
"name": "find-missing-security-md",
"type": "filesystem",
"path": ".",
"actions": { "new": "block" },
"rules": [
{ "type": "not_exists", "path": "SECURITY.md" }
],
"outputs": [{
"format": "markdown",
"destination": "git:pr",
"fields": ["Path", "Reason", "Passing"]
}]
}]
}
Dockerfile must have USER instruction:
{
"file": [{
"name": "find-root-containers",
"type": "text",
"path": "Dockerfile",
"actions": { "new": "block", "changed": "block" },
"rules": [
{ "type": "not_contains", "value": "USER" }
],
"outputs": [{
"format": "markdown",
"destination": "git:pr",
"fields": ["Key", "Reason", "Passing"]
}]
}]
}
package.json must have test script:
{
"file": [{
"name": "find-missing-tests",
"type": "json",
"path": "package.json",
"actions": { "new": "warn" },
"rules": [
{ "type": "not_exists", "key": "scripts.test" }
],
"outputs": [{
"format": "markdown",
"destination": "git:pr"
}]
}]
}
CI workflow must have minimal permissions:
{
"file": [{
"name": "find-overprivileged-workflows",
"type": "yaml",
"path": ".github/workflows/*.yml",
"actions": { "new": "block", "changed": "block" },
"rules": [
{ "type": "ne", "key": "permissions.contents", "value": "read" }
],
"outputs": [{
"format": "markdown",
"destination": "git:pr"
}]
}]
}
PR Policies
Assert rules against pull request metadata and file changes. Only runs when CODEWARD_MODE=diff.
PR-Level Keys
Keys are case-insensitive (e.g., Title, title, and TITLE are equivalent).
| Key | Description |
|---|---|
title | PR title |
| state | PR state (open, closed, merged) |
| commits | Number of commits |
| changed_files | Number of files changed |
| total_added | Total lines added |
| total_removed | Total lines removed |
| user.login | PR author username |
| user.type | Author type (User, Bot, Organization) |
| comments.count | Number of comments |
| requested_reviewers.count | Number of requested reviewers |
File-Level Keys
Use files.* prefix to check individual file changes:
| Key | Description |
|---|---|
files.filename | File path |
files.status | File status: added, removed, modified, renamed |
files.additions | Lines added in this file |
files.deletions | Lines deleted |
files.changes | Total changes (additions + deletions) |
files.patch | Unified diff content |
files.previous_filename | Original filename (for renamed files) |
files.sha | File content SHA |
files.blob_url | GitHub blob URL |
files.raw_url | Raw file content URL |
files.contents_url | GitHub API contents URL |
File Filtering
| Field | Description |
|---|---|
file_pattern | Regex pattern to match file paths (glob supported as fallback) |
file_status | Filter by status: new, changed, removed, renamed, or * |
action | Per-rule action: info, warn, or block |
Note: content_rules is not supported. Use files.* keys instead to inspect file contents (e.g., files.patch).
Examples
Limit PR size:
{
"pr": [{
"name": "find-large-prs",
"rules": [
{ "type": "ge", "key": "changed_files", "value": "20", "action": "warn" },
{ "type": "ge", "key": "total_added", "value": "500", "action": "warn" }
],
"outputs": [{
"format": "markdown",
"destination": "git:pr"
}]
}]
}
Block WIP PRs:
{
"pr": [{
"name": "find-wip-prs",
"rules": [
{ "type": "contains", "key": "title", "value": "WIP", "action": "block" },
{ "type": "contains", "key": "title", "value": "work in progress", "action": "block" }
],
"outputs": [{
"format": "markdown",
"destination": "git:pr"
}]
}]
}
No changes to sensitive files:
{
"pr": [{
"name": "find-sensitive-file-changes",
"rules": [
{
"type": "exists",
"key": "files.filename",
"file_pattern": ".*\\.env.*",
"action": "block"
}
],
"outputs": [{
"format": "markdown",
"destination": "git:pr"
}]
}]
}
Check patch content for secrets:
{
"pr": [{
"name": "find-hardcoded-secrets",
"rules": [
{
"type": "contains",
"key": "files.patch",
"value": "password=",
"file_status": "*",
"action": "block"
}
],
"outputs": [{
"format": "markdown",
"destination": "git:pr"
}]
}]
}
Rule Operators Reference
Available for all policy types:
| Operator | Description | Example |
|---|---|---|
eq | Equals | "CRITICAL" |
ne | Not equals | Exclude specific values |
lt | Less than | Version or numeric comparison |
gt | Greater than | Version or numeric comparison |
le | Less than or equal | |
ge | Greater than or equal | |
contains | Substring match | "GPL" in license name |
not_contains | Substring not present | |
hasPrefix | Starts with | "@types/" packages |
hasSuffix | Ends with | .test.js files |
regex | Regular expression | "^CVE-2024-.*" |
Conditional Logic (implies)
The implies operator (available at the policy level) 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" }
]
}
Additional for validation/filesystem:
| exists | Key or file exists | |
| not_exists | Key or file does not exist | |
Version-aware comparisons: lt, gt, le, ge automatically detect semantic versions and compare correctly (e.g., 1.10.0 > 1.9.0).
Best Practices
- Start with warnings: Use
warnfor new policies, promote toblockafter validation - Focus on
new: Only blocknewfindings — let existing issues be tracked separately - Keep policies small: One intent per policy makes debugging easier
- Use meaningful names: Policy names appear in reports
- Add JSON outputs: Export to files for dashboards and automation
- Enable dependency_tree: When filtering by
Relationship,Parents, orChildren