import fs from "node:fs"; import path from "node:path"; import dotenv from "dotenv"; import { isDangerousHostEnvOverrideVarName, isDangerousHostEnvVarName, normalizeEnvVarKey, } from "../infra/host-env-security.js"; import { collectConfigServiceEnvVars } from "./config-env-vars.js"; import { resolveStateDir } from "./paths.js"; import type { OpenClawConfig } from "./types.js"; function isBlockedServiceEnvVar(key: string): boolean { return isDangerousHostEnvVarName(key) || isDangerousHostEnvOverrideVarName(key); } /** * Read and parse `~/.openclaw/.env` (or `$OPENCLAW_STATE_DIR/.env`), returning * a filtered record of key-value pairs suitable for embedding in a service * environment (LaunchAgent plist, systemd unit, Scheduled Task). */ export function readStateDirDotEnvVars( env: Record, ): Record { const stateDir = resolveStateDir(env as NodeJS.ProcessEnv); const dotEnvPath = path.join(stateDir, ".env"); let content: string; try { content = fs.readFileSync(dotEnvPath, "utf8"); } catch { return {}; } const parsed = dotenv.parse(content); const entries: Record = {}; for (const [rawKey, value] of Object.entries(parsed)) { if (!value?.trim()) { continue; } const key = normalizeEnvVarKey(rawKey, { portable: true }); if (!key) { continue; } if (isBlockedServiceEnvVar(key)) { continue; } entries[key] = value; } return entries; } /** * Durable service env sources survive beyond the invoking shell and are safe to * persist into gateway install metadata. * * Precedence: * 1. state-dir `.env` file vars * 2. config service env vars */ export function collectDurableServiceEnvVars(params: { env: Record; config?: OpenClawConfig; }): Record { return { ...readStateDirDotEnvVars(params.env), ...collectConfigServiceEnvVars(params.config), }; }