/** * Session-identity markers Claude Code exports into every Bash child it runs * (`CLAUDECODE` plus the `CLAUDE_CODE_*`/`CLAUDE_PID` family). Any process * started from inside a Claude session inherits them, and outside that session * they are always stale. The destructive one is CLAUDE_CODE_CHILD_SESSION: a * claude CLI that sees it treats itself as a nested subagent session and * silently turns OFF transcript persistence — which breaks `--resume`-based * continuity (respawn after a tmux session dies, role switch) for every bot. * * The poisoning path mirrors SESSION_CLI_HOME_ENV_KEYS: pm2 persists the env * of whichever process ran `botmux start/restart` (or raw `pm2 start`) into * every managed app, so one self-upgrade issued from a bot session bakes the * issuing session's markers into all daemons; the next time the shared tmux * server is (re)born it forks from the poisoned daemon, seeds its global env * with the markers, and every bot CLI on the machine stops saving transcripts. * * CLAUDE_EFFORT is deliberately absent: it is a behavior knob a user or * per-bot env may legitimately set, not a session-identity marker. */ export declare const CLAUDE_SESSION_MARKER_ENV_KEYS: readonly ["CLAUDECODE", "CLAUDE_CODE_CHILD_SESSION", "CLAUDE_CODE_SESSION_ID", "CLAUDE_CODE_ENTRYPOINT", "CLAUDE_CODE_EXECPATH", "CLAUDE_PID"]; /** * Runtime markers that belong only to a v3 workflow/goal worker. * * A workflow worker may invoke `botmux restart`; PM2 persists that caller env * into the long-lived daemons. If these keys survive, every ordinary worker * forked by those daemons mistakes itself for a workflow worker: it skips * screen/status updates and goal-only CLI restrictions leak into chat * sessions. Keep the complete file-backed goal contract together with the * workflow identity fields so a partial scrub cannot leave a normal worker in * a hybrid mode. * * Do not apply this scrub at worker.ts boot: real workflow workers share that * entry point and receive these keys explicitly from the ephemeral pool. */ export declare const WORKFLOW_WORKER_ENV_KEYS: readonly ["BOTMUX_WORKFLOW", "BOTMUX_WORKFLOW_PTY_LOG_PATH", "BOTMUX_WORKFLOW_RUN_ID", "BOTMUX_WORKFLOW_NODE_ID", "BOTMUX_GOAL_PATH", "BOTMUX_GOAL_INPUTS_PATH", "BOTMUX_GOAL_OUTPUT_DIR", "BOTMUX_GOAL_MANIFEST_PATH", "BOTMUX_GOAL_ATTEMPT_DIR", "BOTMUX_V3_GOAL"]; /** Remove workflow-only identity from a non-workflow process boundary. */ export declare function scrubWorkflowWorkerEnv(env: NodeJS.ProcessEnv): void; /** Delete inherited Claude session markers (CLAUDE_SESSION_MARKER_ENV_KEYS, * plus BOUNDARY_ONLY_CLAUDE_ENV_KEYS) from `env` in place. Called at the same * botmux-owned process boundaries as scrubSessionCliHomeEnv — pm2 invocation * env (cli.ts pm2Env), daemon boot (index-daemon.ts), worker boot (worker.ts) * — so a daemon (re)started from inside a Claude session never carries that * session's identity into anything it forks (workers, the shared tmux * server). */ export declare function scrubClaudeSessionMarkerEnv(env: NodeJS.ProcessEnv): void; /** * Dashboard-only Feishu/Lark H5 passwordless-login configuration. Every key in * this family is read by ONE consumer — resolveDashboardH5AuthConfig() in * src/dashboard/h5-auth.ts, inside the dashboard process — and by nothing else * in the fleet. A session's CLI child has no reason to see any of it, and * `..._APP_SECRET` is a full Feishu app credential: whoever holds it can mint * app_access_token for the H5 login app, i.e. forge the identity the Dashboard * authenticates on. The daemon loads ~/.botmux/.env wholesale (index-daemon.ts * dotenv), so without this scrub the secret rode into every PTY/direct CLI * child and every one-shot the daemon forks (session group-title). * * Redaction is BOTH belt and braces: * - these exact names sit in REDACTED_CHILD_ENV_KEYS, which also drives the * tmux pane wrapper's `unset` clause and the tmux client-env strip; * - redactChildEnv() additionally sweeps the whole * {@link DASHBOARD_H5_ENV_PREFIX}, so a future H5 knob is redacted the day * it is added rather than the day someone remembers this list. */ export declare const DASHBOARD_H5_ENV_PREFIX = "BOTMUX_DASHBOARD_FEISHU_H5_"; export declare const DASHBOARD_H5_ENV_KEYS: readonly ["BOTMUX_DASHBOARD_FEISHU_H5_ENABLED", "BOTMUX_DASHBOARD_FEISHU_H5_BRAND", "BOTMUX_DASHBOARD_FEISHU_H5_APP_ID", "BOTMUX_DASHBOARD_FEISHU_H5_APP_SECRET", "BOTMUX_DASHBOARD_FEISHU_H5_ALLOWED_OPEN_IDS", "BOTMUX_DASHBOARD_FEISHU_H5_ENTRY_PATH", "BOTMUX_DASHBOARD_FEISHU_H5_SESSION_TTL_MS", "BOTMUX_DASHBOARD_FEISHU_H5_SECURE_COOKIE", "BOTMUX_DASHBOARD_FEISHU_H5_TRUSTED_PROXY_HOPS"]; /** * Delete the entire Dashboard-only Feishu H5 login family from `env` IN PLACE: * every named key in {@link DASHBOARD_H5_ENV_KEYS} plus a * {@link DASHBOARD_H5_ENV_PREFIX} sweep, so a knob added tomorrow is stripped * the day it ships rather than the day someone extends the list. * * The Dashboard is the family's ONLY consumer, and it receives the values by * dotenv-loading ~/.botmux/.env itself (index-dashboard.ts) — NOT through the * shared PM2 env block (the H5 keys are deliberately absent from * DAEMON_ENV_KEYS). Every other botmux-owned process must therefore hold none * of it. Call sites: * - index-daemon.ts boot: the daemon dotenv-loads the same .env wholesale, so * it must drop the family right after dotenv — a daemon holding the H5 * APP_SECRET can mint app_access_token for the Dashboard's login app. * - core/maintenance.ts detachedRestartEnv(): the detached `botmux restart` * the DASHBOARD spawns (update/restart button) inherits the dashboard's env, * which legitimately holds the secrets; the restart driver does not need * them and must not carry them toward pm2. * redactChildEnv() keeps its own equivalent strip as the second line of * defense at every CLI-child boundary (PTY/tmux pane unset/one-shot). */ export declare function stripDashboardH5Env(env: NodeJS.ProcessEnv): void; /** * Terminal/interactivity fingerprints of whichever process invoked a pm2 * mutation. pm2 persists the caller's env into every managed app (and into * dump.pm2 for resurrect), so a `botmux restart` issued from an agent's * non-interactive shell — Claude Code / Codex tool shells export NO_COLOR=1, * CODEX_CI=1, PAGER=cat, plus the terminal-app identity of whatever terminal * hosted them — bakes "you have no colors, you are in CI" into every daemon, * which every worker and session PTY then inherits: all bot CLI TUIs render * colorless, and TERMINFO can even point at a terminal app's private terminfo * dir. A daemon is a headless service: every key here describes the invoker's * terminal or harness, never the machine, so deleting them at the pm2 * boundary is always correct. Session PTYs set their own TERM (the backends * spawn with name 'xterm-256color'), and a user who wants genuinely colorless * bots keeps the per-bot `env` channel — like CLAUDE_EFFORT, the ambient * "export it in the shell that runs `botmux restart`" channel is sacrificed * because at this boundary it cannot be told apart from contamination. */ export declare const INVOKER_TERMINAL_ENV_KEYS: readonly ["NO_COLOR", "FORCE_COLOR", "CLICOLOR", "CLICOLOR_FORCE", "TERM", "COLORTERM", "TERMINFO", "TERM_PROGRAM", "TERM_PROGRAM_VERSION", "TERM_SESSION_ID", "CI", "CODEX_CI", "PAGER", "GIT_PAGER", "GH_PAGER"]; /** Delete inherited invoker-terminal fingerprints from `env` in place. */ export declare function scrubInvokerTerminalEnv(env: NodeJS.ProcessEnv): void; /** * Env vars that must never reach a spawned CLI child. The bot's IM-app creds * (a child CLI's own Lark OAuth reads `process.env.LARK_APP_ID` as the app to * authorize and gets hijacked by the botmux IM app → no docs scopes → 403 * loop), daemon-side GitHub API tokens, the Dashboard-only Feishu H5 login * credentials (DASHBOARD_H5_ENV_KEYS), and claude-code's session markers * (CLAUDE_SESSION_MARKER_ENV_KEYS). The * child resolves Lark via the namespaced `BOTMUX_LARK_APP_ID` or via bots.json * on disk (im/lark/client.ts); the worker keeps its own bare creds * (worker-pool.ts forkWorker) for lark-upload — only the *child* is redacted. * * Two leak vectors, two layers (both keyed off this list): * - PTY / direct spawn: `redactChildEnv()` deletes them from the env object. * - tmux: the new pane inherits the tmux *server's* global env, which the * client env can't override, so the shell wrapper `unset`s them before exec * (see SHELL_WRAPPER_SCRIPT in tmux-backend.ts). */ export declare const REDACTED_CHILD_ENV_KEYS: readonly ["LARK_APP_ID", "LARK_APP_SECRET", "GITHUB_TOKEN", "GH_TOKEN", "BOTMUX_DASHBOARD_FEISHU_H5_ENABLED", "BOTMUX_DASHBOARD_FEISHU_H5_BRAND", "BOTMUX_DASHBOARD_FEISHU_H5_APP_ID", "BOTMUX_DASHBOARD_FEISHU_H5_APP_SECRET", "BOTMUX_DASHBOARD_FEISHU_H5_ALLOWED_OPEN_IDS", "BOTMUX_DASHBOARD_FEISHU_H5_ENTRY_PATH", "BOTMUX_DASHBOARD_FEISHU_H5_SESSION_TTL_MS", "BOTMUX_DASHBOARD_FEISHU_H5_SECURE_COOKIE", "BOTMUX_DASHBOARD_FEISHU_H5_TRUSTED_PROXY_HOPS", "CLAUDECODE", "CLAUDE_CODE_CHILD_SESSION", "CLAUDE_CODE_SESSION_ID", "CLAUDE_CODE_ENTRYPOINT", "CLAUDE_CODE_EXECPATH", "CLAUDE_PID", "TMUX", "TMUX_PANE", "BOTMUX_PM2_GRACEFUL_EXIT_CODE"]; /** * Session-level CLI data-root pointers: claude-family → CLAUDE_CONFIG_DIR, * codex → CODEX_HOME. Botmux computes these PER SESSION (read isolation pins * `/claude|codex`; Seed/Relay pin their fork dirs via adapter * spawnEnv), so a value INHERITED from the surrounding environment is always * stale. The poisoning path: pm2 persists the env of whichever process ran * `botmux start/restart` into every managed app (and into dump.pm2 for * resurrect), so one self-upgrade issued from a bot session bakes that * session's injected CLAUDE_CONFIG_DIR into ALL daemons and workers — and * every non-isolated sibling bot then reads/writes that bot's home. Scrubbed * at each process boundary botmux owns: the pm2 invocation env (cli.ts * pm2Env), daemon boot (index-daemon.ts) and worker boot (worker.ts) — the * boot scrubs cover envs pm2 resurrects from a stale dump. Scrubbing * process.env (not just the spawned child's env) keeps worker-side resolvers * that consult it dynamically — codex submit confirmation / resume fallback / * transcript bridge (services/codex-paths.ts), slash-command discovery * (core/command-discovery.ts) — consistent with the CLI child. * * Do NOT pin a "default" instead of deleting: CLAUDE_CONFIG_DIR=~/.claude is * not a no-op — once the var is set, Claude Code reads its state file from * $CLAUDE_CONFIG_DIR/.claude.json instead of ~/.claude.json, which lacks * hasCompletedOnboarding → every non-isolated session reruns first-run * onboarding (theme picker + login). * * GROK_HOME is deliberately ABSENT: botmux never injects it per session (grok * has no per-bot isolation, and per-bot env rejects the key — see * core/per-bot-env.ts), so it can never carry a sibling bot's home. Its * contract is the opposite of per-session: process-level only, where worker * and CLI child must resolve the SAME value (the worker installs ready-gate * hooks and drains transcripts under it — see services/grok-paths.ts), so * scrubbing it on any single side would split-brain that documented * configuration channel. */ export declare const SESSION_CLI_HOME_ENV_KEYS: readonly ["CLAUDE_CONFIG_DIR", "CODEX_HOME"]; /** * Pin the daemon-selected session owner under the public BOTMUX_* contract and * the legacy private name used by older wrappers. ownerOpenId is the only * authority: every worker backend must call this after config-controlled env * merges, and an ownerless session must delete both keys. Otherwise a managed * CLI can observe a stale/different app-scoped owner across backends. */ export declare function applySessionOwnerEnv(env: NodeJS.ProcessEnv, ownerOpenId: string | undefined): void; /** Delete inherited session-level CLI data-root pointers from `env` in place * (see SESSION_CLI_HOME_ENV_KEYS). Values a session actually needs are * computed and re-set AFTER this scrub (worker isolation pins / adapter * spawnEnv). */ export declare function scrubSessionCliHomeEnv(env: NodeJS.ProcessEnv): void; /** * Botmux-managed, session/bot-scoped env keys that reach the CLI pane via the * `/usr/bin/env KEY=VAL` wrapper injection (buildBotmuxEnvAssignments), NOT via * the tmux client env. They MUST be stripped from the env handed to the `tmux` * binary (see tmuxEnv), because the first `tmux new-session` that boots a server * copies its client env into the server's *global* environment — which then * leaks into every co-tenant session sharing that socket, including the user's * own interactive tmux. A leaked `BOTMUX_SESSION_ID` / `BOTMUX_CHAT_ID` makes a * plain Claude Code run in the user's terminal believe it is a botmux session * and misroute its AskUserQuestion hook to a Lark thread. Stripping is invisible * to botmux's own sessions: the pane still gets the correct per-session values * from the env(1) injection, which lands after rcfile load. * * The `BOTMUX` prefix is swept wholesale by {@link isBotmuxManagedTmuxEnvKey} * (covers daemon-internal BOTMUX_BOT_INDEX / BOTMUX_QUIET_RESTART / … that were * never meant to reach a pane). These non-prefixed keys come from * BOTMUX_INJECTED_ENV_KEYS; the bare IM-app creds are folded in from * REDACTED_CHILD_ENV_KEYS so a server botmux bootstraps can never seed them * into its global env either. */ export declare const BOTMUX_INJECTED_ENV_KEYS: readonly ["__OWNER_OPEN_ID", "BOTMUX", "SESSION_DATA_DIR", "IS_SANDBOX", "BOTMUX_SESSION_ID", "BOTMUX_CHAT_ID", "BOTMUX_MCP_GATEWAY_SOCKET", "BOTMUX_MCP_GATEWAY_REQUIRED", "BOTMUX_CHAT_TYPE", "BOTMUX_LARK_APP_ID", "BOTS_CONFIG", "BOTMUX_ROOT_MESSAGE_ID", "BOTMUX_OWNER_OPEN_ID", "BOTMUX_TURN_ID", "BOTMUX_DISPATCH_ATTEMPT", "BOTMUX_BRAND_LABEL", "BOTMUX_READ_ISOLATION", "BOTMUX_API_ONLY", "BOTMUX_USAGE_DISPLAY", "BOTMUX_PI_INITIAL_PROMPT_FILE", "BOTMUX_DAEMON_IPC_PORT", "BOTMUX_READ_ISOLATED", "BOTMUX_ORIGIN_CHANNEL_ID", "BOTMUX_LARK_LIST_BOTS_API_ENABLED", "BOTMUX_LARK_LIST_BOTS_API_TIMEOUT_MS", "BOTMUX_READY_COMMAND", "BOTMUX_CODEX_APP_CONTROL_BOOTSTRAP", "HERMES_HOME", "HERMES_BOTMUX_SOURCE_HOME", "HERMES_BOTMUX_PROFILES_ROOT", "CLAUDE_CONFIG_DIR", "CODEX_HOME", "CLAUDE_CODE_RESUME_TOKEN_THRESHOLD", "CJADK_INTERACTIVE"]; /** * Session-only botmux identity and capabilities of the process that invoked a * pm2 mutation. A `botmux restart` issued from inside a bot session carries * that session's routing identity and capabilities; persisted by pm2 into the * fleet, every daemon then carries a stale foreign turn identity, and a * plugin service started from the same env would misroute its own * `botmux send` to a long-dead thread. * * This is a DELIBERATELY hand-maintained list. It is NOT derived from * BOTMUX_INJECTED_ENV_KEYS: that list is the tmux/pane TRANSPORT whitelist * and mixes session-only keys with ambient/daemon config that merely needs * pane delivery (CLAUDE_CODE_RESUME_TOKEN_THRESHOLD is ambient-only — worker * reads it from its own process.env and per-bot env REJECTS it, so a boot * scrub would silently kill the user's setting; HERMES_HOME and the two * HERMES_BOTMUX_* roots are ambient install-location config nothing in * botmux ever sets, same contract as GROK_HOME; BOTS_CONFIG / * SESSION_DATA_DIR / BOTMUX_LARK_LIST_BOTS_API_* are documented ambient or * ecosystem-block config). The reverse also holds: session/sandbox routing * keys the pane transport never carries (BOTMUX_SESSION_SCOPE, * BOTMUX_SEND_RELAY) still need scrubbing here. Every entry below is * session-scoped BY CONSTRUCTION: the daemon/worker computes and injects it * per session AFTER every boundary scrub, and no ambient/env-file channel for * it exists. */ export declare const SESSION_TURN_MARKER_ENV_KEYS: readonly ["BOTMUX", "BOTMUX_SESSION_ID", "BOTMUX_CHAT_ID", "BOTMUX_CHAT_TYPE", "BOTMUX_ROOT_MESSAGE_ID", "BOTMUX_TURN_ID", "BOTMUX_DISPATCH_ATTEMPT", "BOTMUX_SESSION_SCOPE", "BOTMUX_OWNER_OPEN_ID", "__OWNER_OPEN_ID", "BOTMUX_ORIGIN_CHANNEL_ID", "BOTMUX_SEND_RELAY", "BOTMUX_MCP_GATEWAY_SOCKET", "BOTMUX_MCP_GATEWAY_REQUIRED", "BOTMUX_DAEMON_IPC_PORT", "BOTMUX_READ_ISOLATION", "BOTMUX_READ_ISOLATED", "BOTMUX_API_ONLY", "IS_SANDBOX", "BOTMUX_BRAND_LABEL", "BOTMUX_USAGE_DISPLAY", "BOTMUX_PI_INITIAL_PROMPT_FILE", "BOTMUX_CODEX_APP_CONTROL_BOOTSTRAP", "BOTMUX_READY_COMMAND", "BOTMUX_LARK_APP_ID", "CJADK_INTERACTIVE"]; /** Delete inherited session-only identity/capabilities from `env` in place. */ export declare function scrubSessionTurnMarkerEnv(env: NodeJS.ProcessEnv): void; /** Proxy env vars that must reach the CLI child process so it can dial the * upstream API on hosts without direct internet access. Forwarded explicitly * by buildBotmuxEnvAssignments (tmux/tmux-pipe/zellij backends) and * prepareDirectSandbox (bwrap --setenv); the pty backend inherits them via the * full child env. * Deliberately NOT in BOTMUX_INJECTED_ENV_KEYS: that list drives tmuxEnv() * stripping and scrubTmuxServerGlobalEnv() cleanup — adding proxy keys there * would delete the user's own tmux server proxy config. */ export declare const PROXY_ENV_KEYS: readonly ["http_proxy", "https_proxy", "HTTP_PROXY", "HTTPS_PROXY", "no_proxy", "NO_PROXY", "all_proxy", "ALL_PROXY"]; /** * True for any env key botmux must keep out of the tmux CLIENT env it hands to * the `tmux` binary. This is stricter than server-global scrub: besides * botmux-owned routing/profile vars, we also strip daemon-side GitHub tokens so * a botmux-started tmux server never seeds them into its shared global env. */ export declare function isBotmuxManagedTmuxEnvKey(key: string): boolean; /** * True for env keys botmux is allowed to repair out of an already-running tmux * SERVER global environment. This intentionally excludes user-wide GitHub * tokens: botmux panes must `unset` them locally, but daemon startup must not * rewrite a shared tmux server's general-purpose env table. */ export declare function isBotmuxManagedTmuxServerGlobalEnvKey(key: string): boolean; /** * Build the base environment for a spawned CLI child: copy the worker's env * and REMOVE the keys in REDACTED_CHILD_ENV_KEYS. * * Why `delete` and not `{ ...env, KEY: undefined }`: node-pty stringifies an * `undefined` env value to the literal string "undefined" rather than omitting * the key (verified against the bundled node-pty). So `{ ...env, LARK_APP_ID: * undefined }` hands the child `LARK_APP_ID="undefined"` — still truthy, so any * SDK probing `process.env.LARK_APP_ID` takes the Lark path with appId * "undefined". Only deleting the key truly unsets it. * * NOTE: this covers the PTY path and the tmux *client* env. The tmux *server* * global-env vector is closed separately by the wrapper's `unset` — see the * comment on REDACTED_CHILD_ENV_KEYS. * * Returns a fresh object; the input env is not mutated. */ export declare function redactChildEnv(base: NodeJS.ProcessEnv): NodeJS.ProcessEnv; //# sourceMappingURL=child-env.d.ts.map