Skip to content

[feature] Add file type shortcuts for st.file_uploader and st.chat_input#14140

Merged
lukasmasuch merged 9 commits intodevelopfrom
lukasmasuch/file-type-shortcuts
Mar 3, 2026
Merged

[feature] Add file type shortcuts for st.file_uploader and st.chat_input#14140
lukasmasuch merged 9 commits intodevelopfrom
lukasmasuch/file-type-shortcuts

Conversation

@lukasmasuch
Copy link
Copy Markdown
Collaborator

@lukasmasuch lukasmasuch commented Feb 26, 2026

Describe your changes

Adds support for category shortcuts and MIME types in the type parameter for st.file_uploader and the file_type parameter for st.chat_input:

  • Category shortcuts: "image", "audio", "video", "text" (expand to MIME wildcards like "image/*")
  • MIME types: "image/jpeg", "application/pdf" (passed through as-is)
  • MIME wildcards: "image/*", "audio/*" (passed through as-is)
  • Extensions: ".jpg", "pdf" (existing behavior preserved with automatic pairing)

Changes

Backend (lib/streamlit/elements/lib/file_uploader_utils.py):

  • Add CATEGORY_SHORTCUTS constant and classify_file_type() function
  • Update normalize_upload_file_type() to handle shortcuts and MIME types
  • Update enforce_filename_restriction() to skip validation for MIME-only types (trusts browser filtering)

Frontend (frontend/lib/src/components/widgets/):

  • Update FileUploader/utils.ts to handle MIME types as direct keys in react-dropzone
  • Update ChatInput/fileUpload/fileUploadUtils.ts to validate MIME types with wildcard support
  • Update FileDropzoneInstructions.tsx to display category names (e.g., "image" instead of "image/*")
  • Update ChatFileUploadButton.tsx to show file types in upload button tooltip

Tests:

  • Comprehensive backend unit tests for all type formats

  • Frontend unit tests for MIME type handling

  • E2E test verifying file type shortcuts display correctly

  • Product spec

Testing Plan

  • Unit tests (Python and TypeScript)
  • E2E test (test_file_uploader_type_shortcuts)
  • Manual testing via make debug

…ploader

Extend the `type` parameter in `st.file_uploader` and `file_type` in
`st.chat_input` to accept category shortcuts (`"image"`, `"audio"`,
`"video"`, `"text"`), full MIME types (`"image/jpeg"`, `"application/pdf"`),
and MIME type wildcards (`"image/*"`, `"audio/*"`).

This aligns with the HTML `<input accept>` attribute while providing
convenient shortcuts for common use cases.

Changes:
- Backend: Update normalize_upload_file_type() to handle shortcuts, MIME
  types, and wildcards; skip backend validation for MIME-only types
- Frontend: Update getAccept() to pass MIME types to react-dropzone;
  update isFileTypeAllowed() for MIME type matching
- UI: Display category names instead of extensions in dropzone instructions
- Update ChatFileUploadButton to display supported file types in tooltip
- Add formatTypeForDisplay helper for consistent type formatting
- Add E2E test verifying file type shortcuts display correctly in file uploader
Copilot AI review requested due to automatic review settings February 26, 2026 12:50
@lukasmasuch lukasmasuch added change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users labels Feb 26, 2026
@snyk-io
Copy link
Copy Markdown
Contributor

snyk-io bot commented Feb 26, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 26, 2026

✅ PR preview is ready!

Name Link
📦 Wheel file https://core-previews.s3-us-west-2.amazonaws.com/pr-14140/streamlit-1.54.0-py3-none-any.whl
📦 @streamlit/component-v2-lib Download from artifacts
🕹️ Preview app pr-14140.streamlit.app (☁️ Deploy here if not accessible)

The spec is not needed for this feature PR.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds support for file-type shortcuts and MIME-based specifiers for uploads, extending Streamlit’s existing extension-only behavior for both st.file_uploader and st.chat_input (file attachments). This aligns accepted types with HTML <input accept> semantics while keeping legacy extension normalization/pairing.

Changes:

  • Backend: classify/normalize type specifiers (shortcuts → MIME wildcards, MIME passthrough, extensions normalized + paired) and skip backend validation when only MIME-based types are provided.
  • Frontend: update react-dropzone accept mapping and chat file validation to support MIME types/wildcards; update UI text/tooltip formatting to display categories like image instead of image/*.
  • Tests: add/extend Python + TS unit tests and add an e2e test for shortcut display behavior.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
specs/2026-02-26-file-type-shortcuts/product-spec.md Product spec describing shortcuts/MIME support and expected behavior.
lib/streamlit/elements/lib/file_uploader_utils.py Adds classification/normalization for shortcuts + MIME types; adjusts backend filename enforcement behavior.
lib/tests/streamlit/file_uploader_utils_test.py Adds unit tests covering new type formats and backend enforcement behavior with MIME-only lists.
frontend/lib/src/components/widgets/FileUploader/utils.ts Updates Dropzone accept config builder to support MIME keys directly.
frontend/lib/src/components/widgets/FileUploader/utils.test.ts Adds unit tests for MIME/wildcard handling in getAccept and isMimeType.
frontend/lib/src/components/widgets/FileUploader/FileDropzoneInstructions.tsx Formats display text for MIME wildcards/types and extensions.
frontend/lib/src/components/widgets/ChatInput/fileUpload/fileUploadUtils.ts Extends chat upload validation to support MIME types/wildcards + extensions.
frontend/lib/src/components/widgets/ChatInput/fileUpload/fileUploadUtils.test.ts Adds unit tests for MIME and wildcard matching behavior in chat uploads.
frontend/lib/src/components/widgets/ChatInput/fileUpload/ChatFileUploadButton.tsx Adds tooltip display of allowed file types, formatting wildcards nicely.
frontend/lib/src/components/widgets/ChatInput/ChatInput.tsx Passes fileType into the upload button for tooltip display.
e2e_playwright/st_file_uploader.py Adds two uploaders demonstrating shortcut + mixed type inputs.
e2e_playwright/st_file_uploader_test.py Adds e2e assertions that shortcuts display as categories (e.g. image, not image/*).
Comments suppressed due to low confidence (1)

lib/streamlit/elements/lib/file_uploader_utils.py:146

  • The error message reports Allowed: {allowed_types}, but server-side validation is now only applied to dot-prefixed extensions. In mixed lists (e.g. ["image/*", ".png"]), this message can be misleading because the MIME entries weren’t part of the check. Consider reporting only the validated extension list (or clarifying in the message that MIME types/shortcuts are not enforced on the backend).
    if not any(
        normalized_filename.endswith(allowed_type) for allowed_type in extension_types
    ):
        raise StreamlitAPIException(
            f"Invalid file extension: `{extension}`. Allowed: {allowed_types}"
        )

@lukasmasuch lukasmasuch changed the title [feature] Add file type shortcuts for st.file_uploader and st.chat_input [feature] Add file type shortcuts for st.file_uploader and st.chat_input Feb 26, 2026
@lukasmasuch lukasmasuch removed the area:security Related to security concerns label Feb 26, 2026
Update expected tooltip text to include file type (TXT) since the
single_file chat input now has file_type="txt" configured.
…pace handling

- Extract formatTypeForDisplay to shared FileHelper utility to eliminate
  code duplication between ChatFileUploadButton and FileDropzoneInstructions
- Add whitespace handling to classify_file_type in Python backend
- Add unit tests for new shared utility and whitespace handling
@lukasmasuch lukasmasuch added the ai-review If applied to PR or issue will run AI review workflow label Feb 26, 2026
@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Feb 26, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Consolidated Code Review

Summary

This PR adds support for category shortcuts ("image", "audio", "video", "text") and MIME type patterns ("image/jpeg", "application/pdf", "image/*") in the type parameter for st.file_uploader and the file_type parameter for st.chat_input. The implementation spans backend normalization, frontend accept/validation logic, display formatting, and tests at all levels. The architecture is clean — backend normalizes types, frontend uses them for browser filtering and display, and a shared formatTypeForDisplay utility handles presentation.

Code Quality

The code is well-organized and follows existing codebase patterns:

  • classify_file_type() is cleanly designed with a Literal return type and frozenset for CATEGORY_SHORTCUTS.
  • normalize_upload_file_type() refactor neatly separates MIME types and extensions before combining.
  • formatTypeForDisplay() in FileHelper.ts correctly handles all three type formats.
  • getAccept() smartly uses STREAMLIT_MIME_TYPE as a fallback key for extensions in react-dropzone's Accept config.
  • isFileTypeAllowed() in fileUploadUtils.ts (ChatInput) is well-structured with proper MIME matching and wildcard support.

Minor: The acceptedExtensions prop name in FileDropzoneInstructions.tsx is now a misnomer since it also carries MIME types. Not blocking, but worth tracking for follow-up.

Bugs Found (Both Reviewers Agree)

Bug 1: Directory upload filtering ignores MIME types [Critical] (Raised by gpt-5.3-codex-high, confirmed)

FileUploader.tsx:312-323 — the isFileTypeAllowed callback used for directory upload filtering uses only fileName.endsWith(ext.toLowerCase()). When element.type contains MIME values like "image/*" or "application/pdf", this check will never match any filename, causing all files to be rejected during directory uploads with MIME/shortcut types.

The ChatInput's isFileTypeAllowed (in fileUploadUtils.ts) already handles this correctly with separate MIME and extension matching paths. The FileUploader's directory filter should reuse that shared logic or implement equivalent MIME-aware matching.

Bug 2: Mixed MIME + extension types cause false backend rejections [Critical] (Raised by opus-4.6-thinking, confirmed)

file_uploader_utils.py:134-140 — when allowed_types contains both MIME types and extensions (e.g., ["image/*", ".json"]), enforce_filename_restriction extracts only extension types ([".json"]) for validation. Since extensions exist, it proceeds to validate. A file like photo.jpg that legitimately matches image/* on the frontend will be rejected on the backend because .jpg doesn't match .json.

The fix: skip backend extension validation when ANY MIME types are present in allowed_types, since the backend cannot determine whether a file was intended to match a MIME pattern:

has_mime_types = any("/" in t for t in allowed_types)
extension_types = [t.lower() for t in allowed_types if t.startswith(".")]
if has_mime_types or not extension_types:
    return

The existing test ("mixed_invalid_ext", "photo.gif", ["audio/*", ".png"], False) does not catch this because photo.gif doesn't match audio/* either. A test case like ("mixed_mime_match", "photo.jpg", ["image/*", ".json"], True) would expose the bug.

Additionally, the error message at line 149 shows the full allowed_types list (including MIME types) but only validates extensions, which could confuse users.

Test Coverage

Test coverage is comprehensive for the logic that works correctly:

  • Backend unit tests: Strong parameterized coverage of classify_file_type (20 cases), normalize_upload_file_type (22+ cases), and enforce_filename_restriction MIME behavior (8 cases).
  • Frontend unit tests: FileHelper.test.ts, utils.test.ts, and fileUploadUtils.test.ts cover formatting, MIME matching, wildcards, mixed types, and edge cases.
  • E2E tests: Verify display behavior for shortcuts and mixed types, with both positive and negative assertions.

Gaps (both reviewers agree):

  • No tests cover actual file upload behavior with MIME types — only display and validation logic.
  • No directory upload tests with MIME/shortcut types (which would have caught Bug 1).
  • No backend test for the mixed MIME + extension case that triggers Bug 2.

Backwards Compatibility

Fully backwards compatible. Existing extension-based usage continues to work identically, including extension pairing. The new shortcuts and MIME types are purely additive.

Security & Risk

Both reviewers agree that skipping backend validation for MIME-only types is acceptable because:

  1. The existing extension check was always filename-only (no content validation).
  2. The browser's accept attribute handles frontend filtering.
  3. Null byte injection is still caught regardless.

Bug 2 introduces a security-adjacent risk: in the mixed types scenario, the backend validation is stricter than intended and will reject legitimate uploads that the frontend allowed.

Accessibility

No regressions. The tooltip on ChatFileUploadButton now includes file types (an improvement). The aria-label on the upload button remains appropriate. Existing keyboard/focus behavior is preserved.

Recommendations

  1. [Blocking] Fix FileUploader directory filtering to support MIME types — reuse the MIME-aware isFileTypeAllowed logic from fileUploadUtils.ts or implement equivalent matching.
  2. [Blocking] Fix enforce_filename_restriction to skip validation when any MIME types are present in allowed_types, preventing false rejections in mixed type scenarios.
  3. [Blocking] Add tests for both bugs: directory upload with MIME types (frontend) and mixed MIME + extension backend validation (e.g., ("mixed_mime_match", "photo.jpg", ["image/*", ".json"], True)).
  4. [Non-blocking] Consider showing only extension_types (not full allowed_types) in the error message at line 149.
  5. [Non-blocking] Rename acceptedExtensions prop to acceptedTypes in a follow-up PR.

Verdict

CHANGES REQUESTED: Two confirmed bugs make the new MIME/shortcut feature broken in specific but realistic scenarios: (1) directory uploads with MIME/shortcut types will reject all files on the frontend, and (2) mixed MIME + extension types will cause false rejections on the backend. Both should be fixed before merge.


Consolidated review by opus-4.6-thinking. Individual reviews from gpt-5.3-codex-high and opus-4.6-thinking are included below.


📋 Review by `gpt-5.3-codex-high`

Summary

This PR adds support for file type category shortcuts and MIME patterns for st.file_uploader and st.chat_input, updates frontend accept/validation logic, and extends backend normalization/tests plus e2e coverage for display behavior.

Code Quality

frontend/lib/src/components/widgets/FileUploader/FileUploader.tsx:312-323 still validates directory uploads using extension-only suffix matching (fileName.endsWith(ext.toLowerCase())). With this PR, element.type can now contain MIME values like "image/*" or "application/pdf" (normalized in lib/streamlit/elements/lib/file_uploader_utils.py:68-115), so this logic will reject valid files for directory uploads.

This becomes user-visible because directory mode always applies filterDirectoryFiles in frontend/lib/src/components/widgets/FileUploader/FileUploader.tsx:493-501.

Test Coverage

Unit coverage for normalization and MIME handling is strong in:

  • lib/tests/streamlit/file_uploader_utils_test.py
  • frontend/lib/src/components/widgets/FileUploader/utils.test.ts
  • frontend/lib/src/components/widgets/ChatInput/fileUpload/fileUploadUtils.test.ts

However, the new e2e test e2e_playwright/st_file_uploader_test.py:966-998 only verifies instruction text rendering and does not cover directory upload behavior with MIME/shortcut types, which is why the regression in FileUploader.tsx was not detected.

Backwards Compatibility

Extension-based flows appear preserved, but the new advertised MIME/shortcut behavior is not compatible with directory uploads in st.file_uploader due to the extension-only directory filter path.

Security & Risk

lib/streamlit/elements/lib/file_uploader_utils.py:121-140 now skips backend validation when only MIME types are provided and relies on browser-side filtering. This may be acceptable as best-effort, but it increases bypass risk via crafted requests and should be explicitly acknowledged/documented as non-security enforcement.

Accessibility

No clear a11y regressions found in the changed frontend files. Existing keyboard/focus behavior in ChatFileUploadButton remains intact (tabIndex={-1} wrapper, labeled button), and tooltip content changes are non-structural.

Recommendations

  1. Update frontend/lib/src/components/widgets/FileUploader/FileUploader.tsx directory filtering to support MIME and wildcard types (reuse shared MIME-aware logic, similar to ChatInput/fileUpload/fileUploadUtils.ts).
  2. Add tests for directory uploads with type="image" / MIME patterns, at least one frontend/unit path and one e2e scenario.
  3. Clarify documentation/behavior notes for MIME-only backend enforcement limits to avoid implying server-side type guarantees.

Verdict

CHANGES REQUESTED: The new MIME/shortcut feature is functionally broken for st.file_uploader directory uploads because directory filtering still assumes extension-only type entries.


This is an automated AI review by gpt-5.3-codex-high. Please verify the feedback and use your judgment.

📋 Review by `opus-4.6-thinking`

Summary

This PR adds support for category shortcuts ("image", "audio", "video", "text") and MIME types ("image/jpeg", "application/pdf", "image/*") in the type parameter for st.file_uploader and the file_type parameter for st.chat_input. The changes span backend normalization logic, frontend accept/validation handling, display formatting, and comprehensive tests at all levels.

The implementation is well-structured with a clean separation of concerns: backend normalizes types, frontend uses them for browser-level filtering and display, and a shared formatTypeForDisplay utility handles presentation.

Code Quality

The code is clean, well-organized, and follows existing codebase patterns. Specific observations:

  • classify_file_type() (lib/streamlit/elements/lib/file_uploader_utils.py:50-65): Well-designed classifier with a clear return type using Literal. The use of frozenset for CATEGORY_SHORTCUTS is appropriate.
  • normalize_upload_file_type() refactor is clean, separating MIME types and extensions into distinct lists before combining them. Extension pairing continues to work correctly.
  • formatTypeForDisplay() (frontend/lib/src/util/FileHelper.ts:107-120): Properly extracted to a shared utility and handles all three type formats correctly.
  • getAccept() (frontend/lib/src/components/widgets/FileUploader/utils.ts:41-66): Smart use of STREAMLIT_MIME_TYPE as a fallback key for extensions in the react-dropzone Accept config, while MIME types/wildcards become their own keys.
  • isFileTypeAllowed() in fileUploadUtils.ts is well-structured with proper separation of MIME matching and extension matching, including wildcard support.

Minor observations:

  • The acceptedExtensions prop name in FileDropzoneInstructions.tsx is now a misnomer since it also carries MIME types. Not blocking but worth noting for future cleanup.

Test Coverage

Test coverage is comprehensive:

  • Backend unit tests (lib/tests/streamlit/file_uploader_utils_test.py): Good parameterized tests for classify_file_type (20 cases covering shortcuts, MIME, extensions, case/whitespace), normalize_upload_file_type (22+ cases including mixed types and edge cases), and MIME type enforcement behavior (8 cases). Anti-regression assertions are present (e.g., null byte with MIME types still fails).
  • Frontend unit tests: FileHelper.test.ts covers formatTypeForDisplay for all three type categories. utils.test.ts covers isMimeType and getAccept with mixed scenarios. fileUploadUtils.test.ts covers MIME matching, wildcards, mixed types, case insensitivity, and edge cases.
  • E2E test (st_file_uploader_test.py): Tests display behavior for both a single shortcut type and mixed types. Includes both positive assertions (correct text shown) and negative assertions (raw MIME wildcards like "image/*" not shown). Follows best practices by using get_element_by_key for stable locators.
  • E2E test (st_chat_input_test.py): Updated tooltip assertion to include file type display.

One gap: there are no tests (backend or E2E) that verify actual file upload behavior with MIME types — only display and validation logic. The unit tests are sufficient for the logic, but an E2E test uploading, say, a .jpg file to a type="image" uploader would further strengthen confidence.

Backwards Compatibility

Fully backwards compatible. Existing extension-based usage (type="png", type=[".jpg", ".pdf"]) continues to normalize and behave identically. Extension pairing (.jpg <-> .jpeg, etc.) still works. The new shortcuts and MIME types are purely additive.

Security & Risk

Backend validation bypass for MIME-only types (acceptable): When only MIME types are specified (e.g., type="image"), backend filename validation is intentionally skipped. This is acceptable because:

  1. The existing extension check was always filename-only (no content validation), so the security boundary hasn't fundamentally changed.
  2. The accept attribute on the frontend file picker handles browser-level filtering.
  3. Null byte injection is still caught regardless.

Bug: Mixed MIME + extension types cause false backend rejections: This is the most significant issue. When a user specifies mixed types like type=["image", ".json"] (normalizing to ["image/*", ".json"]), the backend enforce_filename_restriction will only validate against extensions ([".json"]). This means:

  • A user uploads photo.jpg via the frontend → the frontend correctly allows it (matches image/*) → the backend rejects it because .jpg does not match .json.

The problematic logic is at file_uploader_utils.py:134-140:

extension_types = [t.lower() for t in allowed_types if t.startswith(".")]
if not extension_types:
    return

When MIME types and extensions are mixed, the backend proceeds with extension-only validation, causing false rejections for files that legitimately matched a MIME pattern. The fix should be to skip backend validation entirely when ANY MIME types are present in allowed_types, since the backend cannot distinguish whether a file was intended to match a MIME pattern or an extension:

has_mime_types = any("/" in t for t in allowed_types)
extension_types = [t.lower() for t in allowed_types if t.startswith(".")]
if has_mime_types or not extension_types:
    return

The existing test ("mixed_invalid_ext", "photo.gif", ["audio/*", ".png"], False) does not catch this because photo.gif doesn't match audio/* either. A test like ("mixed_mime_match", "photo.jpg", ["image/*", ".json"], True) would expose the bug.

Accessibility

  • The tooltip on ChatFileUploadButton now includes file types, improving information disclosure for all users.
  • The aria-label on the upload button remains "Upload a file" / "Upload files" without file types, which is acceptable — the tooltip provides the supplementary detail.
  • No new interactive elements without accessible names.
  • The existing tabIndex: -1 on the dropzone wrapper (avoiding duplicate tab stops) is preserved.

Recommendations

  1. Fix the mixed MIME + extension backend validation bug. When allowed_types contains both MIME types (containing /) and extensions (starting with .), a file that matches the MIME pattern but not an extension will be incorrectly rejected by the backend. Skip backend extension validation when any MIME types are present. Add a test case like ("mixed_mime_match_no_ext", "photo.jpg", ["image/*", ".json"], True) to verify.

  2. Improve the error message at file_uploader_utils.py:149. Currently it shows the full allowed_types list (including MIME types) in the error: "Invalid file extension: '.gif'. Allowed: ['audio/*', '.png']". Since the backend only validates extensions, consider showing only extension_types in the error to avoid confusion, or (if recommendation 1 is adopted) this becomes moot for the mixed case.

  3. Consider renaming the acceptedExtensions prop in FileDropzoneInstructions.tsx to acceptedTypes in a follow-up, since it now also carries MIME types. Not blocking for this PR.

Verdict

CHANGES REQUESTED: The backend validation logic in enforce_filename_restriction has a bug when MIME types and extensions are mixed — files matching a MIME pattern but not an extension will be incorrectly rejected server-side. This is a real user-facing regression that should be fixed before merge.


This is an automated AI review by opus-4.6-thinking.

@github-actions github-actions bot added the do-not-merge PR is blocked from merging label Feb 26, 2026
Bug fixes:
- Fix directory upload filtering to support MIME types (was using extension-only matching)
- Fix backend validation to skip when MIME types are present in mixed types
  (prevents false rejections for files matching MIME patterns)

Improvements:
- Move shared isFileTypeAllowed() to FileHelper.ts for reuse
- Rename acceptedExtensions prop to acceptedTypes (now carries MIME types too)
- Add comprehensive tests for MIME type matching and mixed type scenarios
@sfc-gh-lmasuch sfc-gh-lmasuch added ai-review If applied to PR or issue will run AI review workflow and removed do-not-merge PR is blocked from merging labels Feb 26, 2026
@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Feb 26, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Consolidated Code Review: PR #14140 — Add file type shortcuts for st.file_uploader and st.chat_input

Summary

This PR adds support for category shortcuts ("image", "audio", "video", "text") and MIME types/wildcards ("image/jpeg", "image/*") in the type parameter for st.file_uploader and the file_type parameter for st.chat_input. Previously, only file extensions were supported.

Key changes:

  • Backend: New classify_file_type() and updated normalize_upload_file_type() to handle shortcuts, MIME types, and extensions. enforce_filename_restriction() now skips backend validation when MIME types are present (trusting browser filtering).
  • Frontend: Updated FileUploader/utils.ts to build react-dropzone Accept objects with MIME type keys. Centralized isFileTypeAllowed and formatTypeForDisplay into FileHelper.ts. Updated tooltip and instruction displays for file type shortcuts.
  • Tests: Comprehensive backend unit tests, frontend unit tests, and an E2E test for file uploader display.

Both reviewers agreed the PR is well-structured, follows existing patterns, and has strong test coverage. The main disagreement was over the severity of a multi-part extension matching regression.

Code Quality

Consensus: The code is clean, well-organized, with good separation of concerns.

Both reviewers agreed on:

  • The centralization of isFileTypeAllowed into FileHelper.ts with a re-export from fileUploadUtils.ts is a good pattern.
  • formatTypeForDisplay is cleanly implemented and properly placed.
  • Backend classify_file_type and normalize_upload_file_type are well-structured.

Unique finding (opus-4.6-thinking):

  • isMimeType is defined independently in both FileUploader/utils.ts (lines 27-29) and FileHelper.ts (line 105). The utils.ts version should import from FileHelper.ts to prevent drift. Minor code hygiene issue.

Unique finding (gpt-5.3-codex-high) — verified:

  • Multi-part extension matching regression in isFileTypeAllowed. The old FileUploader.tsx code used fileName.endsWith(ext), which correctly matched filenames like archive.tar.gz against allowed type .tar.gz. The new centralized isFileTypeAllowed in FileHelper.ts extracts only the last extension segment (.gz) via lastIndexOf("."), which would fail to match .tar.gz.

    Consolidation assessment: This regression is real but narrow in scope. It only affects the FileUploader directory upload path (filterDirectoryFilescheckFileTypeAllowed). For regular (non-directory) uploads, react-dropzone handles filtering via the browser's accept attribute, so isFileTypeAllowed is not invoked. Additionally, the old ChatInput implementation already used the same lastIndexOf(".") logic, so there is no regression for ChatInput. While this is a genuine behavioral change for directory uploads with multi-part extensions, the scenario (directory upload + .tar.gz-style type filter) is uncommon and can be addressed in a follow-up.

Test Coverage

Both reviewers agreed the test coverage is strong across:

  • Backend normalization/classification behavior (excellent classify_file_type and normalize_upload_file_type tests).
  • Frontend isFileTypeAllowed with MIME matching, wildcards, mixed types.
  • E2E display behavior (dropzone instructions and chat tooltip text).

Agreed gaps:

  • No frontend unit/E2E test for multi-part extensions through the isFileTypeAllowed path, particularly for directory uploads.

Unique gap (opus-4.6-thinking):

  • FileDropzoneInstructions.test.tsx: No tests were added for rendering with MIME types or shortcuts (e.g., acceptedTypes: ["image/*", "application/pdf"]). The formatTypeForDisplay logic is tested in FileHelper.test.ts, but an integration test would strengthen coverage.
  • No unit test file exists for ChatFileUploadButton. The new fileTypes prop and tooltip content generation are only covered by the E2E test.

Backwards Compatibility

Both reviewers agreed this change is backwards compatible for the primary use cases:

  • Existing extension-only usage is preserved with identical behavior.
  • The new shortcuts and MIME types are additive.
  • The frontend re-export of isFileTypeAllowed maintains backwards compatibility.

Disagreement on multi-part extensions:

  • gpt-5.3-codex-high flagged this as a backwards-compatibility regression (blocking).
  • opus-4.6-thinking did not identify this issue.
  • Resolution: The regression is real but limited to FileUploader directory uploads with multi-part extensions (e.g., .tar.gz). This is an edge case that doesn't warrant blocking the PR.

Security & Risk

Both reviewers agreed:

  • No high-severity security issues.
  • The backend validation bypass with MIME types is documented, intentional, and sound — the browser accept attribute is a UI hint, not a security boundary.
  • Null byte protection is preserved and correctly runs before the MIME type bypass.
  • No new attack surface; shortcuts expand to well-defined MIME wildcards.

Accessibility

Both reviewers agreed:

  • The ChatFileUploadButton tooltip now includes file type information, providing better context.
  • The FileDropzoneInstructions correctly displays human-readable type names.

Unique finding (opus-4.6-thinking):

  • The aria-label on the upload button does not include file type information. Consider adding file type info to the aria-label for screen reader users, since tooltip content is not accessible to assistive technology.

Recommendations

  1. (Minor) Resolve isMimeType duplication: In FileUploader/utils.ts, import from FileHelper.ts instead of redefining.
  2. (Minor) Update isFileTypeAllowed to support multi-part extension matching (e.g., .tar.gz) to match the old endsWith behavior from FileUploader.tsx. This could be a follow-up PR.
  3. (Minor) Add a test for isFileTypeAllowed covering multi-part extension cases.
  4. (Minor) Add MIME type rendering tests to FileDropzoneInstructions.test.tsx.
  5. (Minor) Consider adding file types to the aria-label in ChatFileUploadButton.tsx for screen reader accessibility.
  6. (Minor) Consider adding a unit test for ChatFileUploadButton tooltip behavior.

Verdict

APPROVED — Well-implemented feature with comprehensive test coverage, good code organization, and maintained backwards compatibility for all primary use cases. The multi-part extension matching regression identified by gpt-5.3-codex-high is real but narrowly scoped (only affects directory uploads with compound extensions like .tar.gz) and can be addressed in a follow-up. All other findings are minor improvements.


Consolidated review by opus-4.6-thinking. Individual reviews below.


📋 Review by `gpt-5.3-codex-high`

Summary

This PR adds support for file type shortcuts (image, audio, video, text) and MIME types/wildcards in st.file_uploader and st.chat_input, with corresponding backend normalization and frontend accept/validation/display updates. The overall architecture is solid, but I found one backward-compatibility regression in frontend extension matching that should be fixed before merge.

Code Quality

The changes are generally clean and well-structured (good extraction into shared frontend helpers and expanded backend normalization tests).
However, there is one functional issue:

  • Regression (blocking): multi-part extensions are no longer matched by frontend validation.
    In frontend/lib/src/util/FileHelper.ts (lines ~165-185), extension validation only compares against the suffix after the last . (.gz for archive.tar.gz). This breaks acceptance for allowed types like .tar.gz or csv.gz in frontend validation paths.
    This became user-impacting for directory uploads because frontend/lib/src/components/widgets/FileUploader/FileUploader.tsx (lines ~313-345) now routes directory filtering through this helper.

Test Coverage

Coverage is strong for:

  • backend normalization/classification behavior,
  • MIME and wildcard handling in frontend helpers,
  • display behavior in e2e (dropzone instruction text and chat tooltip text).

Coverage gap:

  • no frontend unit/e2e test asserting acceptance of multi-part extensions (e.g. .tar.gz) through the new shared isFileTypeAllowed path, especially for directory uploads.

Backwards Compatibility

There is a backward-compatibility regression for file type filters that rely on multi-part extensions during frontend validation (notably directory uploads in st.file_uploader). Historically, suffix-based matching allowed .tar.gz; current matching logic rejects it.

Security & Risk

No high-severity security issue identified in this PR.
Main risk is behavioral regression: legitimate files may be rejected client-side for existing apps using multi-part extension filters.

Accessibility

No new accessibility issues found in the changed paths. The chat upload button correctly avoids duplicate tab stops by setting tabIndex={-1} on the dropzone wrapper and keeping a semantic inner button.

Recommendations

  1. Update extension matching in frontend/lib/src/util/FileHelper.ts to support full-suffix matching for dotted extensions (e.g. .tar.gz), while preserving current MIME and single-extension behavior.
  2. Add frontend unit tests for isFileTypeAllowed covering multi-part extension allow/reject cases.
  3. Add or extend an e2e scenario for directory uploads using a multi-part extension filter to prevent future regressions.

Verdict

CHANGES REQUESTED: The multi-part extension regression in frontend file-type validation should be fixed before merging.


This is an automated AI review by gpt-5.3-codex-high. Please verify the feedback and use your judgment.

📋 Review by `opus-4.6-thinking`

Summary

This PR adds support for category shortcuts ("image", "audio", "video", "text") and MIME types ("image/jpeg", "image/*") in the type parameter for st.file_uploader and the file_type parameter for st.chat_input. Previously, only file extensions (.jpg, pdf) were supported.

Key changes:

  • Backend: New classify_file_type() function and updated normalize_upload_file_type() to handle shortcuts, MIME types, and extensions. enforce_filename_restriction() now skips backend validation when MIME types are present (trusting browser filtering).
  • Frontend: Updated FileUploader/utils.ts to build react-dropzone Accept objects with MIME type keys. Centralized isFileTypeAllowed and formatTypeForDisplay into FileHelper.ts. Updated tooltip and instruction displays for file type shortcuts.
  • Tests: Comprehensive backend unit tests, frontend unit tests, and an E2E test for the file uploader display.

Code Quality

The code is well-structured, follows existing patterns, and has clear separation of concerns. Specific observations:

  1. isMimeType duplication (frontend/lib/src/components/widgets/FileUploader/utils.ts:27-29 and frontend/lib/src/util/FileHelper.ts:105): The same isMimeType function is defined independently in both files. The FileHelper.ts version is the canonical utility location, and FileUploader/utils.ts should import from it rather than redefining it. This is a minor code hygiene issue that could cause drift if one is updated but not the other.

  2. Centralization of isFileTypeAllowed: The function was correctly moved to FileHelper.ts with a re-export from fileUploadUtils.ts (frontend/lib/src/components/widgets/ChatInput/fileUpload/fileUploadUtils.ts:22) for backwards compatibility. Good pattern.

  3. formatTypeForDisplay (frontend/lib/src/util/FileHelper.ts:200-213): Clean implementation that handles all three type categories (MIME wildcards, MIME types, extensions). Properly placed in the shared utility module.

  4. classify_file_type (lib/streamlit/elements/lib/file_uploader_utils.py:50-65): The MIME check on line 63 (if "/" in value) uses the raw value while the shortcut check uses normalized (stripped and lowered). This is functionally correct since whitespace doesn't affect the presence of /, but the inconsistency is worth noting. The function is called with already-stripped values from normalize_upload_file_type, so this doesn't cause issues in practice.

  5. getTooltipContent in ChatFileUploadButton.tsx (line 71-78): Defined inside the component, which means it's recreated every render. However, since it depends on props (fileTypes, acceptFile), this is acceptable for a simple string computation.

Test Coverage

Python unit tests (lib/tests/streamlit/file_uploader_utils_test.py):

  • Excellent coverage of classify_file_type with shortcuts, MIME types, extensions, case insensitivity, and whitespace handling.
  • Thorough normalize_upload_file_type tests covering legacy extensions, shortcuts, MIME types, mixed types, extension pairing, whitespace, and empty strings.
  • Good enforce_filename_restriction MIME type tests covering MIME-only, mixed types, extension-only, and null byte injection.

Frontend unit tests:

  • FileHelper.test.ts: Good coverage of formatTypeForDisplay, isMimeType, and the new isFileTypeAllowed with MIME matching, wildcards, mixed types, and directory scenarios.
  • FileUploader/utils.test.ts: Good coverage of getAccept with MIME types, wildcards, extensions, and mixed types.
  • fileUploadUtils.test.ts (ChatInput): Comprehensive MIME type tests added to isFileTypeAllowed, including wildcards, mixed types, case insensitivity.

Gaps:

  • FileDropzoneInstructions.test.tsx: No tests were added for rendering with MIME types or shortcuts (e.g., acceptedTypes: ["image/*", "application/pdf"]). The formatTypeForDisplay logic is tested in FileHelper.test.ts, but an integration test verifying that "image/*" renders as "image" in the instructions component would strengthen coverage.
  • No unit test file exists for ChatFileUploadButton. The new fileTypes prop and tooltip content generation are only covered by the E2E test. A unit test verifying the tooltip text with and without fileTypes would be beneficial.

E2E tests (e2e_playwright/st_file_uploader_test.py):

  • Good test for type shortcuts display with both positive assertions (correct text) and negative assertions (raw MIME wildcard not shown). Follows E2E best practices by using key-based locators.

Backwards Compatibility

This change is fully backwards compatible:

  • Existing extension-only usage (e.g., type=["png", ".jpg"]) is preserved with identical behavior, including automatic extension pairing.
  • The new shortcuts and MIME types are additive - they don't change any existing API signatures or defaults.
  • The frontend re-export of isFileTypeAllowed from fileUploadUtils.ts maintains backwards compatibility for any internal imports.
  • The enforce_filename_restriction behavior for extension-only types is unchanged; only MIME-containing configurations get the new skip-validation behavior.

Security & Risk

  1. Backend validation bypass with MIME types: When MIME types are present in allowed_types, enforce_filename_restriction (lib/streamlit/elements/lib/file_uploader_utils.py:137-147) skips server-side validation entirely, including for mixed configurations (MIME + extensions). This is documented and intentional. The rationale is sound - the backend cannot reliably map filenames to MIME types, so it defers to browser filtering. However, it's worth noting that the browser's accept attribute is a UI hint, not a hard security boundary. Users who previously relied on extension-only types still get server-side validation, so this doesn't regress the security posture for existing users.

  2. Null byte protection preserved: The null byte check runs before the MIME type bypass, so null byte injection attacks are still blocked regardless of type configuration. This is correctly tested (null_byte_with_mime test case).

  3. No new attack surface: The shortcuts expand to well-defined MIME wildcards. No user input is passed through unsanitized to security-sensitive operations.

Accessibility

  • The ChatFileUploadButton tooltip now includes file type information (e.g., "Upload or drag and drop a file (TXT)"), which provides better context for all users.
  • The existing aria-label on the upload button (aria-label={Upload ${getUploadDescription(acceptFile)}}) does not include file type information. Consider adding file type info to the aria-label as well for screen reader users, since the tooltip content is not accessible to assistive technology.
  • The FileDropzoneInstructions component correctly displays human-readable type names (e.g., "image" instead of "image/*"), improving readability for all users.

Recommendations

  1. Resolve isMimeType duplication: In frontend/lib/src/components/widgets/FileUploader/utils.ts, import isMimeType from ~lib/util/FileHelper instead of redefining it. This prevents future drift between the two implementations.

  2. Add MIME type test to FileDropzoneInstructions.test.tsx: Add a test case with acceptedTypes: ["image/*", "application/pdf", ".json"] to verify the rendering of mixed type configurations in the instructions component.

  3. Consider adding file types to the aria-label: In ChatFileUploadButton.tsx (line 95), the aria-label doesn't include file type information. Screen reader users won't get the file type context that sighted users see in the tooltip. Consider updating the aria-label to match the tooltip content.

  4. Minor: Consider adding a unit test for ChatFileUploadButton: While no test file exists for this component (pre-existing gap), the new tooltip behavior added in this PR would benefit from a unit test verifying the tooltip text with fileTypes provided and with fileTypes as undefined.

Verdict

APPROVED: Well-implemented feature with comprehensive backend test coverage and good frontend coverage. The code follows existing patterns, maintains backwards compatibility, and handles edge cases properly. The recommendations above are minor improvements that could be addressed in a follow-up.


This is an automated AI review by opus-4.6-thinking.

…tensions

- Import isMimeType from FileHelper.ts instead of redefining in utils.ts
- Fix multi-part extension matching (e.g., .tar.gz) by using endsWith()
- Add tests for multi-part extensions (.tar.gz, .csv.gz)
- Add MIME type rendering tests for FileDropzoneInstructions
@sfc-gh-lmasuch sfc-gh-lmasuch added the ai-review If applied to PR or issue will run AI review workflow label Feb 26, 2026
@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Feb 26, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Consolidated Code Review

Summary

This PR adds support for category shortcuts ("image", "audio", "video", "text") and MIME types ("image/jpeg", "image/*") in the type parameter of st.file_uploader and the file_type parameter of st.chat_input. The implementation spans backend normalization (classify_file_type, updated normalize_upload_file_type), frontend display/filtering (shared FileHelper.ts, updated getAccept/isFileTypeAllowed/formatTypeForDisplay), and UI updates (dropzone instructions, chat upload tooltip). Both reviewers agree the code is well-structured, follows existing conventions, and includes comprehensive test coverage.

Code Quality

Consensus: Both reviewers found the code well-organized with clean separation of concerns — the classify_file_typenormalize_upload_file_type → frontend pipeline is clear, and shared logic in FileHelper.ts avoids duplication.

Minor observations (non-blocking, both reviewers):

  • getTooltipContent in ChatFileUploadButton.tsx is a closure recreated each render. Negligible cost, but could be a useMemo or inline computed value for marginal readability improvement.
  • In classify_file_type, the MIME check uses "/" in value on the original value rather than normalized. Not a bug (strip/lower don't affect /), but a stylistic inconsistency.

Test Coverage

Consensus: Both reviewers agree coverage is comprehensive across all layers:

  • Backend: Parameterized tests for classification, normalization, extension pairing, mixed types, MIME-only, null byte injection.
  • Frontend: Tests for isMimeType, formatTypeForDisplay, isFileTypeAllowed, getAccept, wildcard matching, case insensitivity.
  • E2E: Display verification for shortcuts, mixed types, and chat tooltip text.

Non-blocking gap (both reviewers): No E2E test for actual file upload/rejection with MIME-type-only restrictions. Unit tests cover the logic, and browser MIME filtering is inherently limited in E2E testing.

Backwards Compatibility

Consensus: Fully backward compatible. Extension handling is preserved, frontend display for extensions is unchanged, backend validation for extension-only types continues to work. New behavior (shortcuts and MIME types) is additive.

Security & Risk

Disagreement — resolved:

  • gpt-5.3-codex-high flagged enforce_filename_restriction (lines 137-147) skipping backend validation for mixed MIME+extension configs as a high-severity issue, arguing it removes defense-in-depth and recommending that extension checks remain active in mixed configs.
  • opus-4.6-thinking assessed this as a deliberate, correct trade-off, noting the backend only has access to the filename and cannot validate MIME-based restrictions.

Resolution: The opus-4.6-thinking assessment is correct. Consider a mixed config like ["image/*", ".json"] — this means "accept any image OR .json files." If a user uploads photo.png, the backend sees only the filename. It cannot determine whether photo.png is a valid image matching image/*. If we enforced extension checks in mixed configs (as gpt-5.3-codex-high suggests), the backend would see .png is not in the extension list [".json"] and incorrectly reject a legitimate upload. The skip is architecturally necessary because:

  1. The backend has no MIME type information — only the filename.
  2. Enforcing extension checks alongside MIME patterns would produce false rejections for files matching MIME wildcards.
  3. The null byte injection check always runs regardless.
  4. No existing security guarantee is weakened — MIME-based types are a new feature that never had backend validation.
  5. Frontend enforcement via react-dropzone's accept attribute and isFileTypeAllowed remains active.

That said, the code comment at lines 143-146 could be slightly more explicit about why the backend cannot partially enforce extensions in mixed configs (the false-rejection problem). This is a documentation-only suggestion, not blocking.

Accessibility

Consensus: No regressions. Tooltip now includes file type info, improving discoverability. FileDropzoneInstructions displays formatted types in visible text accessible to screen readers. The aria-label on the upload button remains concise.

Minor (non-blocking): File type details conveyed via tooltip alone may not reach all users. Consider exposing critical type info in an always-visible or aria-describedby surface in a follow-up.

Recommendations

  1. (Non-blocking) Consider adding basic MIME type format validation on the backend (e.g., type/subtype regex) in normalize_upload_file_type to catch typos like "image / jpeg".
  2. (Non-blocking) Consider adding a frontend unit test for ChatFileUploadButton with fileTypes prop for faster regression detection (currently only E2E covers this).
  3. (Non-blocking) Enhance the inline comment at enforce_filename_restriction lines 143-146 to explicitly note the false-rejection problem that prevents partial extension enforcement in mixed configs.

Verdict

APPROVED: Well-structured feature addition with comprehensive test coverage, clean separation of concerns, and full backward compatibility. The backend validation skip for MIME types is an architecturally sound trade-off, not a security regression. All recommendations are non-blocking improvements.


Consolidated review by opus-4.6-thinking. Individual reviews from 2/2 expected models received.


📋 Review by `gpt-5.3-codex-high`

Summary

This PR adds support for file type shortcuts and MIME types in st.file_uploader and st.chat_input, including backend normalization, frontend accept/filter logic, and UI display updates (dropzone instructions + chat upload tooltip). The implementation is mostly clean and well-tested at unit level, but there is a significant server-side validation regression that should be addressed before merge.

Code Quality

The overall structure is good: reusable type helpers were extracted, frontend filtering logic was centralized in FileHelper, and tests were expanded substantially.

Issue identified:

  • High: lib/streamlit/elements/lib/file_uploader_utils.py:137-147 now short-circuits backend filename validation whenever any MIME type is present (has_mime_types), including mixed configs like ["image/*", ".json"]. This removes extension-based defense-in-depth in those mixed cases and accepts arbitrary filenames on backend if frontend filtering is bypassed.
  • The behavior is explicitly codified as expected in lib/tests/streamlit/file_uploader_utils_test.py:170-174 (mixed_any_file expected True), which confirms this is not accidental but still risky.

Test Coverage

Coverage is strong for normalization and frontend matching logic:

  • Python unit tests for classification/normalization and restriction behavior were added.
  • Frontend unit tests cover getAccept, MIME wildcard matching, mixed MIME+extension checks, and display formatting.
  • E2E tests cover the new display behavior for shortcuts and MIME labels, plus chat tooltip text update.

Gap:

  • No end-to-end test validates backend enforcement behavior for mixed MIME+extension restrictions under a bypass-like scenario. Current tests validate and enshrine permissive backend behavior instead.

Backwards Compatibility

Mostly additive and backward compatible:

  • Existing extension-based type usage remains supported.
  • New shortcut and MIME formats are added without changing API signatures.

Compatibility concern:

  • Server-side restriction semantics are effectively weakened for any configuration including MIME types, which may surprise developers expecting extension constraints to remain enforced in mixed configurations.

Security & Risk

  • Primary risk: backend no longer enforces extension restrictions when MIME types are present (lib/streamlit/elements/lib/file_uploader_utils.py:137-147). A crafted upload request can bypass frontend filtering and submit files outside explicit extension constraints in mixed type configs.
  • This is a defense-in-depth regression rather than a direct remote exploit, but it increases risk for apps relying on type constraints for input hygiene.

Accessibility

No major accessibility regressions observed in the changed frontend code:

  • Upload button remains a semantic button with an accessible name.
  • Existing focus/tab-stop handling in the dropzone wrapper remains intact.

Minor consideration:

  • File type details are currently conveyed via tooltip content; if this information is critical, consider also exposing it in an always-available or screen-reader-friendly surface.

Recommendations

  1. Update backend validation logic to keep extension checks active in mixed MIME+extension configs where possible (e.g., use uploaded file metadata and/or extension fallback instead of unconditional early return).
  2. Add regression tests that assert backend behavior for mixed configs under non-ideal client input, so this does not silently relax further.
  3. Document the exact trust model for MIME-based filtering (frontend/browser vs backend) in API docs and inline comments to set clear expectations.

Verdict

CHANGES REQUESTED: The feature implementation is close, but backend validation is currently too permissive for mixed MIME+extension constraints and should be tightened before merge.


This is an automated AI review by gpt-5.3-codex-high. Please verify the feedback and use your judgment.

📋 Review by `opus-4.6-thinking`

Summary

This PR adds support for category shortcuts ("image", "audio", "video", "text") and MIME types ("image/jpeg", "image/*") in the type parameter of st.file_uploader and the file_type parameter of st.chat_input. Previously only file extensions (.jpg, "pdf") were supported. The implementation spans backend normalization, frontend display/filtering, and comprehensive tests.

Key changes:

  • Backend: New classify_file_type() function and updated normalize_upload_file_type() to handle shortcuts (→ MIME wildcards), MIME types (pass-through), and extensions (existing behavior). enforce_filename_restriction() skips server-side validation when MIME types are present.
  • Frontend: getAccept() in FileUploader/utils.ts now uses MIME types as direct react-dropzone accept keys. isFileTypeAllowed() moved to shared FileHelper.ts with MIME wildcard matching support. formatTypeForDisplay() added for user-friendly display (image/*"image", .jpg"JPG").
  • UI: FileDropzoneInstructions and ChatFileUploadButton tooltip now display formatted file types.

Code Quality

The code is well-structured and follows existing patterns in the codebase.

Strengths:

  • Clean separation: classify_file_typenormalize_upload_file_type → frontend getAccept/isFileTypeAllowed/formatTypeForDisplay is a clear pipeline.
  • Shared logic in FileHelper.ts (isFileTypeAllowed, isMimeType, formatTypeForDisplay) avoids duplication between FileUploader and ChatInput widgets.
  • Re-exports in FileUploader/utils.ts and ChatInput/fileUpload/fileUploadUtils.ts maintain backward compatibility for existing imports.
  • Docstrings are clear and well-written (NumPy style in Python, JSDoc in TypeScript).
  • The CATEGORY_SHORTCUTS frozenset is appropriately module-level and immutable.

Minor observations:

  • In ChatFileUploadButton.tsx (line 71), getTooltipContent is a closure recreated each render. Given it's a simple string computation for a tooltip, the cost is negligible, but it could alternatively be a derived value rather than a function for slight readability improvement.
  • In classify_file_type (line 63), the MIME check uses "/" in value (on the original, non-stripped value). This is correct since "/" is unaffected by strip/lower, but it's a subtle divergence from the normalized variable used for the shortcut check. Not a bug, just a style note.

Test Coverage

Test coverage is comprehensive across all three layers:

Backend (Python):

  • ClassifyFileTypeTest: Parameterized tests for shortcuts, MIME types, extensions, case insensitivity, and whitespace handling.
  • FileUploaderUtilsTest: Extended with category shortcuts, MIME types, MIME wildcards, mixed types, extension pairing with mixed types, whitespace handling, and empty string filtering.
  • EnforceFilenameRestrictionMimeTypeTest: New test class covering MIME-only (validation skipped), mixed (validation skipped), extension-only (still validated), and null byte injection with MIME types.

Frontend (TypeScript):

  • FileHelper.test.ts: Tests for isMimeType, formatTypeForDisplay, isFileTypeAllowed (MIME matching, wildcard matching, case insensitivity, mixed types, directory uploads).
  • utils.test.ts (FileUploader): Tests for getAccept with MIME types, wildcards, mixed types, and extensions.
  • fileUploadUtils.test.ts (ChatInput): MIME type matching, wildcard matching, mixed MIME+extension matching, case insensitivity.
  • FileDropzoneInstructions.test.tsx: Tests for rendering MIME wildcards as category names, full MIME types, and mixed types.

E2E (Playwright):

  • test_file_uploader_type_shortcuts: Verifies display of type="image" as "image" (not "image/*") and mixed types rendering.
  • test_single_file_upload_button_tooltip: Updated to verify tooltip includes file type "(TXT)".

Coverage gaps (non-blocking):

  • No E2E test for actual file upload/rejection with MIME-type-only restrictions. Unit tests cover the logic adequately, and E2E testing of browser MIME filtering is inherently limited.
  • No frontend unit test for ChatFileUploadButton rendering the tooltip with fileTypes prop (the E2E test covers this path).

Backwards Compatibility

The changes are fully backward compatible:

  1. Extension handling preserved: Existing type=["png", ".jpg"] calls produce identical normalized output. Extension pairing (.jpg.jpeg) continues to work.
  2. Frontend display unchanged for extensions: formatTypeForDisplay(".jpg")"JPG" matches the previous inline type.replace(/^\./, "").toUpperCase() behavior.
  3. Backend validation unchanged for extension-only types: enforce_filename_restriction still validates when only extensions are specified.
  4. New behavior is additive: Shortcuts and MIME types are new input formats that previously would have been treated as extensions (e.g., type="image" would have become type=[".image"]). This is now correctly interpreted as a category shortcut.
  5. Tooltip text change: ChatFileUploadButton tooltip now appends file types (e.g., "(TXT)"). This is a UI improvement, not a breaking change.

Security & Risk

Security trade-off (by design, well-documented):

  • enforce_filename_restriction (file_uploader_utils.py, lines 137-147) skips backend validation entirely when any MIME type is present in allowed_types. This means a malicious client bypassing the browser could upload arbitrary files when MIME types or shortcuts are used.
  • This is a deliberate and documented trade-off: the backend only has access to the filename (not the file's MIME type), so it cannot validate MIME-based restrictions server-side.
  • The null byte injection check (line 134) always runs regardless of MIME types, preserving the existing security boundary.
  • For extension-only restrictions, the server-side validation is fully preserved.

Risk assessment: Low. The frontend still enforces MIME filtering via react-dropzone's accept attribute and the isFileTypeAllowed validator. The backend never had MIME-based validation before (it's a new feature), so no existing security guarantee is weakened.

Accessibility

  • The ChatFileUploadButton tooltip now includes file type information, improving discoverability for all users.
  • The aria-label on the upload button (Upload ${getUploadDescription(acceptFile)}) does not include file types — this is acceptable since the tooltip provides additional context on hover, and the aria-label remains concise for screen readers.
  • FileDropzoneInstructions displays formatted file types in visible text, accessible to all users including screen reader users.
  • No new interactive elements or focus changes introduced.

Recommendations

  1. Consider adding MIME type format validation on the backend (normalize_upload_file_type): Currently, any string containing "/" is treated as a MIME type and passed through. Adding a basic regex check (e.g., type/subtype format) could catch user typos like "image / jpeg" (with spaces around the slash) and provide a helpful error message. This is a minor enhancement, not blocking.

  2. Consider memoizing the tooltip content in ChatFileUploadButton: The getTooltipContent function (line 71-77) could be replaced with a useMemo or an inline computed value since it only depends on acceptFile and fileTypes. This is a micro-optimization and purely stylistic.

  3. Consider a frontend unit test for ChatFileUploadButton with fileTypes prop: While the E2E test covers this, a unit test asserting the tooltip content format would be faster to run and more targeted for regression detection.

Verdict

APPROVED: Well-structured feature addition with comprehensive test coverage, clean separation of concerns, full backward compatibility, and a clearly documented security trade-off for MIME-based restrictions. The code follows existing patterns and conventions throughout.


This is an automated AI review by opus-4.6-thinking.

Comment on lines +21 to +22
// Re-export for backwards compatibility
export { isFileTypeAllowed }
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

suggestion (non-blocking): This doesn't seem to be improted in many places, this could be a good opportunity to remove the unnecessary re-export.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Removed 👍

.map(ext => ext.replace(/^\./, "").toUpperCase())
.join(", ")}`
if (acceptedTypes.length) {
return ` • ${acceptedTypes.map(formatTypeForDisplay).join(", ")}`
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

suggestion (non-blocking): For maintainability, consider DRYing this and the logic in getTooltipContent in frontend/lib/src/components/widgets/ChatInput/fileUpload/ChatFileUploadButton.tsx.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Extracted into its own function

Comment on lines +21 to +22
// Re-export for backwards compatibility and local usage
export { isMimeType }
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

suggestion: Remove this, it looks like isMimeType was added in this PR, so there are no needed usages to maintain.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Removed 👍

Comment on lines +24 to +25
// Custom MIME type for file extensions that don't have a standard MIME type mapping.
// This acts as a fallback to allow any file with matching extensions.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

suggestion (non-blocking): Utilize JSDoc for this comment for better ecosystem integration.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Changed 👍

lukasmasuch and others added 2 commits March 2, 2026 22:54
Consolidate utility functions in FileHelper.ts by removing backwards
compatibility re-exports and adding a formatTypesForDisplay helper for
cleaner array formatting.
@lukasmasuch lukasmasuch enabled auto-merge (squash) March 3, 2026 09:45
@lukasmasuch lukasmasuch merged commit ee026de into develop Mar 3, 2026
42 checks passed
@lukasmasuch lukasmasuch deleted the lukasmasuch/file-type-shortcuts branch March 3, 2026 10:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants