You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Code Review — PR #334: Support contributor author avatars
Overview
This PR adds support for local avatar images in contribution author profiles. Authors can now reference a local relative path (e.g., ./images/jane-doe.webp), which is validated for path traversal, file existence, extension, and size — then copied into the website's asset directory and rewritten to a site-rooted URL during import. A bonus socialAutoGenerate build-step override is also included.
The implementation is generally well-structured and follows existing patterns in the codebase. Below are specific issues found.
Issues
1. Side effects inside a validation method (medium)
WebContributionProcessor.Validation.cs, line 108
profile.AvatarSourcePath=fullPath;
ValidateAuthorAvatar mutates profile.AvatarSourcePath as a side effect of validation. The enclosing ValidateAuthorProfiles takes an IReadOnlyDictionary, which signals "we won't change anything" — but the profile objects inside it are silently mutated. This makes validation carry hidden import-prep responsibilities. Consider splitting into ValidateAuthorAvatar (pure, returns errors) and ResolveAuthorAvatarPath (sets AvatarSourcePath, called separately in the import path), or at minimum rename the method to something like ValidateAndResolveAuthorAvatar and drop the IReadOnlyDictionary facade.
PrepareAuthorProfileForImport mutates errors and result.CopiedAssetCount — but is called inside a LINQ Select, which is semantically a pure projection. The ToArray() forces full evaluation, meaning all profiles are processed (and their avatars copied) even if an earlier one fails. The errors.Count > 0 guard only prevents writing the catalog; already-copied avatar files from successful profiles are left in place when a later copy fails. A foreach loop with early exit on failure would be clearer and more correct:
This logic is also used for StaticBlogAssetsPath elsewhere, so it's consistent — but it hardcodes the convention that Hugo-style static/ is stripped for the public URL. If someone configures a non-default TargetAuthorAssetsPath that happens to start with static/ for unrelated reasons, they'd silently get a wrong URL. At minimum, a code comment explaining the Hugo convention would help future maintainers.
4. AvatarSourcePath assigned before all validation checks pass (minor)
profile.AvatarSourcePath = fullPath is set before the extension and size checks. This means a file with an unsupported extension or excessive size still ends up with a populated AvatarSourcePath. Since errors block the import, this doesn't cause a bug in practice — but it means a later reader of the profile can't trust that a non-null AvatarSourcePath implies the avatar passed all checks. Assigning only after all checks pass (or checking AvatarSourcePath only when errors.Count == 0) would be safer by convention.
5. Unquoted avatar paths in test YAML helper (minor)
Bare ../jane-doe.webp and ./images/jane-doe.webp happen to parse correctly with most YAML parsers, but YAML path values should ideally be quoted to be unambiguous and to avoid surprises with values like ./foo: bar. Using $"avatar: \"{avatar}\"\n" is safer and matches what a real author YAML file would likely contain.
Missing Test Coverage
The new tests cover the happy path (copy + catalog rewrite) and one security path (traversal rejection). The following cases are not tested:
Unsupported extension: An avatar file ending in .exe or .txt should produce an error — currently untested.
Size limit: A file exceeding MaxAssetBytes should be rejected — currently untested.
Non-existent avatar file: A valid relative path pointing to a file that doesn't exist should produce an "avatar does not exist" error — currently untested.
External URL avatar: An https:// avatar should pass through import unchanged — currently untested.
Root-path avatar: A /assets/... avatar should pass through unchanged — currently untested.
These edge cases are well-handled in the implementation; adding tests would complete the coverage and guard against regressions.
Positive Notes
The path-traversal guard (TryResolveAuthorAsset) correctly uses Path.GetFullPath + prefix check — same pattern as the existing TryResolveBundleAsset.
Creating a clean imported copy in PrepareAuthorProfileForImport (rather than mutating the original profile) is a good choice.
The TargetAuthorAssetsPath option has a sensible default and is validated with TryResolveInside before use.
The socialAutoGenerate / social-auto-generate alias pair follows the established naming convention for pipeline step options.
Schema update for the new pipeline property is included.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Validation