Skip to content

Commit c085a77

Browse files
chenxiaoyu209claude
andcommitted
fix(clawhub): prevent temp workspace from chmod'ing shared /tmp to 0700
When createTempDownloadTarget is called with tmpDir: os.tmpdir(), the @openclaw/fs-safe tempWorkspace helper calls ensurePrivateDirectory which unconditionally chmods the root directory to 0700. Passing /tmp as rootDir causes the shared system /tmp to be chmod'd, breaking all other services that depend on world-writable /tmp (MySQL, PHP-FPM, etc.). - Remove 5 explicit tmpDir: os.tmpdir() overrides from clawhub.ts download functions, letting createTempDownloadTarget use its safe default resolvePreferredOpenClawTmpDir() which resolves to the OpenClaw-owned /tmp/openclaw directory. - Change withTempDir default rootDir from os.tmpdir() to resolvePreferredOpenClawTmpDir() so all callers that don't pass an explicit rootDir are safe. - Add regression test verifying temp dirs are created under the OpenClaw-owned temp root, not directly under /tmp. Fixes #101224 Co-Authored-By: Claude <[email protected]>
1 parent c64fa18 commit c085a77

3 files changed

Lines changed: 18 additions & 7 deletions

File tree

src/infra/clawhub.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1385,7 +1385,6 @@ export async function downloadClawHubPackageArchive(params: {
13851385
const target = await createTempDownloadTarget({
13861386
prefix: "openclaw-clawhub-clawpack",
13871387
fileName: npmTarballName,
1388-
tmpDir: os.tmpdir(),
13891388
});
13901389
await fs.writeFile(target.path, bytes);
13911390
return {
@@ -1428,7 +1427,6 @@ export async function downloadClawHubPackageArchive(params: {
14281427
const target = await createTempDownloadTarget({
14291428
prefix: "openclaw-clawhub-package",
14301429
fileName: `${params.name}.zip`,
1431-
tmpDir: os.tmpdir(),
14321430
});
14331431
await fs.writeFile(target.path, bytes);
14341432
return {
@@ -1475,7 +1473,6 @@ export async function downloadClawHubSkillArchive(params: {
14751473
const target = await createTempDownloadTarget({
14761474
prefix: "openclaw-clawhub-skill",
14771475
fileName: `${params.slug}.zip`,
1478-
tmpDir: os.tmpdir(),
14791476
});
14801477
await fs.writeFile(target.path, bytes);
14811478
return {
@@ -1518,7 +1515,6 @@ export async function downloadClawHubSkillArchiveUrl(params: {
15181515
const target = await createTempDownloadTarget({
15191516
prefix: "openclaw-clawhub-skill",
15201517
fileName: "skill.zip",
1521-
tmpDir: os.tmpdir(),
15221518
});
15231519
await fs.writeFile(target.path, bytes);
15241520
return {
@@ -1555,7 +1551,6 @@ export async function downloadClawHubGitHubSkillArchive(params: {
15551551
const target = await createTempDownloadTarget({
15561552
prefix: "openclaw-clawhub-github-skill",
15571553
fileName: `${params.commit}.zip`,
1558-
tmpDir: os.tmpdir(),
15591554
});
15601555
await fs.writeFile(target.path, bytes);
15611556
return {

src/infra/install-source-utils.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
resolveArchiveSourcePath,
99
withTempDir,
1010
} from "./install-source-utils.js";
11+
import { resolvePreferredOpenClawTmpDir } from "./tmp-openclaw-dir.js";
1112

1213
const execFileSyncMock = vi.hoisted(() => vi.fn(() => "/tmp/openclaw-test-global-npmrc\n"));
1314
const runCommandWithTimeoutMock = vi.fn();
@@ -145,6 +146,21 @@ describe("withTempDir", () => {
145146
expect(value).toBe("done");
146147
await expectPathMissing(observedDir);
147148
});
149+
150+
it("uses OpenClaw-owned temp root instead of shared /tmp to avoid chmod of system directories", async () => {
151+
const preferredRoot = resolvePreferredOpenClawTmpDir();
152+
let observedDir = "";
153+
154+
await withTempDir("openclaw-install-source-utils-", async (tmpDir) => {
155+
observedDir = tmpDir;
156+
await fs.writeFile(path.join(tmpDir, "ok"), "ok", "utf-8");
157+
});
158+
159+
// The temp dir must be under the OpenClaw-owned root, not directly under /tmp.
160+
// This prevents the underlying temp workspace from chmod'ing the shared /tmp.
161+
expect(observedDir.startsWith(preferredRoot)).toBe(true);
162+
await expectPathMissing(observedDir);
163+
});
148164
});
149165

150166
describe("resolveArchiveSourcePath", () => {

src/infra/install-source-utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Resolves and packages install sources for plugin installs.
22
import fs from "node:fs/promises";
3-
import os from "node:os";
43
import path from "node:path";
54
import { isRecord } from "@openclaw/normalization-core/record-coerce";
65
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
@@ -11,6 +10,7 @@ import { resolveArchiveKind } from "./archive.js";
1110
import { pathExists } from "./fs-safe.js";
1211
import { applyNpmFreshnessBypassEnv, type NpmProjectInstallEnvOptions } from "./npm-install-env.js";
1312
import { withTempWorkspace } from "./private-temp-workspace.js";
13+
import { resolvePreferredOpenClawTmpDir } from "./tmp-openclaw-dir.js";
1414

1515
/** Metadata npm reports when resolving a registry spec or packed archive. */
1616
export type NpmSpecResolution = {
@@ -141,7 +141,7 @@ export async function withTempDir<T>(
141141
fn: (tmpDir: string) => Promise<T>,
142142
options?: { rootDir?: string },
143143
): Promise<T> {
144-
const rootDir = options?.rootDir ?? os.tmpdir();
144+
const rootDir = options?.rootDir ?? resolvePreferredOpenClawTmpDir();
145145
return await withTempWorkspace({ rootDir, prefix }, async (tmp) => fn(tmp.dir));
146146
}
147147

0 commit comments

Comments
 (0)