High severity8.4NVD Advisory· Published Mar 29, 2026· Updated Mar 31, 2026
CVE-2026-33572
CVE-2026-33572
Description
OpenClaw before 2026.2.17 creates session transcript JSONL files with overly broad default permissions, allowing local users to read transcript contents. Attackers with local access can read transcript files to extract sensitive information including secrets from tool output.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
openclawnpm | < 2026.2.17 | 2026.2.17 |
Affected products
1Patches
1095d52209965fix(security): create session transcript files with 0o600 permissions (#18066)
6 files changed · +32 −2
CHANGELOG.md+1 −0 modified@@ -18,6 +18,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Security: replace deprecated SHA-1 sandbox configuration hashing with SHA-256 for deterministic sandbox cache identity and recreation checks. Thanks @kexinoh. +- Security/Sessions: create new session transcript JSONL files with user-only (`0o600`) permissions and extend `openclaw security audit --fix` to remediate existing transcript file permissions. - Security/Logging: redact Telegram bot tokens from error messages and uncaught stack traces to prevent accidental secret leakage into logs. Thanks @aether-ai-agent. - Sandbox/Security: block dangerous sandbox Docker config (bind mounts, host networking, unconfined seccomp/apparmor) to prevent container escape via config injection. Thanks @aether-ai-agent. - Sandbox: preserve array order in config hashing so order-sensitive Docker/browser settings trigger container recreation correctly. Thanks @kexinoh.
src/config/sessions/sessions.test.ts+4 −0 modified@@ -183,6 +183,10 @@ describe("appendAssistantMessageToSessionTranscript", () => { expect(result.ok).toBe(true); if (result.ok) { expect(fs.existsSync(result.sessionFile)).toBe(true); + const sessionFileMode = fs.statSync(result.sessionFile).mode & 0o777; + if (process.platform !== "win32") { + expect(sessionFileMode).toBe(0o600); + } const lines = fs.readFileSync(result.sessionFile, "utf-8").trim().split("\n"); expect(lines.length).toBe(2);
src/config/sessions/transcript.ts+4 −1 modified@@ -72,7 +72,10 @@ async function ensureSessionHeader(params: { timestamp: new Date().toISOString(), cwd: process.cwd(), }; - await fs.promises.writeFile(params.sessionFile, `${JSON.stringify(header)}\n`, "utf-8"); + await fs.promises.writeFile(params.sessionFile, `${JSON.stringify(header)}\n`, { + encoding: "utf-8", + mode: 0o600, + }); } export async function appendAssistantMessageToSessionTranscript(params: {
src/gateway/server-methods/chat.ts+4 −1 modified@@ -119,7 +119,10 @@ function ensureTranscriptFile(params: { transcriptPath: string; sessionId: strin timestamp: new Date().toISOString(), cwd: process.cwd(), }; - fs.writeFileSync(params.transcriptPath, `${JSON.stringify(header)}\n`, "utf-8"); + fs.writeFileSync(params.transcriptPath, `${JSON.stringify(header)}\n`, { + encoding: "utf-8", + mode: 0o600, + }); return { ok: true }; } catch (err) { return { ok: false, error: err instanceof Error ? err.message : String(err) };
src/security/fix.test.ts+4 −0 modified@@ -255,6 +255,9 @@ describe("security fix", () => { const sessionsStorePath = path.join(sessionsDir, "sessions.json"); await fs.writeFile(sessionsStorePath, "{}\n", "utf-8"); await fs.chmod(sessionsStorePath, 0o644); + const transcriptPath = path.join(sessionsDir, "sess-main.jsonl"); + await fs.writeFile(transcriptPath, '{"type":"session"}\n', "utf-8"); + await fs.chmod(transcriptPath, 0o644); const env = { ...process.env, @@ -269,6 +272,7 @@ describe("security fix", () => { expectPerms((await fs.stat(allowFromPath)).mode & 0o777, 0o600); expectPerms((await fs.stat(authProfilesPath)).mode & 0o777, 0o600); expectPerms((await fs.stat(sessionsStorePath)).mode & 0o777, 0o600); + expectPerms((await fs.stat(transcriptPath)).mode & 0o777, 0o600); expectPerms((await fs.stat(includePath)).mode & 0o777, 0o600); }); });
src/security/fix.ts+15 −0 modified@@ -366,6 +366,21 @@ async function chmodCredentialsAndAgentState(params: { const storePath = path.join(sessionsDir, "sessions.json"); // eslint-disable-next-line no-await-in-loop params.actions.push(await params.applyPerms({ path: storePath, mode: 0o600, require: "file" })); + + // Fix permissions on session transcript files (*.jsonl) + // eslint-disable-next-line no-await-in-loop + const sessionEntries = await fs.readdir(sessionsDir, { withFileTypes: true }).catch(() => []); + for (const entry of sessionEntries) { + if (!entry.isFile()) { + continue; + } + if (!entry.name.endsWith(".jsonl")) { + continue; + } + const p = path.join(sessionsDir, entry.name); + // eslint-disable-next-line no-await-in-loop + params.actions.push(await params.applyPerms({ path: p, mode: 0o600, require: "file" })); + } } }
Vulnerability mechanics
Synthesis attempt was rejected by the grounding validator. Re-run pending.
References
5- github.com/openclaw/openclaw/commit/095d522099653367e1b76fa5bb09d4ddf7c8a57cnvdPatchWEB
- github.com/advisories/GHSA-vr7j-g7jv-h5mpghsaADVISORY
- github.com/openclaw/openclaw/security/advisories/GHSA-vr7j-g7jv-h5mpnvdVendor AdvisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2026-33572ghsaADVISORY
- www.vulncheck.com/advisories/openclaw-insufficient-file-permissions-in-session-transcript-filesnvdThird Party AdvisoryWEB
News mentions
0No linked articles in our index yet.