Skip to content

Configuration Reference

All VibeWarden configuration lives in vibewarden.yaml at the root of your project. Every field can also be set via an environment variable — the variable name is the YAML key path uppercased and prefixed with VIBEWARDEN_, with dots replaced by underscores. Example: server.port → VIBEWARDEN_SERVER_PORT.

Environment variable substitution in YAML values is supported using ${VAR} syntax. This is recommended for secrets:

database:
  external_url: "postgres://user:${DB_PASSWORD}@db.example.com:5432/vibewarden"

profile

Type: string Default: dev Accepted values: dev, tls, prod

Selects the deployment profile. Affects TLS settings, credential validation strictness, and which generated files are produced.

profile: dev

server

Settings for the VibeWarden HTTP listener.

Field Type Default Description
server.host string 127.0.0.1 Host/IP to bind to
server.port int 8443 Port to listen on
server.max_connections int 1000 Maximum concurrent inbound connections per listener. 0 disables the cap. Negative values are rejected by vibew validate.
server.mem_limit string 512MB Memory cap for the sidecar container in the generated compose file. "0" or "" disables the cap. Generate-time only.
server.cpu_limit float 1.0 CPU cap for the sidecar container, in cores (0.5 = half a core). 0 disables the cap. Generate-time only.
server.pids_limit int 200 Cap on processes/OS threads the sidecar container may create. 0 disables the cap. Generate-time only.
server:
  host: 0.0.0.0
  port: 8443
  max_connections: 1000
  mem_limit: "512MB"
  cpu_limit: 1.0
  pids_limit: 200

server.max_connections

Once the cap is reached, new connections are refused (accepted and immediately closed) until an existing connection ends. Established connections and in-flight requests are unaffected. This bounds file-descriptor use during a connection flood so the sidecar degrades instead of dying, taking the app it protects with it.

Two caveats worth knowing:

  • The cap is per listener, not per process. While the port-80 HTTPS redirect server runs (self-signed and external TLS providers) the process ceiling is 2 × max_connections.
  • HTTP/3 is not covered. QUIC arrives over a single UDP socket rather than a TCP listener, so it cannot cause the descriptor exhaustion this cap exists to prevent, but it also means this is not a general request-concurrency limit. For per-client request throttling use the rate_limit block instead.

See ADR-110.

server.mem_limit, server.cpu_limit, server.pids_limit

These three keys cap the vibewarden sidecar container, so a memory leak or a runaway loop in the sidecar cannot exhaust the host and take the app it protects down with it. server.max_connections is the graceful in-process refusal; these are the backstop for everything it cannot see.

They are generate-time only. vibew generate and vibew bundle render them as mem_limit, cpus and pids_limit on the vibewarden service of the generated docker-compose.yml; the running sidecar never reads them, so changing one requires regenerating and recreating the container, not just a restart with a new config.

Four things worth knowing:

  • Only the sidecar is capped. Your app container and the stack services (kratos, postgres, redis, openbao) are untouched — VibeWarden does not put limits on containers it does not own.
  • A disabled cap omits the key. Setting a limit to 0 (or "" for mem_limit) removes it from the generated file entirely rather than emitting an explicit 0.
  • GOMEMLIMIT is derived from mem_limit at 90% of the cap and set on the sidecar container. Without it a memory cap would make the sidecar more likely to be OOM-killed, since the Go GC does not see the cgroup ceiling. Disabling mem_limit drops GOMEMLIMIT too. GOMAXPROCS needs no counterpart: Go reads the cgroup CPU quota itself.
  • overrides.compose_file bypasses all of this. Your compose file is copied verbatim, so no limits are injected; set them yourself if you want them.

mem_limit accepts both spellings you are likely to type: "512MB" and Docker's "512M", plus "1GB"/"1g" and a plain byte count.

See ADR-111.


health

Settings for the public /_vibewarden/health endpoint.

Field Type Default Description
health.expose_version bool true When true, the health JSON includes the running binary version. Set to false to omit it — hardening against version fingerprinting on internet-facing deployments.

The X-Vibewarden: 1 response header is always emitted regardless of this setting, so tooling that detects a VibeWarden sidecar should rely on that header rather than the version field.

health:
  expose_version: false   # omit version from /_vibewarden/health (hardened)

upstream

Settings for the upstream application being protected.

Field Type Default Description
upstream.host string 127.0.0.1 Upstream host
upstream.port int 3000 Upstream port
upstream.health.enabled bool false Enable active upstream health checking
upstream.health.path string /health HTTP path to probe
upstream.health.interval duration 10s Time between probes
upstream.health.timeout duration 5s Per-probe timeout
upstream.health.unhealthy_threshold int 3 Consecutive failures to mark unhealthy
upstream.health.healthy_threshold int 2 Consecutive successes to mark healthy
upstream:
  host: 127.0.0.1
  port: 3000
  health:
    enabled: true
    path: /health
    interval: 10s
    timeout: 5s

app

Controls how your application is included in the generated Docker Compose file. When neither build nor image is set, no app service is rendered and VibeWarden falls back to host.docker.internal.

Field Type Default Description
app.build string "" Docker build context path (e.g., .) — used in dev/tls profiles
app.image string "" Docker image reference (e.g., ghcr.io/org/myapp:latest) — used in prod profile
app:
  build: .        # dev workflow: build from source
  # image: ghcr.io/org/myapp:latest   # prod workflow: use pre-built image

tls

Field Type Default Description
tls.enabled bool true Enable TLS
tls.domain string "" Domain for the TLS certificate. Required for all ACME providers
tls.provider string "" Certificate provider: letsencrypt (falls back to ZeroSSL when tls.email is set; see ADR-083), zerossl, buypass (explicit opt-in only, upstream currently unhealthy), letsencrypt-staging, self-signed, or external
tls.cert_path string "" Path to PEM certificate. Required when provider is external
tls.key_path string "" Path to PEM private key. Required when provider is external
tls.email string "" ACME account email. Required for ZeroSSL, recommended for Let's Encrypt (cert expiry notifications)
tls.acme_ca string "" Override ACME directory URL (e.g. ZeroSSL: https://acme.zerossl.com/v2/DV90)
tls.storage_path string "" Directory for ACME certificate storage. Applies to letsencrypt only
tls:
  enabled: true
  provider: letsencrypt
  domain: myapp.example.com

auth

auth (top-level)

Field Type Default Description
auth.mode string none Auth strategy and single source of truth for whether auth is on. Set to none to disable, or kratos, jwt, or api-key to enable. See ADR-065
auth.public_paths list [] Paths that do not require authentication. If a valid session cookie is present, identity headers are still set (optional auth). /_vibewarden/* is always public
auth.session_cookie_name string ory_kratos_session Kratos session cookie name
auth.login_url string /self-service/login/browser Redirect for unauthenticated users
auth.on_kratos_unavailable string 503 Behavior when Kratos is unreachable: 503 or allow_public
auth.identity_schema string email_password Identity schema: email_password, email_only, username_password, social, or a file path
auth.role_paths map {} Maps role names (user, admin, moderator) to URL path patterns requiring that role. Users whose Kratos role trait does not match receive HTTP 403. Only valid when auth.mode is kratos. When empty, no role enforcement is performed but X-User-Role is still set

auth.jwt

Used when auth.mode is jwt.

Field Type Default Description
auth.jwt.jwks_url string "" Direct JWKS endpoint URL. Takes precedence over issuer_url
auth.jwt.issuer_url string "" OIDC issuer base URL for auto-discovery
auth.jwt.issuer string "" Expected iss claim value. Required
auth.jwt.audience string "" Expected aud claim value. Required
auth.jwt.claims_to_headers map see below JWT claims injected as upstream request headers
auth.jwt.allowed_algorithms list [RS256, ES256] Accepted signing algorithms. Never include none or HS256 in production
auth.jwt.cache_ttl duration 1h How long the JWKS is cached locally

Default claims_to_headers:

claims_to_headers:
  sub: X-User-Id
  email: X-User-Email
  email_verified: X-User-Verified

auth.api_key

Used when auth.mode is api-key.

Field Type Default Description
auth.api_key.header string X-API-Key Request header carrying the API key
auth.api_key.keys list [] Static key entries (see below)
auth.api_key.openbao_path string "" KV path in OpenBao where key hashes are stored
auth.api_key.cache_ttl duration 5m TTL for keys fetched from OpenBao
auth.api_key.scope_rules list [] Ordered path+method authorization rules

Each entry in keys:

Field Type Description
name string Human-readable label
hash string Hex-encoded SHA-256 of the plaintext key
scopes list Permission scopes granted to this key

Each entry in scope_rules:

Field Type Description
path string Glob pattern matched against the request path
methods list HTTP methods this rule applies to (empty = all methods)
required_scopes list Scopes the key must possess

auth.social_providers

List of OAuth2/OIDC social login providers (used with auth.mode: kratos).

Field Type Description
provider string Provider name: google, github, apple, facebook, microsoft, gitlab, discord, slack, spotify, oidc
client_id string OAuth2 client ID
client_secret string OAuth2 client secret
scopes list OAuth2 scopes to request (optional; provider defaults apply)
label string Custom login button label (optional)
team_id string Apple Developer Team ID (Apple only)
key_id string Apple private key ID (Apple only)
id string Unique identifier for OIDC entries
issuer_url string OIDC issuer URL (OIDC provider only)

auth.ui

Field Type Default Description
auth.ui.mode string built-in built-in or custom
auth.ui.app_name string "" Application name on built-in login pages
auth.ui.logo_url string "" Logo image URL shown above the form on built-in pages
auth.ui.favicon_url string "" Favicon URL for built-in pages
auth.ui.custom_css_url string "" Extra stylesheet loaded last on built-in pages
auth.ui.primary_color string #7C3AED Accent color for built-in pages
auth.ui.background_color string #1a1a2e Background color for built-in pages
auth.ui.login_url string "" Custom login page URL (when mode: custom)
auth.ui.registration_url string "" Custom registration page URL
auth.ui.settings_url string "" Custom account settings page URL
auth.ui.recovery_url string "" Custom account recovery page URL
auth.ui.show_registration bool true Self-service registration: link, built-in page, and Kratos flow
auth.ui.show_recovery bool true Self-service account recovery: link, built-in page, and Kratos flow

In built-in mode VibeWarden serves the auth pages itself at /_vibewarden/login, /_vibewarden/registration, /_vibewarden/recovery, /_vibewarden/verification and /_vibewarden/settings, and points the generated kratos/kratos.yml flows at them.

Branding applies to all five pages: app_name prefixes the <title> and is shown above the form, logo_url renders as an image above it, favicon_url is linked as the page icon, and custom_css_url is linked after the built-in styles so its rules win. logo_url, favicon_url and custom_css_url must each be an absolute http(s) URL or a root-relative path such as /static/logo.svg; anything else (including javascript: and data: URIs) is rejected at config validation. Assets served from your own app need a public_paths entry, otherwise the login page cannot load them while the visitor is still unauthenticated.

Set show_registration: false for a closed-registration deployment (users are created by an admin only) and show_recovery: false when you do not offer self-service password recovery. Each flag drives three things at once: the link on the built-in login page, the built-in page itself (which then returns 404), and selfservice.flows.{registration,recovery}.enabled in the generated kratos/kratos.yml. That keeps the page and Kratos from disagreeing, which used to leave dead links on the login page.

In custom mode the generated flows point at your auth.ui.*_url values instead. Absolute URLs are used as-is; a path such as /login is resolved against your public base URL (tls.domain, else localhost:<server.port>). The verification and error flows have no key of their own and fall back to auth.ui.login_url, as does any *_url you leave empty.


kratos

Used when auth.mode is kratos.

Field Type Default Description
kratos.public_url string http://127.0.0.1:4433 Kratos public API URL. In the generated Docker Compose stack, use http://kratos:4433
kratos.admin_url string http://127.0.0.1:4434 Kratos admin API URL. In the generated Docker Compose stack, use http://kratos:4434
kratos.dsn string "" Kratos database DSN (postgres URL)
kratos.external bool false Connect to a user-managed Kratos instance instead of starting one
kratos.smtp.host string localhost SMTP server host
kratos.smtp.port int 1025 SMTP server port
kratos.smtp.from string no-reply@vibewarden.local Sender address for Kratos emails

rate_limit

Field Type Default Description
rate_limit.enabled bool true Enable rate limiting
rate_limit.store string memory Backing store: memory or redis
rate_limit.trust_proxy_headers bool false Read X-Forwarded-For for client IP. Enable only behind a trusted proxy
rate_limit.exempt_paths list [] Glob patterns that bypass rate limiting. /_vibewarden/* is always exempt

rate_limit.per_ip and rate_limit.per_user

Field Type Default (per_ip) Default (per_user) Description
requests_per_second float 10 100 Sustained token refill rate
burst int 20 200 Maximum tokens that can accumulate

rate_limit.redis

Only read when store is redis.

Field Type Default Description
url string "" Redis URL (redis:// or rediss://). Takes precedence over all other fields
address string localhost:6379 Redis address in host:port form
password string "" Redis AUTH password
db int 0 Logical database index
pool_size int 0 (auto) Connection pool size (0 = auto based on CPU count)
key_prefix string vibewarden Key namespace prefix
fallback bool true Fail-open on Redis failure (true) or fail-closed (false)
health_check_interval duration 30s How often to probe Redis for recovery

security_headers

Field Type Default Description
security_headers.enabled bool true Enable security headers middleware
security_headers.hsts_max_age int 31536000 HSTS max-age in seconds (1 year)
security_headers.hsts_include_subdomains bool true Add includeSubDomains to HSTS
security_headers.hsts_preload bool false Add preload to HSTS
security_headers.content_type_nosniff bool true Set X-Content-Type-Options: nosniff
security_headers.frame_option string DENY X-Frame-Options: DENY, SAMEORIGIN, or "" to disable
security_headers.content_security_policy string default-src 'self'; style-src 'self' 'unsafe-inline' Content-Security-Policy value
security_headers.referrer_policy string strict-origin-when-cross-origin Referrer-Policy value
security_headers.permissions_policy string "" Permissions-Policy value
security_headers.cross_origin_opener_policy string same-origin Cross-Origin-Opener-Policy value
security_headers.cross_origin_resource_policy string same-origin Cross-Origin-Resource-Policy value
security_headers.permitted_cross_domain_policies string none X-Permitted-Cross-Domain-Policies value
security_headers.suppress_via_header bool true Remove Via header from proxied responses

The headers are applied to every response the sidecar serves, not just proxied app responses: the built-in login UI (/_vibewarden/login), the admin API (/_vibewarden/admin/*, including its 401 responses), and the Kratos self-service surface (/self-service/*) all carry the same set.

Operator rules under response_headers still win on conflict, but only on the app-proxied route: response_headers is not applied to VibeWarden's own routes, so it cannot override or strip a security header on the login UI, the admin API or the Kratos proxy.


cors

Field Type Default Description
cors.enabled bool false Enable CORS plugin
cors.allowed_origins list [] Permitted origins. Use ["*"] for development only
cors.allowed_methods list [GET, POST, PUT, DELETE, OPTIONS] Permitted HTTP methods
cors.allowed_headers list [Content-Type, Authorization] Permitted request headers
cors.exposed_headers list [] Response headers exposed via Access-Control-Expose-Headers
cors.allow_credentials bool false Set Access-Control-Allow-Credentials: true. Must not be combined with allowed_origins: ["*"]
cors.max_age int 0 Preflight cache duration in seconds. 0 omits the header

waf

VibeWarden's built-in Web Application Firewall inspects every inbound request for common attack patterns in-process, with no external dependencies.

waf (top-level)

Field Type Default Description
waf.enabled bool true Enable the WAF middleware
waf.mode string detect Detection mode: block (reject with 400) or detect (pass through and log)

waf.rules

Per-rule toggles. All rules are enabled by default when waf.enabled is true. Set a rule to false to disable that check while keeping all others active.

Field Type Default Description
waf.rules.sqli bool true Detect SQL injection patterns in query strings and request bodies
waf.rules.xss bool true Detect cross-site scripting payloads
waf.rules.path_traversal bool true Detect directory traversal sequences (e.g. ../../etc/passwd)
waf.rules.command_injection bool true Detect shell command-injection metacharacters

waf.content_type_validation

Field Type Default Description
waf.content_type_validation.enabled bool false Enable Content-Type validation on body-bearing requests
waf.content_type_validation.allowed list [application/json, application/x-www-form-urlencoded, multipart/form-data] Permitted media types. Requests with other types receive 415 Unsupported Media Type
waf:
  enabled: true
  mode: block
  rules:
    sqli: true
    xss: true
    path_traversal: true
    command_injection: true
  content_type_validation:
    enabled: true
    allowed:
      - application/json
      - application/x-www-form-urlencoded
      - multipart/form-data

body_size

Field Type Default Description
body_size.max string "" Global maximum body size (e.g., 1MB, 512KB). Empty means no limit
body_size.overrides list [] Per-path overrides

Each entry in body_size.overrides:

Field Type Description
path string URL path prefix to match
max string Maximum body size for this path
body_size:
  max: 1MB
  overrides:
    - path: /api/upload
      max: 50MB

ip_filter

Field Type Default Description
ip_filter.enabled bool false Enable IP filter plugin
ip_filter.mode string blocklist allowlist or blocklist
ip_filter.addresses list [] IP addresses or CIDR ranges
ip_filter.trust_proxy_headers bool false Read X-Forwarded-For for client IP
ip_filter:
  enabled: true
  mode: allowlist
  addresses:
    - 10.0.0.0/8
    - 192.168.1.100

resilience

Field Type Default Description
resilience.timeout duration 30s Upstream response timeout. 0 disables the timeout

resilience.circuit_breaker

Field Type Default Description
resilience.circuit_breaker.enabled bool false Enable circuit breaker
resilience.circuit_breaker.threshold int 5 Consecutive failures to trip the circuit
resilience.circuit_breaker.timeout duration 60s How long the circuit stays open before probing

resilience.retry

Field Type Default Description
resilience.retry.enabled bool false Enable retry with exponential backoff
resilience.retry.max_attempts int 3 Total attempts including the initial request
resilience.retry.backoff duration 100ms Wait before the first retry
resilience.retry.max_backoff duration 10s Upper bound on computed backoff
resilience.retry.retry_on list [502, 503, 504] HTTP status codes that trigger a retry

telemetry

Field Type Default Description
telemetry.enabled bool true Master switch for all telemetry
telemetry.path_patterns list [] URL path normalization patterns using :param syntax
telemetry.prometheus.enabled bool true Enable Prometheus pull-based exporter at /_vibewarden/metrics
telemetry.otlp.enabled bool false Enable OTLP push-based exporter
telemetry.otlp.endpoint string "" OTLP HTTP endpoint URL. Required when otlp.enabled is true
telemetry.otlp.headers map {} HTTP headers for OTLP authentication
telemetry.otlp.interval duration 30s Export interval
telemetry.otlp.protocol string http Transport protocol. Only http is supported
telemetry.logs.otlp bool false Export structured events via OTLP. Requires otlp.endpoint
telemetry.traces.enabled bool false Enable distributed tracing. Requires otlp.enabled

Legacy metrics: section

The metrics: config block is deprecated. Settings are automatically migrated to telemetry: at startup with a deprecation warning. Migrate before the next major version.


observability

Controls generation of the optional local observability stack (Grafana, Prometheus, Loki, Promtail).

Field Type Default Description
observability.enabled bool false Activate the observability compose profile (configs are always generated)
observability.grafana_port int 3001 Host port for Grafana
observability.prometheus_port int 9090 Host port for Prometheus
observability.loki_port int 3100 Host port for Loki
observability.retention_days int 7 Log retention period in Loki

database

Field Type Default Description
database.external_url string "" Full postgres:// connection URL. When set, the local Postgres container is omitted from the generated Compose file
database.tls_mode string require PostgreSQL SSL mode: disable, require, verify-ca, verify-full
database.pool.max_conns int 10 Maximum open connections
database.pool.min_conns int 2 Minimum idle connections
database.connect_timeout duration 10s Connection establishment timeout

egress

Configuration for the egress proxy plugin. See the Egress Proxy guide for a full feature walkthrough.

egress (top-level)

Field Type Default Description
egress.enabled bool false Enable the egress proxy plugin
egress.listen string 127.0.0.1:8081 TCP address the egress proxy binds to
egress.default_policy string deny Disposition for requests that match no route: deny or allow
egress.allow_insecure bool false Permit plain HTTP egress requests globally. Per-route allow_insecure overrides this
egress.default_timeout duration 30s Request timeout applied when a route does not specify its own
egress.default_body_size_limit string "" Global maximum request body size (e.g. 10MB). Empty means no limit
egress.default_response_size_limit string "" Global maximum response body size (e.g. 50MB). Empty means no limit

egress.dns

DNS-level SSRF protection settings.

Field Type Default Description
egress.dns.block_private bool true Block requests to RFC 1918 private, loopback, and reserved IP ranges
egress.dns.allowed_private list [] CIDR ranges exempt from block_private. Example: ["10.0.0.0/8"]

egress.routes

Ordered list of egress route definitions. Routes are evaluated in declaration order; the first matching route wins.

Field Type Default Description
name string — Unique human-readable identifier for this route. Required
pattern string — URL glob matched against outbound request URLs (must start with http:// or https://). Required
methods list [] HTTP methods this route applies to. Empty means all methods
timeout duration "" Per-route request timeout. Empty uses egress.default_timeout
secret string "" OpenBao KV path of the secret to fetch and inject
secret_header string "" Request header to inject the secret value into (e.g. Authorization)
secret_format string "" Value template — {value} is replaced with the resolved secret (e.g. Bearer {value})
rate_limit string "" Rate limit expression (e.g. 100/s, 500/m). Empty means no per-route limit
body_size_limit string "" Maximum request body size. Empty uses egress.default_body_size_limit
response_size_limit string "" Maximum response body size. Empty uses egress.default_response_size_limit
allow_insecure bool false Permit plain HTTP for this route, overriding the global setting

egress.routes[].circuit_breaker

Field Type Description
threshold int Consecutive failures to trip the circuit. Must be > 0
reset_after duration How long the circuit stays open before allowing a probe request

egress.routes[].retries

Field Type Default Description
max int — Maximum retry attempts (not counting the initial request). Must be >= 1
methods list [GET, HEAD, PUT, DELETE] HTTP methods eligible for retry. Empty uses the default idempotent set
backoff string exponential Backoff strategy: exponential or fixed

egress.routes[].mtls

Field Type Description
cert_path string Filesystem path to the PEM-encoded client certificate
key_path string Filesystem path to the PEM-encoded private key
ca_path string Filesystem path to a PEM-encoded CA bundle used to verify the server's certificate (optional; uses system roots when empty)

egress.routes[].validate_response

Field Type Description
status_codes list Allowed HTTP status code expressions (e.g. ["2xx", "301"]). Empty means no validation
content_types list Allowed MIME type prefixes (e.g. ["application/json"]). Empty means no validation

egress.routes[].cache

Field Type Description
enabled bool Activate in-memory response caching for this route
ttl duration Cache entry lifetime. Zero means never expire (not recommended)
max_size int Maximum cached response body size in bytes. Zero means no per-entry limit

egress.routes[].sanitize

Field Type Description
headers list Header names whose values are replaced with [REDACTED] in log events (forwarded request is unchanged)
query_params list Query parameter names stripped from the URL before forwarding
body_fields list JSON field names replaced with [REDACTED] in the request body before forwarding. Only applied when Content-Type is application/json

egress.routes[].headers

Field Type Description
inject map Static headers added to (or overwriting) the outbound request
strip_request list Request header names removed before forwarding upstream
strip_response list Response header names removed from the upstream response before returning to the app. Server and X-Powered-By are always stripped
egress:
  enabled: true
  listen: "127.0.0.1:8081"
  default_policy: deny
  default_timeout: "30s"

  dns:
    block_private: true

  routes:
    - name: stripe-api
      pattern: "https://api.stripe.com/**"
      methods: ["POST"]
      timeout: "10s"
      secret: app/stripe
      secret_header: Authorization
      secret_format: "Bearer {value}"
      rate_limit: "100/s"
      circuit_breaker:
        threshold: 5
        reset_after: "30s"
      retries:
        max: 3
        backoff: exponential

secrets

Field Type Default Description
secrets.enabled bool false Enable secrets management plugin
secrets.provider string openbao Secret store backend
secrets.cache_ttl duration 5m How long fetched secrets are cached in memory

secrets.openbao

Field Type Default Description
secrets.openbao.address string "" OpenBao server URL (e.g., http://openbao:8200)
secrets.openbao.mount_path string secret KV v2 mount path
secrets.openbao.auth.method string "" Auth method: token or approle
secrets.openbao.auth.token string "" Static token (used when method: token)
secrets.openbao.auth.role_id string "" AppRole role_id (used when method: approle)
secrets.openbao.auth.secret_id string "" AppRole secret_id (used when method: approle)

secrets.inject

Field Type Description
secrets.inject.headers list Secrets injected as HTTP request headers
secrets.inject.env_file string Path to write a .env file with secret values
secrets.inject.env list Secrets written to the env file

Each entry in secrets.inject.headers:

Field Type Description
secret_path string KV path of the secret
secret_key string Key within the secret map
header string HTTP header name

secrets.dynamic

Field Type Default Description
secrets.dynamic.postgres.enabled bool false Enable dynamic Postgres credential generation
secrets.dynamic.postgres.roles list [] OpenBao database roles to request credentials for

Each entry in roles:

Field Type Description
name string OpenBao database role name
env_var_user string Env var to write the generated username into
env_var_password string Env var to write the generated password into

secrets.health

Field Type Default Description
secrets.health.check_interval duration 6h How often to run secret health checks
secrets.health.max_static_age duration 2160h Maximum acceptable age of a static secret (90 days)
secrets.health.weak_patterns list [] Substrings that indicate a weak or default secret

webhooks

Field Type Description
webhooks.endpoints list Webhook endpoints to deliver audit events to

Each entry in webhooks.endpoints:

Field Type Default Description
url string — HTTP(S) endpoint to POST events to. Required
events list — Event types to deliver. Use ["*"] to subscribe to all
format string raw Payload format: raw, slack, or discord
timeout_seconds int 10 Per-request HTTP timeout in seconds

audit

Field Type Default Description
audit.enabled bool true Enable the audit log sink
audit.output string stdout Write destination: stdout or a file path

log

Field Type Default Description
log.level string info Log level: debug, info, warn, error
log.format string json Log format: json or text

admin

Field Type Default Description
admin.enabled bool false Enable the admin API at /_vibewarden/admin/*
admin.token string "" Bearer token for admin API authentication

The token is accepted in either header form, so use whichever your client prefers:

curl -H "X-Admin-Key: $VIBEWARDEN_ADMIN_TOKEN" https://localhost:8443/_vibewarden/admin/users
curl -H "Authorization: Bearer $VIBEWARDEN_ADMIN_TOKEN" https://localhost:8443/_vibewarden/admin/users

If both headers are present, X-Admin-Key wins.

Admin-token authentication has a built-in, non-configurable brute-force lockout: 10 consecutive failed attempts from one client IP within 1 minute lock that IP out for 1 minute. During the lockout the token is not compared and requests get 429 Too Many Requests with a Retry-After header and the error code too_many_failed_attempts. The attempt that trips the lockout emits a single audit.auth.lockout audit event in place of the usual audit.auth.failure; requests received during the cooldown emit no audit event at all. A successful authentication resets the counter. See Production hardening for the full behaviour.


overrides

Escape hatches for advanced users. All fields are optional.

Field Type Description
overrides.kratos_config string Path to a custom kratos.yml file. When set, VibeWarden uses it instead of generating one
overrides.compose_file string Path to a custom docker-compose.yml file
overrides.identity_schema string Path to a custom Kratos identity schema JSON file

Full example vibewarden.yaml

profile: dev

server:
  host: 0.0.0.0
  port: 8443

tls:
  enabled: true
  provider: self-signed

upstream:
  host: 127.0.0.1
  port: 3000

auth:
  mode: jwt
  jwt:
    jwks_url: "https://dev-abc123.us.auth0.com/.well-known/jwks.json"
    issuer:   "https://dev-abc123.us.auth0.com/"
    audience: "https://api.your-app.com"
    claims_to_headers:
      sub: X-User-Id
      email: X-User-Email
      email_verified: X-User-Verified
  public_paths:
    - /health
    - /static/*

rate_limit:
  enabled: true
  store: memory
  per_ip:
    requests_per_second: 10
    burst: 20
  per_user:
    requests_per_second: 100
    burst: 200

security_headers:
  enabled: true
  hsts_max_age: 31536000
  content_security_policy: ""

telemetry:
  enabled: true
  prometheus:
    enabled: true
  path_patterns:
    - "/api/v1/users/:id"
    - "/api/v1/orders/:order_id"

log:
  level: info
  format: json