mirror of
https://github.com/nearai/ironclaw.git
synced 2026-09-02 23:56:24 +08:00
* fix(docker): restore Reborn in-worker SSH (#7723) Co-authored-by: Henry Park <16583448+henrypark133@users.noreply.github.com> (cherry picked from commitb0ba342268) * fix(workspace): honor IRONCLAW_REBORN_WORKSPACE_ROOT on 1.3 (#7804) * fix(workspace): honor IRONCLAW_REBORN_WORKSPACE_ROOT on 1.3 The durable workspace-root override landed on release/2026-08-11 ine12bbae4d2and was never forward-ported: neither origin/main nor release/2026-08-17 has it. Both CLI boot paths resolve the workspace root from `std::env::current_dir()`, so a container's project files and landed attachments are written under the container workdir instead of the mounted volume, and do not survive a redeploy. Port the workspace-root slice only. The two release-branch commits that carry it (e12bbae4d2,bdf8aef022) also carry the whole rc1->1.1/1.2 startup-migration program (~11.7k lines); that stack is out of scope here and is already staged on origin/port/firat-workspace-artifacts-1.2-to-1.3. - `local_runtime_workspace_root` resolves the override and falls back to cwd, used by both the standalone and hosted single-tenant builders. `optional_path_env` rejects an empty value rather than silently treating it as unset. - The Docker entrypoint defaults the root to `$IRONCLAW_REBORN_HOME/workspace` and fails closed on Railway when it resolves outside RAILWAY_VOLUME_MOUNT_PATH, matching the existing IRONCLAW_REBORN_HOME guard. - The root pass also creates and chowns the workspace root before the gosu privilege drop. This has no counterpart on release/2026-08-11, which has no root pass; without it a workspace root outside IRONCLAW_REBORN_HOME fails the later mkdir as the unprivileged user. Test: `scripts/ci/test-reborn-docker-entrypoint.sh` gains a default-root assertion and an explicit-override case, and the existing ssh_root chown assertion now pins the workspace root. Both new assertions were verified to fail when the entrypoint default is broken. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(workspace): resolve workspace root before the Railway guard Addresses review on #7804. Drop the workspace root from the root pass's mkdir/chown (P2). The containment check runs after the gosu privilege drop, so the root pass reached chown before validation: `IRONCLAW_REBORN_WORKSPACE_ROOT=/etc` would have had its ownership changed to `ironclaw` before startup failed closed. Removing it also deletes this port's only deviation from the release/2026-08-11 original. The default root still works unprivileged — IRONCLAW_REBORN_HOME is chowned in the root pass, so the later profile- gated mkdir creates the subdirectory as `ironclaw`. A root outside IRONCLAW_REBORN_HOME now fails loudly at that mkdir instead of silently widening ownership. Compare canonicalized paths in the Railway containment guard (P1). A symlink beneath the mount whose target is outside it, or a `..` segment, passed the lexical prefix test while the runtime resolved the real ephemeral target — booting a deployment whose project files silently do not persist, the exact failure the guard exists to prevent. Both sides are resolved, so a mount path that itself traverses a symlink does not reject every root. The diagnostic reports resolved and original spellings. Test: the entrypoint self-test gains a rejected escaping-symlink case, a contained positive control, and a trailing-slash normalization case. The new checks use `assert_eq`, which the regression-test gate recognizes as a meaningful shell assertion — `[ ... != ... ]` is not in its vocabulary, which is why the required check rejected the previous commit. Each assertion was verified to fail under a targeted mutation (lexical containment restored, default root broken, normalization removed). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commita5b8345d21) * fix(docker): harden the forward-ported SSH and workspace-root paths Review of the two forward-ported commits surfaced four defects. All of them are latent in the shipped 1.3.x code, not introduced by the port -- `git blame` puts every one ond4cf2411b0(#7723) orab7ac3d87e(#7804). They are fixed here rather than ported forward knowingly. **Workspace root is now prepared before the privilege drop.** The root branch chowned only $IRONCLAW_REBORN_HOME and /workspace, then exec'd gosu; the resolved $IRONCLAW_REBORN_WORKSPACE_ROOT was created afterwards, unprivileged. An explicit override onto a fresh root-owned volume therefore hit EACCES and, under `set -eu`, aborted the boot. The chown stays non-recursive so start-sshd.sh keeps root ownership of $IRONCLAW_REBORN_HOME/ssh. **The Railway containment guard no longer accepts an unresolved path.** `readlink -f` exits non-zero when any non-final component is missing, so the `|| printf` fallback handed the raw spelling to a glob comparison, and `$RAILWAY_VOLUME_MOUNT_PATH/missing/../../tmp` passed the very `..`-escape check the guard exists to enforce -- silently placing project files on ephemeral storage. Verified empirically in debian:bookworm-slim (coreutils 9.1). Now `readlink -m`, which canonicalizes missing components, with no raw-spelling fallback: an unresolvable path fails closed. **sshd forwarding is disabled explicitly.** OpenSSH defaults AllowTcpForwarding and AllowAgentForwarding to yes; the generated config now sets those and PermitTunnel to no. **A pasted private key is refused before it reaches disk.** `ssh-keygen -l -f` exits 0 on a private key, so one supplied by mistake was written verbatim into authorized_keys (mode 644, persisted). It never granted a login -- PEM lines do not match authorized_keys grammar -- so the harm was secret persistence plus a silently broken setup. The value is now rejected on a PEM header or on carrying more than one key, without ever echoing the key material. That last check needs care: an operator almost always supplies the key via `cat id_ed25519.pub`, whose value carries a trailing newline. A naive line-count rejection would refuse the most common paste, and because the entrypoint runs start-sshd under `set -e` that would abort the whole container boot rather than merely disabling SSH. Surrounding whitespace and blank lines are therefore stripped before the one-key check, and the PEM check runs first so a real private key still gets the actionable "supply the matching .pub" message. Verified against a real sshd in debian:bookworm-slim, comparing behavior with the shipped release/1.3.1 script as the baseline: - 16/16 regression checks, against a 12-pass/1-fail baseline. Every baseline pass survives; real logins still succeed for ed25519, RSA and ECDSA. - 10/10 operator-paste cases: plain, trailing newline, leading newline, trailing spaces, CRLF and surrounding blank lines all accept and complete a real SSH login; private key, two keys, garbage and whitespace-only are refused with accurate messages. - Unset key still exits 0 with no listener, preserving the opt-in contract. - Generated config still passes `sshd -t` with every original auth directive. The trailing-newline case is pinned in CI by a second container in the existing `Verify in-worker SSH` step, reusing the image already built there -- it adds about ten seconds to a job that takes roughly twenty-two minutes, and no new job. The other three fixes carry shell-suite regression tests, each proven to fail before its fix. Fixes 3 and 4 cannot be reached from that suite (start-sshd.sh needs real root and a real sshd binary), which is precisely why the CI container check exists. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(docker): document the in-worker SSH ingress and its privilege model `IRONCLAW_REBORN_SSH_PUBLIC_KEY` was documented nowhere, despite being the single switch that stands up an SSH listener in the runtime image. AGENTS.md requires environment variables to be documented in `.env.example`. - `.env.example` gains the variable next to IRONCLAW_REBORN_WORKSPACE_ROOT: off unless set, enables sshd on container port 2222, public-key-only login as `agent`, and the port must be published by the orchestrator to be reachable. - The operator Docker guide gains an SSH Access section covering the same three facts plus the auth directives the generated sshd_config actually sets, and states plainly that `agent` is a uid-1000 alias of `ironclaw` -- an SSH session holds the full runtime identity, so the private key deserves the same care as shell access to the service. - Two Dockerfile comments (no build-logic change) record why `agent` is a deliberate UID alias, so a later reader does not mistake it for a lower-privileged account and widen the SSH surface on that assumption, and that the entrypoint performs the only privilege drop -- anything bypassing it (`docker run --entrypoint`, `docker exec` without `--user`, a platform custom start command) runs as root. Every claim was checked against docker/reborn/start-sshd.sh, entrypoint.sh and the Dockerfile runtime stage rather than restated from the review comments. Verified: check-guidance.py OK, docs_publication_boundary.py OK. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(docker): never chown an unvalidated workspace root as root The previous commit's workspace-root fix introduced a privilege escalation. It added $IRONCLAW_REBORN_WORKSPACE_ROOT to the root branch's `chown`, but the Railway containment check that validates an operator-supplied override runs *after* the privilege drop -- it needs $effective_profile, which is resolved from the config file further down, past `exec gosu`. The raw value was therefore chowned to the runtime uid before anything could reject it. Reproduced in debian:bookworm-slim: with `IRONCLAW_REBORN_WORKSPACE_ROOT=/etc` on a Railway-profile boot, /etc went from root:root to ironclaw:ironclaw, and only then did startup fail. A non-recursive chown of /etc still lets uid 1000 create entries there (/etc/ld.so.preload being the obvious one), so this is a real escalation primitive, and the container filesystem keeps it across restarts. The root pass now pre-creates the workspace root only when it provably lives inside a directory this entrypoint already manages -- under $IRONCLAW_REBORN_HOME, or under the Railway volume mount -- comparing canonicalized paths so `$RAILWAY_VOLUME_MOUNT_PATH/../etc` cannot spell its way in. Anything else is left untouched: on Railway the containment check rejects it moments later, and elsewhere the later unprivileged `mkdir -p` reports the failure exactly as it did before. That still covers the case the fix exists for, an override onto a fresh root-owned volume mount, which is what the deployment guide documents. Regression tests (`workspace_root_outside_managed`) assert the root pass never passes an out-of-tree path to chown, in both a plain and a `..`-spelled form; both fail without the guard and pass with it. Two existing cases were reconciled with the new shape: the chown stub now appends, because the root pass legitimately issues two chown calls rather than one, and an overwriting stub silently dropped the first; and `workspace_root_privdrop` now sets RAILWAY_VOLUME_MOUNT_PATH, making explicit the volume-mount scenario its own comment already described. Not addressed here: $IRONCLAW_REBORN_HOME is chowned on the same path with no validation either. That predates this PR, and unlike the workspace root it has no containment contract to respect -- any path is a legitimate home by design -- so tightening it is a behavior change to shipped configuration handling rather than a fix to this regression. Tracked with the other uid-alias findings. Verified: entrypoint self-tests pass on debian:bookworm-slim as a non-root user (as CI runs them); the real-sshd regression harness still reports 16/16 against the release/1.3.1 baseline; ws12 workflow contracts and check-guidance pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(docker): refuse a filesystem-root home or workspace root `${VAR%/}` turns the single-character input `/` into an empty string, and neither root path handled that. `IRONCLAW_REBORN_HOME=/` reached `mkdir -p "" /workspace` and died with a confusing "cannot create directory ''". `IRONCLAW_REBORN_WORKSPACE_ROOT=/` reached `readlink -m ""` in the containment guard, which exits non-zero and printed nothing at all -- under `set -eu` the boot died silently, with no diagnostic for an operator to act on. That silent mode was introduced by the containment guard two commits ago. The obvious repair -- restoring `/` the way the file already does for RAILWAY_VOLUME_MOUNT_PATH eight lines above -- is wrong here, and testing it is what showed why. Restoring the value makes the root pass's `chown ironclaw:ironclaw "$IRONCLAW_REBORN_HOME"` newly *reachable* as `chown ironclaw:ironclaw /`, handing uid 1000 ownership of the container's entire root filesystem. Verified in a container: the chown succeeds. That trades a loud crash for a total loss of the root boundary, which is strictly worse than the bug being fixed. Both values are therefore refused with a diagnostic naming the variable. Nothing legitimately runs with either path set to the filesystem root, so the safe reading of `/` is operator error -- stop and say so. Regression tests (`slash_root`) drive both variables through the root pass and assert three things: the run is refused, the diagnostic names the variable, and *nothing is chowned on the way to that refusal*. That last assertion is the one that matters -- it fails against the restore-to-`/` shape as well as against the original bug, so it pins the escalation rather than just the crash. All three assertions fail without the guards and pass with them. Verified in debian:bookworm-slim: `IRONCLAW_REBORN_HOME=/` leaves `/` owned by root:root and exits with the diagnostic; the entrypoint suite passes as a non-root user (as CI runs it); the real-sshd regression harness still reports 16/16 against the release/1.3.1 baseline, including a full entrypoint -> start-sshd -> real SSH login as agent:1000 with the ssh state directory still root-owned. Also adds the missing Rust coverage for the same variable: `local_runtime_workspace_root` now has tests for an explicit override, the unset fallback to the current directory, and the set-but-empty failure asserting the error names IRONCLAW_REBORN_WORKSPACE_ROOT. Each was proven discriminating by breaking the production function and confirming only the matching test failed. Scoped to the resolution function deliberately: proving the value reaches the composed RebornHostBindings would require a new public accessor in ironclaw_composition, which is a production API change and does not belong in a release-blocking forward-port. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Henry Park <16583448+henrypark133@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
565 lines
29 KiB
Plaintext
565 lines
29 KiB
Plaintext
# Database Configuration
|
||
DATABASE_URL=postgres://localhost/ironclaw
|
||
DATABASE_POOL_SIZE=30 # multi-tenant default; reduce to 5-10 for single-user or low-resource deployments
|
||
|
||
# LLM Provider
|
||
# LLM_BACKEND=nearai # default
|
||
# Possible values: nearai, ollama, openai_compatible, openai, anthropic, github_copilot, tinfoil, openai_codex, gemini_oauth
|
||
# LLM_REQUEST_TIMEOUT_SECS=120 # Increase for local LLMs (Ollama, vLLM, LM Studio)
|
||
|
||
# === Anthropic Direct ===
|
||
# Two auth modes:
|
||
# 1. API key: Set ANTHROPIC_API_KEY (from console.anthropic.com/settings/keys)
|
||
# 2. OAuth token: Set ANTHROPIC_OAUTH_TOKEN (from `claude login`)
|
||
# OAuth tokens use Authorization: Bearer instead of x-api-key header.
|
||
# ANTHROPIC_API_KEY=sk-ant-...
|
||
# ANTHROPIC_OAUTH_TOKEN=sk-ant-oat01-... # from `claude login` credentials
|
||
# ANTHROPIC_MODEL=claude-sonnet-4-20250514
|
||
|
||
# === OpenAI Direct ===
|
||
# OPENAI_API_KEY=sk-...
|
||
# Reuse Codex CLI auth.json instead of setting OPENAI_API_KEY manually.
|
||
# Works with both OpenAI API-key mode and Codex ChatGPT OAuth mode.
|
||
# In ChatGPT mode this uses the private `chatgpt.com/backend-api/codex` endpoint.
|
||
# LLM_USE_CODEX_AUTH=true
|
||
# CODEX_AUTH_PATH=~/.codex/auth.json
|
||
|
||
# === GitHub Copilot ===
|
||
# Uses the OAuth token from your Copilot IDE sign-in (for example
|
||
# ~/.config/github-copilot/apps.json on Linux/macOS), or run `ironclaw onboard`
|
||
# and choose the GitHub device login flow.
|
||
# LLM_BACKEND=github_copilot
|
||
# GITHUB_COPILOT_TOKEN=gho_...
|
||
# GITHUB_COPILOT_MODEL=gpt-4o
|
||
# IronClaw injects standard VS Code Copilot headers automatically.
|
||
# Optional advanced headers for custom overrides:
|
||
# GITHUB_COPILOT_EXTRA_HEADERS=Copilot-Integration-Id:vscode-chat
|
||
|
||
# === NEAR AI (Chat Completions API) ===
|
||
# Two auth modes:
|
||
# 1. Session token (default): Uses browser OAuth (GitHub/Google) on first run.
|
||
# Session token stored in ~/.ironclaw/session.json automatically.
|
||
# Base URL defaults to https://private.near.ai
|
||
# 2. API key: Set NEARAI_API_KEY to use API key auth from cloud.near.ai.
|
||
# Base URL defaults to https://cloud-api.near.ai
|
||
# When NEARAI_API_KEY is set at startup, IronClaw bootstraps the
|
||
# `nearai` MCP integration automatically, using the default endpoint
|
||
# unless NEARAI_BASE_URL is also set:
|
||
# legacy startup persists a `nearai` MCP server, and Reborn local-dev
|
||
# seeds product-auth and activates the bundled NEAR AI MCP extension.
|
||
# Leave NEARAI_MODEL / NEARAI_BASE_URL unset to use the built-in defaults
|
||
# (model `deepseek-ai/DeepSeek-V4-Flash`; base URL chosen by auth mode:
|
||
# cloud-api.near.ai with an API key, private.near.ai for session tokens) and
|
||
# to let the WebUI onboarding selection take effect. Set them only for a pure
|
||
# env-configured deployment — they apply process-wide. Precedence: an explicit
|
||
# WebUI/config.toml selection wins over these env vars, which in turn win over
|
||
# the built-in defaults.
|
||
# NEARAI_MODEL=deepseek-ai/DeepSeek-V4-Flash
|
||
# NEARAI_BASE_URL=https://private.near.ai
|
||
# NEARAI_AUTH_URL=https://private.near.ai
|
||
# NEARAI_SESSION_TOKEN=sess_... # hosting providers: set this
|
||
# NEARAI_SESSION_PATH=~/.ironclaw/session.json # optional, default shown
|
||
# NEARAI_API_KEY=... # API key from cloud.near.ai
|
||
|
||
# Local LLM Providers (Ollama, LM Studio, vLLM, LiteLLM)
|
||
|
||
# === Ollama ===
|
||
# OLLAMA_MODEL=llama3.2
|
||
# LLM_BACKEND=ollama
|
||
# OLLAMA_BASE_URL=http://localhost:11434 # default
|
||
|
||
# === OpenAI-compatible (LM Studio, vLLM, Anything-LLM) ===
|
||
# LLM_MODEL=llama-3.2-3b-instruct-q4_K_M
|
||
# LLM_BACKEND=openai_compatible
|
||
# LLM_BASE_URL=http://localhost:1234/v1
|
||
# LLM_API_KEY=sk-... # optional for local servers
|
||
# Custom HTTP headers for OpenAI-compatible providers
|
||
# Format: comma-separated key:value pairs
|
||
# LLM_EXTRA_HEADERS=HTTP-Referer:https://github.com/nearai/ironclaw,X-Title:ironclaw
|
||
|
||
# === OpenRouter (300+ models via OpenAI-compatible) ===
|
||
# LLM_MODEL=anthropic/claude-sonnet-4 # see openrouter.ai/models for IDs
|
||
# LLM_BACKEND=openai_compatible
|
||
# LLM_BASE_URL=https://openrouter.ai/api/v1
|
||
# LLM_API_KEY=sk-or-...
|
||
|
||
# LLM_EXTRA_HEADERS=HTTP-Referer:https://myapp.com,X-Title:MyApp
|
||
|
||
|
||
# === Together AI (via OpenAI-compatible) ===
|
||
# LLM_MODEL=meta-llama/Llama-3.3-70B-Instruct-Turbo
|
||
# LLM_BACKEND=openai_compatible
|
||
# LLM_BASE_URL=https://api.together.xyz/v1
|
||
# LLM_API_KEY=...
|
||
|
||
# === Fireworks AI (via OpenAI-compatible) ===
|
||
# LLM_MODEL=accounts/fireworks/models/llama4-maverick-instruct-basic
|
||
# LLM_BACKEND=openai_compatible
|
||
# LLM_BASE_URL=https://api.fireworks.ai/inference/v1
|
||
# LLM_API_KEY=fw_...
|
||
|
||
# === MiniMax ===
|
||
# LLM_BACKEND=minimax
|
||
# MINIMAX_API_KEY=...
|
||
# MINIMAX_MODEL=MiniMax-M2.7
|
||
# MINIMAX_BASE_URL=https://api.minimax.io/v1 # default (global); use https://api.minimaxi.com/v1 for China
|
||
|
||
# === Anthropic Direct ===
|
||
# LLM_BACKEND=anthropic
|
||
# ANTHROPIC_MODEL=claude-sonnet-4-6
|
||
# ANTHROPIC_API_KEY=sk-ant-...
|
||
# ANTHROPIC_BASE_URL=https://api.anthropic.com # default
|
||
# Prompt cache retention — controls Anthropic server-side prompt caching:
|
||
# none = disabled (no cache_control injected)
|
||
# short = 5-minute TTL, 1.25× (125%) write surcharge (default)
|
||
# long = 1-hour TTL, 2.0× (200%) write surcharge
|
||
# ANTHROPIC_CACHE_RETENTION=short
|
||
|
||
# === OpenAI Codex (ChatGPT subscription, OAuth) ===
|
||
# LLM_BACKEND=openai_codex
|
||
# OPENAI_CODEX_MODEL=gpt-5.5 # default (must be entitled to your ChatGPT plan)
|
||
# OPENAI_CODEX_CLIENT_ID=app_EMoamEEZ73f0CkXaXp7hrann # override (rare)
|
||
# OPENAI_CODEX_AUTH_URL=https://auth.openai.com # override (rare)
|
||
# OPENAI_CODEX_API_URL=https://chatgpt.com/backend-api/codex # override (rare)
|
||
|
||
# === Google Gemini (OAuth, Gemini CLI compatible) ===
|
||
# LLM_BACKEND=gemini_oauth
|
||
# GEMINI_MODEL=gemini-2.5-flash # default
|
||
# GEMINI_CREDENTIALS_PATH=~/.gemini/oauth_creds.json # default
|
||
# GEMINI_API_KEY=... # optional: use API key instead of OAuth
|
||
# GEMINI_API_KEY_AUTH_MECHANISM=query # "query" (default) or "header"
|
||
# GEMINI_SAFETY_BLOCK_NONE=true # disable safety filters (default: false)
|
||
# GEMINI_CLI_CUSTOM_HEADERS=Key:Value,Key2:Value2
|
||
# GEMINI_TOP_P=0.95
|
||
# GEMINI_TOP_K=40
|
||
# GEMINI_SEED=42
|
||
# GEMINI_PRESENCE_PENALTY=0.0
|
||
# GEMINI_FREQUENCY_PENALTY=0.0
|
||
# GEMINI_RESPONSE_MIME_TYPE=application/json
|
||
# GEMINI_RESPONSE_JSON_SCHEMA={"type":"object"}
|
||
# GEMINI_CACHED_CONTENT=cachedContents/abc123
|
||
|
||
# For full provider setup guide see docs/LLM_PROVIDERS.md
|
||
|
||
# Progressive tool disclosure. The default is `namespaces`; set `off` for the
|
||
# rollback path. `bridged` additionally enables reviewed per-profile pins.
|
||
# REBORN_TOOL_DISCLOSURE=namespaces
|
||
# JSON map from validated capability-surface profile ids to capability ids.
|
||
# Invalid JSON/ids reject startup; unset means no pins. Pins affect visibility
|
||
# only and never bypass authorization.
|
||
# REBORN_TOOL_DISCLOSURE_PROFILE_PINS={"interactive_tools":["github.search_code"]}
|
||
|
||
# Channel Configuration
|
||
# CLI is always enabled
|
||
|
||
# Slack Bot (optional)
|
||
SLACK_BOT_TOKEN=xoxb-...
|
||
SLACK_APP_TOKEN=xapp-...
|
||
SLACK_SIGNING_SECRET=...
|
||
|
||
# Telegram Bot (optional)
|
||
TELEGRAM_BOT_TOKEN=...
|
||
|
||
# Reborn Telegram WASM v2 ProductAdapter (issue #3285) — DEFAULT OFF.
|
||
# When true, the v2 ProductAdapter path takes mutually-exclusive ownership
|
||
# of the telegram webhook installation. Legacy v1 Telegram MUST NOT be
|
||
# configured for the same installation while this flag is true; the host
|
||
# fails closed on startup if both are active.
|
||
# REBORN_TELEGRAM_V2_ENABLED=false
|
||
|
||
# HTTP Webhook Server (optional)
|
||
HTTP_HOST=0.0.0.0
|
||
HTTP_PORT=8080
|
||
HTTP_WEBHOOK_SECRET=your-webhook-secret
|
||
# Webhook authentication uses HMAC-SHA256 signature verification.
|
||
# Callers must send an X-IronClaw-Signature header with format: sha256=<hex_digest>
|
||
# where the digest is HMAC-SHA256(HTTP_WEBHOOK_SECRET, raw_request_body) in lowercase hex.
|
||
#
|
||
# Example (bash):
|
||
# BODY='{"content":"hello"}'
|
||
# SIG=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$HTTP_WEBHOOK_SECRET" | cut -d' ' -f2)
|
||
# curl -X POST http://localhost:8080/webhook \
|
||
# -H "Content-Type: application/json" \
|
||
# -H "X-IronClaw-Signature: sha256=$SIG" \
|
||
# -d "$BODY"
|
||
#
|
||
# DEPRECATED: Passing "secret" in the JSON body still works but will be removed in a future release.
|
||
|
||
# Signal Channel (optional, requires signal-cli daemon --http)
|
||
# SIGNAL_HTTP_URL=http://127.0.0.1:8080
|
||
# SIGNAL_ACCOUNT=+1234567890
|
||
# SIGNAL_ALLOW_FROM=+1234567890,uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx # comma-separated, * for all, empty = deny/require pairing
|
||
# SIGNAL_ALLOW_FROM_GROUPS= # comma-separated group IDs, * for all, empty = deny all groups
|
||
# SIGNAL_DM_POLICY=pairing # open | allowlist | pairing
|
||
# SIGNAL_GROUP_POLICY=allowlist # allowlist | open | disabled
|
||
# SIGNAL_GROUP_ALLOW_FROM= # comma-separated, empty = inherit from ALLOW_FROM
|
||
# SIGNAL_IGNORE_ATTACHMENTS=false
|
||
# SIGNAL_IGNORE_STORIES=true
|
||
|
||
# Agent Settings
|
||
AGENT_NAME=ironclaw
|
||
AGENT_MAX_PARALLEL_JOBS=5
|
||
AGENT_JOB_TIMEOUT_SECS=3600
|
||
AGENT_STUCK_THRESHOLD_SECS=300
|
||
# Maximum tokens per job (0 = unlimited, also settable via settings.json agent.max_tokens_per_job)
|
||
# AGENT_MAX_TOKENS_PER_JOB=0
|
||
# Enable planning phase before tool execution (default: true)
|
||
AGENT_USE_PLANNING=true
|
||
|
||
# Self-repair settings
|
||
SELF_REPAIR_CHECK_INTERVAL_SECS=60
|
||
SELF_REPAIR_MAX_ATTEMPTS=3
|
||
|
||
# Heartbeat settings (proactive periodic execution)
|
||
# When enabled, reads HEARTBEAT.md checklist and reports findings
|
||
HEARTBEAT_ENABLED=false
|
||
HEARTBEAT_INTERVAL_SECS=1800
|
||
HEARTBEAT_NOTIFY_CHANNEL=cli
|
||
HEARTBEAT_NOTIFY_USER=default
|
||
|
||
# Memory hygiene settings (automatic cleanup of stale workspace documents)
|
||
# Runs on each heartbeat tick; discovers cleanup targets from .config metadata
|
||
# MEMORY_HYGIENE_ENABLED=true
|
||
# MEMORY_HYGIENE_VERSION_KEEP_COUNT=50 # max versions to keep per document
|
||
# MEMORY_HYGIENE_CADENCE_HOURS=12 # minimum hours between cleanup passes
|
||
|
||
# Trigger poller (scheduled / cron trigger firing)
|
||
# OFF by default. Enable to allow the host background poller to scan due trigger
|
||
# records and auto-fire scheduled (cron) triggers. Without this, triggers can be
|
||
# created and listed but will never fire automatically.
|
||
# IRONCLAW_TRIGGER_POLLER_ENABLED=true # accepts 1/true to enable, 0/false to disable (default: false; any other value is a fatal startup error)
|
||
# IRONCLAW_TRIGGER_POLLER_INTERVAL_SECS=60 # how often the poller checks for due triggers (integer seconds, 1..=3600)
|
||
# IRONCLAW_CREDENTIAL_REFRESH_ENABLED=true # accepts 1/true to enable, 0/false to disable (default: false; any other value is a fatal startup error)
|
||
|
||
# Turn-runner concurrency (overrides the config-file [runner] section)
|
||
# Highest-precedence layer; a set-but-blank value is a fatal startup error.
|
||
# For every knob below, 0 means "unlimited". Use 0 to stress-test the DB
|
||
# backend (e.g. libSQL) with no concurrency throttle.
|
||
# IRONCLAW_REBORN_RUNNER_WORKER_COUNT=16 # global scheduler slots; 0 = unlimited; positive = that many, verbatim; default 16
|
||
# IRONCLAW_REBORN_RUNNER_MAX_CONCURRENT_RUNS_PER_USER=3 # per (tenant, owner) cap; 0 = unlimited; default 3
|
||
# IRONCLAW_REBORN_RUNNER_MAX_CONCURRENT_TRIGGER_RUNS=8 # scheduled-trigger cap; 0 = unlimited; default 8
|
||
# IRONCLAW_REBORN_RUNNER_MAX_CONCURRENT_CONVERSATION_RUNS=0 # inbound/web cap; 0 = unlimited (default)
|
||
# IRONCLAW_REBORN_RUNNER_MAX_CONCURRENT_UNBOUND_RUNS=4 # unbound prepared-context cap; 0 = unlimited (default 4)
|
||
|
||
# Railway Sandboxes preview. Preferred IronClaw-scoped identifiers fall back
|
||
# to RAILWAY_PROJECT_ID / RAILWAY_ENVIRONMENT_ID used by the Railway CLI.
|
||
# Set exactly one of RAILWAY_TOKEN or RAILWAY_API_TOKEN in the deployment.
|
||
# IRONCLAW_REBORN_RAILWAY_PROJECT_ID=
|
||
# IRONCLAW_REBORN_RAILWAY_ENVIRONMENT_ID=
|
||
# IRONCLAW_REBORN_RAILWAY_CLI_PATH=railway
|
||
# IRONCLAW_REBORN_RAILWAY_IDLE_TIMEOUT_MINUTES=30
|
||
# IRONCLAW_REBORN_RAILWAY_WORKER_IMAGE=python:3.12-slim@sha256:57cd7c3a7a273101a6485ba99423ee568157882804b1124b4dd04266317710de
|
||
# RAILWAY_TOKEN=
|
||
# RAILWAY_API_TOKEN=
|
||
|
||
# Reborn skill prompt injection: "listing" (default) advertises non-activated
|
||
# skills as a one-line "- name: description" listing and loads a skill's full
|
||
# instructions only on explicit $name mention or builtin.skill_activate;
|
||
# "full" restores the legacy inject-bodies-by-keyword-score behavior.
|
||
# IRONCLAW_REBORN_SKILL_INJECTION=listing
|
||
|
||
# Docker Sandbox
|
||
# SANDBOX_ENABLED=true
|
||
# SANDBOX_POLICY=readonly # readonly, workspace_write, or full_access
|
||
# SANDBOX_ALLOW_FULL_ACCESS=false # REQUIRED second opt-in for full_access policy.
|
||
# # FullAccess bypasses Docker entirely and runs
|
||
# # commands directly on the host. Without this
|
||
# # set to "true", full_access is downgraded to
|
||
# # workspace_write.
|
||
# SANDBOX_IMAGE=ironclaw-worker:latest
|
||
# SANDBOX_TIMEOUT_SECS=120
|
||
# SANDBOX_MEMORY_LIMIT_MB=2048
|
||
|
||
# Reborn sandbox container transport
|
||
# Overrides Docker daemon discovery for the Reborn sandbox transport. Accepts a
|
||
# unix socket path (optionally "unix://"-prefixed) or an http://host:port /
|
||
# tcp://host:port address. Unset means "probe local defaults, then the
|
||
# well-known unix sockets".
|
||
#
|
||
# SECURITY: the http(s)/tcp form is plaintext and unauthenticated; the Docker
|
||
# Engine API it reaches can create containers and mount host paths, so it is
|
||
# restricted to a unix socket or a loopback address ("localhost"/127.0.0.1/::1)
|
||
# by default. A non-loopback value (e.g. a remote daemon, or a DinD/CI sidecar
|
||
# reachable at a container-network IP) additionally requires
|
||
# IRONCLAW_REBORN_DOCKER_HOST_ALLOW_REMOTE=1 as an explicit second opt-in.
|
||
# IRONCLAW_REBORN_DOCKER_HOST=unix:///var/run/docker.sock
|
||
# IRONCLAW_REBORN_DOCKER_HOST_ALLOW_REMOTE=false # required to use a non-loopback IRONCLAW_REBORN_DOCKER_HOST
|
||
#
|
||
# Extra domains appended to the sandboxed shell's default egress allowlist
|
||
# (crates.io, npm, PyPI, the Go proxy, GitHub). Comma-separated exact
|
||
# hostnames. Wildcard ("*." or bare "*") entries are rejected: the managed
|
||
# egress proxy cannot represent them with the canonical one-label wildcard
|
||
# semantics, so profile construction fails closed. These add to the
|
||
# defaults, never replace them.
|
||
# IRONCLAW_SANDBOX_EXTRA_ALLOWED_DOMAINS=example.com,internal.example.com
|
||
#
|
||
# Optional managed-egress proxy image override. The reference must include an
|
||
# immutable sha256 digest. Public images are pulled when absent; private images
|
||
# must already exist in the configured Docker daemon.
|
||
# IRONCLAW_REBORN_SANDBOX_PROXY_IMAGE=registry.example.com/iron-proxy@sha256:<64 hex characters>
|
||
|
||
# ACP (Agent Client Protocol) agents
|
||
# ACP_ENABLED=false # Enable ACP agent sandbox mode
|
||
# ACP_MEMORY_LIMIT_MB=4096 # Memory limit for ACP containers
|
||
# ACP_TIMEOUT_SECS=1800 # Maximum session timeout
|
||
# Configure agents via CLI: ironclaw acp add goose --command goose --arg "--stdio"
|
||
|
||
# Safety settings
|
||
SAFETY_MAX_OUTPUT_LENGTH=100000
|
||
SAFETY_INJECTION_CHECK_ENABLED=true
|
||
|
||
# Restart Feature (Docker containers only)
|
||
# Set IRONCLAW_IN_DOCKER=true in the container entrypoint to enable the restart feature.
|
||
# Without this, the restart tool and /restart command will be disabled.
|
||
# IRONCLAW_IN_DOCKER=false
|
||
# IRONCLAW_RESTART_DELAY=5 # default wait before exit (seconds, range: 1-30)
|
||
# IRONCLAW_MAX_FAILURES=10 # max consecutive failures before container exits
|
||
|
||
# ─── OAuth / Social Login ────────────────────────────────────────────────
|
||
# Enable direct OAuth login (Google, GitHub). Disabled by default.
|
||
# OAUTH_ENABLED=true
|
||
|
||
# Base URL for OAuth callback URLs. Defaults to http://localhost:{GATEWAY_PORT}.
|
||
# Set this to your public URL in production (e.g., https://myapp.example.com).
|
||
# OAUTH_BASE_URL=https://myapp.example.com
|
||
|
||
# Restrict OAuth login to specific email domains (comma-separated).
|
||
# When set, only users with verified emails from these domains can log in.
|
||
# Applies to all OAuth providers and OIDC. Leave unset to allow all domains.
|
||
# OAUTH_ALLOWED_DOMAINS=company.com,partner.org
|
||
|
||
# Google OAuth — Create credentials at https://console.cloud.google.com/apis/credentials
|
||
# 1. Create an OAuth 2.0 Client ID (Web application type)
|
||
# 2. Add authorized redirect URI: {OAUTH_BASE_URL}/auth/callback/google
|
||
# 3. Copy Client ID and Client Secret below
|
||
# GOOGLE_CLIENT_ID=
|
||
# GOOGLE_CLIENT_SECRET=
|
||
# GOOGLE_OAUTH_REDIRECT_URI=https://myapp.example.com/auth/callback/google
|
||
|
||
# Reborn Product Auth Google OAuth setup recovery.
|
||
# Prefer these Reborn-specific vars when configuring the WebChat v2 setup flow.
|
||
# IRONCLAW_REBORN_GOOGLE_CLIENT_ID=
|
||
# IRONCLAW_REBORN_GOOGLE_CLIENT_SECRET=
|
||
# IRONCLAW_REBORN_GOOGLE_OAUTH_REDIRECT_URI=https://myapp.example.com/api/reborn/product-auth/oauth/google/callback
|
||
# Optional Reborn setup hint. CURRENTLY INERT (measured 2026-08-05): the value
|
||
# is resolved and stored but nothing reads it, because the auth engine builds
|
||
# its authorization parameters from the vendor recipe's manifest
|
||
# `extra_authorize_params` and the Google recipes declare no `hd`. Setting it
|
||
# adds nothing. For real Google-Workspace restriction of WebChat *login*, use
|
||
# IRONCLAW_REBORN_WEBUI_GOOGLE_ALLOWED_HD below instead.
|
||
# IRONCLAW_REBORN_GOOGLE_HOSTED_DOMAIN_HINT=company.com
|
||
|
||
# Restrict Google login to a specific Workspace (G Suite) domain.
|
||
# Adds the `hd` parameter to the authorization URL and validates server-side.
|
||
# GOOGLE_ALLOWED_HD=company.com
|
||
|
||
# Apple Sign In — Configure in https://developer.apple.com/account/resources/identifiers
|
||
# 1. Register a Services ID (e.g. com.example.myapp) under Identifiers
|
||
# 2. Enable "Sign In with Apple" and configure the return URL: {OAUTH_BASE_URL}/auth/callback/apple
|
||
# 3. Create a key (Keys section), enable "Sign In with Apple", download the .p8 file
|
||
# 4. Note your Team ID (top right of developer portal) and Key ID
|
||
# APPLE_CLIENT_ID=com.example.myapp
|
||
# APPLE_TEAM_ID=XXXXXXXXXX
|
||
# APPLE_KEY_ID=YYYYYYYYYY
|
||
# APPLE_PRIVATE_KEY_PATH=/path/to/AuthKey_YYYYYYYYYY.p8
|
||
# Or inline: APPLE_PRIVATE_KEY_PEM="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
|
||
|
||
# GitHub OAuth — Create an OAuth App at https://github.com/settings/developers
|
||
# 1. Create a new OAuth App
|
||
# 2. Set Authorization callback URL to: {OAUTH_BASE_URL}/auth/callback/github
|
||
# 3. Copy Client ID and generate a Client Secret below
|
||
# GITHUB_CLIENT_ID=
|
||
# GITHUB_CLIENT_SECRET=
|
||
|
||
# NEAR Wallet — No external setup needed. Users sign in with any NEAR wallet
|
||
# (HOT, Meteor, MyNearWallet, etc.) via the near-connect SDK.
|
||
# NEAR_AUTH_ENABLED=true
|
||
# NEAR_AUTH_NETWORK=mainnet # or testnet
|
||
# NEAR_AUTH_RPC_URL=https://rpc.mainnet.near.org # auto-detected from network
|
||
|
||
# ─── OIDC / SSO (Okta, Cognito, etc.) ──────────────────────────────────
|
||
# For reverse-proxy SSO (e.g., AWS ALB + Okta). The gateway validates JWTs
|
||
# from the configured header. See also OAUTH_ALLOWED_DOMAINS above, which
|
||
# applies to OIDC logins too.
|
||
# GATEWAY_OIDC_ENABLED=true
|
||
# GATEWAY_OIDC_JWKS_URL=https://your-idp.example.com/.well-known/jwks.json
|
||
# GATEWAY_OIDC_HEADER=x-amzn-oidc-data
|
||
# GATEWAY_OIDC_ISSUER=https://your-idp.example.com
|
||
# GATEWAY_OIDC_AUDIENCE=your-client-id
|
||
|
||
# ─── Reborn WebUI v2 (`ironclaw serve`) ─────────────────────────
|
||
# The Reborn binary is separate from v1 and does not share auth/middleware
|
||
# with the v1 channels/web stack. These variables configure the v2 native
|
||
# HTTP surface owned by `crates/product/ironclaw_webui`. They have
|
||
# no effect on the v1 binary.
|
||
#
|
||
# Bearer token consulted by `EnvBearerAuthenticator`. Keep this in the
|
||
# environment ONLY — operators must NOT put the token value into
|
||
# `config.toml`. The token is compared with `subtle::ConstantTimeEq` so
|
||
# leading/trailing whitespace counts.
|
||
# IRONCLAW_REBORN_WEBUI_TOKEN=replace-with-strong-random-token
|
||
#
|
||
# UserId stamped onto the `WebUiAuthenticatedCaller` when the bearer
|
||
# token matches. Pair this with the tenant_id resolved from
|
||
# `[identity].installation_tenant` in `config.toml`.
|
||
# IRONCLAW_REBORN_WEBUI_USER_ID=reborn-cli-operator
|
||
#
|
||
# Optional path under /opt/ironclaw to the default TOML copied into
|
||
# `$IRONCLAW_REBORN_HOME/config.toml` by the Reborn Docker entrypoint on first
|
||
# start. Existing home configs are preserved.
|
||
# IRONCLAW_REBORN_DEFAULT_CONFIG=/opt/ironclaw/reborn/config.toml
|
||
#
|
||
# Show the WebChat v2 Projects surface (the conversations-panel "Projects"
|
||
# entry + the /projects route). Hidden by default while the surface is still
|
||
# being finished. The flag is read at serve composition and delivered to the
|
||
# browser via `GET /api/webchat/v2/session` (`features.reborn_projects`).
|
||
# IRONCLAW_REBORN_PROJECTS=true
|
||
#
|
||
# Enable QA-only scrubbed run and full-thread regression artifact downloads.
|
||
# Disabled by default: production users receive no download affordance and the
|
||
# artifact HTTP routes are not mounted. Enable only on trusted QA deployments.
|
||
# IRONCLAW_REBORN_REGRESSION_ARTIFACT_EXPORT=true
|
||
#
|
||
# Enable the admin thread-scraping panel (cross-user transcript artifact
|
||
# collection for debugging/optimization). Disabled by default and gated
|
||
# independently of the QA-only regression export flag: enabling QA self-export
|
||
# must never silently mount tenant-wide admin transcript access.
|
||
# IRONCLAW_REBORN_ADMIN_THREAD_SCRAPE=true
|
||
#
|
||
# Tenant-shared tool credentials (#5459). Any variable matching
|
||
# `IRONCLAW_REBORN_DEV_SECRET__<handle>=<value>` is read once at `serve`
|
||
# startup and stored under `<handle>` at the tenant-shared, admin-managed
|
||
# secret scope, so a keyed WASM tool (network + `use_secret`) resolves its
|
||
# credential for EVERY user of the tenant from one operator-set key. The
|
||
# credential resolver checks the caller's own scope first and falls back to
|
||
# the tenant-shared scope; if neither has the key the tool gates with
|
||
# AuthRequired. The `<handle>` suffix must match the `handle` declared in the
|
||
# tool manifest's `runtime_credentials` (e.g. `market_data_api_key`).
|
||
# Ops/dev provisioning path; inert unless set.
|
||
# IRONCLAW_REBORN_DEV_SECRET__market_data_api_key=replace-with-shared-api-key
|
||
#
|
||
# Reborn Docker deployment/runtime knobs. Keep the local serve host loopback
|
||
# unless a platform such as Railway is fronting the container and requires
|
||
# 0.0.0.0. `IRONCLAW_REBORN_CONFIRM_HOST_ACCESS` is only for
|
||
# local-dev-yolo-style trusted host access and should stay unset for public
|
||
# container deployments.
|
||
#
|
||
# The Reborn Docker image defaults to local-dev so local `docker run` does not
|
||
# require Postgres. Set IRONCLAW_REBORN_PROFILE=production on Railway to seed
|
||
# the production/Postgres config and keep user extension install/activation
|
||
# state durable across image redeploys. If you keep local-dev/local-dev-yolo on
|
||
# Railway, attach a persistent volume at /data or set IRONCLAW_REBORN_HOME under
|
||
# RAILWAY_VOLUME_MOUNT_PATH; otherwise local-dev extension state under
|
||
# `$IRONCLAW_REBORN_HOME/local-dev/system/extensions` is container-local. The
|
||
# entrypoint fails closed for Railway local-dev profiles without a volume unless
|
||
# IRONCLAW_REBORN_ALLOW_EPHEMERAL_RAILWAY=true is set for disposable tests.
|
||
# IRONCLAW_REBORN_HOME=/data/ironclaw-reborn
|
||
# Durable project/workspace root. The Docker entrypoint defaults this to
|
||
# `$IRONCLAW_REBORN_HOME/workspace`; non-container runs otherwise use cwd.
|
||
# IRONCLAW_REBORN_WORKSPACE_ROOT=/data/ironclaw-reborn/workspace
|
||
# Off unless set. Setting this to an OpenSSH PUBLIC key enables an in-container
|
||
# sshd listening on container port 2222, for public-key-only login as user
|
||
# `agent`. The orchestrator must still publish that port (for example
|
||
# `docker run -p 2222:2222`) for it to be reachable; it is not published by
|
||
# default.
|
||
# IRONCLAW_REBORN_SSH_PUBLIC_KEY=ssh-ed25519 AAAA...replace-with-your-public-key
|
||
# IRONCLAW_REBORN_PROFILE=local-dev
|
||
# IRONCLAW_REBORN_LOG=info
|
||
# IRONCLAW_REBORN_POSTGRES_URL=postgres://user:password@db.example.com/ironclaw?sslmode=require
|
||
# IRONCLAW_REBORN_SECRET_MASTER_KEY=replace-with-independent-secret-key-material
|
||
# IRONCLAW_REBORN_SERVE_HOST=127.0.0.1
|
||
# IRONCLAW_REBORN_SERVE_PORT=3000
|
||
# IRONCLAW_REBORN_CONFIRM_HOST_ACCESS=false
|
||
#
|
||
# There is no Slack or Telegram enablement variable. Channel webhook routes are
|
||
# always mounted; a channel goes live when its extension is installed and set up
|
||
# in the WebUI at /extensions.
|
||
#
|
||
# WebChat v2 SSO login (Google / GitHub). Setting either CLIENT_ID
|
||
# surfaces that provider's login button on the `serve` listener; the
|
||
# matching CLIENT_SECRET is required for the real code exchange. Each
|
||
# distinct OAuth identity maps to its own persisted user (looked up by
|
||
# provider + subject, linked across providers by a verified email), so
|
||
# different people who log in become different users with isolated
|
||
# threads. Every session is a stateless signed token (keyed off
|
||
# IRONCLAW_REBORN_WEBUI_TOKEN + the host tenant), accepted alongside the
|
||
# env bearer above.
|
||
#
|
||
# Admission is fail-closed: when a provider is configured you MUST set
|
||
# IRONCLAW_REBORN_WEBUI_ALLOWED_EMAIL_DOMAINS (below) — otherwise `serve`
|
||
# refuses to start. GitHub has no org allowlist and Google's `hd` check is
|
||
# optional, so without an email-domain allowlist any Google/GitHub account
|
||
# could log in (open registration).
|
||
#
|
||
# These deliberately live in the IRONCLAW_REBORN_WEBUI_* namespace,
|
||
# SEPARATE from the bare GOOGLE_CLIENT_ID / IRONCLAW_REBORN_GOOGLE_* vars
|
||
# in the OAuth section above:
|
||
# - bare GOOGLE_CLIENT_ID / GITHUB_CLIENT_ID → v1 gateway SSO login.
|
||
# - IRONCLAW_REBORN_GOOGLE_* → Reborn product-auth
|
||
# (connect a Google *credential* for the agent; requires a redirect
|
||
# URI). Setting these without the redirect URI makes every
|
||
# ironclaw command fail at startup — keep them unset unless
|
||
# you are configuring product-auth.
|
||
# - IRONCLAW_REBORN_WEBUI_GOOGLE_* (here) → Reborn WebChat *login*.
|
||
# Register {base}/auth/callback/{provider} as the authorized redirect URI,
|
||
# where {base} is IRONCLAW_REBORN_WEBUI_BASE_URL below (or the bound
|
||
# listener address in local dev).
|
||
# IRONCLAW_REBORN_WEBUI_GOOGLE_CLIENT_ID=
|
||
# IRONCLAW_REBORN_WEBUI_GOOGLE_CLIENT_SECRET=
|
||
# Optional: additionally restrict Google login to one Workspace domain
|
||
# (server-side `hd` claim) — narrower than the email-domain allowlist below.
|
||
# IRONCLAW_REBORN_WEBUI_GOOGLE_ALLOWED_HD=example.com
|
||
# IRONCLAW_REBORN_WEBUI_GITHUB_CLIENT_ID=
|
||
# IRONCLAW_REBORN_WEBUI_GITHUB_CLIENT_SECRET=
|
||
#
|
||
# REQUIRED when any provider above is configured: comma-separated list of
|
||
# verified-email domains allowed to log in. A login is admitted only when
|
||
# the provider asserts a VERIFIED email whose domain is in this list;
|
||
# everyone else is rejected and no session is minted. Set it to your own
|
||
# domain(s).
|
||
# IRONCLAW_REBORN_WEBUI_ALLOWED_EMAIL_DOMAINS=example.com,team.example.com
|
||
#
|
||
# Public base URL used to build the OAuth callback URLs for Reborn WebUI
|
||
# login and product-auth flows, including Notion MCP. Reborn-scoped only —
|
||
# it does NOT fall back to the v1 gateway's OAUTH_BASE_URL, so a legacy v1
|
||
# setting cannot rewrite Reborn callback URLs. Absent this var, Reborn uses
|
||
# the bound listener address. Set it to your public URL in production. Google
|
||
# product-auth still uses IRONCLAW_REBORN_GOOGLE_OAUTH_REDIRECT_URI explicitly.
|
||
# Also backs two setup links, both of which stay absent while it is unset (the
|
||
# copy around them keeps naming the destination in words):
|
||
# - the one-click connect link appended to an OAuth-strategy channel's
|
||
# "connect your account" notice (<base>/chat?connect=<extension>);
|
||
# - the personal-account setup link in device-link install guidance
|
||
# (<base>/extensions?configure=<extension>&setup=personal_account), which a
|
||
# user reading that guidance in Telegram or Slack cannot otherwise reach.
|
||
# IRONCLAW_REBORN_WEBUI_BASE_URL=https://myapp.example.com
|
||
#
|
||
# Optional per-call HTTP timeout (whole seconds) for the OAuth provider
|
||
# token/userinfo requests, applied to every configured provider. Defaults
|
||
# to 20s. Raise it on a slow or cross-border path to the provider (e.g.
|
||
# `github.com`) where the exchange times out. If the provider host is
|
||
# actually blocked/throttled, route through a proxy instead — the OAuth
|
||
# client honors the standard HTTPS_PROXY / ALL_PROXY env vars.
|
||
# IRONCLAW_REBORN_WEBUI_OAUTH_HTTP_TIMEOUT_SECS=30
|
||
|
||
# ─── Skills ────────────────────────────────────────────────────────────
|
||
# Regex-pattern skill auto-activation is a config-file setting, not an env
|
||
# var: set `regex_activation_enabled = false` under `[skills]` in the
|
||
# ironclaw config file to make only keyword/tag activation and explicit
|
||
# `/skill` mentions select a skill (see
|
||
# crates/app/ironclaw_config/src/config_file.rs, SkillsSection). The former
|
||
# SKILLS_REGEX_ACTIVATION_ENABLED env var is not read by anything.
|
||
|
||
# ─── Test / CI: OS keychain ────────────────────────────────────────────
|
||
# Set to "1" to stop the secrets master-key lookup from touching the real OS
|
||
# keychain (macOS Keychain / Linux Secret Service). Without it, a test or
|
||
# tool run that resolves the master key while SECRETS_MASTER_KEY is unset
|
||
# pops a macOS Keychain authorization dialog (or blocks on a locked Secret
|
||
# Service). Crate unit tests are already covered by cfg!(test); set this for
|
||
# integration/e2e runs (the CI workflows set it automatically). Leave UNSET in
|
||
# production so the real keychain fallback works.
|
||
# IRONCLAW_DISABLE_OS_KEYCHAIN=1
|
||
|
||
# Logging
|
||
RUST_LOG=ironclaw=debug,tower_http=debug
|