1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-04-30 19:15:47 +02:00

Compare commits

...

2 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
a69400289d fix: prevent immutable cache headers on file access errors
When a file exists in the database as storedInternal but is missing from
disk, the error response was incorrectly getting Cache-Control:
max-age=31536000, immutable. This was because the handler set immutable
cache headers before attempting to stream the file, and by the time the
stream encountered the ENOENT error, HTTP headers were already committed.

Fix: add fs.promises.access() check in FileServerFileResolver for stored
original files before returning the path. If the file is not accessible
on disk, the access check throws, propagating to errorHandler which
correctly sets Cache-Control: max-age=300.

For thumbnail/webpublic stored files, detectType() already performed this
check implicitly via fs.stat(). Only original files were affected.

This fixes both the /files/:key and /proxy/:url* endpoints since both
call resolveFileByAccessKey for local files.

Agent-Logs-Url: https://github.com/misskey-dev/misskey/sessions/5dd1c781-b9ad-43cf-b107-79c502d2e602

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
2026-04-07 13:31:53 +00:00
copilot-swe-agent[bot]
961c39e65f Initial plan 2026-04-07 13:13:03 +00:00
2 changed files with 47 additions and 0 deletions

View File

@@ -112,6 +112,14 @@ export class FileServerFileResolver {
};
}
// Verify the file actually exists on disk before returning.
// Unlike the thumbnail/webpublic cases above where detectType() implicitly
// checks this, the original file path is returned without an existence check.
// Without this check, the handler would set immutable cache headers before
// discovering the file is missing, causing CDN caches to cache the error
// response for 1 year (max-age=31536000, immutable) instead of max-age=300.
await fs.promises.access(path);
return {
kind: 'stored',
fileRole: 'original',

View File

@@ -479,6 +479,24 @@ describe('FileServerService', () => {
expect(res.headers['cache-control']).toBe('max-age=86400');
});
test('GET /files/:key DBに存在するがストレージ上に存在しないファイルにアクセスしたときエラーのキャッシュをimmutableにしない', async () => {
const accessKey = randomString();
// Insert into DB as storedInternal but do NOT write the file to disk
await insertDriveFile({
accessKey,
storedInternal: true,
isLink: false,
});
const res = await fastify.inject({
method: 'GET',
url: `/files/${accessKey}`,
});
expect(res.statusCode).toBe(500);
expect(res.headers['cache-control']).toBe('max-age=300');
});
test('GET /files/:key 外部リンクを取得して配信する', async () => {
const accessKey = randomString();
await insertDriveFile({
@@ -766,5 +784,26 @@ describe('FileServerService', () => {
expect(res.headers['cache-control']).toBe('max-age=31536000, immutable');
expect(res.headers['content-disposition'] ?? '').toContain('dummy.png');
});
test('GET /proxy/:url* DBに存在するがストレージ上に存在しないファイルにアクセスしたときエラーのキャッシュをimmutableにしない', async () => {
const accessKey = randomString();
// Insert into DB as storedInternal but do NOT write the file to disk
await insertDriveFile({
accessKey,
storedInternal: true,
isLink: false,
});
const res = await fastify.inject({
method: 'GET',
url: `/proxy/any?url=${encodeURIComponent(`${config.url}/files/${accessKey}`)}&origin=1`,
headers: {
'user-agent': 'Mozilla/5.0',
},
});
expect(res.statusCode).toBe(500);
expect(res.headers['cache-control']).toBe('max-age=300');
});
});
});