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.
Where next
Section titled “Where next”- Security policies — the full mechanism reference this guide assumes.
- Tutorial: restrict a pipeline with a security policy — write one and watch it reject a real write.
- Designing pipelines — the policy-vs-deployment-wide-toggle distinction this guide assumes you’ve already made.
- Threat model — what an
admintoken can and can’t do to a policy once one exists.