diff --git a/CHANGELOG.md b/CHANGELOG.md index 9034bad4d54..a344f963ba5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -207,6 +207,7 @@ Docs: https://docs.openclaw.ai - Discord/config types: add missing `autoArchiveDuration` to `DiscordGuildChannelConfig` so TypeScript config definitions match the existing schema and runtime support. (#43427) Thanks @davidguttman. - Docs/IRC: fix five `json55` code-fence typos in the IRC channel examples so Mintlify applies JSON5 syntax highlighting correctly. (#50842) Thanks @Hollychou924. - Discord/commands: trim overlong slash-command descriptions to Discord's 100-character limit and map rejected deploy indexes from Discord validation payloads back to command names/descriptions, so deploys stop failing on long descriptions and startup logs identify the rejected commands. (#54118) thanks @huntharo +- Media/store: enforce the intended media file mode after writes and redirect downloads so restrictive umasks do not silently narrow saved media permissions. ## 2026.3.23 diff --git a/src/media/store.ts b/src/media/store.ts index 32acd951d32..7675f56095a 100644 --- a/src/media/store.ts +++ b/src/media/store.ts @@ -229,7 +229,8 @@ async function downloadToFile( } }); pipeline(res, out) - .then(() => { + .then(async () => { + await enforceMediaFileMode(dest); const sniffBuffer = Buffer.concat(sniffChunks, Math.min(sniffLen, 16384)); const rawHeader = res.headers["content-type"]; const headerMime = Array.isArray(rawHeader) ? rawHeader[0] : rawHeader; @@ -285,15 +286,20 @@ function buildSavedMediaResult(params: { }; } +async function enforceMediaFileMode(filePath: string): Promise { + await fs.chmod(filePath, MEDIA_FILE_MODE); +} + async function writeSavedMediaBuffer(params: { dir: string; id: string; buffer: Buffer; }): Promise { const dest = path.join(params.dir, params.id); - await retryAfterRecreatingDir(params.dir, () => - fs.writeFile(dest, params.buffer, { mode: MEDIA_FILE_MODE }), - ); + await retryAfterRecreatingDir(params.dir, async () => { + await fs.writeFile(dest, params.buffer, { mode: MEDIA_FILE_MODE }); + await enforceMediaFileMode(dest); + }); return dest; } @@ -366,6 +372,7 @@ export async function saveMediaSource( const id = buildSavedMediaId({ baseId, ext }); const finalDest = path.join(dir, id); await fs.rename(tempDest, finalDest); + await enforceMediaFileMode(finalDest); return buildSavedMediaResult({ dir, id, size, contentType: mime }); } // local path