fix: Non-members cannot see public ACL attachments (#11326)

closes #11324
This commit is contained in:
Tom Moor
2026-01-31 15:53:03 -05:00
committed by GitHub
parent 446a0e1071
commit 03493ea3dc
2 changed files with 75 additions and 1 deletions
+69
View File
@@ -340,6 +340,75 @@ describe("#files.get", () => {
expect(res.headers.get("Content-Disposition")).toEqual("attachment");
});
it("should succeed with status 200 ok when public-read avatar in uploads bucket is requested by non-owner", async () => {
const owner = await buildUser();
const otherUser = await buildUser({ teamId: owner.teamId });
const key = AttachmentHelper.getKey({
id: randomUUID(),
name: "avatar.jpg",
userId: owner.id,
});
await buildAttachment({
key,
teamId: owner.teamId,
userId: owner.id,
contentType: "image/jpg",
acl: "public-read",
});
ensureDirSync(
path.dirname(path.join(env.FILE_STORAGE_LOCAL_ROOT_DIR, key))
);
copyFileSync(
path.resolve(__dirname, "..", "test", "fixtures", "avatar.jpg"),
path.join(env.FILE_STORAGE_LOCAL_ROOT_DIR, key)
);
// Non-owner user should be able to access public-read attachment
const res = await server.get(`/api/files.get?key=${key}`, {
headers: {
Authorization: `Bearer ${otherUser.getJwtToken()}`,
},
});
expect(res.status).toEqual(200);
expect(res.headers.get("Content-Type")).toEqual("image/jpg");
});
it("should fail with status 403 when private attachment in uploads bucket is requested by non-owner", async () => {
const owner = await buildUser();
const otherUser = await buildUser({ teamId: owner.teamId });
const key = AttachmentHelper.getKey({
id: randomUUID(),
name: "document.pdf",
userId: owner.id,
});
await buildAttachment({
key,
teamId: owner.teamId,
userId: owner.id,
contentType: "application/pdf",
acl: "private",
});
ensureDirSync(
path.dirname(path.join(env.FILE_STORAGE_LOCAL_ROOT_DIR, key))
);
copyFileSync(
path.resolve(__dirname, "..", "test", "fixtures", "avatar.jpg"),
path.join(env.FILE_STORAGE_LOCAL_ROOT_DIR, key)
);
// Non-owner user should NOT be able to access private attachment
const res = await server.get(`/api/files.get?key=${key}`, {
headers: {
Authorization: `Bearer ${otherUser.getJwtToken()}`,
},
});
expect(res.status).toEqual(403);
});
it("should succeed with status 200 ok when exported file is requested using signature", async () => {
const user = await buildUser();
const fileName = "export-markdown.zip";
+6 -1
View File
@@ -77,10 +77,15 @@ router.get(
const forceDownload = !!ctx.input.query.download;
const isSignedRequest = !!ctx.input.query.sig;
const { isPublicBucket, fileName } = AttachmentHelper.parseKey(key);
const skipAuthorize = isPublicBucket || isSignedRequest;
const cacheHeader = "max-age=604800, immutable";
const attachment = await Attachment.findByKey(key);
// Skip authorization for public bucket, signed requests, or public-read ACL attachments
const skipAuthorize =
isPublicBucket ||
isSignedRequest ||
(attachment && !attachment.isPrivate);
if (!skipAuthorize) {
if (!attachment && !!ctx.input.query.key) {
throw NotFoundError();