Skip to content

Configuration

LatchGate is configured through a latchgate.toml file. A full example is at latchgate.example.toml.

Terminal window
latchgate init # interactive wizard (preset picker)
latchgate init --preset coding # specific preset, skip wizard
latchgate init --preset lockdown --location user # user-global install

See Presets for available presets and their security postures.

Configuration values are resolved in this order (highest wins):

  1. Environment variables (LATCHGATE_*) — highest priority
  2. TOML file (latchgate.toml)
  3. Compiled defaults — lowest priority

This allows operators to override any setting via environment variables without modifying the TOML file. LatchGate Platform uses this to configure per-tenant instances via env at process start time.

Invalid environment variable values are a hard startup error (fail-closed). An operator who sets LATCHGATE_LEASE_TTL_SECONDS=abc gets a startup failure, not a silent fallback to the default.

LatchGate exposes two sockets: a client socket (for agent processes) and an admin socket (for operator tools). They are separate so agent processes cannot reach approval, audit, or revocation endpoints.

# Primary: Unix Domain Socket (production default)
listen_uds_path = "/run/latchgate/gate.sock"
listen_admin_uds_path = "/run/latchgate/gate-admin.sock"
# Optional TCP (dev only). Requires the unsafe flag.
# listen_http_addr = "127.0.0.1:3000"
# listen_admin_http_addr = "127.0.0.1:3001"
# unsafe_expose_http = false

Production deployments should use UDS-only transport. The TCP listener is gated behind unsafe_expose_http = true, which is recorded in audit events.

When all three TLS fields are set, the admin TCP listener uses mutual TLS. This does not require unsafe_expose_http = true — mTLS is a production-grade transport.

admin_tls_cert = "/certs/server.crt"
admin_tls_key = "/certs/server.key"
admin_tls_ca = "/certs/ca.crt"

Both sides must present certificates signed by the same CA. Clients without a valid certificate are rejected at the TLS handshake — before any application logic runs.

Setting one or two fields but not all three is a hard startup error (fail-closed). LatchGate will not silently fall back to plain HTTP on a partially configured admin listener.

In managed deployments these are typically set via environment variables (LATCHGATE_ADMIN_TLS_CERT, LATCHGATE_ADMIN_TLS_KEY, LATCHGATE_ADMIN_TLS_CA).

The [identity] section controls how callers are authenticated at lease issuance.

Uses SO_PEERCRED on the Unix socket to map the caller’s UID to a principal and scope set.

Manage principals from the operator TUI (Setup → Principals, 63): press a to add a principal — the form prompts for UID, name, scopes, and owner — and r to remove the selected one.

CLI equivalent
Terminal window
latchgate config add-principal --uid 1001 --name agent-jira \
--scopes tools:call --owner alice@company.com
latchgate config remove-principal --uid 1001

See CLI Reference.

The TUI and CLI both edit the [identity.peercred.principals] table, which can also be written by hand:

[identity]
provider = "peercred"
[identity.peercred]
allow_unmapped = false # deny UIDs not in the map
[identity.peercred.principals]
1001 = { principal = "agent-jira", scopes = ["tools:call"], owner = "alice@company.com" }
1002 = { principal = "agent-email", scopes = ["tools:call", "email:send"], owner = "bob@company.com" }

The optional owner field records which person or team is responsible for the agent principal. It is informational — LatchGate does not enforce it — but it is useful for forensic attribution and organizational mapping. Existing configs without owner continue to work.

Returns a synthetic dev:anonymous principal. Rejected at production startup unless unsafe dev mode is active (requires both the unsafe-dev Cargo feature and LATCHGATE_UNSAFE_DEV=1).

Approval and admin endpoints require operator authentication. Production requires named credentials with DPoP proof-of-possession.

Manage operators from the operator TUI (Setup → Operators, 62): press a to add an operator — this generates a DPoP keypair, writes the PEM, and updates TOML in one step, displaying the API key once — and r to remove the selected one.

CLI equivalent
Terminal window
latchgate config add-operator --name alice # generates keypair, writes PEM, updates TOML
latchgate config remove-operator --name alice

For manual keypair generation, use latchgate operator keygen.

Both paths write to the [operator_credentials] table:

[operator_credentials.alice]
api_key = "key-alice-random-secret"
dpop_jkt = "base64url-sha256-thumbprint"
[operator_credentials.bob]
api_key = "key-bob-random-secret"
dpop_jkt = "base64url-sha256-thumbprint"

In dev mode, a single credential without dpop_jkt is accepted. The CLI auto-discovers it — no --operator-key flag needed. Production requires named credentials with DPoP.

receipt_signing_key_path = "/etc/latchgate/receipt-signing.key"
grant_signing_key_path = "/etc/latchgate/grant-signing.key"
receipt_keys_jwks_path = "/etc/latchgate/receipt-keys.jwks"

Ed25519 key files are auto-generated on first run (32-byte seed, mode 0600). Back these up. The JWKS file accumulates all historical receipt verifying keys so old receipts remain verifiable after rotation.

redis_url = "redis://127.0.0.1:6379"
opa_url = "http://127.0.0.1:8181"

Both are optional. When omitted:

  • No redis_url: the gate uses SQLite for budgets and approvals, and a bounded in-memory DashMap (100k entries) for replay protection. Production-grade for single-instance deployments.
  • No opa_url: the gate uses the embedded regorus engine (Rego evaluation in-process). Same policies, same data, no network dependency.

Set these when you need HA replay protection (Redis), external policy management (OPA), or multi-instance coordination.

Redis holds four categories of operational state: DPoP jti replay cache entries (short TTL), per-session budget counters (call count, keyed by session ID), pending approval records (short TTL), and the revocation epoch counter.

If Redis restarts without persistence, the replay cache and budget counters reset. This means: previously-seen jti values could be replayed within the replay TTL window (short-lived, low risk), budget counters reset to zero (sessions get a fresh budget — operationally noisy but not a security breach), and pending approvals are lost (agents must re-submit). The revocation epoch resets to 0, which is safe — new leases carry the new epoch.

For production with Redis, configure at minimum appendonly yes (AOF). The --infra Docker stack uses --save "" --appendonly no (volatile) — this is intentional for dev/eval only.

# Redis persistence recommendation for production:
# In your redis.conf (not in latchgate.toml):
# appendonly yes
# appendfsync everysec

LatchGate decrypts secrets from a SOPS-encrypted file at each action execution and injects only the secrets declared in the action manifest (least privilege). Undeclared keys in the SOPS file are discarded.

# Path to the SOPS-encrypted secrets file.
# When set, the Gate decrypts this file at each action call and injects
# only the secrets declared in the action manifest.
# When unset, actions that declare secrets receive an empty env map.
[secrets]
sops_secrets_file = "/etc/latchgate/secrets.enc.yaml"
# Optional: path to an age key file for SOPS decryption.
# When set, exported as SOPS_AGE_KEY_FILE when invoking SOPS.
# When unset, SOPS uses its default key discovery.
sops_key_file = "/etc/latchgate/sops-age.key"

Secrets are decrypted at each action execution and filtered to only the manifest-declared keys (least privilege). See Secrets Management for setup, encryption backends, and the full security model.

# Root directory for fs_read, fs_write, fs_delete operations.
# All paths in manifests and learned paths are resolved relative to this directory.
# Actions that declare filesystem operations will fail at dispatch with
# "no fs_root_path configured" if this is unset.
# fs_root_path = "/home/agent/project"
# Allowed prefixes for per-session filesystem roots.
# MCP sessions request their root at lease time (from IDE project CWD).
# The gate validates the requested path starts with one of these prefixes.
# Default: [$HOME]. Empty list disables per-session roots entirely.
# fs_root_allowed_prefixes = ["/home/user/projects", "/tmp/sandboxes"]

When fs_root_path is set, all filesystem provider operations are confined to this directory tree. Relative paths in action manifests and learned paths are resolved against fs_root_path. The gate refuses to resolve paths that escape this root via .. traversal.

When unset, filesystem actions fail at dispatch time — there is no fallback to “allow everything”. This is intentional: filesystem access must be explicitly configured.

fs_root_allowed_prefixes controls which directories MCP sessions may use as per-session filesystem roots. When an MCP adapter requests a session-scoped fs_root at lease time (typically the IDE’s project directory), the gate checks that the requested path starts with one of these prefixes. If not, the lease is issued without a session root and the global fs_root_path applies. Defaults to [$HOME]; set to an empty list to disable per-session roots entirely.

Infrastructure connection strings for provider I/O. Each backend is a named section under [host_io] with at least a url field. Omitting a backend disables the corresponding latchgate:io/* import — actions that declare it will fail at runtime.

[host_io.database]
url = "postgres://user:pass@localhost:5432/latchgate"
[host_io.queue]
url = "amqp://user:pass@localhost:5672/%2f"
[host_io.storage]
url = "s3://my-artifact-bucket"
[host_io.smtp]
url = "smtp://user:pass@smtp.example.com:587"

Connection strings (including credentials) are operator-managed and set at startup. Providers cannot influence which server is targeted — only the operation parameters are provider-controlled and validated against allowed_sinks.

In v0.1 only the http_api provider ships. The database, queue, storage, and smtp backends are accepted by the parser for forward compatibility but have no effect — the providers that consume them are not yet built. Configuring them now is harmless but does not enable those actions.

For defense-in-depth, configure a forward proxy (Squid) for outbound HTTP from WASM providers. This enforces a domain allowlist independently of the kernel’s validate_sink() check.

# Forward proxy for outbound HTTP from WASM providers.
# SECURITY: strongly recommended in production.
# egress_proxy_url = "http://squid:3128"

When set, all outbound HTTP requests from providers are routed through this proxy. The proxy enforces its own domain allowlist (deploy/squid/allowed_domains), providing a second layer of egress control beyond the kernel’s sink validation.

Per-deployment operators can further narrow the effective allowlist without changing manifests:

# Intersect manifest allowlists with this operator-controlled set.
# Can only REMOVE domains from the manifest set — never add.
# egress_runtime_allowlist = ["api.github.com", "hooks.slack.com"]

The effective allowlist the kernel enforces is manifest_domains ∩ egress_runtime_allowlist. A domain absent from a manifest cannot become reachable through this field — it is a narrowing lens, not an expansion. Typical use: deploying a subset of actions and limiting network reach to only the domains those actions actually need, or constraining a CI environment to a known-safe set.

SECURITY: operator-controlled only. Agent or caller input must never flow into this field — it is a deployment-time infrastructure constraint.

This field is TOML-only — it cannot be overridden via environment variable. Modify latchgate.toml directly or use latchgate config set.

When a Squid sidecar enforces egress, the gate can maintain a live allowlist file for Squid to read:

# Atomic write target for the effective allowlist (manifest ∪ learned ∩ runtime).
# Rewritten on startup, after `domains add/remove/clear`, and after a
# domain is learned through the approval flow.
# egress_live_allowlist_path = "/etc/squid/allowed_domains.txt"
# Command executed after each rewrite. Typical values:
# "squid -k reconfigure"
# "docker compose exec squid squid -k reconfigure"
# Leave empty to reload manually or via an external file-watcher.
# egress_reload_command = ""

The effective domain set written to this file is (manifest_domains ∪ learned_domains) ∩ egress_runtime_allowlist. The file is rewritten atomically (tmp => fsync => rename) on three occasions: server startup (initial sync after registry and ledger are ready), after latchgate domains add/remove/clear (CLI-driven ledger mutation), and after a domain is learned through the approval flow. If egress_reload_command is set, it is executed after each successful rewrite.

lease_ttl_seconds = 300 # default lease lifetime (5 min)

Outbound webhook notifications for security events. Configure zero or more endpoints — each subscribes to specific event types with independent retry policies.

Webhook delivery mode controls how events are persisted and delivered:

# Delivery mode: "outbox" (default) or "async"
# outbox: events persisted to SQLite before delivery — zero event loss
# async: in-process channel, fire-and-forget (dev only)
webhook_mode = "outbox"
[[webhooks]]
name = "slack-approvals"
url = "https://hooks.slack.com/services/T.../B.../xxx"
secret = "whsec_your-signing-secret-here"
events = ["approval.pending", "approval.expired"]
[[webhooks]]
name = "security-siem"
url = "https://siem.corp.internal/api/v1/events"
secret = "whsec_siem-secret"
events = ["action.denied", "revocation", "action.executed"]
headers = { "Authorization" = "Bearer ${SIEM_TOKEN}" }

Generate signing secrets with openssl rand -hex 32. HTTPS is required in production — HTTP is allowed only for localhost in dev mode. Header values support ${ENV_VAR} expansion.

For the full field reference, event types, payload format, signing verification examples, and integration guides (Slack, Teams, Discord, PagerDuty, Opsgenie, SIEM), see Webhooks.

ledger_db_path = "data/audit.db" # SQLite database
ledger_jsonl_path = "data/audit.jsonl" # optional JSONL for SIEM export
response_schema_enforcement = "deny" # "deny" (production) or "warn" (onboarding)

In "deny" mode (production default), responses that fail schema validation are rejected. In "warn" mode, the violation is logged but the response is returned to the caller. Use "warn" only during initial onboarding to identify schema mismatches — switch to "deny" before production.

manifests_dir = "definitions/manifests" # action manifest YAML files
wasm_providers_dir = "target/providers" # compiled .wasm provider modules

Manifests and providers are loaded from these directories on startup. The defaults are relative to the working directory. For Docker deployments, these are set to /opt/latchgate/definitions/manifests and /opt/latchgate/providers respectively.

log_level = "info" # trace, debug, info, warn, error
log_format = "auto" # auto (default), json (production), pretty (dev)
# log_file = "/var/log/latchgate/gate.log" # optional JSON-lines log file
# log_rotation = "daily" # daily (default), hourly, never — only when log_file is set
# log_max_files = 7 # rotated files to keep (default: 7)

auto selects pretty when running in an interactive terminal (TTY), and json otherwise. Production deployments should use json explicitly for structured log aggregation.

When log_file is set, every log event is appended as a JSON line to that file regardless of log_format. The file is created if it does not exist; the parent directory must exist. latchgate up sets this automatically; latchgate serve defaults to stderr only.

The log level can also be overridden at runtime via the RUST_LOG environment variable (standard tracing/env_filter behavior). The LATCHGATE_LOG_LEVEL env var overrides the log_level config field.

public_base_url = "http://localhost:3000" # canonical URL for htu computation

The public_base_url is used for DPoP htu (HTTP URI) claim validation. It must match the URL that clients use to reach the gate. Never derive this from the Host header.

Two independent sandboxing layers are configured here: WASM provider isolation and agent process containment.

[sandbox]
mode = "strict" # "strict" or "degraded_ok"
strict_for_actions = true # enforce minimum hardening for action containers

strict mode (recommended for CI/production) requires seccomp + no-new-privileges. AppArmor or SELinux is required on Linux. degraded_ok mode is for development hosts lacking AppArmor/SELinux — it records sandbox_degraded=true in audit events.

The [sandbox.agent] section configures latchgate sandbox — Linux namespace containment for the agent process itself. These values serve as defaults; CLI flags override them.

[sandbox.agent]
workspace = "/home/dev/myproject" # Host dir mounted as /workspace (RW). Default: CWD.
allow_hosts = [ # Proxy allowlist — HTTPS only, port 443.
"api.anthropic.com",
"api.openai.com",
"generativelanguage.googleapis.com",
]
ro_mounts = ["/opt/node-22"] # Extra read-only bind mounts.
pass_env = ["TERM", "LANG", "ANTHROPIC_API_KEY"] # Env vars to pass through.
gate_socket = "/run/latchgate/gate.sock" # Gate UDS path on host.

For the full security model, platform requirements, and namespace details, see Agent Sandbox.

The [rate_limits] section configures five independent token-bucket limiters:

[rate_limits]
lease_rps = 50 # POST /v1/leases (default: 50)
operator_write_rps = 20 # operator write endpoints (default: 20)
operator_read_rps = 100 # operator read endpoints (default: 100)
execute_rps_per_session = 20 # execute path, per session (default: 20)
execute_rps_anonymous = 5 # execute path, unauthenticated peers (default: 5)
  • lease_rpsPOST /v1/leases (default 50 rps). DPoP verification is CPU-heavy; this caps CPU DoS risk. Shared per process.
  • operator_write_rps — operator write endpoints: approve, deny, drain, revoke (default 20 rps). Shared per process.
  • operator_read_rps — operator read endpoints: list approvals, get approval, audit query, status (default 100 rps). Shared per process.
  • execute_rps_per_sessionPOST /v1/actions/{id}/execute, per session (default 20 rps). Each session (identified from the lease JWT payload) gets its own bucket. Applied before DPoP verification — a misbehaving agent cannot burn CPU on cryptographic checks without hitting its bucket first.
  • execute_rps_anonymous — execute path for requests without a parseable session hint (default 5 rps). Per-peer buckets (keyed by UID on UDS, remote IP on TCP). Lower than session rate because anonymous requests are suspicious.

Each bucket has capacity equal to its rps and refills to full in one second. Requests over the limit return 429 with {"error": "rate_limit_exceeded"}. Tune downward for hardened deployments; the limiters are a brute-force / DoS mitigation, not a substitute for authentication or policy.

These variables control startup behavior and are not config field overrides:

VariableEffect
LATCHGATE_UNSAFE_DEV=1Bypass all production startup checks. Requires the binary to be compiled with --features unsafe-dev. Both gates must be satisfied — the env var alone has no effect on a release binary. See Unsafe dev mode.
LATCHGATE_CONFIGOverride config file path (alternative to --config flag)
RUST_LOGOverride tracing log filter at runtime (standard tracing behavior)

Config fields can be overridden via a LATCHGATE_* environment variable. Env vars take precedence over TOML values. Invalid values cause a hard startup error (fail-closed).

VariableConfig fieldTypeNotes
LATCHGATE_LOG_LEVELlog_levelstringtrace, debug, info, warn, error
LATCHGATE_LOG_FORMATlog_formatenumauto, json, or pretty
LATCHGATE_LOG_FILElog_filestringempty = unset (stderr only)
LATCHGATE_LOG_ROTATIONlog_rotationenumdaily, hourly, or never
LATCHGATE_LOG_MAX_FILESlog_max_filesusizerotated files to keep (default: 7)
LATCHGATE_LISTEN_UDS_PATHlisten_uds_pathstring
LATCHGATE_LISTEN_ADMIN_UDS_PATHlisten_admin_uds_pathstring
LATCHGATE_LISTEN_HTTP_ADDRlisten_http_addrsocket addre.g. 127.0.0.1:3000
LATCHGATE_LISTEN_ADMIN_HTTP_ADDRlisten_admin_http_addrsocket addre.g. 127.0.0.1:3001
LATCHGATE_UNSAFE_EXPOSE_HTTPunsafe_expose_httpbooltrue/false/1/0 only
LATCHGATE_ADMIN_TLS_CERTadmin_tls_certstringempty = unset. All three admin TLS fields must be set together or not at all.
LATCHGATE_ADMIN_TLS_KEYadmin_tls_keystringempty = unset. Partial configuration is a startup error.
LATCHGATE_ADMIN_TLS_CAadmin_tls_castringempty = unset. CA used to verify both server and client certificates.
LATCHGATE_PUBLIC_BASE_URLpublic_base_urlstring
LATCHGATE_REDIS_URLredis_urlstring
LATCHGATE_OPA_URLopa_urlstring
LATCHGATE_LEASE_TTL_SECONDSlease_ttl_secondsu64
LATCHGATE_MANIFESTS_DIRmanifests_dirstring
LATCHGATE_WASM_PROVIDERS_DIRwasm_providers_dirstring
LATCHGATE_LEDGER_DB_PATHledger_db_pathstring
LATCHGATE_LEDGER_JSONL_PATHledger_jsonl_pathstringempty = unset
LATCHGATE_RECEIPT_SIGNING_KEY_PATHreceipt_signing_key_pathstringempty = unset
LATCHGATE_GRANT_SIGNING_KEY_PATHgrant_signing_key_pathstringempty = unset
LATCHGATE_RECEIPT_KEYS_JWKS_PATHreceipt_keys_jwks_pathstringempty = unset
LATCHGATE_RESPONSE_SCHEMA_ENFORCEMENTresponse_schema_enforcementenumdeny or warn
LATCHGATE_SOPS_KEY_FILEsops_key_filestringempty = unset
LATCHGATE_SOPS_SECRETS_FILEsops_secrets_filestringempty = unset
LATCHGATE_HOST_IO_DATABASE_URLhost_io.database.urlstringempty = disable backend
LATCHGATE_HOST_IO_QUEUE_URLhost_io.queue.urlstringempty = disable backend
LATCHGATE_HOST_IO_STORAGE_URLhost_io.storage.urlstringempty = disable backend
LATCHGATE_HOST_IO_SMTP_URLhost_io.smtp.urlstringempty = disable backend
LATCHGATE_OPERATOR_API_KEYoperator_credentials (platform)stringsets a single “platform” credential
LATCHGATE_OPERATOR_DPOP_JKToperator_credentials (platform)stringDPoP thumbprint for platform credential
LATCHGATE_FS_ROOT_ALLOWED_PREFIXESfs_root_allowed_prefixescolon-separated pathsempty = disable per-session roots

For Option<String> fields (marked “empty = unset”), setting the env var to an empty string (LATCHGATE_LEDGER_JSONL_PATH="") unsets the field (maps to None). For Host I/O backends, an empty value disables the backend (LATCHGATE_HOST_IO_DATABASE_URL="" removes the database backend).

Boolean fields accept only true, false, 1, 0 (case-insensitive). Ambiguous values like yes or on are rejected.

The following configuration fields are TOML-only — they cannot be overridden via environment variables:

  • webhook_mode — webhook delivery mode (outbox or async)
  • egress_live_allowlist_path — Squid live-sync file path
  • egress_reload_command — command to run after allowlist rewrite
  • egress_runtime_allowlist — operator-controlled domain narrowing set
  • fs_root_path — filesystem root for provider I/O
  • sandbox.mode — sandbox strictness level
  • sandbox.strict_for_actions — minimum hardening enforcement
  • sandbox.agent.* — agent process sandbox configuration
  • rate_limits.* — token-bucket capacities (lease_rps, operator_write_rps, operator_read_rps, execute_rps_per_session, execute_rps_anonymous)

Modify latchgate.toml directly or use latchgate config set.

The following values are compile-time constants in latchgate-core/src/security_constants.rs. They are not configurable via TOML or environment variables. Each was previously a config field whose only safe value was the default; exposing them invited misconfiguration with no legitimate upside.

ConstantValuePurpose
REPLAY_TTL_SECS180Anti-replay cache TTL. Tied to allowed_clock_skew + max_proof_age.
OPA_TIMEOUT_MS1000Policy evaluation timeout (ms). Timeout fires => DENY.
MAX_LEASE_TTL_SECS3600Hard ceiling on lease lifetime. lease_ttl_seconds cannot exceed this.
APPROVAL_TTL_SECS300Pending approval expiry. Unapproved requests are auto-denied after this window.
MAX_CONCURRENT_EXECUTIONS4WASM concurrency semaphore permits.
MAX_REQUEST_BODY_BYTES1 048 576Transport-level HTTP body size limit (1 MB).
REDIS_KEY_PREFIXlatchgate:jti:Redis key namespace prefix.
SOPS_BINsopsPath to the SOPS binary.
SOPS_CACHE_TTL_SECS30In-memory decrypted secrets cache TTL.
FS_ROOT_BLOCKED_PATHS21 system pathsPaths never valid as a per-session fs_root, regardless of fs_root_allowed_prefixes. Blocks /, /bin, /boot, /dev, /etc, /home, /lib, /lib64, /media, /mnt, /opt, /proc, /root, /run, /sbin, /snap, /srv, /sys, /tmp, /usr, /var. Defense-in-depth for misconfigured allowlists.
SESSION_FS_ROOT_EVICTION_GRACE_SECS60Grace period added to lease TTL before evicting a session’s fs_root. Prevents disruption of in-flight executions at lease expiry.

If a deployment genuinely requires a different value, the constant must be changed at the source level and the binary recompiled.

Dev mode bypasses all production startup checks. It is double-gated to prevent accidental activation:

  1. Compile-time gate: the binary must be compiled with --features unsafe-dev. Release binaries published to GHCR and Homebrew do not include this feature — LATCHGATE_UNSAFE_DEV has no effect on them.
  2. Runtime gate: LATCHGATE_UNSAFE_DEV=1 (or true) must be set in the environment.

Both gates must be satisfied. If the binary lacks the feature, the env var is ignored. If the feature is compiled in but the env var is absent, dev mode is inactive.

When active, the gate emits a loud ERROR-level log line at startup: UNSAFE DEV MODE ACTIVE. If this line appears in a production log, the build is wrong.

latchgate up uses a binary compiled with the unsafe-dev feature and sets LATCHGATE_UNSAFE_DEV=1 internally. This is the intended way to run in dev mode for evaluation and development. Never deploy a binary compiled with --features unsafe-dev.

Outside unsafe dev mode, the server refuses to start if:

  • Identity provider is none
  • allow_unmapped = true on peercred provider
  • Empty peercred principal map
  • Signing keys are ephemeral (paths not configured)
  • Receipt JWKS path is missing
  • Operator credentials missing or missing dpop_jkt
  • unsafe_expose_http set without the flag
  • response_schema_enforcement = "warn"
  • Actions declare egress_profile = proxy_allowlist but egress_proxy_url is not configured
  • file:// object storage URL in production
  • webhook_mode = async with configured endpoints
  • Wildcard ACL contains high/critical-risk actions

These exist to prevent insecure configurations from reaching production.

Each production check can be individually relaxed under a [posture] section. Every field defaults to false (the check is enforced). Setting a field to true disables exactly one check — the rest stay enforced. This is how the generated config records the relaxations applied by latchgate up, so the posture survives restarts and is inherited by child processes.

[posture]
identity_insecure = false # skip caller identity validation (allow provider = none)
signing_insecure = false # skip persistent signing key / JWKS validation (ephemeral keys)
schema_insecure = false # allow response_schema_enforcement = "warn"
operator_auth_insecure = false # skip operator DPoP proof-of-possession validation
storage_insecure = false # allow file:// object storage
webhooks_insecure = false # allow webhook_mode = "async" with endpoints
egress_insecure = false # skip egress proxy coverage validation
acl_insecure = false # skip wildcard ACL risk validation

Prefer the latchgate up flags over editing this section by hand — they set the same fields and surface the relaxation in the startup posture banner. The flags map directly:

FlagField
--insecure-identityidentity_insecure
--insecure-signingsigning_insecure
--schema-warnschema_insecure
--insecure-operator-authoperator_auth_insecure
--insecure-storagestorage_insecure
--insecure-webhookswebhooks_insecure
--insecure-egressegress_insecure
--insecure-aclacl_insecure

Any relaxed field weakens a production guarantee. Set these only for local development with no untrusted network exposure, never in a deployed gate.

For production hardening and UDS transport setup, see Deployment. For a first-run walkthrough, see Getting Started. For the full CLI reference, see CLI Reference. For secrets setup, see Secrets Management.