Skip to content

Users: Validate avatar uploads against configured image file types - #23415

Merged
AndyButland merged 8 commits into
v17/devfrom
claude/confident-bardeen-8kk7ey
Jul 20, 2026
Merged

AndyButland merged 8 commits into
v17/devfrom
claude/confident-bardeen-8kk7ey

Conversation

@iOvergaard

@iOvergaard iOvergaard commented Jul 17, 2026 •

Copy link
Copy Markdown
Contributor

Prerequisites

  • I have added steps to test this contribution in the description below

Description

This PR fixes avatar upload validation in UserService.SetAvatarAsync so it validates the uploaded file extension against the configured image file types with correct set semantics.

Bugs fixed

  • Configured image types were ignored. Validation used a hardcoded extension list instead of the configured Umbraco:CMS:Content:Imaging:ImageFileTypes. It now validates against ContentSettings.Imaging.ImageFileTypes.
  • Substring matching. The old check did a substring match on a comma-joined string, so invalid extensions passed if they were a substring of an allowed one (e.g. if matched inside tiff). Replaced with proper case-insensitive set membership (InvariantContains).
  • Crash on extension-less filenames. Path.GetExtension(...)[1..] threw when the file had no extension. Empty extensions are now guarded and rejected.
  • Case-sensitive disallowed check. The DisallowedUploadedFileExtensions check is now case-insensitive.

Extension extraction uses the existing GetFileExtension() helper, and the validation is extracted into a small IsAllowedAvatarFileExtension helper for readability.

Tests

Added unit tests in UserServiceSetAvatarTests covering:

  • Accepting the default configured image file types (incl. case-insensitive matching)
  • Rejecting invalid extensions, including the if/tiff substring case and extension-less filenames
  • Respecting a custom configured ImageFileTypes list (accepting configured types, rejecting others)
  • The disallowed-extensions list overriding an otherwise-allowed image type

Test Plan

Run the unit test suite:

dotnet test tests/Umbraco.Tests.UnitTests --filter "FullyQualifiedName~UserServiceSetAvatarTests"

All 12 test cases pass.

https://claude.ai/code/session_019hpAn9TYikmsqKt3bFwtoo

Copilot AI review requested due to automatic review settings July 17, 2026 13:25
@claude

claude Bot commented Jul 17, 2026 •

Copy link
Copy Markdown

Claude finished @iOvergaard's task in 6m 49s —— View job


PR Review Complete

  • Gather context and load review skill
  • Load review standards and area documentation
  • Read changed files
  • Perform code review
  • Post findings (inline comments + summary)
  • Apply labels (area/backend, area/frontend)

Review posted. Summary below.


Verdict: Approved with Suggestions for improvement

The PR correctly fixes three real bugs in the old validation code (substring match on comma-joined string, unchecked index-1 slice on extension-less filenames, hardcoded list ignoring configured ImageFileTypes). Tests are well-structured and cover the main scenarios.

Important finding: Accepting SVG for avatar uploads is a new user-writable XSS surface — SVG files served as image/svg+xml can carry embedded scripts. This mirrors the existing ResizeImageUrlFactory SVG carve-out, but avatars are accessible to any authenticated backoffice user. Worth either adding SVG sanitization on upload or explicitly documenting the accepted risk.

Suggestions: Test method names don't follow the Can_/Cannot_ convention; the custom-config test is missing an SVG case; and the displayableImageFileTypes() JSDoc (not in diff) should be updated to clarify it is appropriate for avatar upload contexts after this change.

Copilot AI left a comment

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.

Pull request overview

This PR aims to make avatar uploads consistent between the backoffice UI and the backend by using the configured image file types (plus an SVG carve-out) instead of a hardcoded allow-list.

Changes:

  • Updated UserService.SetAvatarAsync to validate avatar extensions against ContentSettings.Imaging.ImageFileTypes with a special-case for svg and case-insensitive matching.
  • Updated backoffice avatar upload components to use displayableImageFileTypes() when building the file input accept list.
  • Added unit tests covering default/configured file types, disallowed extensions overriding, and case-insensitive matching.

Reviewed changes

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

File Description
src/Umbraco.Core/Services/UserService.cs Switches avatar validation to configured image types and adds SVG carve-out/disallowed override logic.
src/Umbraco.Web.UI.Client/src/packages/user/user/workspace/user/components/user-workspace-avatar/user-workspace-avatar.element.ts Aligns avatar upload accept list with “displayable” image types from temporary file config.
src/Umbraco.Web.UI.Client/src/packages/user/current-user/workspace/current-user-workspace-avatar.element.ts Same alignment for current-user avatar upload accept list.
tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/UserServiceSetAvatarTests.cs Adds unit test coverage for the new backend avatar extension validation behavior.

Comment thread src/Umbraco.Core/Services/UserService.cs Outdated
Comment thread src/Umbraco.Core/Services/UserService.cs Outdated
Comment thread src/Umbraco.Core/Services/UserService.cs Outdated
Comment thread tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/UserServiceSetAvatarTests.cs Outdated
Comment thread tests/Umbraco.Tests.UnitTests/Umbraco.Core/Services/UserServiceSetAvatarTests.cs Outdated
@claude

claude Bot commented Jul 17, 2026

Copy link
Copy Markdown

PR Review

Target: origin/main · Based on commit: 1bf35ae · Reviewed: 4 files (0 noise files skipped)

Fixes a genuine bug in avatar upload validation — the old code used substring matching on a comma-joined string (so .if slipped through as a substring of tiff), threw on extension-less filenames, ignored the configured ImageFileTypes, and used a case-sensitive HashSet.Contains for the disallowed extensions list. The replacement correctly uses GetFileExtension(), InvariantContains, and the configuration-aware allowlist, with an explicit SVG carve-out to mirror the existing pattern in ResizeImageUrlFactory.

  • Other changes: Avatar upload now accepts SVG files (new); custom Umbraco:CMS:Content:Imaging:ImageFileTypes configuration is now respected (previously silently overridden by a hardcoded list). Both changes affect the set of extensions a backoffice user may use for their avatar.

Important

  • src/Umbraco.Core/Services/UserService.cs:1029: SVG accepted for avatar uploads is a new user-writable stored XSS surface — see inline comment.

Suggestions

  • tests/.../UserServiceSetAvatarTests.cs:40: Test method names don't follow the Can_/Cannot_ convention — see inline comment.
  • tests/.../UserServiceSetAvatarTests.cs:53: Custom-config test is missing an SVG case to document that the SVG carve-out applies regardless of configured ImageFileTypes — see inline comment.
  • src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/config/config.repository.ts:87 (not in diff): The displayableImageFileTypes() JSDoc says "not for upload contexts" — the two frontend changes in this PR correctly use it for upload (because the server now accepts SVG for avatars), but the JSDoc should be updated to reflect this so future readers don't second-guess the pattern.

Approved with Suggestions for improvement

Good to go — the bug fix and test coverage are solid. Please carefully consider the SVG XSS note above (even if the decision is to accept the risk given the existing codebase precedent, a brief comment in the code documenting that conclusion would help future reviewers).

@iOvergaard
iOvergaard force-pushed the claude/confident-bardeen-8kk7ey branch 2 times, most recently from d659e4e to 35efd51 Compare July 17, 2026 13:50
SetAvatarAsync ignored the configured Umbraco:CMS:Content:Imaging:ImageFileTypes
list and used a hardcoded set, and checked membership with a substring match on
the comma-joined string, so extensions like "if" passed by matching inside
"tiff". The extension-less case also threw from Path.GetExtension(...)[1..].

Validate against ContentSettings.Imaging.ImageFileTypes using the existing
GetFileExtension helper and case-insensitive set membership, guard empty
extensions, and apply the same invariant check to DisallowedUploadedFileExtensions.
The backoffice avatar upload continues to build its accept list from the same
configured imageFileTypes, keeping client and server in sync.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hpAn9TYikmsqKt3bFwtoo
@AndyButland

Copy link
Copy Markdown
Contributor

Is this intentionally targeted at main? If not, could you re-target for v17/dev please

@iOvergaard
iOvergaard changed the base branch from main to v17/dev July 17, 2026 18:29
@iOvergaard

Copy link
Copy Markdown
Contributor Author

@AndyButland I thought it might not be stable enough to be changing this behaviour outright on v17, but the rebase itself went smoothly. We can see how it pans out on v17.

@iOvergaard

Copy link
Copy Markdown
Contributor Author

I had already added the 17.7.0 label some hours ago anyway (muscle memory, haha!).

@iOvergaard iOvergaard changed the title User: Align avatar validation with displayable image types Users: Validate avatar uploads against configured image file types Jul 17, 2026

@AndyButland AndyButland left a comment

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.

Looks good @iOvergaard.

I made a couple of changes relating to testing, as what you had originally, whilst covering well, introduced a unit test on UserService that needed a lot of mocking. I extracted the core logic of this PR into an extension method where it could be more easily fully unit tested, and extended the existing integration test to had a couple of higher level tests.

Manually verified via the backoffice UI to confirm that image settings are respected when selecting images for an avatar.

I did find an unexpected issue, in that configuring images seems to only be additive. E.g.:

  "Imaging": {
    "ImageFileTypes": [
      "jpg",
      "png",
      "svg"
    ]
  },

This doesn't remove the default image types to leave only the three listed. So removal has to be done with code (e.g. in Program.cs):

builder.Services.PostConfigure<ContentSettings>(settings =>
{
    settings.Imaging.ImageFileTypes.Remove("svg");
    settings.Imaging.ImageFileTypes.Remove("webp");
});

With this done I can no longer select .webp or .svg images for the avatar.

@AndyButland
AndyButland enabled auto-merge (squash) July 20, 2026 05:22
@sonarqubecloud

Copy link
Copy Markdown

@AndyButland
AndyButland disabled auto-merge July 20, 2026 05:37
@AndyButland
AndyButland enabled auto-merge (squash) July 20, 2026 05:44
@AndyButland
AndyButland merged commit afa0646 into v17/dev Jul 20, 2026
30 checks passed
@AndyButland
AndyButland deleted the claude/confident-bardeen-8kk7ey branch July 20, 2026 05:53
@iOvergaard

Copy link
Copy Markdown
Contributor Author

I did find an unexpected issue, in that configuring images seems to only be additive.

That doesn't sound right, @AndyButland. While that has probably very little to do with this pull request, I am surprised if that is how appsettings work. Are we sure about that?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants