Skip to main content

View as markdown

# Config Reference

A clawpatrol gateway config mixes operational settings in the required top-level gateway { ... } block with policy blocks. Policy blocks (approver, credential, tunnel, endpoint, rule) dispatch to a plugin chosen by the block's first label.

# How to read this page

Each block section lists the attributes the loader accepts, with:

Plugin-dispatched kinds (approver, credential, tunnel, endpoint, rule) list one subsection per registered type.

# Top-level blocks

Operational settings live under the required top-level gateway { ... } block. The optional defaults { ... } block carries policy fallbacks. Labeled policy blocks (profile, approver, credential, endpoint, rule, tunnel) are documented in their own sections.

AttributeTypeRequiredDescription
schema_versionintnoThe config grammar this file targets. The gateway accepts a range of versions and rejects anything newer than it understands with an upgrade error. Omitting it loads as legacy grammar (version 0) with a deprecation warning.
gatewayblockyesCarries every operational scalar and the two transport sub-blocks. Required: configs missing the block fail to load.
defaultsblocknoHolds the optional defaults { ... } block with the policy defaults (unknown_host, llm_*, human_*). nil when the block is absent — every field has a built-in default.
pluginblocknoLists every plugin "<name>" { source = "..." } block at the top of the file. The loader spawns each subprocess (and registers its declared types) before running pass-1 symbol building, so plugin-supplied (kind, type) pairs are available by the time policy blocks are dispatched.

# gateway { ... }

The gateway block carries operational settings — listen addresses, the WireGuard / Tailscale transport sub-blocks, session and retention windows, telemetry, and the resolver.

AttributeTypeRequiredDescription
dashboard_listenstringnoThe bind address for the dashboard + JSON API HTTP server. Default 127.0.0.1:8080. Set to a routable address to expose the dashboard off-host (the same mux is also served on the WG netstack / tsnet stack at this port).
public_urlstringnoThe canonical externally-reachable gateway URL. Used in generated control-plane links such as join targets, OAuth redirect URIs, and (when public_url has a host but wireguard.endpoint doesn't) the host clients dial for WireGuard.
state_dirstringnoThe directory holding clawpatrol.db (and anything a plugin persists to disk under it). Defaults to ${HOME}/.clawpatrol.
dashboard_session_ttlstringnoHow long a dashboard login session stays valid after the operator types the password. time.ParseDuration format ("24h", "30m"). Default 24h.
dashboard_config_writesboolnoAllows authenticated dashboard users to append generated config snippets to the gateway HCL. Default false: config remains read-only and changes happen out-of-band.
resolverstringnoThe DNS resolver address the gateway uses for upstream lookups when the runtime needs an explicit resolver.
log_pathstringnoAn optional file path for gateway log output.
telemetryboolnoOpts in/out of the update-checker / anonymous usage ping (doc/telemetry.md). nil = default on; explicit telemetry = false silences the goroutine. Env vars CLAWPATROL_TELEMETRY=0 and DO_NOT_TRACK=1 also work.
session_keepstringnoThe hard retention floor for the sessions table. Sessions whose last_at is older than this get deleted by the background sweeper. Default 720h (30d), "0" / "off" disables. time.ParseDuration format.
limitsblocknoLimits, if present, overrides the two gateway-wide body-size limits (rules-engine body buffer and persisted action body storage). nil uses the DefaultBody*Limit constants, which match today's hardcoded behavior.
wireguardblocknoWireGuard, if present, enables the embedded userspace WireGuard server. Required block when running WG-mode deployments.
tailscaleblocknoTailscale, if present, enables the embedded tsnet node and the Tailscale control plane (OAuth key minting, exit-node routing). Both transports may be enabled simultaneously.

Nested block limits {}:

The optional limits { ... } sub-block inside gateway { ... }. It exposes the two independent body-size limits as human-readable size strings (e.g. "256KiB", "1MiB"):

Both are independent: a deployment may log more (or less) than it rule-matches. Empty fields fall back to the DefaultBody*Limit constants, which equal today's hardcoded behavior.

AttributeTypeRequiredDescription
body_bufferstringno
body_storagestringno

Nested block wireguard {}:

The body of the wireguard { ... } sub-block inside gateway { ... }. Presence of the block enables the WG transport.

AttributeTypeRequiredDescription
subnet_cidrstringnoThe private subnet assigned to onboarded clients. Required (e.g. "10.55.0.0/24").
listen_portintnoThe UDP port the gateway binds for WG peers. Default 51820.
host_loopback_portintnoThe TCP port the gateway binds on 127.0.0.1 for host-local clients (single-host deployments where clawpatrol-run loops back to the gateway on the same box). Default 8443. Make it operator-controlled so two gateways can coexist on one host (dev/test, blue-green, multi-tenant) without colliding on the loopback landing pad.
endpointstringnoThe host:port advertised in client wg.conf as Endpoint = .... Host defaults to public_url's host; port defaults to listen_port. Set only for split-host deployments (gateway sits behind a different hostname/IP for WG than for the dashboard).
interfacestringnoThe WireGuard interface name the gateway manages. Mostly irrelevant in userspace mode; leave unset.
server_pubstringnoThe WireGuard server public key advertised to clients. Normally derived from gateway state; only set when bootstrapping from an external key.

Nested block tailscale {}:

The body of the tailscale { ... } sub-block inside gateway { ... }. Presence of the block enables tsnet.

AttributeTypeRequiredDescription
authkeystringnoThe Tailscale auth key for the embedded tsnet node. Required when the tailscale block is present. Falls back to $TS_AUTHKEY if empty.
hostnamestringnoThe device name requested for the tsnet node. Default "clawpatrol-gateway".
control_urlstringnoThe Tailscale control-plane URL. Empty → Tailscale's hosted control plane.
tags[]stringnoThe Tailscale device-tag list applied to keys the gateway mints for onboarded clients (tag:client etc.). The autoApprovers exit-node ACL must reference these tags.
operators[]stringnoAllowlists tailnet logins permitted to use the dashboard without typing the root password. Each entry is either an exact login ("alice@example.com") or a domain wildcard ("*@example.com"). Tagged devices (whose whois login is the tag name, not a user email) never match a wildcard entry — agents on the tailnet can never bypass the gate through this path. Empty / unset → tailnet-allowlist auth is disabled. The stored root password is then the only way in. Lives under tailscale {} because matching requires the tsnet whois identity; there is no whois without an active tsnet node.
funnelboolnoEnables Tailscale Funnel on :443 so the join bootstrap and credential webhook paths are internet-reachable via the tsnet cert domain. Requires HTTPS enabled for the tailnet; if public_url is unset the gateway derives it from the tsnet cert domain at startup.
oauth_client_idstringnoThe OAuth client id used to mint per-device tailnet auth keys at approval time.
oauth_client_secretstringnoThe OAuth client secret paired with OAuthClientID.

# profile "<name>" { ... }

Names a set of credentials. Profiles bind to dashboard owners; an owner's profile determines which credentials — and, transitively via each credential's endpoint / endpoints binding, which endpoints — their gateway requests can reach. Rules ride along automatically because they're attached to endpoints.

AttributeTypeRequiredDescription
credentials[]credentialyesBare-name credential references, or { credential = name, <disambiguator> = "..." } object entries for multi-credential dispatch (e.g. placeholder for header-token credentials).
profile "default" {
  credentials = [bearer_token.github, postgres_credential.postgres-prod]
}

# approver blocks

Block syntax: approver "<type>" "<name>" { ... }

Registered types: human_approver, llm_approver.

# approver "human_approver" "<name>"

Targets one channel. Timeout / require_approvers override the global defaults block on a per-approver basis.

Credential references a credential whose body satisfies HITLNotifier (slack_tokens today; future Discord / Telegram / SMTP credentials). Leave empty for a dashboard-only approver (no channel notification; operator clicks approve/deny on the dashboard).

AttributeTypeRequiredDescription
channelstringyesThe destination channel, chat id, or equivalent notifier-specific target.
credentialref(credential)noReferences the notifier credential used to post approval requests. Leave empty for dashboard-only approval.
timeoutintnoOverrides the gateway's human_timeout for this approver, in seconds.
require_approversintnoThe number of separate human approvals required before the request is allowed.
interactiveboolnoToggles in-channel approve/deny buttons. Requires the referenced credential's signing_secret slot pasted via the dashboard AND Slack's Interactivity URL pointed at the gateway. Default false: message includes only an "Open dashboard" link.
classifierref(approver)noOptionally references an llm_approver by name. When set, the approver calls the classifier's Summarize method before posting the HITL notification, enriching the Slack card with request summary metadata. Classifier failures are non-fatal — the generic card is used as fallback.
messagestringnoAn optional Go-template-style string with {{var}} placeholders. When set, the expanded text replaces the default section body in the Slack (or other notifier) card. Supported vars mirror the CEL facet namespace: {{http.method}}, {{http.path}}, {{k8s.verb}}, {{sql.tables}}, {{http.body_json.resource_id}}, {{profile}}, {{endpoint}}, {{reason}}, etc. Classifier (if also set) still runs; Message takes display precedence.
approver "human_approver" "example" {
  channel = "#approvals"
}

# approver "llm_approver" "<name>"

Carries the model + the credential used to authenticate the call to the model API + the inline policy text the model judges against. policy is a heredoc-friendly string attribute on the approver block itself — no separate policy "<name>" {} block.

AttributeTypeRequiredDescription
modelstringyesThe model id used for policy judgment, such as a claude-, gpt-, or o*-prefixed model.
credentialref(credential)yesReferences the HTTP credential used to authenticate the model API call.
policystringnoThe prose the model judges requests against. Typically a heredoc on the approver block.
approver "llm_approver" "example" {
  model = "claude-haiku-4-5-20251001"
  credential = bearer_token.example
}

# credential blocks

Block syntax: credential "<type>" "<name>" { ... }

Registered types: anthropic_manual_key, anthropic_oauth_subscription, aws_credential, bearer_token, clickhouse_credential, cookie_token, discord_bot_token, gemini_api_key, github_oauth, google_gke_credential, header_token, mtls_credential, notion_mcp_oauth, notion_oauth, openai_codex_oauth, passthrough, postgres_credential, slack_tokens, ssh_key, tailscale_auth, telegram_bot_token.

# credential "anthropic_manual_key" "<name>"

No configurable attributes.

credential "anthropic_manual_key" "example" {}

# credential "anthropic_oauth_subscription" "<name>"

No configurable attributes.

credential "anthropic_oauth_subscription" "example" {}

# credential "aws_credential" "<name>"

Schema is intentionally empty: access key id and secret access key (and optional session token) live in the secret store as named slots, filled via the dashboard or CLAWPATROL_SECRET__ env vars. Cluster + region come from the kubernetes endpoint at request time.

No configurable attributes.

credential "aws_credential" "example" {}

# credential "bearer_token" "<name>"

AttributeTypeRequiredDescription
idempotency_keyboolnoStamps a deterministic Idempotency-Key header on non-GET/HEAD HTTP requests when the agent did not provide one.
credential "bearer_token" "example" {}

# credential "clickhouse_credential" "<name>"

Database, when set, is the discriminator the dispatcher uses to pick this credential when several clickhouse_credential blocks bind the same endpoint(s). At request time the gateway reads the agent-declared database off the wire and picks the credential whose database matches; an unset database field is the catchall (one allowed per (profile, endpoint)).

AttributeTypeRequiredDescription
userstringnoThe upstream ClickHouse user the gateway injects.
databasestringnoLimits this credential to ClickHouse requests for that database. Empty acts as the catchall.
credential "clickhouse_credential" "example" {}
AttributeTypeRequiredDescription
cookie_namestringnoThe HTTP cookie name that receives the secret value.
credential "cookie_token" "example" {}

# credential "discord_bot_token" "<name>"

Injects Discord bot tokens for REST and Gateway SDK traffic.

No configurable attributes.

credential "discord_bot_token" "example" {}

# credential "gemini_api_key" "<name>"

No configurable attributes.

credential "gemini_api_key" "example" {}

# credential "github_oauth" "<name>"

No configurable attributes.

credential "github_oauth" "example" {}

# credential "google_gke_credential" "<name>"

No configurable attributes.

credential "google_gke_credential" "example" {}

# credential "header_token" "<name>"

AttributeTypeRequiredDescription
headerstringyesThe HTTP header name to overwrite with the secret value.
prefixstringnoPrepended to the secret before injection, for schemes such as "Bearer " or "Token ".
credential "header_token" "example" {
  header = "X-API-Key"
}

# credential "mtls_credential" "<name>"

No configurable attributes.

credential "mtls_credential" "example" {}

# credential "notion_mcp_oauth" "<name>"

No configurable attributes.

credential "notion_mcp_oauth" "example" {}

# credential "notion_oauth" "<name>"

No configurable attributes.

credential "notion_oauth" "example" {}

# credential "openai_codex_oauth" "<name>"

No configurable attributes.

credential "openai_codex_oauth" "example" {}

# credential "passthrough" "<name>"

A credential that injects nothing. It exists only as a handle the operator declares, binds to endpoints, and lists in a profile's credentials — so the existing credential→endpoint→profile claim path works for endpoints that simply don't need auth injection (public APIs, services reached over an already-authenticated tunnel, open-internal endpoints). Write one passthrough credential per group of credential-less endpoints a profile should claim, or share one across several. The gateway forwards matching requests verbatim — no header, signature, or token rewrite — while the profile's rules still apply.

No configurable attributes.

credential "passthrough" "example" {}

# credential "postgres_credential" "<name>"

Database, when set, is the discriminator the dispatcher uses to pick this credential when several postgres_credential blocks bind the same endpoint(s). At request time the gateway reads the agent-declared database off the StartupMessage and picks the credential whose database matches; an unset database field is the catchall (one allowed per (profile, endpoint)).

AttributeTypeRequiredDescription
userstringnoThe upstream Postgres role the gateway authenticates as.
databasestringnoLimits this credential to sessions whose StartupMessage declares the same database. Empty acts as the catchall.
credential "postgres_credential" "example" {}

# credential "slack_tokens" "<name>"

No configurable attributes.

credential "slack_tokens" "example" {}

# credential "ssh_key" "<name>"

No configurable attributes.

credential "ssh_key" "example" {}

# credential "tailscale_auth" "<name>"

Has no operator-facing fields — there is nothing to paste. Per-tailnet selection (control_url, tags) lives on the tunnel block instead.

No configurable attributes.

credential "tailscale_auth" "example" {}

# credential "telegram_bot_token" "<name>"

No configurable attributes.

credential "telegram_bot_token" "example" {}

# endpoint blocks

Block syntax: endpoint "<type>" "<name>" { ... }

Registered types: clickhouse_https, clickhouse_native, https, kubernetes, openai_codex_https, postgres, ssh.

# endpoint "clickhouse_https" "<name>"

Family: sql.

AttributeTypeRequiredDescription
hosts[]stringyesThe set of ClickHouse HTTPS hostnames or host:port pairs this endpoint intercepts.
endpoint "clickhouse_https" "example" {
  hosts = ["api.example.com"]
}

# endpoint "clickhouse_native" "<name>"

Addresses one ClickHouse server reachable via the binary native protocol. Operators bind a single clickhouse_credential; the runtime parses the agent's Hello and substitutes the credential's (user, password) where the agent embedded a placeholder.

TLS toggles TLS on both hops: the gateway terminates the agent's TLS using a leaf minted off the gateway CA, parses the Hello in plaintext, then re-wraps to upstream. The wrapped client therefore keeps speaking native-over-TLS exactly as it would against the real cloud ClickHouse — clawpatrol run is transparent to its TLS posture. Default false: WG-only deployments where the operator wants plaintext on the inner hop (typical self-hosted ClickHouse on 9000 behind a private network) leave it off.

AcceptInvalidCertificate mirrors clickhouse-client's flag of the same name: when true and tls is on, the gateway skips upstream cert validation. Use for self-hosted ClickHouse fronted by a private CA. Default false keeps full validation against system roots.

Family: sql.

AttributeTypeRequiredDescription
hosts[]stringyesThe set of ClickHouse native-protocol hostnames or host:port pairs this endpoint intercepts.
portintnoThe default upstream port for hosts that omit one. Defaults to 9000 without TLS and 9440 with TLS.
tlsboolnoEnables ClickHouse native-over-TLS on the upstream hop.
accept_invalid_certificateboolnoSkips upstream certificate validation when TLS is enabled.
endpoint "clickhouse_native" "example" {
  hosts = ["api.example.com"]
}

# endpoint "https" "<name>"

Family: http.

AttributeTypeRequiredDescription
hosts[]stringyesThe set of HTTPS hostnames or host:port pairs this endpoint intercepts.
endpoint "https" "example" {
  hosts = ["api.example.com"]
}

# endpoint "kubernetes" "<name>"

ClusterName + Region are EKS auth parameters: when the bound credential is aws_credential, the gateway presigns an STS GetCallerIdentity URL scoped to (region, cluster_name) and stamps the result as a k8s-aws-v1.<…> bearer. Leave both unset for self-hosted clusters with a non-EKS credential (bearer_token, mtls_credential).

Family: k8s.

AttributeTypeRequiredDescription
hosts[]stringnoAn optional list of Kubernetes API hostnames or host:port pairs to intercept.
serverstringnoThe Kubernetes API server URL or host:port used when hosts is not set.
ca_certstringnoThe PEM-encoded cluster CA, often loaded with <<file:cluster-ca.pem>>.
cluster_namestringnoThe EKS cluster name used by aws_credential.
regionstringnoThe AWS region used by aws_credential for EKS auth.
endpoint "kubernetes" "example" {}

# endpoint "openai_codex_https" "<name>"

Family: http.

AttributeTypeRequiredDescription
hosts[]stringyesThe chatgpt.com host list intercepted for Codex subscription-auth traffic.
endpoint "openai_codex_https" "example" {
  hosts = ["api.example.com"]
}

# endpoint "postgres" "<name>"

Addresses a single RDS-or-equivalent server. Tunnel topologies (kubectl-portforward-ssh and friends) aren't supported in this iteration — operators run the gateway with network reachability already arranged.

SSLMode mirrors libpq's sslmode names — "disable" / "prefer" / "require" / "verify-full". Default "prefer": try TLS, fall back to plain when the upstream replies 'N'. "require" hard-fails on 'N'. "verify-full" additionally validates the upstream cert against Host. "disable" skips the SSLRequest probe entirely — fine for self-hosted pg on a private network where WG already encrypts the path.

Family: sql.

AttributeTypeRequiredDescription
hoststringyesThe upstream Postgres host:port pair.
sslmodestringnoControls upstream TLS negotiation. Valid values mirror libpq: "disable", "prefer", "require", and "verify-full".
endpoint "postgres" "example" {
  host = "db.internal:5432"
}

# endpoint "ssh" "<name>"

Binds one or more host:port tuples. The credentials that authenticate against it live on credential blocks via the framework-level endpoint = X / endpoints = [...] binding. When a profile wields more than one SSH credential at the endpoint, each ambiguous credential carries a user = "..." disambiguator — either on its profile-inline entry ({ credential = X, user = "..." }) or on the credential block itself — and the agent's wire-protocol username picks the matching entry. The agent's username is also passed through verbatim as the upstream SSH user; credentials carry only auth material (key / password / host_pubkey), never a username override.

Family: ssh.

AttributeTypeRequiredDescription
hosts[]stringyesThe set of SSH host:port pairs this endpoint intercepts.
endpoint "ssh" "example" {
  hosts = ["api.example.com"]
}

# rule blocks

Block syntax: rule "<name>" { ... }

# rule "<name>"

The gohcl-tagged decode target. The match predicate is family-agnostic at the HCL layer (just a CEL string); the facet's *cel.Env decides which variables are valid once the family has been inferred from the endpoint refs.

AttributeTypeRequiredDescription
endpointref(endpoint)noThe single endpoint this rule attaches to. Use endpoint or endpoints, not both.
endpoints[]ref(endpoint)noThe list of endpoints this rule attaches to. All referenced endpoints must share one protocol family.
priorityintnoOrders matching rules. Higher values run first; equal priorities preserve declaration order.
disabledboolnoKeeps the rule in config while excluding it from runtime evaluation.
conditionstringnoA CEL expression evaluated against the family-specific variable set. An absent / empty condition matches everything — the catch-all pattern (rule "X-default" { priority = -100; verdict = "deny" }) relies on this.
credentialref(credential)noCredential, if set, is a bare-name reference to a credential block. The runtime treats it as an extra match predicate (request must have been dispatched against this credential) evaluated before the CEL expression.
verdictstringnoThe outcome when the rule matches. Set exactly one of verdict ("allow" / "deny") or approve.
reasonstringnoThe operator-facing explanation recorded when the rule matches.
approve[]ref(approver)noA list of bare-name approver references. The approvers run in order; the request is allowed only if every stage approves. Set this or verdict, not both.
rule {}

# tunnel blocks

Block syntax: tunnel "<type>" "<name>" { ... }

Registered types: kubernetes_port_forward, local_command, ssh_port_forward, tailscale.

# tunnel "kubernetes_port_forward" "<name>"

Configures the tunnel runtime.

AttributeTypeRequiredDescription
contextstringnoSelects a kubeconfig context; empty uses the current context. Ignored when Server is set (the plugin builds its own per-tunnel kubeconfig).
namespacestringnoSelects the Kubernetes namespace for kubectl commands.
podstringnoNames an existing pod to port-forward to. Exactly one of pod, service, selector, or template must be set.
servicestringnoNames a service to port-forward to.
selectormap[string]stringnoMatches a ready pod to port-forward to.
templatestringnoA pod manifest to apply and port-forward to.
portintyesThe pod-side port the forwarder targets. For service mode it's the service port; kubectl resolves the matching targetPort.
cleanupstringnoControls whether a template-created pod is deleted on tunnel teardown. "delete" (default) is right for the common create-on-demand case; "keep" disables deletion. Created pods are stamped with clawpatrol.dev/managed-by=clawpatrol and clawpatrol.dev/tunnel=<name> labels; unless cleanup is "keep", a startup sweep deletes any pod carrying those labels that a previous daemon lifetime left behind (e.g. after a crash skipped graceful teardown).
serverstringnoThe Kubernetes apiserver URL. When set the plugin writes a per-tunnel kubeconfig (server + ca_cert + bearer minted from the bound credential) and invokes kubectl with --kubeconfig pointing at it; no external kubeconfig or KUBECONFIG env is needed. The Context field is then ignored.
ca_certstringnoThe cluster CA PEM. Supports <<file:path.pem>> for out-of-line storage; the loader inlines the file contents. Required when Server is set against EKS (the apiserver presents a per-cluster CA that no system trust store carries).
cluster_namestringnoThe EKS cluster name, used by an aws_credential to scope the STS presign (sets the X-K8s-Aws-Id header). Only meaningful alongside Server + an aws_credential.
regionstringnoThe AWS region the EKS cluster lives in; SigV4 needs it. Only meaningful alongside Server + an aws_credential.
sharestringnoControls whether runtime instances are singleton, per-endpoint, or per-request.
keepalivestringnoKeeps an idle tunnel runtime warm for the given duration.
viaref(tunnel)noChains kubectl access through another tunnel.
credentialref(credential)noReferences an optional credential block for Kubernetes access.
tunnel "kubernetes_port_forward" "example" {
  port = 30
}

# tunnel "local_command" "<name>"

Configures the tunnel runtime.

AttributeTypeRequiredDescription
command[]stringyesThe argv vector to spawn for the tunnel process.
listenstringyesThe local address the spawned command exposes.
ready_probestringnoAn optional TCP address to poll before the tunnel is ready.
ready_timeoutstringnoOverrides the default readiness wait duration.
envmap[string]stringnoAdds environment variables to the spawned command.
sharestringnoControls whether runtime instances are singleton, per-endpoint, or per-request.
keepalivestringnoKeeps an idle tunnel runtime warm for the given duration.
viaref(tunnel)noChains this tunnel through another tunnel.
credentialref(credential)noReferences an optional credential block for the tunnel runtime.
tunnel "local_command" "example" {
  command = ["example"]
  listen = "example"
}

# tunnel "ssh_port_forward" "<name>"

Configures the tunnel runtime.

AttributeTypeRequiredDescription
bastionstringnoThe SSH server host:port; required when via is unset.
userstringyesThe SSH username for the bastion login.
sharestringnoControls whether runtime instances are singleton, per-endpoint, or per-request.
keepalivestringnoKeeps an idle tunnel runtime warm for the given duration.
viaref(tunnel)noChains the SSH connection through another tunnel.
credentialref(credential)yesReferences an ssh credential block used for bastion authentication.
tunnel "ssh_port_forward" "example" {
  bastion = "bastion.example:22"
  user = "example"
  credential = bearer_token.example
}

# tunnel "tailscale" "<name>"

Configures the tunnel runtime.

AttributeTypeRequiredDescription
authkeystringnoThe Tailscale auth key; env fallback is CLAWPATROL_TUNNEL__AUTHKEY.
oauth_client_secretstringnoA Tailscale OAuth client secret (tskey-client-...). When set, tsnet mints a fresh, short-lived device key from the OAuth client on every join instead of relying on a static authkey — so there is no long-lived key that can expire out from under the tunnel. Requires tags (untagged OAuth keys are rejected by Tailscale). Env fallback is CLAWPATROL_TUNNEL__OAUTH_CLIENT_SECRET.
control_urlstringnoOverrides the Tailscale control-plane URL.
hostnamestringnoThe tsnet node name; defaults to clawpatrol-tunnel-.
state_dirstringnoStores tsnet node state; defaults under the gateway CA directory.
tags[]stringnoTailscale tags requested for the tsnet node.
sharestringnoControls whether runtime instances are singleton, per-endpoint, or per-request.
keepalivestringnoKeeps an idle tunnel runtime warm for the given duration.
viaref(tunnel)noChains this tunnel through another tunnel.
credentialref(credential)noReferences an optional credential block for the tunnel runtime.
tunnel "tailscale" "example" {}