Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Configuration & environment variables

Barbacane is configured through CLI flags (see the CLI reference) and a small set of environment variables. The security-related variables below were introduced by the security hardening pass and several change default behavior in breaking but deliberate ways — Barbacane fails closed rather than running insecurely.

Security environment variables

VariableComponentDefaultEffect
BARBACANE_CONTROL_ADMIN_TOKENControl planeunsetRequired. Bearer token that must accompany every control-plane API request (except GET /health and the data-plane WebSocket). The server refuses to start if unset.
BARBACANE_CONTROL_ALLOWED_ORIGINSControl planeunset (no cross-origin)Comma-separated CORS allowlist of browser origins permitted to call the API, e.g. https://ui.example.com.
BARBACANE_TRUSTED_PUBKEYData planeunsetHex-encoded Ed25519 public key. When set, the data plane requires every loaded .bca artifact to carry a valid signature produced by the matching private key; load fails otherwise. When unset, the artifact’s content hashes are still verified, but signature checking is skipped (a startup warning is logged).
BARBACANE_SIGNING_KEYCompilerunsetPath to a PKCS#8 Ed25519 private key. When set, barbacane compile signs the artifact’s content hash. When unset, the artifact is built unsigned.
BARBACANE_SECRETS_DIRData planeunsetBase directory that file:// secret references are confined to. Required to use file:// secrets — references are rejected when it is unset, and any path resolving outside this directory (after symlink/.. resolution) is rejected.
BARBACANE_ALLOW_INTERNAL_EGRESSData planefalseSet to 1/true to disable the plugin SSRF guard and allow plugin egress (HTTP calls, Kafka/NATS broker connections, LDAP directory connections and WebSocket upstreams) to internal/loopback/link-local/cloud-metadata addresses. Guarded clients connect to the address vetted by the guard rather than resolving again (DNS-rebinding safe), except a NATS tls:// broker, which keeps its hostname so the TLS handshake carries SNI. Leave off unless you have legitimate internal upstreams or brokers.
BARBACANE_MAX_UPSTREAM_RESPONSE_BYTESData plane16777216 (16 MiB)Maximum size of an upstream response body that the buffered plugin HTTP-call path will read into host memory. Bodies larger than this are rejected, bounding host memory against a hostile or compromised upstream. Streaming dispatchers are unaffected.
BARBACANE_MAX_CONNECTIONSData plane10000Maximum number of concurrently served ingress connections. Beyond this, new connections are dropped (load shed) rather than letting file descriptors and tasks grow without bound under a connection flood.

Breaking-by-design defaults

These changes are intentional secure defaults. Adopt them as follows:

  1. The control plane will not start without BARBACANE_CONTROL_ADMIN_TOKEN. Generate a strong random token and pass it to every client as Authorization: Bearer <token>. Previously the API was unauthenticated.

  2. file:// secrets require BARBACANE_SECRETS_DIR. If you reference secrets like file:///run/secrets/api-key, set BARBACANE_SECRETS_DIR=/run/secrets. env:// references are unaffected.

  3. MCP clients must initialize a session. Non-initialize MCP requests (tools/list, tools/call, …) without a valid Mcp-Session-Id are now rejected; call initialize first and reuse the returned session id.

  4. Plugin egress to internal addresses is blocked by default. This covers plugin HTTP calls, Kafka/NATS broker connections, and WebSocket upstreams. If a plugin legitimately reaches an internal upstream or broker, set BARBACANE_ALLOW_INTERNAL_EGRESS=1 (or prefer an explicit allowlist when one is available).

  5. Plugins may only use their declared capabilities. A plugin whose WASM imports a host function outside the capabilities declared in its plugin.toml fails to load. Official plugins already declare the correct capabilities; custom plugins must list theirs under [capabilities] host_functions = [...].

Request headers reaching your upstream

A request carries only the headers its operation admits. Everything else is dropped at ingress, before any plugin or the dispatcher sees it, so the spec describes what an upstream receives and not merely what a client may send.

An operation admits:

SourceHeaders
Baseline, on every operationframing and negotiation (host, content-type, content-length, content-encoding, transfer-encoding, accept, accept-encoding, accept-language, accept-charset, user-agent, range, the if-* conditionals, cache-control, pragma, expect), CORS (origin, access-control-request-method, access-control-request-headers), tracing (traceparent, tracestate, x-request-id), the WebSocket handshake (upgrade, connection, sec-websocket-*), and the proxy chain (x-forwarded-for, x-forwarded-proto, x-forwarded-host, x-real-ip, forwarded)
Declared parametersevery in: header parameter, and cookie when the operation declares an in: cookie parameter
Security schemesthe credential header the operation’s security requirement names: an apiKey scheme’s own name, or authorization for http, oauth2 and openIdConnect
Plugins in the chainthe headers a plugin’s own configuration names, such as apikey-auth’s header_name, a rate-limit header: partition, or cache’s vary list

authorization is deliberately not in the baseline. It travels because the operation says it is authenticated, and for no other reason. An operation that runs an authentication middleware must therefore declare a security requirement naming a scheme defined under components.securitySchemes. Compiling one that does not is an error (E1057), since the credential would be dropped and the middleware would reject every request.

The x-auth-* namespace is never accepted from a client. It carries what an auth plugin tells acl and the upstream about the caller, and declaring one in a spec is a compile error (E1056).

The WAF is not filtered this way. It inspects what the client actually sent, which is the point of it.

Finding what to declare

barbacane_request_headers_dropped_total counts what was dropped. It carries no header name, since a name is attacker-controlled and would let a caller grow the series set without bound. To see the names, run with --dev, which logs each dropped header at warn level on the request that carried it; without --dev the same goes to debug.

Admin endpoints (loopback by default)

The data plane serves /health, /metrics, and /provenance on a dedicated admin port (--admin-bind). These endpoints are unauthenticated so metrics scrapers can reach them; /provenance and /metrics expose build and operational metadata. Keep the admin port bound to loopback (the default) or behind a trusted network boundary. Binding it to a non-loopback address (e.g. --admin-bind 0.0.0.0:...) logs a startup warning because it exposes that metadata off-host.

Artifact signing quickstart

# 1. Generate a dev keypair (PKCS#8 Ed25519). Any tool that emits PKCS#8 works;
#    keep the private key secret and distribute only the public key.

# 2. Sign at compile time:
BARBACANE_SIGNING_KEY=/path/to/ed25519.pk8 \
  barbacane compile -m barbacane.yaml -o api.bca

# 3. Require verification on the data plane (pin the public key):
BARBACANE_TRUSTED_PUBKEY=<hex-public-key> \
  barbacane serve --artifact api.bca --listen 0.0.0.0:8080

The signature covers the artifact’s content hash (artifact_hash), which binds every spec, route, and plugin WASM checksum plus the capability-enforcement surface (capabilities_enforced, each plugin’s declared host_functions / body_access, and the MCP config), so any tampering with the artifact (including attempts to weaken the sandbox) fails verification on load.

Verifying release container images

Release images are signed keylessly with Sigstore cosign using the GitHub Actions OIDC identity (no long-lived signing keys), and each image carries a CycloneDX SBOM as a cosign attestation. Verify a published image before running it:

# Signature
cosign verify \
  --certificate-identity-regexp 'https://github.com/barbacane-dev/barbacane/\.github/workflows/.*' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  ghcr.io/barbacane-dev/barbacane:0.8.0

# SBOM attestation (CycloneDX)
cosign verify-attestation --type cyclonedx \
  --certificate-identity-regexp 'https://github.com/barbacane-dev/barbacane/\.github/workflows/.*' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  ghcr.io/barbacane-dev/barbacane:0.8.0

The same commands work against Docker Hub (docker.io/barbacane/barbacane:0.8.0) and the other images (barbacane-control, barbacane-standalone). The identity regexp intentionally matches any workflow under the repo, so it covers both tagged-release signing and manual re-signing runs. A successful verification confirms the signature was checked against the Rekor transparency log and a trusted Fulcio certificate authority.