Merge zotan's security patches from v2023.15.6

This commit is contained in:
Crimekillz 2024-03-30 21:47:52 +01:00
parent 78a27c0d75
commit 3526e8f891
4 changed files with 57 additions and 11 deletions

View File

@ -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 ## v2023.12.5
This is a followup security release. Upgrading is recommended. This is a followup security release. Upgrading is recommended.

View File

@ -1,6 +1,6 @@
{ {
"name": "iceshrimp", "name": "iceshrimp",
"version": "2023.12.5", "version": "2023.12.6",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://iceshrimp.dev/iceshrimp/iceshrimp.git" "url": "https://iceshrimp.dev/iceshrimp/iceshrimp.git"

View File

@ -123,28 +123,42 @@ export default class Resolver {
apLogger.debug("Getting object from remote, authenticated as user:"); apLogger.debug("Getting object from remote, authenticated as user:");
apLogger.debug(JSON.stringify(this.user, null, 2)); 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 this.user
? await signedGet(value, this.user) ? await signedGet(uri, this.user)
: await getJsonActivity(value) : await getJsonActivity(uri)
); );
const object = res.content as IObject; let object = res.content as IObject;
if ( if (
object == null || object == null ||
(Array.isArray(object["@context"]) (Array.isArray(object["@context"])
? !(object["@context"] as unknown[]).includes( ? !(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") : object["@context"] !== "https://www.w3.org/ns/activitystreams")
) { ) {
throw new Error("invalid response"); throw new Error("invalid response");
} }
if (object.id != null && new URL(res.finalUrl).host != new URL(object.id).host) return {res, object};
throw new Error("Object ID host doesn't match final url host");
return object;
} }
private resolveLocal(url: string): Promise<IObject> { private resolveLocal(url: string): Promise<IObject> {

View File

@ -49,6 +49,8 @@ export default async function (ctx: Koa.Context) {
return; return;
} }
ctx.set("X-Content-Type-Options", "nosniff");
const isThumbnail = file.thumbnailAccessKey === key; const isThumbnail = file.thumbnailAccessKey === key;
const isWebpublic = file.webpublicAccessKey === key; const isWebpublic = file.webpublicAccessKey === key;