chainsaw policy
Manage release policies
Manage release policies
| Subcommand | What it does |
|---|---|
chainsaw policy audit | Report live policies whose thresholds can never fire, or fire on everything |
chainsaw policy create | Create a new policy |
chainsaw policy delete | Delete a policy |
chainsaw policy disable | Disable a policy |
chainsaw policy enable | Enable a policy |
chainsaw policy eval | Evaluate a Rego policy bundle against a JSON input fixture |
chainsaw policy export | Export all policies as YAML or JSON for backup |
chainsaw policy flip-to-block | Flip a monitor-mode policy to block (with would-block preview) |
chainsaw policy gate | Run a policy decision for one of the five enforcement surfaces |
chainsaw policy import | Import policies from a YAML or JSON file |
chainsaw policy lint | Scan policy files for rules vulnerable to recent semantic changes |
chainsaw policy list | List policies for the current org |
chainsaw policy preflight | Show which policy conditions are supported per ecosystem |
chainsaw policy rollout | Staged monitor→block rollout for policies |
chainsaw policy rollout status | List monitor-mode policies and their would-block stats |
chainsaw policy show | Show one policy’s full configuration |
chainsaw policy simulate | Test whether a package would be blocked by current policies |
chainsaw policy [command]
Manage release policies for your org — list, create, simulate, and back them up.
Examples: chainsaw policy list chainsaw policy show pol_01H8… chainsaw policy create –name block-criticals –mode block –condition ‘{“cvssMin”: 9.0}’ chainsaw policy simulate lodash@4.17.11 chainsaw policy export –format yaml –output policies.yaml
chainsaw policy audit
Report live policies whose thresholds can never fire, or fire on everything
chainsaw policy audit [flags]
Inspect this org’s LIVE policies for numeric thresholds the evaluator can never satisfy, or always satisfies.
(Unrelated to chainsaw audit, which reads the audit-event log.
policy lint asks the same kind of question of policy FILES on disk;
this asks it of the rows the server is enforcing right now.)
Two shapes, and they are opposite problems:
never fires e.g. cvssMin: 999. No package can score above 10, and a policy’s conditions are AND-ed, so a BLOCK policy carrying it blocks nothing — while every screen that lists it reads “we refuse criticals”.
matches everything e.g. cvssMin: 0, which every package satisfies including ones with no CVE at all (they score 0). If nothing else on the policy narrows it, a BLOCK policy carrying it blocks every request that reaches it.
Each finding names the row, the field, the value, the valid range, and what the policy actually does — derived from the evaluator’s own comparison, which is quoted in the finding.
Policy writes have been bounds-checked since v0.21.19, but only for the violations a write INTRODUCES: an existing row can still be disabled, approved or rolled back without being rewritten, which is deliberate and is also why bad rows written earlier survive untouched. This command is how you find them.
This command is READ-ONLY and does not repair anything. It deliberately has
no –fix: rewriting a block policy’s threshold changes what that policy
refuses, and that is an operator’s decision, not a sweep’s. Fix a row with
chainsaw policy create / the policy API once you have decided what it
was meant to say.
Exit codes: 0 no out-of-range or match-all thresholds 1 warnings only — values inside the accepted range that still match every package (cvssMin: 0 and friends) 2 any error — a value outside the range the API accepts today
Examples: chainsaw policy audit chainsaw policy audit –json
Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--json | bool | — | Output as JSON |
The global flags apply here too.
chainsaw policy create
Create a new policy
chainsaw policy create [flags]
Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--condition | string | — | Conditions as a JSON object (see docs) |
--description | string | — | Human-readable description |
--has-hidden-unicode | bool | — | Condition: artifact contains >=threshold hidden/unicode payload runes |
--has-install-script | bool | — | Condition: package declares an install/postinstall lifecycle script |
--hidden-unicode-kinds | string | — | Comma-separated subset: zero_width,bidi_override,tag |
--install-script-fetches-remote | bool | — | Condition: install script body references curl/wget/fetch/etc. |
--json | bool | — | Print created policy as JSON |
--mode | string | monitor | Action mode: allow|monitor|block|quarantine |
--name | string | — | Policy name (required) |
--precedence | int | 0 | Evaluation precedence (lower = higher priority; 0 auto-assigns the next free slot) |
--publish-velocity-anomaly | bool | — | Condition: publisher exceeded publish velocity threshold in 24h |
--publish-velocity-threshold-24h | int | 0 | Override default 24h publish-velocity threshold (>=1) |
--publisher-changed | bool | — | Condition: publisher/maintainer set changed vs the prior persisted version |
--status | string | enabled | Initial status: enabled|disabled |
--version-anomaly-kinds | string | — | Comma-separated subset: semver_regression,major_skip,timestamp_regression |
--version-anomaly | bool | — | Condition: metadiff flagged any version-sequence anomaly |
The global flags apply here too.
chainsaw policy delete
Delete a policy
chainsaw policy delete <policy-id> [flags]
Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--dry-run | bool | — | Preview what would be deleted without actually deleting |
--yes | bool | — | Skip confirmation prompt |
The global flags apply here too.
chainsaw policy disable
Disable a policy
chainsaw policy disable <policy-id>
chainsaw policy enable
Enable a policy
chainsaw policy enable <policy-id>
chainsaw policy eval
Evaluate a Rego policy bundle against a JSON input fixture
chainsaw policy eval [flags]
Evaluate a chainsaw.policy Rego bundle against a JSON fixture in the canonical input shape (internal/policy/schema/input.schema.json).
Designed for the rule-author dev loop:
chainsaw policy eval –bundle ./policies –input fixtures/young-maintainer.json
Exit codes: 0 decision is allow or monitor 1 decision is block or quarantine 2 evaluation error (syntax error in rego, malformed input, etc.)
Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--bundle | string | — | Path to a directory or .rego file containing the policy bundle (required) |
--input | string | — | Path to a JSON input fixture (matches internal/policy/schema/input.schema.json) (required) |
The global flags apply here too.
chainsaw policy export
Export all policies as YAML or JSON for backup
chainsaw policy export [flags]
Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--format | string | yaml | Output format: yaml|json |
The global flags apply here too.
chainsaw policy flip-to-block
Flip a monitor-mode policy to block (with would-block preview)
chainsaw policy flip-to-block <policy-id> [flags]
Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--json | bool | — | Output as JSON |
--yes | bool | — | Skip confirmation prompt |
The global flags apply here too.
chainsaw policy gate
Run a policy decision for one of the five enforcement surfaces
chainsaw policy gate <surface> [flags]
Run the unified policy DSL decision for an enforcement surface.
Surface must be one of: pr, proxy, publish, deploy, runtime.
(“promote” is accepted for schema compatibility but is RESERVED — no production caller evaluates policy there, so it is not advertised as a live enforcement surface. See core/policy/input.go.)
This is the entry point every chainsaw CI / git hook / k8s webhook / package-manager hook calls into. The same Rego rule in –bundle fires at every surface where its input fields are populated.
chainsaw policy gate proxy –bundle ./policies –input event.json chainsaw policy gate pr –bundle ./policies –input pr.json
Exit codes match chainsaw policy eval so callers can wire the same
exit-code → CI-status mapping at every surface.
Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--bundle | string | — | Path to a directory or .rego file containing the policy bundle (required) |
--input | string | — | Path to a JSON input fixture (the surface stamps its own surface tag) (required) |
The global flags apply here too.
chainsaw policy import
Import policies from a YAML or JSON file
chainsaw policy import <file> [flags]
Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--dry-run | bool | — | Show what would be imported without creating anything |
The global flags apply here too.
chainsaw policy lint
Scan policy files for rules vulnerable to recent semantic changes
chainsaw policy lint [flags]
Scan policy JSON/YAML files for rules that depend on semantics that have recently shifted under them.
Three checks run today:
Standalone codesmell (ERROR): a rule that gates ONLY on one of the five demoted Wave-3 codesmell signals (UsesEval, NetworkAccess, ShellAccess, FilesystemAccess, EnvVarAccess) with no identifier, scope, or other condition. The save-time validator already rejects these — lint is the discovery tool for files already on disk.
Three-state nil-as-false reliance (WARNING): a rule that gates on RepoArchived=false or FirstTimeCollaborator=false. Now that those fields are *bool, “false” means “confirmed false” — not “unknown or false” as the old two-state shape allowed. Operators may have intended either reading; lint flags the call site so they can verify intent.
Degenerate numeric thresholds (ERROR or WARNING): a bounded condition (cvssMin/Max, epssMin/Max, trustScoreMin/Max, requireSlsaLevel, packageAge, cooldownDays, or a min above its paired max) whose value the evaluator can never satisfy, or always satisfies. ERROR when the value is outside the range the API enforces on save (cvssMin: 999); WARNING when it is legal but degenerate (cvssMin: 0 matches every package, including ones with no CVE). This is the same classifier “chainsaw policy audit” runs over live rows, so a file and the row it becomes cannot disagree.
Pointing –input at a DIRECTORY is a sweep: the walker skips .git, node_modules, vendor, .gradle, target, build, dist and .venv/venv, and any JSON/YAML it picks up that is not a policy document (package.json, tsconfig.json, .github/workflows/*.yml) is reported as skipped rather than as a malformed policy — and does not count toward the rule total. Naming a file explicitly keeps the strict reading: an unparseable file you asked for is an error.
Exit codes: 0 clean 1 warnings only 2 any errors — your policies have problems 12 the tree could not be fully inspected (a directory or a candidate policy file could not be read), so the result is INCOMPLETE and must not be read as clean. Distinct from 2 on purpose: a CI gate has to be able to tell “your policies are bad” from “the scan never ran”.
Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--format | string | text | Output format: text|json |
--input | string | — | Policy file or directory to scan (recursive for dirs) |
The global flags apply here too.
chainsaw policy list
List policies for the current org
chainsaw policy list
List every policy in the current org with a summary of its conditions.
The CONDITIONS column is width-bounded: a cell ending in “+N more” has been elided, and “-” means the rule has no runtime conditions and fires on every identifier match. A row in this table proves a policy EXISTS; it does not prove what the policy is configured to do.
Run chainsaw policy show <id> for one policy’s complete configuration,
or chainsaw policy list --json for every field of every policy.
chainsaw policy preflight
Show which policy conditions are supported per ecosystem
chainsaw policy preflight [flags]
Fetch the proxy policy compatibility matrix from the server and report which conditions are unsupported for which ecosystems. Reuses the same GET /api/policies/support-matrix endpoint the Web UI uses, so what you see here is byte-identical to the inline warnings rendered next to policy condition inputs in the dashboard.
Use this in CI to catch policies that reference conditions silently inert on the target ecosystem before applying them. Pass –policy <file-or-dir> and the command exits 1 when a condition YOUR rules use cannot produce a verdict on a printed ecosystem — and names the rule, the condition and the ecosystem.
Two shapes are reported separately, because the fix differs. A condition the matrix marks unsupported is DISCARDED by the proxy: the whole rule stops enforcing. A condition the matrix marks supported on an ecosystem with no vulnerability advisory source (apt, yum, dnf, swift, huggingface, cocoapods) is EVALUATED, against a signal nothing populates — so it never matches, and a non-match means “could not look”, not “looked and found nothing”.
When –policy names a DIRECTORY, the sweep skips .git/node_modules/vendor and friends and ignores JSON/YAML that is not a policy document; anything it could not READ is listed and exits 12 (the gate covered only part of your policy set). Naming a single file keeps the strict reading: it must parse.
Without –policy it is a pure informational dump of the compatibility matrix and exits 0. (Every ecosystem has at least one unsupported condition, so gating on the raw matrix would fail every run for every org — which is what it used to do.)
Examples: chainsaw policy preflight chainsaw policy preflight –ecosystem npm chainsaw policy preflight –ecosystem npm –json chainsaw policy preflight –unsupported-only chainsaw policy preflight –policy ./policies –ecosystem npm
Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--ecosystem | string | — | Filter to a single ecosystem (e.g. npm, pip, maven). Default: all ecosystems. |
--json | bool | — | Output the raw matrix response as JSON. |
--policy | string | — | Policy file or directory to gate on. Only conditions these rules actually use drive the exit code. Without it the command is an informational dump and exits 0. |
--unsupported-only | bool | — | Only print rows containing at least one unsupported (none) cell. |
The global flags apply here too.
chainsaw policy rollout
Staged monitor→block rollout for policies
chainsaw policy rollout [command]
Inspect and advance the monitor→block rollout for policies.
Examples: chainsaw policy rollout status # list monitor policies + stats chainsaw policy flip-to-block <id> –yes # atomic flip with audit
chainsaw policy rollout status
List monitor-mode policies and their would-block stats
chainsaw policy rollout status [flags]
Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--json | bool | — | Output as JSON |
The global flags apply here too.
chainsaw policy show
Show one policy’s full configuration
chainsaw policy show <policy-id>
Print everything a policy is configured with — mode, status, expiry, the identifier it targets, the requester scope it is limited to, and every condition that must hold for it to fire.
This is the command that answers “is this rule actually configured to do
what I think”. chainsaw policy list proves a row exists and summarises
its conditions in a bounded column; it cannot show a policy’s full
configuration, and a policy with no conditions at all looks the same
there as one with fifteen.
Conditions are rendered from the server’s own JSON, so a condition the engine understands is a condition this prints — there is no curated field list here to fall behind the engine.
An empty Conditions block is not a rendering gap. It means the rule has no runtime conditions and fires on every identifier match.
Examples: chainsaw policy show pol_01H8… chainsaw policy show pol_01H8… –json
chainsaw policy simulate
Test whether a package would be blocked by current policies
chainsaw policy simulate <package@version> [flags]
Preview which of your org’s live policies a package coordinate would hit.
Matching uses the proxy evaluator’s own identifier matcher, so wildcards ("*") and semver constraints ("<2.15.0", “^1.0.0”) behave exactly as they do in enforcement. Expired exceptions are skipped, as the evaluator skips them.
Outcomes: allow|block|quarantine|monitor a live rule matched and the CLI could decide it (runtime conditions aside) conditional a rule matched, but deciding it needs something this command does not have — server-side signals (CVSS, EPSS, trust score), a repository (–repository), a version, or requester scope. The –json “unevaluated” array names them. no_match no live rule targets this coordinate
Exit code is 1 for block/quarantine, 0 otherwise — including conditional, which is informational.
Examples: chainsaw policy simulate lodash@4.17.11 chainsaw policy simulate log4j:log4j-core@2.14.1 –repository maven-central –json
Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--json | bool | — | Output as JSON |
--repository | string | — | Repository the install would come from (e.g. npm-proxy). Without it, repo-scoped policies report ‘conditional’ rather than a guessed verdict. |
The global flags apply here too.