Skip to main content

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

  1. Rule evaluates → Check if condition matches
  2. Match found (Passed = true) → Create a finding
  3. Finding triggers actioninfo, warn, or block
  4. 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):

OperatorReports WhenExample
eqValue equals targetSeverity equals CRITICAL
neValue does not equal targetSeverity does not equal LOW
gtValue is greater than targetchanged_files is greater than 30
ltValue is less than targetchanged_files is less than 5
containsValue contains substringtitle contains WIP
not_containsValue does not contain substringDockerfile does not contain USER
regexValue matches patterntitle matches ^feat:
existsKey or file existsscripts.test exists
not_existsKey or file does not existSECURITY.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

TypeWhat It ScansCommon Use Cases
vulnerabilityCVEs in dependenciesBlock critical/high severity, track fixes
licenseLicense names and categoriesBlock copyleft, flag unknown licenses
packageDependency additions/removalsReview new packages, track version changes
fileFile contents and filesystemEnforce config standards, require files
prPR metadata and file changesLimit 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):

FieldDescription
VulnerabilityIDCVE identifier (e.g., CVE-2024-1234)
PkgIDPackage identifier
PkgNamePackage name
InstalledVersionCurrently installed version
FixedVersionVersion that fixes the vulnerability
StatusFix status (e.g., "fixed", "affected")
SeverityCRITICAL, HIGH, MEDIUM, LOW, UNKNOWN
Relationshipdirect or indirect (requires dependency_tree: true)
ChildrenChild packages (requires dependency_tree: true)
ParentsParent packages (requires dependency_tree: true)
SourcesAffected sources (requires dependency_tree: true)

Filterable Fields (for policy rules — all 25 fields):

FieldDescription
VulnerabilityIDCVE identifier (e.g., CVE-2024-1234)
PkgIDPackage identifier
PkgNamePackage name
InstalledVersionCurrently installed version
FixedVersionVersion that fixes the vulnerability
StatusFix status (e.g., "fixed", "affected")
SeverityCRITICAL, HIGH, MEDIUM, LOW, UNKNOWN
TitleVulnerability title
DescriptionDetailed description
PublishedDateWhen vulnerability was published
LastModifiedDateWhen vulnerability was last modified
ClosestFixedVersionClosest relevant fix version (computed)
SeveritySourceSource of severity rating
PrimaryURLPrimary reference URL
PkgIdentifierPURLPackage URL format identifier
PkgIdentifierUIDPackage unique identifier
DataSourceIDVulnerability database source ID
DataSourceNameVulnerability database source name
DataSourceURLVulnerability database source URL
CweIDsCWE identifiers (array — matches if any element matches)
ReferencesReference URLs (array — matches if any element matches)
Relationshipdirect or indirect (requires dependency_tree: true)
ChildrenChild packages (array, requires dependency_tree: true)
ParentsParent packages (array, requires dependency_tree: true)
SourcesAffected 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):

FieldDescription
SeverityLicense risk severity
CategoryLicense category (e.g., Permissive, Copyleft, Forbidden)
PkgNamePackage using this license
NameLicense name (e.g., MIT, GPL-3.0)
Relationshipdirect or indirect (requires dependency_tree: true)
ChildrenChild packages (requires dependency_tree: true)
ParentsParent packages (requires dependency_tree: true)
SourcesAffected sources (requires dependency_tree: true)

Filterable Fields (for policy rules — all 9 fields):

FieldDescription
SeverityLicense risk severity: CRITICAL, HIGH, MEDIUM, LOW, UNKNOWN
CategoryLicense category (e.g., Permissive, Copyleft, Forbidden)
PkgNamePackage using this license
NameLicense name (e.g., MIT, GPL-3.0)
Relationshipdirect or indirect (requires dependency_tree: true)
ChildrenChild packages (array, requires dependency_tree: true)
ParentsParent packages (array, requires dependency_tree: true)
SourcesAffected sources (array, requires dependency_tree: true)
DependencyTreeFull 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):

FieldDescription
IDPackage identifier
NamePackage name
VersionPackage version
Relationshipdirect or indirect (requires dependency_tree: true)
ChildrenChild packages (requires dependency_tree: true)
ParentsParent packages (requires dependency_tree: true)
SourcesAffected sources (requires dependency_tree: true)

Filterable Fields (for policy rules — all 9 fields):

FieldDescription
IDPackage identifier
NamePackage name
VersionPackage version
Relationshipdirect or indirect (requires dependency_tree: true)
IdentifierPURLPackage URL format identifier
IdentifierUIDPackage unique identifier
ChildrenChild packages (array, requires dependency_tree: true)
ParentsParent packages (array, requires dependency_tree: true)
SourcesAffected 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:

FieldRequiredDescription
typeYestext, json, yaml, yml, or filesystem
pathYesFile path or glob pattern to check

Validation Types and Rule Keys

TypeRule KeysAvailable Operators
texttype, valueregex, contains, not_contains, hasPrefix, hasSuffix, eq, ne
jsontype, key, valueeq, ne, lt, gt, le, ge, contains, not_contains, regex, exists, not_exists
yaml / ymltype, key, valueSame as json
filesystemtype, pathexists, 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 ValueDescriptionUse Case
all (default)All values must matchEnforce valid image registries
anyAt least one value must matchCheck if a sidecar exists
noneNo values should matchForbid latest tags or privileged mode

Examples:

  1. 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"
    }
    ]
    }]
    }
  2. 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:

FieldDescription
KeyThe rule key checked
TypeThe rule type used
ValueThe expected value
ReasonExplanation of pass/fail
PassingBoolean result
PathFile 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).

KeyDescription
titlePR 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:

KeyDescription
files.filenameFile path
files.statusFile status: added, removed, modified, renamed
files.additionsLines added in this file
files.deletionsLines deleted
files.changesTotal changes (additions + deletions)
files.patchUnified diff content
files.previous_filenameOriginal filename (for renamed files)
files.shaFile content SHA
files.blob_urlGitHub blob URL
files.raw_urlRaw file content URL
files.contents_urlGitHub API contents URL

File Filtering

FieldDescription
file_patternRegex pattern to match file paths (glob supported as fallback)
file_statusFilter by status: new, changed, removed, renamed, or *
actionPer-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:

OperatorDescriptionExample
eqEquals"CRITICAL"
neNot equalsExclude specific values
ltLess thanVersion or numeric comparison
gtGreater thanVersion or numeric comparison
leLess than or equal
geGreater than or equal
containsSubstring match"GPL" in license name
not_containsSubstring not present
hasPrefixStarts with"@types/" packages
hasSuffixEnds with.test.js files
regexRegular 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

  1. Start with warnings: Use warn for new policies, promote to block after validation
  2. Focus on new: Only block new findings — let existing issues be tracked separately
  3. Keep policies small: One intent per policy makes debugging easier
  4. Use meaningful names: Policy names appear in reports
  5. Add JSON outputs: Export to files for dashboards and automation
  6. Enable dependency_tree: When filtering by Relationship, Parents, or Children