diff --git a/CHANGELOG.md b/CHANGELOG.md index 41de6def2..da7d7746c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,33 @@ +## v2023.12.6 +This is a security release. Upgrading is therefore strongly recommended. + +### Backend +- When fetching activities, their identifiers are now validated much more strictly +- Drive files now have the `X-Content-Type-Options` header set to `nosniff` +- The queue dashboard path is now validated more strictly +- The AP object resolver logic was improved to better handle edge cases +- Poll notifications are no longer generated for muted notes + +### Frontend +- Remote (cross-origin) videos now plays properly +- Emoji reactions on the landing page timeline preview are now aligned properly + +### Mastodon client API +- The default reaction is now returned with /v1/instance + +### Miscellaneous +- The podman documentation was improved +- The example nginx config now has gzip enabled +- The Dockerfile now references the required dependencies for decoding AVIF images +- The installation requirements now mention postgresql-contrib +- Various translation updates + +### Attribution +### Attribution +This release was made possible by project contributors: CookiLover311, Crimekillz, Jegler, Laura Hausmann, Lilian, Norm, Salif Mehmed, jeder, konkonkon, naskya & 老周部落 + +Furthermore, I want to give special thanks to Oneric for the extraordinarily detailed security disclosure. + ## v2023.12.5 This is a followup security release. Upgrading is recommended. diff --git a/package.json b/package.json index e7c695e80..a76363652 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iceshrimp", - "version": "2023.12.5", + "version": "2023.12.6", "repository": { "type": "git", "url": "https://iceshrimp.dev/iceshrimp/iceshrimp.git" diff --git a/packages/backend/src/remote/activitypub/resolver.ts b/packages/backend/src/remote/activitypub/resolver.ts index dd56f8012..5b9610098 100644 --- a/packages/backend/src/remote/activitypub/resolver.ts +++ b/packages/backend/src/remote/activitypub/resolver.ts @@ -123,28 +123,42 @@ export default class Resolver { apLogger.debug("Getting object from remote, authenticated as user:"); apLogger.debug(JSON.stringify(this.user, null, 2)); - const res = ( + const {res, object} = await this.doFetch(value); + + if (object.id == null) throw new Error("Object has no ID"); + if (res.finalUrl === object.id) return object; + + if (new URL(res.finalUrl).host !== new URL(object.id).host) + throw new Error("Object ID host doesn't match final url host"); + + const {res: finalRes, object: finalObject} = await this.doFetch(object.id); + + if (finalRes.finalUrl !== finalObject.id) + throw new Error("Object ID still doesn't match final URL after second fetch attempt") + + return finalObject; + } + + private async doFetch(uri: string) { + let res = ( this.user - ? await signedGet(value, this.user) - : await getJsonActivity(value) + ? await signedGet(uri, this.user) + : await getJsonActivity(uri) ); - const object = res.content as IObject; + let object = res.content as IObject; if ( object == null || (Array.isArray(object["@context"]) ? !(object["@context"] as unknown[]).includes( - "https://www.w3.org/ns/activitystreams", - ) + "https://www.w3.org/ns/activitystreams", + ) : object["@context"] !== "https://www.w3.org/ns/activitystreams") ) { throw new Error("invalid response"); } - if (object.id != null && new URL(res.finalUrl).host != new URL(object.id).host) - throw new Error("Object ID host doesn't match final url host"); - - return object; + return {res, object}; } private resolveLocal(url: string): Promise { diff --git a/packages/backend/src/server/file/send-drive-file.ts b/packages/backend/src/server/file/send-drive-file.ts index 087736902..2482f0ce6 100644 --- a/packages/backend/src/server/file/send-drive-file.ts +++ b/packages/backend/src/server/file/send-drive-file.ts @@ -49,6 +49,8 @@ export default async function (ctx: Koa.Context) { return; } + ctx.set("X-Content-Type-Options", "nosniff"); + const isThumbnail = file.thumbnailAccessKey === key; const isWebpublic = file.webpublicAccessKey === key;