mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-30 08:51:40 +00:00
fix: use 0o644 for inbound media files to allow sandbox read access (#17943)
* fix: use 0o644 for inbound media files to allow sandbox read access Inbound media files were saved with 0o600 permissions, making them unreadable from Docker sandbox containers running as different users. Change to 0o644 (world-readable) so sandboxed agents can access downloaded attachments. Fixes #17941 Co-Authored-By: Claude <noreply@anthropic.com> * test(media): assert URL-sourced inbound files use 0o644 * test(media): make redirect file-mode assertion platform-aware * docs(media): clarify 0o644 is for sandbox UID compatibility --------- Co-authored-by: zerone0x <zerone0x@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
@@ -89,6 +89,9 @@ describe("media store redirects", () => {
|
||||
expect(saved.contentType).toBe("text/plain");
|
||||
expect(path.extname(saved.path)).toBe(".txt");
|
||||
expect(await fs.readFile(saved.path, "utf8")).toBe("redirected");
|
||||
const stat = await fs.stat(saved.path);
|
||||
const expectedMode = process.platform === "win32" ? 0o666 : 0o644;
|
||||
expect(stat.mode & 0o777).toBe(expectedMode);
|
||||
});
|
||||
|
||||
it("fails when redirect response omits location header", async () => {
|
||||
|
||||
@@ -14,6 +14,9 @@ const resolveMediaDir = () => path.join(resolveConfigDir(), "media");
|
||||
export const MEDIA_MAX_BYTES = 5 * 1024 * 1024; // 5MB default
|
||||
const MAX_BYTES = MEDIA_MAX_BYTES;
|
||||
const DEFAULT_TTL_MS = 2 * 60 * 1000; // 2 minutes
|
||||
// Files are intentionally readable by non-owner UIDs so Docker sandbox containers can access
|
||||
// inbound media. The containing state/media directories remain 0o700, which is the trust boundary.
|
||||
const MEDIA_FILE_MODE = 0o644;
|
||||
type RequestImpl = typeof httpRequest;
|
||||
type ResolvePinnedHostnameImpl = typeof resolvePinnedHostname;
|
||||
|
||||
@@ -170,7 +173,7 @@ async function downloadToFile(
|
||||
let total = 0;
|
||||
const sniffChunks: Buffer[] = [];
|
||||
let sniffLen = 0;
|
||||
const out = createWriteStream(dest, { mode: 0o600 });
|
||||
const out = createWriteStream(dest, { mode: MEDIA_FILE_MODE });
|
||||
res.on("data", (chunk) => {
|
||||
total += chunk.length;
|
||||
if (sniffLen < 16384) {
|
||||
@@ -284,7 +287,7 @@ export async function saveMediaSource(
|
||||
const ext = extensionForMime(mime) ?? path.extname(source);
|
||||
const id = ext ? `${baseId}${ext}` : baseId;
|
||||
const dest = path.join(dir, id);
|
||||
await fs.writeFile(dest, buffer, { mode: 0o600 });
|
||||
await fs.writeFile(dest, buffer, { mode: MEDIA_FILE_MODE });
|
||||
return { id, path: dest, size: stat.size, contentType: mime };
|
||||
} catch (err) {
|
||||
if (err instanceof SafeOpenError) {
|
||||
@@ -323,6 +326,6 @@ export async function saveMediaBuffer(
|
||||
}
|
||||
|
||||
const dest = path.join(dir, id);
|
||||
await fs.writeFile(dest, buffer, { mode: 0o600 });
|
||||
await fs.writeFile(dest, buffer, { mode: MEDIA_FILE_MODE });
|
||||
return { id, path: dest, size: buffer.byteLength, contentType: mime };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user