Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .dev.vars.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ ANTHROPIC_API_KEY=sk-ant-...
# CF_AI_GATEWAY_ACCOUNT_ID=your-account-id
# CF_AI_GATEWAY_GATEWAY_ID=your-gateway-id
# CF_AI_GATEWAY_MODEL=workers-ai/@cf/meta/llama-3.3-70b-instruct-fp8-fast
# CF_AIG_TOKEN=your-ai-gateway-auth-token

# Legacy AI Gateway (still supported)
# AI_GATEWAY_API_KEY=your-key
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ These are the env vars passed TO the container (internal names):
| `CF_AI_GATEWAY_ACCOUNT_ID` | (env var) | Account ID for AI Gateway |
| `CF_AI_GATEWAY_GATEWAY_ID` | (env var) | Gateway ID for AI Gateway |
| `OPENCLAW_GATEWAY_TOKEN` | `--token` flag | Mapped from `MOLTBOT_GATEWAY_TOKEN` |
| `CF_AIG_TOKEN` | `cf-aig-authorization` header via fetch hook | AI Gateway auth token, injected for `gateway.ai.cloudflare.com` requests |
| `OPENCLAW_DEV_MODE` | `controlUi.allowInsecureAuth` | Mapped from `DEV_MODE` |
| `TELEGRAM_BOT_TOKEN` | `channels.telegram.botToken` | |
| `DISCORD_BOT_TOKEN` | `channels.discord.token` | |
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@ The previous `AI_GATEWAY_API_KEY` + `AI_GATEWAY_BASE_URL` approach is still supp
| `CF_AI_GATEWAY_ACCOUNT_ID` | Yes* | Your Cloudflare account ID (used to construct the gateway URL) |
| `CF_AI_GATEWAY_GATEWAY_ID` | Yes* | Your AI Gateway ID (used to construct the gateway URL) |
| `CF_AI_GATEWAY_MODEL` | No | Override default model: `provider/model-id` (e.g. `workers-ai/@cf/meta/llama-3.3-70b-instruct-fp8-fast`). See [Choosing a Model](#choosing-a-model) |
| `CF_AIG_TOKEN` | No | AI Gateway authentication token. Enables [Authenticated Gateway](https://developers.cloudflare.com/ai-gateway/configuration/authentication/) and BYOK. Create in CF Dashboard > AI > AI Gateway > Settings > Create token |
| `ANTHROPIC_API_KEY` | Yes* | Direct Anthropic API key (alternative to AI Gateway) |
| `ANTHROPIC_BASE_URL` | No | Direct Anthropic API base URL |
| `OPENAI_API_KEY` | No | OpenAI API key (alternative provider) |
Expand Down
17 changes: 17 additions & 0 deletions src/gateway/env.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,23 @@ describe('buildEnvVars', () => {
expect(result.CF_ACCOUNT_ID).toBe('acct-123');
});

it('passes CF_AIG_TOKEN to container', () => {
const env = createMockEnv({ CF_AIG_TOKEN: 'aig-token-abc123' });
const result = buildEnvVars(env);
expect(result.CF_AIG_TOKEN).toBe('aig-token-abc123');
});

it('does not include CF_AIG_TOKEN when not set', () => {
const env = createMockEnv();
const result = buildEnvVars(env);
expect(result.CF_AIG_TOKEN).toBeUndefined();
});

it('rejects CF_AIG_TOKEN with control characters', () => {
const env = createMockEnv({ CF_AIG_TOKEN: 'token\x00injected' });
expect(() => buildEnvVars(env)).toThrow('invalid control characters');
});

it('combines all env vars correctly', () => {
const env = createMockEnv({
ANTHROPIC_API_KEY: 'sk-key',
Expand Down
14 changes: 14 additions & 0 deletions src/gateway/env.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
import type { MoltbotEnv } from '../types';

/**
* Validation: Environment variables passed to the container should be
* validated for control characters that could cause injection or parsing
* issues. Use validateEnvValue() for any new sensitive token additions.
*/
function validateEnvValue(value: string): string {
// eslint-disable-next-line no-control-regex -- intentionally matching control characters
if (/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/.test(value)) {
throw new Error('Environment variable contains invalid control characters');
}
return value;
}

/**
* Build environment variables to pass to the OpenClaw container process
*
Expand Down Expand Up @@ -46,6 +59,7 @@ export function buildEnvVars(env: MoltbotEnv): Record<string, string> {
if (env.SLACK_BOT_TOKEN) envVars.SLACK_BOT_TOKEN = env.SLACK_BOT_TOKEN;
if (env.SLACK_APP_TOKEN) envVars.SLACK_APP_TOKEN = env.SLACK_APP_TOKEN;
if (env.CF_AI_GATEWAY_MODEL) envVars.CF_AI_GATEWAY_MODEL = env.CF_AI_GATEWAY_MODEL;
if (env.CF_AIG_TOKEN) envVars.CF_AIG_TOKEN = validateEnvValue(env.CF_AIG_TOKEN);
if (env.CF_ACCOUNT_ID) envVars.CF_ACCOUNT_ID = env.CF_ACCOUNT_ID;
if (env.CDP_SECRET) envVars.CDP_SECRET = env.CDP_SECRET;
if (env.WORKER_URL) envVars.WORKER_URL = env.WORKER_URL;
Expand Down
1 change: 1 addition & 0 deletions src/gateway/r2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export async function ensureRcloneConfig(sandbox: Sandbox, env: MoltbotEnv): Pro

await sandbox.exec(`mkdir -p $(dirname ${RCLONE_CONF_PATH})`);
await sandbox.writeFile(RCLONE_CONF_PATH, rcloneConfig);
await sandbox.exec(`chmod 600 ${RCLONE_CONF_PATH}`);
await sandbox.exec(`touch ${CONFIGURED_FLAG}`);

console.log('Rclone configured for R2 bucket:', getR2BucketName(env));
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface MoltbotEnv {
CF_AI_GATEWAY_GATEWAY_ID?: string; // AI Gateway ID
CLOUDFLARE_AI_GATEWAY_API_KEY?: string; // API key for requests through the gateway
CF_AI_GATEWAY_MODEL?: string; // Override model: "provider/model-id" e.g. "workers-ai/@cf/meta/llama-3.3-70b-instruct-fp8-fast"
CF_AIG_TOKEN?: string; // AI Gateway authentication token (cf-aig-authorization header)
// Legacy AI Gateway configuration (still supported for backward compat)
AI_GATEWAY_API_KEY?: string; // API key for the provider configured in AI Gateway
AI_GATEWAY_BASE_URL?: string; // AI Gateway URL (e.g., https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_id}/anthropic)
Expand Down
32 changes: 28 additions & 4 deletions start-openclaw.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ endpoint = https://${CF_ACCOUNT_ID}.r2.cloudflarestorage.com
acl = private
no_check_bucket = true
EOF
chmod 600 "$RCLONE_CONF"
touch /tmp/.rclone-configured
echo "Rclone configured for bucket: $R2_BUCKET"
}
Expand Down Expand Up @@ -159,10 +160,7 @@ config.gateway.port = 18789;
config.gateway.mode = 'local';
config.gateway.trustedProxies = ['10.1.0.0'];

if (process.env.OPENCLAW_GATEWAY_TOKEN) {
config.gateway.auth = config.gateway.auth || {};
config.gateway.auth.token = process.env.OPENCLAW_GATEWAY_TOKEN;
}
// Gateway token is passed via --token CLI flag (line 325), not written to config file.

if (process.env.OPENCLAW_DEV_MODE === 'true') {
config.gateway.controlUi = config.gateway.controlUi || {};
Expand Down Expand Up @@ -261,6 +259,7 @@ if (process.env.SLACK_BOT_TOKEN && process.env.SLACK_APP_TOKEN) {
}

fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
fs.chmodSync(configPath, 0o600);
console.log('Configuration patched successfully');
EOFPATCH

Expand Down Expand Up @@ -309,6 +308,31 @@ if r2_configured; then
echo "Background sync loop started (PID: $!)"
fi

# ============================================================
# AI GATEWAY AUTHENTICATION HOOK
# ============================================================
# OpenClaw's config schema does not support custom headers on providers.
# To send the cf-aig-authorization header required by AI Gateway
# Authenticated Gateway / BYOK, we install a Node.js --require hook
# that patches globalThis.fetch for requests to gateway.ai.cloudflare.com.
if [ -n "$CF_AIG_TOKEN" ]; then
cat > /tmp/aig-auth-hook.cjs << 'EOFHOOK'
const _fetch = globalThis.fetch;
globalThis.fetch = function (input, init) {
const url = typeof input === 'string' ? input : (input && input.url) || '';
if (process.env.CF_AIG_TOKEN && url.includes('gateway.ai.cloudflare.com')) {
init = Object.assign({}, init);
const h = new Headers(init.headers);
h.set('cf-aig-authorization', 'Bearer ' + process.env.CF_AIG_TOKEN);
init.headers = h;
}
return _fetch.call(this, input, init);
};
EOFHOOK
export NODE_OPTIONS="${NODE_OPTIONS:+$NODE_OPTIONS }--require /tmp/aig-auth-hook.cjs"
echo "AI Gateway authentication hook installed"
fi

# ============================================================
# START GATEWAY
# ============================================================
Expand Down
1 change: 1 addition & 0 deletions wrangler.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
// - Legacy AI Gateway (still supported):
// - AI_GATEWAY_API_KEY: API key
// - AI_GATEWAY_BASE_URL: Gateway endpoint URL
// - CF_AIG_TOKEN: AI Gateway authentication token (enables Authenticated Gateway + BYOK)
//
// Authentication:
// - MOLTBOT_GATEWAY_TOKEN: Token to protect gateway access
Expand Down