chainsaw policy

Manage release policies

Manage release policies

SubcommandWhat it does
chainsaw policy auditReport live policies whose thresholds can never fire, or fire on everything
chainsaw policy createCreate a new policy
chainsaw policy deleteDelete a policy
chainsaw policy disableDisable a policy
chainsaw policy enableEnable a policy
chainsaw policy evalEvaluate a Rego policy bundle against a JSON input fixture
chainsaw policy exportExport all policies as YAML or JSON for backup
chainsaw policy flip-to-blockFlip a monitor-mode policy to block (with would-block preview)
chainsaw policy gateRun a policy decision for one of the five enforcement surfaces
chainsaw policy importImport policies from a YAML or JSON file
chainsaw policy lintScan policy files for rules vulnerable to recent semantic changes
chainsaw policy listList policies for the current org
chainsaw policy preflightShow which policy conditions are supported per ecosystem
chainsaw policy rolloutStaged monitor→block rollout for policies
    chainsaw policy rollout statusList monitor-mode policies and their would-block stats
chainsaw policy showShow one policy’s full configuration
chainsaw policy simulateTest 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

FlagTypeDefaultDescription
--jsonbool—Output as JSON

The global flags apply here too.

chainsaw policy create

Create a new policy

chainsaw policy create [flags]

Flags

FlagTypeDefaultDescription
--conditionstring—Conditions as a JSON object (see docs)
--descriptionstring—Human-readable description
--has-hidden-unicodebool—Condition: artifact contains >=threshold hidden/unicode payload runes
--has-install-scriptbool—Condition: package declares an install/postinstall lifecycle script
--hidden-unicode-kindsstring—Comma-separated subset: zero_width,bidi_override,tag
--install-script-fetches-remotebool—Condition: install script body references curl/wget/fetch/etc.
--jsonbool—Print created policy as JSON
--modestringmonitorAction mode: allow|monitor|block|quarantine
--namestring—Policy name (required)
--precedenceint0Evaluation precedence (lower = higher priority; 0 auto-assigns the next free slot)
--publish-velocity-anomalybool—Condition: publisher exceeded publish velocity threshold in 24h
--publish-velocity-threshold-24hint0Override default 24h publish-velocity threshold (>=1)
--publisher-changedbool—Condition: publisher/maintainer set changed vs the prior persisted version
--statusstringenabledInitial status: enabled|disabled
--version-anomaly-kindsstring—Comma-separated subset: semver_regression,major_skip,timestamp_regression
--version-anomalybool—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

FlagTypeDefaultDescription
--dry-runbool—Preview what would be deleted without actually deleting
--yesbool—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

FlagTypeDefaultDescription
--bundlestring—Path to a directory or .rego file containing the policy bundle (required)
--inputstring—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

FlagTypeDefaultDescription
--formatstringyamlOutput 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

FlagTypeDefaultDescription
--jsonbool—Output as JSON
--yesbool—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

FlagTypeDefaultDescription
--bundlestring—Path to a directory or .rego file containing the policy bundle (required)
--inputstring—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

FlagTypeDefaultDescription
--dry-runbool—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:

  1. 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.

  2. 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.

  3. 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

FlagTypeDefaultDescription
--formatstringtextOutput format: text|json
--inputstring—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

FlagTypeDefaultDescription
--ecosystemstring—Filter to a single ecosystem (e.g. npm, pip, maven). Default: all ecosystems.
--jsonbool—Output the raw matrix response as JSON.
--policystring—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-onlybool—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
FlagTypeDefaultDescription
--jsonbool—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

FlagTypeDefaultDescription
--jsonbool—Output as JSON
--repositorystring—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.