Skip to content

Commit c687b3d

Browse files
authored
Merge pull request #18 from jgarzik/updates
Security Updates
2 parents 11757a6 + de05131 commit c687b3d

File tree

12 files changed

+506
-97
lines changed

12 files changed

+506
-97
lines changed

dashboard/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dashboard/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "botmaker-dashboard",
33
"private": true,
4-
"version": "1.0.1",
4+
"version": "1.0.2",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

dashboard/src/api.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,15 @@ export async function fetchProxyHealth(): Promise<ProxyHealthResponse> {
180180
}
181181

182182
export async function fetchDynamicModels(baseUrl: string, apiKey?: string): Promise<string[]> {
183-
let url = `${API_BASE}/models/discover?baseUrl=${encodeURIComponent(baseUrl)}`;
183+
const body: { baseUrl: string; apiKey?: string } = { baseUrl };
184184
if (apiKey) {
185-
url += `&apiKey=${encodeURIComponent(apiKey)}`;
185+
body.apiKey = apiKey;
186186
}
187-
const response = await fetch(url, { headers: getAuthHeaders() });
187+
const response = await fetch(`${API_BASE}/models/discover`, {
188+
method: 'POST',
189+
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
190+
body: JSON.stringify(body),
191+
});
188192
const data = await handleResponse<{ models: string[] }>(response);
189193
return data.models;
190194
}

package-lock.json

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "botmaker",
3-
"version": "1.0.1",
3+
"version": "1.0.2",
44
"description": "Web UI for creating and managing OpenClaw bots",
55
"author": "Jeff Garzik",
66
"license": "MIT",

proxy/package-lock.json

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

proxy/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "keyring-proxy",
3-
"version": "1.0.1",
3+
"version": "1.0.2",
44
"description": "HTTP forward proxy for BotMaker - validates bot tokens, injects API keys",
55
"type": "module",
66
"main": "dist/index.js",

proxy/src/routes/proxy.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,14 @@ export function registerProxyRoutes(
7373
keyId = keySelection.keyId;
7474
}
7575

76+
const FORWARDED_HEADERS = ['content-type', 'accept', 'user-agent'];
7677
const headers: Record<string, string> = {};
77-
for (const [key, value] of Object.entries(req.headers)) {
78+
for (const name of FORWARDED_HEADERS) {
79+
const value = req.headers[name];
7880
if (typeof value === 'string') {
79-
headers[key] = value;
81+
headers[name] = value;
8082
} else if (Array.isArray(value)) {
81-
headers[key] = value[0];
83+
headers[name] = value[0];
8284
}
8385
}
8486

src/bots/templates.ts

Lines changed: 25 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,31 @@
66

77
import { mkdirSync, writeFileSync, chmodSync, chownSync, rmSync } from 'node:fs';
88
import { join } from 'node:path';
9+
import { validateHostname } from '../secrets/manager.js';
910

1011
/**
1112
* Try to change file ownership, but gracefully skip if not permitted.
1213
* chown requires root privileges; in CI/dev environments we may not have them.
1314
*/
14-
function tryChown(path: string, uid: number, gid: number): void {
15+
function tryChown(path: string, uid: number, gid: number): boolean {
1516
try {
1617
chownSync(path, uid, gid);
18+
return true;
1719
} catch (err) {
18-
if ((err as NodeJS.ErrnoException).code === 'EPERM') {
19-
// Not running as root - skip chown (acceptable in dev/CI)
20-
return;
21-
}
20+
if ((err as NodeJS.ErrnoException).code === 'EPERM') return false;
2221
throw err;
2322
}
2423
}
2524

25+
const OPENCLAW_UID = 1000;
26+
const OPENCLAW_GID = 1000;
27+
28+
function setOwnership(path: string, mode: number): void {
29+
const owned = tryChown(path, OPENCLAW_UID, OPENCLAW_GID);
30+
// If chown failed, widen permissions so container UID 1000 can still write
31+
chmodSync(path, owned ? mode : mode | 0o022);
32+
}
33+
2634
export interface BotPersona {
2735
name: string;
2836
identity: string;
@@ -257,51 +265,25 @@ function generateIdentityMd(persona: BotPersona): string {
257265
export function createBotWorkspace(dataDir: string, config: BotWorkspaceConfig): void {
258266
const botDir = join(dataDir, 'bots', config.botHostname);
259267
const workspaceDir = join(botDir, 'workspace');
268+
const agentDir = join(botDir, 'agents', 'main', 'agent');
269+
const sessionsDir = join(botDir, 'agents', 'main', 'sessions');
270+
const sandboxDir = join(botDir, 'sandbox');
260271

261-
// Create directories with permissions for bot container (runs as uid 1000)
262-
mkdirSync(botDir, { recursive: true, mode: 0o777 });
263-
mkdirSync(workspaceDir, { recursive: true, mode: 0o777 });
264-
// Ensure parent dir has correct permissions (recursive: true doesn't set mode on existing dirs)
265-
chmodSync(botDir, 0o777);
266-
chmodSync(workspaceDir, 0o777);
272+
for (const dir of [botDir, workspaceDir, agentDir, sessionsDir, sandboxDir]) {
273+
mkdirSync(dir, { recursive: true, mode: 0o755 });
274+
setOwnership(dir, 0o755);
275+
}
267276

268-
// Write openclaw.json at root of bot directory (OPENCLAW_STATE_DIR)
269-
const openclawConfig = generateOpenclawConfig(config);
270277
const configPath = join(botDir, 'openclaw.json');
271-
writeFileSync(configPath, JSON.stringify(openclawConfig, null, 2));
272-
chmodSync(configPath, 0o666);
278+
writeFileSync(configPath, JSON.stringify(generateOpenclawConfig(config), null, 2));
279+
setOwnership(configPath, 0o644);
273280

274-
// Write only persona files — OpenClaw's ensureAgentWorkspace() will create
275-
// AGENTS.md, BOOTSTRAP.md, TOOLS.md, HEARTBEAT.md from its own templates
276-
// (using writeFileIfMissing / wx flag, so our files won't be overwritten).
277281
const soulPath = join(workspaceDir, 'SOUL.md');
278282
const identityPath = join(workspaceDir, 'IDENTITY.md');
279283
writeFileSync(soulPath, generateSoulMd(config.persona));
280284
writeFileSync(identityPath, generateIdentityMd(config.persona));
281-
chmodSync(soulPath, 0o666);
282-
chmodSync(identityPath, 0o666);
283-
284-
// OpenClaw runs as uid 1000 (node user), so we need to set ownership
285-
const OPENCLAW_UID = 1000;
286-
const OPENCLAW_GID = 1000;
287-
288-
const agentDir = join(botDir, 'agents', 'main', 'agent');
289-
mkdirSync(agentDir, { recursive: true, mode: 0o777 });
290-
chmodSync(agentDir, 0o777);
291-
tryChown(agentDir, OPENCLAW_UID, OPENCLAW_GID);
292-
293-
// Pre-create sessions directory for OpenClaw runtime use
294-
const sessionsDir = join(botDir, 'agents', 'main', 'sessions');
295-
mkdirSync(sessionsDir, { recursive: true, mode: 0o777 });
296-
chmodSync(sessionsDir, 0o777);
297-
tryChown(sessionsDir, OPENCLAW_UID, OPENCLAW_GID);
298-
299-
// Pre-create sandbox directory for OpenClaw code execution
300-
// OpenClaw hardcodes /app/workspace for sandbox operations
301-
const sandboxDir = join(botDir, 'sandbox');
302-
mkdirSync(sandboxDir, { recursive: true, mode: 0o777 });
303-
chmodSync(sandboxDir, 0o777);
304-
tryChown(sandboxDir, OPENCLAW_UID, OPENCLAW_GID);
285+
setOwnership(soulPath, 0o644);
286+
setOwnership(identityPath, 0o644);
305287
}
306288

307289
/**
@@ -323,6 +305,7 @@ export function getBotWorkspacePath(dataDir: string, hostname: string): string {
323305
* @param hostname - Bot hostname
324306
*/
325307
export function deleteBotWorkspace(dataDir: string, hostname: string): void {
308+
validateHostname(hostname);
326309
const botDir = join(dataDir, 'bots', hostname);
327310
rmSync(botDir, { recursive: true, force: true });
328311
}

0 commit comments

Comments
 (0)