fix: harden state dir permissions during onboard

This commit is contained in:
Peter Steinberger 2026-03-12 00:26:28 +00:00
parent ebed3bbde1
commit 7e3787517f
2 changed files with 54 additions and 0 deletions

View File

@ -164,6 +164,32 @@ function hashConfigRaw(raw: string | null): string {
.digest("hex");
}
async function tightenStateDirPermissionsIfNeeded(params: {
configPath: string;
env: NodeJS.ProcessEnv;
homedir: () => string;
fsModule: typeof fs;
}): Promise<void> {
if (process.platform === "win32") {
return;
}
const stateDir = resolveStateDir(params.env, params.homedir);
const configDir = path.dirname(params.configPath);
if (path.resolve(configDir) !== path.resolve(stateDir)) {
return;
}
try {
const stat = await params.fsModule.promises.stat(configDir);
const mode = stat.mode & 0o777;
if ((mode & 0o077) === 0) {
return;
}
await params.fsModule.promises.chmod(configDir, 0o700);
} catch {
// Best-effort hardening only; callers still need the config write to proceed.
}
}
function formatConfigValidationFailure(pathLabel: string, issueMessage: string): string {
const match = issueMessage.match(OPEN_DM_POLICY_ALLOW_FROM_RE);
const policyPath = match?.groups?.policyPath?.trim();
@ -1136,6 +1162,12 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
const dir = path.dirname(configPath);
await deps.fs.promises.mkdir(dir, { recursive: true, mode: 0o700 });
await tightenStateDirPermissionsIfNeeded({
configPath,
env: deps.env,
homedir: deps.homedir,
fsModule: deps.fs,
});
const outputConfigBase =
envRefMap && changedPaths
? (restoreEnvRefsFromMap(cfgToWrite, "", envRefMap, changedPaths) as OpenClawConfig)

View File

@ -142,6 +142,28 @@ describe("config io write", () => {
});
});
it.runIf(process.platform !== "win32")(
"tightens world-writable state dir when writing the default config",
async () => {
await withSuiteHome(async (home) => {
const stateDir = path.join(home, ".openclaw");
await fs.mkdir(stateDir, { recursive: true, mode: 0o777 });
await fs.chmod(stateDir, 0o777);
const io = createConfigIO({
env: {} as NodeJS.ProcessEnv,
homedir: () => home,
logger: silentLogger,
});
await io.writeConfigFile({ gateway: { mode: "local" } });
const stat = await fs.stat(stateDir);
expect(stat.mode & 0o777).toBe(0o700);
});
},
);
it('shows actionable guidance for dmPolicy="open" without wildcard allowFrom', async () => {
await withSuiteHome(async (home) => {
const io = createConfigIO({