From f38c5a98aaf01824dbfd75d1d5baab12a6486259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Tue, 20 Aug 2024 16:09:08 +0200 Subject: [PATCH] Content-Type and Content-Disposition usage in media repo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Travis Ralston Signed-off-by: Kévin Commaille --- .../client-server-api/modules/content_repo.md | 42 +++++++++ .../client-server/authed-content-repo.yaml | 78 ++++++++++++++-- data/api/client-server/content-repo.yaml | 88 +++++++++++++++++-- 3 files changed, 197 insertions(+), 11 deletions(-) diff --git a/content/client-server-api/modules/content_repo.md b/content/client-server-api/modules/content_repo.md index c9ac06d7..a48b325d 100644 --- a/content/client-server-api/modules/content_repo.md +++ b/content/client-server-api/modules/content_repo.md @@ -168,3 +168,45 @@ Homeservers have additional content-specific concerns: - Clients or remote homeservers may try to upload malicious files targeting vulnerabilities in either the homeserver thumbnailing or the client decoders. + +##### Serving inline content + +Clients with insecure configurations may be vulnerable to Cross-Site Scripting +attacks when served media with a `Content-Disposition` of `inline`. Clients +SHOULD NOT be hosted on the same domain as the media endpoints for the homeserver +to mitigate most of this risk. Servers SHOULD restrict `Content-Type` headers to +one of the following values when serving content with `Content-Disposition: inline`: + +* `text/css` +* `text/plain` +* `text/csv` +* `application/json` +* `application/ld+json` +* `image/jpeg` +* `image/gif` +* `image/png` +* `image/apng` +* `image/webp` +* `image/avif` +* `video/mp4` +* `video/webm` +* `video/ogg` +* `video/quicktime` +* `audio/mp4` +* `audio/webm` +* `audio/aac` +* `audio/mpeg` +* `audio/ogg` +* `audio/wave` +* `audio/wav` +* `audio/x-wav` +* `audio/x-pn-wav` +* `audio/flac` +* `audio/x-flac` + +These types are unlikely to cause Cross-Site Scripting issues within clients. + +Clients SHOULD NOT rely on servers returning `inline` rather than `attachment` +on `/download`. Server implementations might decide out of an abundance of +caution that all downloads are responded to with `attachment`, regardless of +content type - clients should not be surprised by this behaviour. diff --git a/data/api/client-server/authed-content-repo.yaml b/data/api/client-server/authed-content-repo.yaml index 43cb5881..53416cf3 100644 --- a/data/api/client-server/authed-content-repo.yaml +++ b/data/api/client-server/authed-content-repo.yaml @@ -46,9 +46,29 @@ paths: Content-Type: $ref: '#/components/headers/downloadContentType' Content-Disposition: - description: The name of the file that was previously uploaded, if set. + x-changedInMatrixVersion: + "1.12": This header became required. + description: | + The [disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) + of the returned content. MUST be one of `inline` or `attachment`, + and SHOULD contain a file name. + + If the `Content-Type` is allowed in the [restrictions for serving + inline content](/client-server-api/#serving-inline-content), + servers SHOULD use `inline`, otherwise they SHOULD use + `attachment`. + + If the upload was made with a `filename`, this header MUST + contain the same `filename`. Otherwise, `filename` is excluded + from the header. If the media being downloaded is remote, the + remote server's `filename` in the `Content-Disposition` header + is used as the `filename` instead. When the header is not + supplied, or does not supply a `filename`, the local download + response does not include a `filename`. + required: true schema: type: string + example: "inline; filename=\"filename.jpg\"" content: application/octet-stream: schema: @@ -106,11 +126,21 @@ paths: Content-Type: $ref: '#/components/headers/downloadContentType' Content-Disposition: - description: |- - The `fileName` requested or the name of the file that was previously - uploaded, if set. + x-changedInMatrixVersion: + "1.12": This header became required. + description: | + The [disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) + of the returned content. MUST be one of `inline` or `attachment`, + and MUST contain the file name requested in the path. + + If the `Content-Type` is allowed in the [restrictions for serving + inline content](/client-server-api/#serving-inline-content), + servers SHOULD use `inline`, otherwise they SHOULD use + `attachment`. + required: true schema: type: string + example: "inline; filename=\"filename.jpg\"" content: application/octet-stream: schema: @@ -208,8 +238,24 @@ paths: "200": description: A thumbnail of the requested content. headers: + Content-Disposition: + x-addedInMatrixVersion: "1.12" + description: | + The [disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) + of the returned content. MUST be `inline`, and SHOULD contain a file name (e.g. `thumbnail.png`). + + Servers should note the [Content-Type restrictions for serving inline content](/client-server-api/#serving-inline-content), + as these limitations imply which formats should be used for thumbnail generation. + required: true + schema: + type: string + example: "inline; filename=\"thumbnail.png\"" Content-Type: + x-changedInMatrixVersion: + "1.12": | + This header became required in order to support `Content-Disposition`. description: The content type of the thumbnail. + required: true schema: type: string enum: @@ -512,7 +558,29 @@ components: format: uri headers: downloadContentType: - description: The content type of the file that was previously uploaded. + description: | + The content type of the file that was previously uploaded. + + The server MUST return a `Content-Type` which is either exactly the same + as the original upload, or reasonably close. The bounds of "reasonable" + are: + + * Adding a charset to `text/*` content types. + * Detecting HTML and using `text/html` instead of `text/plain`. + * Using `application/octet-stream` when the server determines the + content type is obviously wrong. For example, an encrypted file being + claimed as `image/png`. + * Returning `application/octet-stream` when the media has an + unknown/unprovided `Content-Type`. For example, being uploaded before + the server tracked content types or when the remote server is + non-compliantly omitting the header entirely. + + Actions not in the spirit of the above are not considered "reasonable". + x-changedInMatrixVersion: + "1.12": | + This header became required in order to support `Content-Disposition`, + and the behaviour to compute its value was clarified. + required: true schema: type: string diff --git a/data/api/client-server/content-repo.yaml b/data/api/client-server/content-repo.yaml index 92ca6caa..1cdfa404 100644 --- a/data/api/client-server/content-repo.yaml +++ b/data/api/client-server/content-repo.yaml @@ -248,9 +248,29 @@ paths: Content-Type: $ref: '#/components/headers/downloadContentType' Content-Disposition: - description: The name of the file that was previously uploaded, if set. + x-changedInMatrixVersion: + "1.12": This header became required. + description: | + The [disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) + of the returned content. MUST be one of `inline` or `attachment`, + and SHOULD contain a file name. + + If the `Content-Type` is allowed in the [restrictions for serving + inline content](/client-server-api/#serving-inline-content), + servers SHOULD use `inline`, otherwise they SHOULD use + `attachment`. + + If the upload was made with a `filename`, this header MUST + contain the same `filename`. Otherwise, `filename` is excluded + from the header. If the media being downloaded is remote, the + remote server's `filename` in the `Content-Disposition` header + is used as the `filename` instead. When the header is not + supplied, or does not supply a `filename`, the local download + response does not include a `filename`. + required: true schema: type: string + example: "inline; filename=\"filename.jpg\"" content: application/octet-stream: schema: @@ -309,11 +329,21 @@ paths: Content-Type: $ref: '#/components/headers/downloadContentType' Content-Disposition: - description: |- - The `fileName` requested or the name of the file that was previously - uploaded, if set. + x-changedInMatrixVersion: + "1.12": This header became required. + description: | + The [disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) + of the returned content. MUST be one of `inline` or `attachment`, + and MUST contain the file name requested in the path. + + If the `Content-Type` is allowed in the [restrictions for serving + inline content](/client-server-api/#serving-inline-content), + servers SHOULD use `inline`, otherwise they SHOULD use + `attachment`. + required: true schema: type: string + example: "inline; filename=\"filename.jpg\"" content: application/octet-stream: schema: @@ -412,8 +442,24 @@ paths: "200": description: A thumbnail of the requested content. headers: + Content-Disposition: + x-addedInMatrixVersion: "1.12" + description: | + The [disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) + of the returned content. MUST be `inline`, and SHOULD contain a file name (e.g. `thumbnail.png`). + + Servers should note the [Content-Type restrictions for serving inline content](/client-server-api/#serving-inline-content), + as these limitations imply which formats should be used for thumbnail generation. + required: true + schema: + type: string + example: "inline; filename=\"thumbnail.png\"" Content-Type: + x-changedInMatrixVersion: + "1.12": | + This header became required in order to support `Content-Disposition`. description: The content type of the thumbnail. + required: true schema: type: string enum: @@ -639,7 +685,15 @@ components: contentType: in: header name: Content-Type - description: The content type of the file being uploaded + description: | + **Optional.** The content type of the file being uploaded. + + Clients SHOULD always supply this header. + + Defaults to `application/octet-stream` if it is not set. + x-changedInMatrixVersion: + "1.12": | + This header became explicitely optional with a default value. example: application/pdf schema: type: string @@ -782,7 +836,29 @@ components: format: uri headers: downloadContentType: - description: The content type of the file that was previously uploaded. + description: | + The content type of the file that was previously uploaded. + + The server MUST return a `Content-Type` which is either exactly the same + as the original upload, or reasonably close. The bounds of "reasonable" + are: + + * Adding a charset to `text/*` content types. + * Detecting HTML and using `text/html` instead of `text/plain`. + * Using `application/octet-stream` when the server determines the + content type is obviously wrong. For example, an encrypted file being + claimed as `image/png`. + * Returning `application/octet-stream` when the media has an + unknown/unprovided `Content-Type`. For example, being uploaded before + the server tracked content types or when the remote server is + non-compliantly omitting the header entirely. + + Actions not in the spirit of the above are not considered "reasonable". + x-changedInMatrixVersion: + "1.12": | + This header became required in order to support `Content-Disposition`, + and the behaviour to compute its value was clarified. + required: true schema: type: string