mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 09:41:11 +00:00
fix(dotenv): block workspace runtime env vars (#62660)
* fix(dotenv): block workspace runtime env vars Co-authored-by: zsx <git@zsxsoft.com> * docs(changelog): add workspace dotenv runtime-control entry * fix(dotenv): block workspace gateway port override --------- Co-authored-by: zsx <git@zsxsoft.com> Co-authored-by: Devin Robison <drobison@nvidia.com>
This commit is contained in:
@@ -18,6 +18,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Slack/media: preserve bearer auth across same-origin `files.slack.com` redirects while still stripping it on cross-origin Slack CDN hops, so `url_private_download` image attachments load again. (#62960) Thanks @vincentkoc.
|
||||
- Gateway/node exec events: mark remote node `exec.started`, `exec.finished`, and `exec.denied` summaries as untrusted system events and sanitize node-provided command/output/reason text before enqueueing them, so remote node output cannot inject trusted `System:` content into later turns. (#62659) Thanks @eleqtrizit.
|
||||
- Agents/timeouts: make the LLM idle timeout inherit `agents.defaults.timeoutSeconds` when configured, disable the unconfigured idle watchdog for cron runs, and point idle-timeout errors at `agents.defaults.llm.idleTimeoutSeconds`. Thanks @drvoss.
|
||||
- Security/dotenv: expand workspace `.env` filtering to block runtime-control variables like gateway routing, ClawHub endpoints/tokens, browser executable overrides, and skip/disable control families, so untrusted repositories cannot steer OpenClaw runtime behavior through repo-local dotenv files. (#62660) Thanks @eleqtrizit.
|
||||
- Agents/failover: classify Z.ai vendor code `1311` as billing and `1113` as auth, including long wrapped `1311` payloads, so these errors stop falling through to generic failover handling. (#49552) Thanks @1bcMax.
|
||||
- QQBot/media-tags: support HTML entity-encoded angle brackets (`<`/`>`) in media-tag regexes so entity-escaped `<qqimg>` tags from upstream are correctly parsed and normalized. (#60493) Thanks @ylc0919.
|
||||
- npm packaging: mirror bundled Slack, Telegram, Discord, and Feishu channel runtime deps at the root and harden published-install verification so fresh installs fail fast on manifest drift instead of missing-module crashes. (#63065) Thanks @scoootscooob.
|
||||
|
||||
@@ -613,3 +613,80 @@ describe("loadCliDotEnv", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("workspace .env blocklist completeness", () => {
|
||||
it("blocks runtime-control variables from workspace .env", async () => {
|
||||
await withIsolatedEnvAndCwd(async () => {
|
||||
await withDotEnvFixture(async ({ cwdDir }) => {
|
||||
const runtimeControlKeys = [
|
||||
"OPENCLAW_UPDATE_PACKAGE_SPEC",
|
||||
"OPENCLAW_GATEWAY_PORT",
|
||||
"OPENCLAW_GATEWAY_URL",
|
||||
"OPENCLAW_CLAWHUB_URL",
|
||||
"CLAWHUB_URL",
|
||||
"OPENCLAW_CLAWHUB_TOKEN",
|
||||
"CLAWHUB_TOKEN",
|
||||
"CLAWHUB_AUTH_TOKEN",
|
||||
"CLAWHUB_CONFIG_PATH",
|
||||
"OPENCLAW_DISABLE_BUNDLED_PLUGINS",
|
||||
"OPENCLAW_ALLOW_INSECURE_PRIVATE_WS",
|
||||
"OPENCLAW_BROWSER_EXECUTABLE_PATH",
|
||||
"BROWSER_EXECUTABLE_PATH",
|
||||
"PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH",
|
||||
"OPENCLAW_SKIP_CHANNELS",
|
||||
"OPENCLAW_SKIP_PROVIDERS",
|
||||
"OPENCLAW_SKIP_CRON",
|
||||
"OPENCLAW_RAW_STREAM",
|
||||
"OPENCLAW_RAW_STREAM_PATH",
|
||||
"OPENCLAW_CACHE_TRACE",
|
||||
"OPENCLAW_CACHE_TRACE_FILE",
|
||||
"OPENCLAW_CACHE_TRACE_MESSAGES",
|
||||
"OPENCLAW_CACHE_TRACE_PROMPT",
|
||||
"OPENCLAW_CACHE_TRACE_SYSTEM",
|
||||
"OPENCLAW_SHOW_SECRETS",
|
||||
"OPENCLAW_PLUGIN_CATALOG_PATHS",
|
||||
"OPENCLAW_MPM_CATALOG_PATHS",
|
||||
"OPENCLAW_NODE_EXEC_HOST",
|
||||
"OPENCLAW_NODE_EXEC_FALLBACK",
|
||||
"OPENCLAW_ALLOW_PROJECT_LOCAL_BIN",
|
||||
];
|
||||
|
||||
await writeEnvFile(
|
||||
path.join(cwdDir, ".env"),
|
||||
`${runtimeControlKeys.map((key) => `${key}=INJECTED_${key}`).join("\n")}\n`,
|
||||
);
|
||||
|
||||
for (const key of runtimeControlKeys) {
|
||||
delete process.env[key];
|
||||
}
|
||||
|
||||
loadWorkspaceDotEnvFile(path.join(cwdDir, ".env"), { quiet: true });
|
||||
|
||||
for (const key of runtimeControlKeys) {
|
||||
expect(process.env[key], `${key} should be blocked by workspace .env`).toBeUndefined();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("still allows user-defined non-control vars through workspace .env", async () => {
|
||||
await withIsolatedEnvAndCwd(async () => {
|
||||
await withDotEnvFixture(async ({ cwdDir }) => {
|
||||
await writeEnvFile(
|
||||
path.join(cwdDir, ".env"),
|
||||
"MY_APP_KEY=user-value\nGITHUB_TOKEN=ghp_test123\nDATABASE_URL_CUSTOM=pg://localhost\n",
|
||||
);
|
||||
|
||||
delete process.env.MY_APP_KEY;
|
||||
delete process.env.GITHUB_TOKEN;
|
||||
delete process.env.DATABASE_URL_CUSTOM;
|
||||
|
||||
loadWorkspaceDotEnvFile(path.join(cwdDir, ".env"), { quiet: true });
|
||||
|
||||
expect(process.env.MY_APP_KEY).toBe("user-value");
|
||||
expect(process.env.GITHUB_TOKEN).toBe("ghp_test123");
|
||||
expect(process.env.DATABASE_URL_CUSTOM).toBe("pg://localhost");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,37 +14,69 @@ const BLOCKED_WORKSPACE_DOTENV_KEYS = new Set([
|
||||
"ALL_PROXY",
|
||||
"ANTHROPIC_API_KEY",
|
||||
"ANTHROPIC_OAUTH_TOKEN",
|
||||
"BROWSER_EXECUTABLE_PATH",
|
||||
"CLAWHUB_AUTH_TOKEN",
|
||||
"CLAWHUB_CONFIG_PATH",
|
||||
"CLAWHUB_TOKEN",
|
||||
"CLAWHUB_URL",
|
||||
"HTTP_PROXY",
|
||||
"HTTPS_PROXY",
|
||||
"NODE_TLS_REJECT_UNAUTHORIZED",
|
||||
"NO_PROXY",
|
||||
"OPENAI_API_KEY",
|
||||
"OPENAI_API_KEYS",
|
||||
"OPENCLAW_AGENT_DIR",
|
||||
"OPENCLAW_ALLOW_INSECURE_PRIVATE_WS",
|
||||
"OPENCLAW_ALLOW_PROJECT_LOCAL_BIN",
|
||||
"OPENCLAW_BROWSER_EXECUTABLE_PATH",
|
||||
"OPENCLAW_BUNDLED_HOOKS_DIR",
|
||||
"OPENCLAW_BUNDLED_PLUGINS_DIR",
|
||||
"OPENCLAW_BUNDLED_SKILLS_DIR",
|
||||
"OPENCLAW_CACHE_TRACE",
|
||||
"OPENCLAW_CACHE_TRACE_FILE",
|
||||
"OPENCLAW_CACHE_TRACE_MESSAGES",
|
||||
"OPENCLAW_CACHE_TRACE_PROMPT",
|
||||
"OPENCLAW_CACHE_TRACE_SYSTEM",
|
||||
"OPENCLAW_CONFIG_PATH",
|
||||
"OPENCLAW_GATEWAY_PASSWORD",
|
||||
"OPENCLAW_GATEWAY_PORT",
|
||||
"OPENCLAW_GATEWAY_SECRET",
|
||||
"OPENCLAW_GATEWAY_TOKEN",
|
||||
"OPENCLAW_GATEWAY_URL",
|
||||
"OPENCLAW_HOME",
|
||||
"OPENCLAW_LIVE_ANTHROPIC_KEY",
|
||||
"OPENCLAW_LIVE_ANTHROPIC_KEYS",
|
||||
"OPENCLAW_LIVE_GEMINI_KEY",
|
||||
"OPENCLAW_LIVE_OPENAI_KEY",
|
||||
"OPENCLAW_MPM_CATALOG_PATHS",
|
||||
"OPENCLAW_NODE_EXEC_FALLBACK",
|
||||
"OPENCLAW_NODE_EXEC_HOST",
|
||||
"OPENCLAW_OAUTH_DIR",
|
||||
"OPENCLAW_PINNED_PYTHON",
|
||||
"OPENCLAW_PINNED_WRITE_PYTHON",
|
||||
"OPENCLAW_PLUGIN_CATALOG_PATHS",
|
||||
"OPENCLAW_PROFILE",
|
||||
"OPENCLAW_RAW_STREAM",
|
||||
"OPENCLAW_RAW_STREAM_PATH",
|
||||
"OPENCLAW_SHOW_SECRETS",
|
||||
"OPENCLAW_STATE_DIR",
|
||||
"OPENCLAW_TEST_TAILSCALE_BINARY",
|
||||
"OPENAI_API_KEY",
|
||||
"OPENAI_API_KEYS",
|
||||
"PI_CODING_AGENT_DIR",
|
||||
"PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH",
|
||||
"UV_PYTHON",
|
||||
]);
|
||||
|
||||
// Block endpoint redirection for any service without overfitting per-provider names.
|
||||
const BLOCKED_WORKSPACE_DOTENV_SUFFIXES = ["_BASE_URL"];
|
||||
const BLOCKED_WORKSPACE_DOTENV_PREFIXES = ["ANTHROPIC_API_KEY_", "OPENAI_API_KEY_"];
|
||||
const BLOCKED_WORKSPACE_DOTENV_PREFIXES = [
|
||||
"ANTHROPIC_API_KEY_",
|
||||
"CLAWHUB_",
|
||||
"OPENAI_API_KEY_",
|
||||
"OPENCLAW_CLAWHUB_",
|
||||
"OPENCLAW_DISABLE_",
|
||||
"OPENCLAW_SKIP_",
|
||||
"OPENCLAW_UPDATE_",
|
||||
];
|
||||
|
||||
function shouldBlockWorkspaceRuntimeDotEnvKey(key: string): boolean {
|
||||
return isDangerousHostEnvVarName(key) || isDangerousHostEnvOverrideVarName(key);
|
||||
|
||||
Reference in New Issue
Block a user