Quick Diagnosis
| Symptom | Likely Cause | Fix |
|---|
| Everything marked "new" | Missing baseline | Mount /main, set CODEWARD_MODE=diff |
| No PR comment | Missing env vars | Set CODEWARD_GITHUB_TOKEN, PR_NUMBER, OWNER, REPOSITORY |
| Empty report | Wrong change filter | Include new in changes |
| Template error with JSON | template set for JSON | Remove template field |
| Invalid field error | Wrong field for policy type | Check Policies |
| Relationship filter ignored | Dependency tree disabled | Set global.dependency_tree: true |
| Rule not working | Misunderstood rule logic | Rules find problems, not enforce requirements |
Rule Logic
Common confusion: Rules define what to search for, not requirements.
| What You Want | Correct Rule |
|---|
| Find CRITICAL vulns | { type: eq, value: CRITICAL } |
| Find large PRs (>30 files) | { type: gt, value: "30" } |
| Find missing test script | { type: not_exists, key: scripts.test } |
| Find WIP PRs | { type: contains, value: WIP } |
| Find Dockerfiles without USER | { type: not_contains, value: USER } |
Think: ✅ "Find what I want to block" — not ❌ "Define requirements".
License Rules Match Nothing
Almost always the Category value. It is matched exactly and is
lowercase, and there is no Copyleft category — a rule using it validates
cleanly and then matches nothing, silently.
| Symptom | Cause | Fix |
|---|
Category eq Copyleft finds nothing | No such category | Category in "restricted,forbidden", or reciprocal for MPL/EPL/CDDL |
Category eq Restricted finds nothing | Values are lowercase | restricted |
A license you expect to be flagged reports unknown | Neither the built-in table nor Intel recognises it | Check LicenseSource in your output to see where the license came from |
LGPL not matched by a reciprocal rule | LGPL is restricted by default | Match restricted, or enable CODEWARD_INTEL_CLASSIFICATION — see License categories |
The seven valid values are forbidden, restricted, reciprocal, notice,
permissive, unencumbered and unknown.
Docker Volumes
| Mount | Purpose | Required |
|---|
/main | Main/base branch | Yes |
/branch | Feature branch | Only for diff mode |
/results | Output files | If using file: destinations |
/tmp/.cache | Intel snapshot segments + license cache | Strongly recommended |
Validation Errors
| Error | Fix |
|---|
invalid format | Use markdown, html, json, or sarif |
invalid destination | Start with git:, github:, log:, file:, or url: |
template must be empty | Remove template for JSON outputs |
invalid field | Use only allowed fields for the policy type |
webhook only valid on url: destinations | Move webhook config to a url: output |
- Persistent cache — mount
/tmp/.cache so snapshot segments survive between runs. This is the
single biggest win: a cold cache re-downloads ~87 MB for an npm project every time, and expands
to ~1.5 GB on disk.
- Cache it in CI too — the GitHub Action does this for you; for other CI systems, cache the
directory
CODEWARD_CACHE_DIR points at.
- Install dependencies — run
npm ci, pip install before scanning
- Air-gapped mode —
--intel-mode local against a pre-seeded cache
- Use a key for large monorepos —
api mode skips the snapshot download entirely
- Limit glob scope — narrow file patterns reduce scan time
- Pin image versions — use
ghcr.io/codeward-io/scan:v0.4.0
Exit Codes
| Code | Meaning |
|---|
| 0 | Success — no blocking findings |
| 1 | Failure — block action triggered, no vulnerability data obtained, or fatal error |
Security Notes
- All scanning runs inside the container — no code leaves the runner
- Outputs go only to configured destinations
- The only outbound calls are to Codeward Intel (snapshot downloads or API queries), package
registries for licence resolution, and your own
url: destinations
- Snapshot manifests are Ed25519-signed and verified before any segment is trusted
- The scanner sends no scan data back to Codeward; see Telemetry & Privacy
- Use repository-scoped tokens with minimal permissions
- Grant only:
contents: read, packages: read, pull-requests: write, issues: write