Skip to content

Commit dbfcef3

Browse files
eleqtrizitzsxsoftdrobison00
authored
fix(dotenv): block workspace runtime env vars (#62660)
* fix(dotenv): block workspace runtime env vars Co-authored-by: zsx <[email protected]> * docs(changelog): add workspace dotenv runtime-control entry * fix(dotenv): block workspace gateway port override --------- Co-authored-by: zsx <[email protected]> Co-authored-by: Devin Robison <[email protected]>
1 parent f470418 commit dbfcef3

3 files changed

Lines changed: 113 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Docs: https://docs.openclaw.ai
1818
- 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.
1919
- 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.
2020
- 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.
21+
- 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.
2122
- 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.
2223
- QQBot/media-tags: support HTML entity-encoded angle brackets (`&lt;`/`&gt;`) in media-tag regexes so entity-escaped `<qqimg>` tags from upstream are correctly parsed and normalized. (#60493) Thanks @ylc0919.
2324
- 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.

src/infra/dotenv.test.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,3 +613,80 @@ describe("loadCliDotEnv", () => {
613613
});
614614
});
615615
});
616+
617+
describe("workspace .env blocklist completeness", () => {
618+
it("blocks runtime-control variables from workspace .env", async () => {
619+
await withIsolatedEnvAndCwd(async () => {
620+
await withDotEnvFixture(async ({ cwdDir }) => {
621+
const runtimeControlKeys = [
622+
"OPENCLAW_UPDATE_PACKAGE_SPEC",
623+
"OPENCLAW_GATEWAY_PORT",
624+
"OPENCLAW_GATEWAY_URL",
625+
"OPENCLAW_CLAWHUB_URL",
626+
"CLAWHUB_URL",
627+
"OPENCLAW_CLAWHUB_TOKEN",
628+
"CLAWHUB_TOKEN",
629+
"CLAWHUB_AUTH_TOKEN",
630+
"CLAWHUB_CONFIG_PATH",
631+
"OPENCLAW_DISABLE_BUNDLED_PLUGINS",
632+
"OPENCLAW_ALLOW_INSECURE_PRIVATE_WS",
633+
"OPENCLAW_BROWSER_EXECUTABLE_PATH",
634+
"BROWSER_EXECUTABLE_PATH",
635+
"PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH",
636+
"OPENCLAW_SKIP_CHANNELS",
637+
"OPENCLAW_SKIP_PROVIDERS",
638+
"OPENCLAW_SKIP_CRON",
639+
"OPENCLAW_RAW_STREAM",
640+
"OPENCLAW_RAW_STREAM_PATH",
641+
"OPENCLAW_CACHE_TRACE",
642+
"OPENCLAW_CACHE_TRACE_FILE",
643+
"OPENCLAW_CACHE_TRACE_MESSAGES",
644+
"OPENCLAW_CACHE_TRACE_PROMPT",
645+
"OPENCLAW_CACHE_TRACE_SYSTEM",
646+
"OPENCLAW_SHOW_SECRETS",
647+
"OPENCLAW_PLUGIN_CATALOG_PATHS",
648+
"OPENCLAW_MPM_CATALOG_PATHS",
649+
"OPENCLAW_NODE_EXEC_HOST",
650+
"OPENCLAW_NODE_EXEC_FALLBACK",
651+
"OPENCLAW_ALLOW_PROJECT_LOCAL_BIN",
652+
];
653+
654+
await writeEnvFile(
655+
path.join(cwdDir, ".env"),
656+
`${runtimeControlKeys.map((key) => `${key}=INJECTED_${key}`).join("\n")}\n`,
657+
);
658+
659+
for (const key of runtimeControlKeys) {
660+
delete process.env[key];
661+
}
662+
663+
loadWorkspaceDotEnvFile(path.join(cwdDir, ".env"), { quiet: true });
664+
665+
for (const key of runtimeControlKeys) {
666+
expect(process.env[key], `${key} should be blocked by workspace .env`).toBeUndefined();
667+
}
668+
});
669+
});
670+
});
671+
672+
it("still allows user-defined non-control vars through workspace .env", async () => {
673+
await withIsolatedEnvAndCwd(async () => {
674+
await withDotEnvFixture(async ({ cwdDir }) => {
675+
await writeEnvFile(
676+
path.join(cwdDir, ".env"),
677+
"MY_APP_KEY=user-value\nGITHUB_TOKEN=ghp_test123\nDATABASE_URL_CUSTOM=pg://localhost\n",
678+
);
679+
680+
delete process.env.MY_APP_KEY;
681+
delete process.env.GITHUB_TOKEN;
682+
delete process.env.DATABASE_URL_CUSTOM;
683+
684+
loadWorkspaceDotEnvFile(path.join(cwdDir, ".env"), { quiet: true });
685+
686+
expect(process.env.MY_APP_KEY).toBe("user-value");
687+
expect(process.env.GITHUB_TOKEN).toBe("ghp_test123");
688+
expect(process.env.DATABASE_URL_CUSTOM).toBe("pg://localhost");
689+
});
690+
});
691+
});
692+
});

src/infra/dotenv.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,37 +14,69 @@ const BLOCKED_WORKSPACE_DOTENV_KEYS = new Set([
1414
"ALL_PROXY",
1515
"ANTHROPIC_API_KEY",
1616
"ANTHROPIC_OAUTH_TOKEN",
17+
"BROWSER_EXECUTABLE_PATH",
18+
"CLAWHUB_AUTH_TOKEN",
19+
"CLAWHUB_CONFIG_PATH",
20+
"CLAWHUB_TOKEN",
21+
"CLAWHUB_URL",
1722
"HTTP_PROXY",
1823
"HTTPS_PROXY",
1924
"NODE_TLS_REJECT_UNAUTHORIZED",
2025
"NO_PROXY",
26+
"OPENAI_API_KEY",
27+
"OPENAI_API_KEYS",
2128
"OPENCLAW_AGENT_DIR",
29+
"OPENCLAW_ALLOW_INSECURE_PRIVATE_WS",
30+
"OPENCLAW_ALLOW_PROJECT_LOCAL_BIN",
31+
"OPENCLAW_BROWSER_EXECUTABLE_PATH",
2232
"OPENCLAW_BUNDLED_HOOKS_DIR",
2333
"OPENCLAW_BUNDLED_PLUGINS_DIR",
2434
"OPENCLAW_BUNDLED_SKILLS_DIR",
35+
"OPENCLAW_CACHE_TRACE",
36+
"OPENCLAW_CACHE_TRACE_FILE",
37+
"OPENCLAW_CACHE_TRACE_MESSAGES",
38+
"OPENCLAW_CACHE_TRACE_PROMPT",
39+
"OPENCLAW_CACHE_TRACE_SYSTEM",
2540
"OPENCLAW_CONFIG_PATH",
2641
"OPENCLAW_GATEWAY_PASSWORD",
42+
"OPENCLAW_GATEWAY_PORT",
2743
"OPENCLAW_GATEWAY_SECRET",
2844
"OPENCLAW_GATEWAY_TOKEN",
45+
"OPENCLAW_GATEWAY_URL",
2946
"OPENCLAW_HOME",
3047
"OPENCLAW_LIVE_ANTHROPIC_KEY",
3148
"OPENCLAW_LIVE_ANTHROPIC_KEYS",
3249
"OPENCLAW_LIVE_GEMINI_KEY",
3350
"OPENCLAW_LIVE_OPENAI_KEY",
51+
"OPENCLAW_MPM_CATALOG_PATHS",
52+
"OPENCLAW_NODE_EXEC_FALLBACK",
53+
"OPENCLAW_NODE_EXEC_HOST",
3454
"OPENCLAW_OAUTH_DIR",
3555
"OPENCLAW_PINNED_PYTHON",
3656
"OPENCLAW_PINNED_WRITE_PYTHON",
57+
"OPENCLAW_PLUGIN_CATALOG_PATHS",
3758
"OPENCLAW_PROFILE",
59+
"OPENCLAW_RAW_STREAM",
60+
"OPENCLAW_RAW_STREAM_PATH",
61+
"OPENCLAW_SHOW_SECRETS",
3862
"OPENCLAW_STATE_DIR",
3963
"OPENCLAW_TEST_TAILSCALE_BINARY",
40-
"OPENAI_API_KEY",
41-
"OPENAI_API_KEYS",
4264
"PI_CODING_AGENT_DIR",
65+
"PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH",
4366
"UV_PYTHON",
4467
]);
4568

69+
// Block endpoint redirection for any service without overfitting per-provider names.
4670
const BLOCKED_WORKSPACE_DOTENV_SUFFIXES = ["_BASE_URL"];
47-
const BLOCKED_WORKSPACE_DOTENV_PREFIXES = ["ANTHROPIC_API_KEY_", "OPENAI_API_KEY_"];
71+
const BLOCKED_WORKSPACE_DOTENV_PREFIXES = [
72+
"ANTHROPIC_API_KEY_",
73+
"CLAWHUB_",
74+
"OPENAI_API_KEY_",
75+
"OPENCLAW_CLAWHUB_",
76+
"OPENCLAW_DISABLE_",
77+
"OPENCLAW_SKIP_",
78+
"OPENCLAW_UPDATE_",
79+
];
4880

4981
function shouldBlockWorkspaceRuntimeDotEnvKey(key: string): boolean {
5082
return isDangerousHostEnvVarName(key) || isDangerousHostEnvOverrideVarName(key);

0 commit comments

Comments
 (0)