Skip to content

WTEL-9538: Fix MIME-type mismatch#183

Merged
vlad-marusyk-wt merged 2 commits into
mainfrom
fix/WTEL-9538/upload-file-url-mime-mismatch
Jun 25, 2026
Merged

WTEL-9538: Fix MIME-type mismatch#183
vlad-marusyk-wt merged 2 commits into
mainfrom
fix/WTEL-9538/upload-file-url-mime-mismatch

Conversation

@vlad-marusyk-wt

Copy link
Copy Markdown
Contributor

Проблема

storage.FileService/UploadFileUrl повертав 403 policy.file.allow при завантаженні файлів за URL з S3, який повертає некоректний заголовок Content-Type: image (без subtype). Існуючий fallback на клієнтський mime спрацьовував лише для точного application/octet-stream, тому валідний image/jpeg від chat-server ігнорувався, а сам "image" не знаходив відповідну політику через строге співставлення в MatchPattern.

Вирішення

Додано нормалізацію та валідацію MIME-типу перед передачею у політику. Якщо заголовок Content-Type валідний (парситься через mime.ParseMediaType і не application/octet-stream) то використовуємо його. Інакше зчитуємо перші 512 байт тіла і визначаємо тип через filetype.Match (перевірка за магічними байтами). Клієнтський Mime тепер використовується лише як останній резерв, коли sniffing не дав результату. Якщо детектований тип не збігається з клієнтським hint то відмова через PolicyErrorExtSuspicious (захист від підміни).

Comment thread grpc_api/file.go
Comment on lines +390 to +417
func resolveUrlUploadMime(res *http.Response, clientHint string) (io.ReadCloser, string, model.AppError) {
parsed, _, parseErr := mime.ParseMediaType(res.Header.Get("Content-Type"))
if parseErr == nil && strings.Contains(parsed, "/") && parsed != "application/octet-stream" {
return res.Body, parsed, nil
}

head := make([]byte, 512)
n, readErr := io.ReadFull(res.Body, head)
if readErr != nil && readErr != io.EOF && readErr != io.ErrUnexpectedEOF {
return nil, "", model.NewInternalError("grpc.upload_file_url.read_head", readErr.Error())
}
head = head[:n]
body := io.NopCloser(io.MultiReader(bytes.NewReader(head), res.Body))

kind, _ := filetype.Match(head)
switch {
case kind != filetype.Unknown:
detected := kind.MIME.Value
if clientHint != "" && clientHint != detected {
return nil, "", model.PolicyErrorExtSuspicious
}
return body, detected, nil
case clientHint != "":
return body, clientHint, nil
default:
return body, "", nil
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

загалом, схожа перевірка вже є нижче по флоу, в PolicyReader.testMimeType (виконується
коли файл уже стрімиться у сторадж):

func (r *PolicyReader) testMimeType(bytes []byte) error {

там матч робиться через strings.HasPrefix(declared, detected), тож, наприклад,
declared=image/jpeg; charset=binary + detected=image/jpeg пройде. тут порівняння через точну рівність, тобто той самий кейс впаде з PolicyErrorExtSuspicious.

думаю, варто вирівняти логіку і нормалізувати clientHint через
https://pkg.go.dev/mime#ParseMediaType (зріже параметри + lowercase)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Готово.

Comment thread grpc_api/file.go Outdated
case clientHint != "":
return body, clientHint, nil
default:
return body, "", nil

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

а що робити потім з порожнім mime-type?
може тут помилку?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

потім воно також фейлиться, але краще було одразу зробити fail fast, переробив

@vlad-marusyk-wt vlad-marusyk-wt merged commit a6cac81 into main Jun 25, 2026
12 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants