Skip to content

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.

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 /webhook on 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.

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: payments

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

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 CA

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

Leave both at their defaults:

security:
allow_shell_checks: false # default
template_sandbox: true # default

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

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.

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.

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.

  • 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.log alongside service.log/gateway.log) so request volume doesn’t drown out what you actually need to grep during an incident.
  • auth.tokens is set, allow_unauthenticated is not.
  • allow_shell_checks and template_sandbox are 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_total and the audit log look like under normal operation, so an anomaly is recognisable as one.