VYPR
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.

PackageAffected versionsPatched versions
openclawnpm
< 2026.2.172026.2.17

Affected products

1
  • cpe:2.3:a:openclaw:openclaw:*:*:*:*:*:node.js:*:*
    Range: <2026.2.17

Patches

1
095d52209965

fix(security): create session transcript files with 0o600 permissions (#18066)

https://github.com/openclaw/openclawbrandonwiseFeb 16, 2026via ghsa
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

News mentions

0

No linked articles in our index yet.