Skip to content
Beta. VectorStep is in beta.Let us know if something breaks.

Restricting pipelines with security policies

Security policies documents the mechanism exhaustively — schema, enforcement points, the read-only endpoints. This guide is the opinionated companion: how to actually decide what goes in a policy, how many you need, and how to roll one out without surprising every pipeline author in the deployment at once.

Start from the executor you’d regret enabling, not from a compliance framework

Section titled “Start from the executor you’d regret enabling, not from a compliance framework”

A security policy is easiest to write backwards from a specific, nameable regret: “if a pipeline touching cardholder data ever gets a shell step, a PCI audit becomes a very bad conversation.” That’s a policy — deny: {executor: shell}, with a reason: that says exactly that. Don’t start from “what does PCI-DSS 4.0 require” and try to derive a rule set from a framework document; VectorStep makes no compliance claims, and a policy built that way tends to accumulate rules nobody can explain the purpose of a year later. A policy that denies exactly the executors that would concern a specific reviewer, with a reason: written for that reviewer, stays legible. A policy that tries to encode a whole framework doesn’t.

One policy per reason to deny, not one policy per team or per pipeline

Section titled “One policy per reason to deny, not one policy per team or per pipeline”

It’s tempting to reach for a policy per team (payments-team-policy, marketing-team-policy) the way you might reach for per-team config elsewhere in VectorStep. Resist it. A policy’s name should describe why something is restricted, not who it applies to — pci-dss-lite, no-host-access, notify-only — because pipelines from more than one team often share the same underlying reason for restriction, and a team-named policy either gets duplicated across teams (drifting out of sync the moment one copy is edited) or becomes a dumping ground of unrelated rules that happen to apply to one team’s pipelines. Mixed membership is fine and expected: a pipeline can declare more than one policy (security_policies: [pci-dss-lite, no-host-access]), and each one stays narrow and independently auditable.

Default allow with targeted denies, unless you’re building an allowlist on purpose

Section titled “Default allow with targeted denies, unless you’re building an allowlist on purpose”

default: allow plus a handful of deny: rules reads clearly as “this policy blocks these specific things, everything else is fine” — the right shape for most restriction sets, since most policies exist to rule out one or two dangerous executors, not to enumerate every safe one. Reach for default: deny plus explicit allow: rules only when you genuinely want an allowlist — “this pipeline may only ever notify, nothing else” — and expect to add an allow: rule every time a legitimately new executor need shows up. Getting this backwards (a default: deny policy an operator forgot they set to allowlist mode) is the single most likely way a policy silently breaks a pipeline nobody meant to restrict that far; if a newly-written pipeline that declares a policy fails to write for a reason you didn’t expect, check default: first.

Reach for security_policies.required only once a policy has proven itself opt-in first

Section titled “Reach for security_policies.required only once a policy has proven itself opt-in first”

Ship a new policy as purely opt-in initially — let a few pipelines declare it, watch it reject what it’s supposed to reject and nothing else — before adding it to security_policies.required.production. A required policy is a hard write-time gate on every pipeline at that stage; if the policy itself has a bug (an overly broad deny:, a typo’d executor name), making it mandatory before it’s proven turns that bug into an outage for every stage: production write, not just the one pipeline you were testing it against. required is the right tool once you’re confident in a policy — it’s the mechanism that actually makes “if it runs, it adheres” true deployment-wide, rather than “adheres, for the pipelines whose authors remembered to opt in.”

A denial should name the fix, not just the violation

Section titled “A denial should name the fix, not just the violation”

reason: isn’t optional on a deny: rule for a reason — it’s what turns a rejected write from “the API said no” into “here’s what to do instead.” Write it for the pipeline author hitting the rejection cold, not for yourself as the policy’s author: “Shell steps run unsandboxed on the host — use a webhook deterministic check instead” tells them the next step; “denied by policy” doesn’t. The write-time error already names the step, the executor, and the policy automatically — reason: is the one part only you can supply, and it’s the part that actually helps.