Skip to main content
Version: Latest

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: Rules define what to search for, not requirements to enforce. When a rule matches, it creates a finding that triggers the configured action.

Think of rules as search queries:

  • ✅ "Find vulnerabilities with CRITICAL severity" → block
  • ✅ "Find files that don't contain USER instruction" → warn
  • ✅ "Find PRs larger than 30 files" → warn

How It Works

  1. Rule evaluates → checks if condition matches
  2. Match found → creates a finding
  3. Finding triggers actioninfo, warn, block, or ignore
  4. Output generated → sent to configured destination

Vulnerability Policies

Detect CVEs in dependencies. The scanner queries the trivy-db BoltDB database directly with built-in lockfile parsers for 17+ ecosystems.

Supported Ecosystems

EcosystemLockfiles
Node.jspackage-lock.json, yarn.lock, pnpm-lock.yaml, bun.lock
PythonPipfile.lock, poetry.lock, requirements.txt, uv.lock
RubyGemfile.lock
Gogo.mod
RustCargo.lock
PHPcomposer.lock
.NETpackages.lock.json
Dart/Flutterpubspec.lock
SwiftPackage.resolved, Podfile.lock
Javagradle.lockfile
Elixirmix.lock
C/C++conan.lock

Fields

FieldFilterableDisplayableDescription
VulnerabilityIDCVE identifier
PkgNamePackage name
PkgIDPackage identifier
InstalledVersionInstalled version
FixedVersionVersion with fix
ClosestFixedVersionClosest relevant fix version
SeverityCRITICAL, HIGH, MEDIUM, LOW, UNKNOWN
StatusFix status
TitleVulnerability title
DescriptionDetailed description
PublishedDatePublication date
LastModifiedDateLast modified date
PrimaryURLReference URL
SeveritySourceSource of severity rating
CweIDsCWE identifiers (matches if any element matches)
CVSSScoreCVSS score (float, best V4 > V3 > V2)
CVSSVectorFull CVSS vector string
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)

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]

# Filter by CVSS score
- name: high-cvss
actions: { new: block }
rules:
- { field: CVSSScore, type: gt, value: "9.0" }

# Only direct dependencies
- name: direct-only
actions: { new: block }
operator: and
rules:
- { field: Severity, type: eq, value: CRITICAL }
- { field: Relationship, type: eq, value: direct }

License Policies

Detect license compliance issues. Multi-source resolution fills in missing license data from lockfiles, local filesystem, upstream registries (npm, PyPI, RubyGems, crates.io, NuGet, Hex.pm, pub.dev, Go proxy), and GitHub API.

License Resolution Priority

PrioritySourceDescription
1LockfileParsed from lockfile metadata
2Local filesystemLICENSE files in dependency directories
3Upstream registriesRegistry APIs (npm, PyPI, etc.)
4GitHub APILast resort for Go modules

Results are cached to disk for 30 days.

Fields

FieldFilterableDisplayableDescription
NameLicense name (e.g., MIT, GPL-3.0)
SeverityRisk severity
CategoryCategory (Permissive, Copyleft, Forbidden, etc.)
PkgNamePackage using this license
LicenseSourceWhere license was resolved from
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)

Examples

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, LicenseSource]
changes: [new]

Package Policies

Track dependency changes: additions, removals, and version updates.

Fields

FieldFilterableDisplayableDescription
IDPackage identifier
NamePackage name
VersionPackage version
PURLPackage URL
UIDUnique identifier
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)

Examples

package:
- name: new-packages
actions: { new: warn }
rules: []
outputs:
- format: markdown
destination: git:pr
fields: [Name, Version]
changes: [new]

File Policies

Validate file contents and filesystem state. Supports JSON, YAML, TOML, text, env, properties, and filesystem checks.

Policy-Specific Fields

FieldRequiredDescription
typeNojson, yaml/yml, toml, text, env, properties, filesystem, or empty (auto-detect)
pathYesFile path or glob pattern
scanNoSet to lines for line-level text scanning
max_concurrencyNoParallel workers for glob patterns

File Types

TypeDescriptionAuto-detected by
jsonJSON file validation.json extension
yaml / ymlYAML file validation.yaml, .yml extension
tomlTOML file validation.toml extension
textPlain text validationFallback
envKEY=VALUE format (.env files).env extension
propertiesJava properties format.properties extension
filesystemFile/directory existence
(empty)Auto-detect from extensionExtension-based

Glob Patterns

File paths support glob patterns for multi-file scanning:

PatternDescription
**/*.yamlAll YAML files recursively
**/*.yaml,**/*.ymlComma-separated patterns
src/**/*.jsonJSON files in src tree
config[0-9].jsonCharacter class matching

Comma-separated patterns: spaces are trimmed, trailing commas ignored, results deduplicated.

Array Wildcards & Match Parameter

Use * in key paths to match array elements (e.g., spec.containers.*.image). Control evaluation with match:

MatchWhen Finding is Reported
all (default)ANY value matches the condition
anyALL values match (no safe values exist)
noneANY value matches (same as all)
file:
# Block if any container uses :latest
- name: no-latest
path: "k8s/**/*.yaml"
type: yaml
actions: { existing: block }
rules:
- key: spec.containers.*.image
type: contains
value: ":latest"
match: all

# Require at least one non-init container
- name: require-non-init
path: "k8s/**/*.yaml"
type: yaml
rules:
- key: spec.initContainers.*.name
type: eq
value: init
match: any # reports only if ALL are "init"
note

match can only be used with wildcard paths containing *.

Line-Level Text Scanning

Use scan: "lines" to process each logical line individually:

  • Line numbers in output (LineNumber field — "42" for single, "14-17" for continuation blocks)
  • Backslash continuation joining — lines ending with \ are joined into logical lines
  • Matched content (MatchedContent field, truncated to 120 chars)
file:
- name: detect-aws-keys
path: "**/*.py"
type: text
scan: lines
actions: { existing: block }
rules:
- type: regex
value: "AKIA[0-9A-Z]{16}"
output_reason: "AWS access key detected"
outputs:
- destination: github:pr
fields: [FilePath, LineNumber, MatchedContent, Reason]

Per-Line Implies

Combine operator: "implies" with scan: "lines" for per-line conditional logic:

file:
- name: dockerfile-apt-cleanup
path: Dockerfile
type: text
scan: lines
operator: implies
actions: { existing: block }
rules:
- type: regex
value: "apt-get install" # trigger
- type: regex
value: "rm -rf /var/lib/apt/lists" # condition
output_reason: "apt-get install without cleanup in same RUN block"

Each logical line is evaluated independently: if the trigger matches, conditions are checked on that same line. Lines where the trigger doesn't match are skipped.

Last Match Rules

Evaluate the last logical line matching a line_filter regex. Useful for "the last USER in a Dockerfile must not be root":

Rule TypeReports When
last_matchLast filtered line matches value
not_last_matchLast filtered line does NOT match value
file:
- name: dockerfile-non-root
path: Dockerfile
type: text
actions: { existing: block }
rules:
- type: last_match
value: root
line_filter: "^USER\\s+"
output_reason: "Last USER instruction runs as root"

If no lines match line_filter, the rule is silently skipped.

Conditional File Validation (implies + exists)

Check file content only when the file exists using operator: "implies" with an exists trigger:

file:
- name: check-engines-if-exists
path: package.json
type: json
operator: implies
actions: { existing: warn }
rules:
- type: exists # trigger
- key: engines.node
type: exists
output_reason: "package.json should specify engines.node"

If the file doesn't exist, the policy passes silently. Works with all file types.

Cross-File Value References

Compare values between files using ref_path and ref_key:

FieldDescription
ref_pathPath to reference file (defaults to same file)
ref_keyKey in reference file (defaults to same key)
ref_typeType of reference file: json, yaml, toml, env, properties (auto-detected)

ref_path/ref_key and value are mutually exclusive.

file:
# Same-file key comparison
- name: port-consistency
path: config.json
type: json
actions: { existing: warn }
rules:
- type: eq
key: server.port
ref_key: proxy.target_port
output_reason: "Server port and proxy target port must match"

# Cross-file comparison
- name: version-sync
path: package.json
type: json
actions: { existing: block }
rules:
- type: eq
key: version
ref_path: Chart.yaml
ref_type: yaml
output_reason: "package.json version must match Chart.yaml version"

Custom Output Reason

Override auto-generated messages with output_reason:

rules:
- key: scripts.test
type: not_exists
output_reason: "All packages must have a test script. Add 'test': 'vitest' to package.json."

Output Fields

FieldDescription
KeyRule key checked
TypeRule type used
Value / ExpectedExpected value
ReasonExplanation (or custom output_reason)
Passed / PassingBoolean result
Path / FilePathFile path checked
RuleRoletrigger or condition (implies logic)
MatchedContentMatched substring (line scanning)
LineNumberLine number(s) (line scanning)

Examples

file:
# Require SECURITY.md
- name: require-security-md
type: filesystem
path: "."
actions: { existing: block }
rules:
- { type: not_exists, path: SECURITY.md }

# Dockerfile must have USER
- name: dockerfile-user
type: text
path: Dockerfile
actions: { existing: block }
rules:
- { type: not_contains, value: USER }

# package.json must have test script
- name: require-test-script
type: json
path: package.json
actions: { existing: warn }
rules:
- { type: not_exists, key: scripts.test }

# TOML validation
- name: cargo-edition
type: toml
path: Cargo.toml
actions: { existing: warn }
rules:
- { type: eq, key: package.edition, value: "2021" }

# Env file validation
- name: env-check
type: env
path: .env.example
actions: { existing: warn }
rules:
- { type: exists, key: DATABASE_URL }

PR Policies

Validate pull request metadata and file changes. Only runs when CODEWARD_MODE=diff.

PR Keys

Keys are case-insensitive. All normalized to lowercase internally.

KeyDescription
numberPR number
titlePR title
bodyPR description
statePR state (open, closed, merged)
draftWhether PR is a draft
changed_filesNumber of changed files
lines_added / lines_removedTotal lines added/removed
commitsNumber of commits
head.ref / head.shaSource branch name / SHA
base.ref / base.shaTarget branch name / SHA
user.login / user.id / user.typePR author info
labels.*.name / labels_countLabel names (wildcard) / count
assignees.*.login / assignees_countAssignee names (wildcard) / count
comments.*.user.login / comments.*.bodyComment info (wildcard)
comments_countNumber of comments
requested_reviewers.*.login / requested_reviewers_countReviewer info (wildcard)
files.new / files.changed / files.removed / files.renamed / files.countFile lists and count

File-Scoped Keys

Use with file_pattern or file_status:

KeyDescription
files.filenameFile path
files.statusadded, removed, modified, renamed
files.additions / files.deletions / files.changesLine counts
files.patchUnified diff content
files.previous_filenameOriginal filename (renamed files)

Per-Rule Actions

PR rules support per-rule action overrides and output_reason:

pr:
- name: pr-quality
rules:
- key: changed_files
type: ge
value: "20"
action: warn
output_reason: "Large PR — consider breaking it up"
- key: title
type: contains
value: WIP
action: block

Wildcard Match Parameter

Same match parameter as file policies for wildcard paths like labels.*.name:

pr:
- name: require-human-review
rules:
- key: comments.*.user.type
type: eq
value: Bot
match: any # reports only if ALL comments are from bots
action: warn
output_reason: "PR needs at least one human comment"

Output Fields

FieldDescription
KeyRule key checked
TypeRule type used
Value / ExpectedExpected value
ReasonExplanation
Passed / PassingBoolean result
Path / FilePathFile path
FilenameFilename (file-scoped rules)
RuleRoletrigger or condition

Examples

pr:
# Limit PR size
- name: pr-size
rules:
- { key: changed_files, type: ge, value: "20", action: warn }
- { key: lines_added, type: ge, value: "500", action: warn }
outputs:
- { format: markdown, destination: git:pr }

# Block WIP PRs
- name: no-wip
rules:
- { key: title, type: contains, value: WIP, action: block }
outputs:
- { format: markdown, destination: git:pr }

# Conditional: if large PR, require docs
- name: docs-for-large-prs
operator: implies
rules:
- { key: changed_files, type: gt, value: "10" }
- { type: exists, file_pattern: "docs/**", file_status: changed, action: warn,
output_reason: "Large PRs should include documentation updates" }
outputs:
- destination: git:pr
fields: [RuleRole, Key, Value, Reason]

RE2 Regex Reference

Go's regexp uses the RE2 engine (linear-time, no backtracking).

Supported: (?i) case-insensitive, (?m) multiline, (?s) dot-all, \b word boundary, (?P<name>...) named groups, [...] character classes, \d \w \s, alternation |.

Unsupported and workarounds:

PCRE FeatureWorkaround
Negative lookahead (?!...)Use not_regex rule type
Positive lookahead (?=...)Two regex rules with and operator
Lookbehind (?<=...) / (?<!...)Use not_regex or last_match with line_filter
Backreferences \1No workaround

Best Practices

  1. Start with warnings — use warn first, promote to block after validation
  2. Focus on new — only block new findings; track existing issues separately
  3. Keep policies small — one intent per policy for easier debugging
  4. Use meaningful names — policy names appear in reports
  5. Enable dependency_tree only when needed — for Relationship, Parents, Children filters