Securing a deployment
Read Threat model first if you haven’t — it explains why each of these steps matters, in particular why containment (step 4) is not optional the way the others can sometimes be skipped on a fully trusted network. This page is the ordered checklist for actually doing it.
1. Network placement
Section titled “1. Network placement”Three zones, and nothing else needs to reach any of them from outside:
- Webhook senders (Alertmanager, a chat platform, anything triggering a
pipeline) need to reach
POST /webhookon VectorStep’s port. Nothing else. - Operators (anyone using the UI or calling the read/write API by hand) need to reach VectorStep’s port over HTTPS. Nothing else.
- VectorStep and the Gateway talk to each other, and to nothing else inbound. Neither needs to be reachable by a webhook sender or a browser directly.
| Port | Service | Faces |
|---|---|---|
VectorStep’s server.port (default 8000) |
VectorStep | Webhook senders and operators — the only port that should ever face a browser or an external sender |
Gateway’s server.port (default 18780) |
Gateway | VectorStep, and whoever authors agents (a Gateway MCP client) — never a browser, never a webhook sender |
The Gateway’s port must never face the internet. Its admin token can
rewrite agent definitions; there is no reason for anything outside your own
operator tooling to reach it. The Docker Compose stack the installer manages
already publishes no host port for the Gateway by default — see
Docker.
2. Authentication
Section titled “2. Authentication”Generate a token per identity that needs one, assign each the lowest role
that does its job, and set auth.tokens:
auth: tokens: - name: platform-admin token: ${VS_TOKEN_PLATFORM_ADMIN} role: admin - name: sre-oncall token: ${VS_TOKEN_SRE} role: operator - name: payments-alerts token: ${VS_TOKEN_PAYMENTS} role: webhook team: paymentsFull role reference, route-by-route requirements, and the UI login/session
mechanism: Security. VectorStep refuses to
start with no tokens configured unless you explicitly set
auth.allow_unauthenticated: true — never do that on anything reachable
from a shared network.
3. TLS
Section titled “3. TLS”Terminate browser-facing TLS at a reverse proxy or ingress — that’s where
most organisations already manage certificate rotation. The
service-to-Gateway hop is different: it’s not usually proxied, and it
carries the Gateway’s bearer token, so give it real wss:// with
verification instead:
executors: gateway: url: wss://gateway.internal:18780/rpc tls: ca: /etc/vectorstep/tls/ca.crt # if the Gateway's cert is signed by a private CAWorked Caddy and nginx snippets, the forwarded_allow_ips setting a
terminating proxy needs, and the in-process server.tls option for either
hop: Deployment → TLS.
4. Containment
Section titled “4. Containment”Leave both at their defaults:
security: allow_shell_checks: false # default template_sandbox: true # defaultThreat model
covers why: with both defaulted, an admin token can author any pipeline
but cannot turn that into host access. Turning allow_shell_checks: true on
is a reasonable choice for a single-team deployment where every pipeline
author is already trusted as an operator of the host — just make it a
deliberate line in your own config, not an inherited default.
5. The Gateway
Section titled “5. The Gateway”The Gateway mints two tokens on first boot: admin (agent writes,
/reload — held by whoever authors agents) and invoke (/rpc runs and
reads only — the one VectorStep’s own executors.gateway.token holds).
Give VectorStep the invoke token, never the admin one — VectorStep never
needs to rewrite an agent definition, so it shouldn’t be able to. Full
detail: Gateway authentication.
Keep the Gateway off any host port (§1). If you need it reachable from
outside its own network for agent authoring, put TLS and its own
admin-scoped credential in front of it rather than exposing the port
directly.
6. Tool policy
Section titled “6. Tool policy”tool_policy in the Gateway’s config is an operator-owned allow/deny list,
evaluated on every tool call regardless of what an individual agent’s own
tools: allowlist says — a deployment-wide backstop an agent author can’t
loosen by editing their own agent. If your agents can reach anything
destructive (a Jira issue deleter, a database write tool, an
infrastructure-changing API), this is where you put a floor under it:
tool_policy: default: allow rules: - deny: {server: atlassian, tool: jira_delete_issue} reason: "Destructive Jira operations are operator-only"Full schema, matching semantics, and the audit trail this produces: Tool policy.
7. Secrets
Section titled “7. Secrets”Every secret — provider API keys, tokens, database credentials — resolves
from an environment variable via ${VAR} substitution. Never commit a
config file with a secret written in directly. On Kubernetes, that means a
Secret referenced via envFrom, never baked into a ConfigMap — see
Kubernetes for the manifests. The
Docker Compose installer generates auth.tokens and the Gateway’s invoke
token for you on first install and writes them into .env, never into a
file that gets version-controlled.
8. Observability of the security surface
Section titled “8. Observability of the security surface”vectorstep_auth_failures_total{reason}— a Prometheus counter of authentication/authorization failures, broken down by reason. Alert on a sustained rise the way you’d alert on any other credential-stuffing signal.- The audit log (
GET /audit,viewer-role) — every config write,/reload, and approval decision, attributed to a token name. See Security → Audit log. - The access log — every HTTP request, kept separate from application logs
(
access.logalongsideservice.log/gateway.log) so request volume doesn’t drown out what you actually need to grep during an incident.
9. Before you expose this
Section titled “9. Before you expose this”-
auth.tokensis set,allow_unauthenticatedis not. -
allow_shell_checksandtemplate_sandboxare both at their defaults, or you’ve made a deliberate, documented call to change one. - TLS terminates somewhere on the browser-facing hop, and the
service-to-Gateway hop is
wss://with verification on. - The Gateway’s port is not reachable from anywhere it doesn’t need to be.
- Every secret is an environment variable, none are in a version-controlled file.
- You know what
vectorstep_auth_failures_totaland the audit log look like under normal operation, so an anomaly is recognisable as one.