Adds custom avatar URL for custom remotes#5155
Conversation
5de1d12 to
7505314
Compare
🤖 Augment PR SummarySummary: Adds support for resolving commit-author avatars via a user-configured URL template for “Custom” remotes. Changes:
Technical Notes: Custom avatar URLs are only honored in trusted workspaces and require explicit user approval; resolved URIs are cached alongside existing avatar sources. 🤖 Was this summary useful? React with 👍 or 👎 |
Commit emails are attacker-controllable, so identity values interpolated
into a `gitlens.remotes` `avatar` template must not be allowed to inject
URL-structural characters. `encodeUrl` uses `encodeURI`, which preserves
`/`, `?`, `#`, `@`, and `:`, so a crafted email could previously bend
the resulting avatar URL's path, query, or fragment.
- Component-encodes `${email}`, `${emailName}`, and `${domain}` before
interpolation
- Splits on the last `@` so RFC 5322 local-parts containing `@` are
preserved (and `domain` can't be truncated by a multi-`@` email)
- Drops `getContext(...)` and the outer `encodeUrl(...)` — only the four
documented tokens are relevant for an avatar URL, and every
substituted value is now pre-encoded
(#5155)
905f7b6 to
1167eb8
Compare
Commit emails are attacker-controllable, so identity values interpolated
into a `gitlens.remotes` `avatar` template must not be allowed to inject
URL-structural characters. `encodeUrl` uses `encodeURI`, which preserves
`/`, `?`, `#`, `@`, and `:`, so a crafted email could previously bend
the resulting avatar URL's path, query, or fragment.
- Component-encodes `${email}`, `${emailName}`, and `${domain}` before
interpolation
- Splits on the last `@` so RFC 5322 local-parts containing `@` are
preserved (and `domain` can't be truncated by a multi-`@` email)
- Drops `getContext(...)` and the outer `encodeUrl(...)` — only the four
documented tokens are relevant for an avatar URL, and every
substituted value is now pre-encoded
(#5155)
…ate for custom remotes, detailing security measures and user approval process. (#5155)
…ate for custom remotes, detailing security measures and user approval process. (#5155)
1167eb8 to
26d5395
Compare
…ate for custom remotes, detailing security measures and user approval process. (#5155)
26d5395 to
7c8578c
Compare
|
augment review |
…ate for custom remotes, detailing security measures and user approval process. (#5155)
7c8578c to
8c4d40a
Compare
|
augment review |
| if (hasAvatarSource) { | ||
| // A provider was consulted but returned no avatar — permanently cache "no result" | ||
| avatar.uri = undefined; | ||
| avatar.timestamp = Infinity; |
There was a problem hiding this comment.
hasAvatarSource can become true for a CustomRemoteProvider even when getApprovedCustomRemoteAvatarUrl(...) returns undefined due to an untrusted workspace, missing template, or not-yet-approved template. In that case this avatar.timestamp = Infinity permanently caches “no result” and can prevent avatars from starting to resolve later in the same session if trust/config/approval changes without an explicit cache reset.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
Covers token interpolation, email parsing edge cases, and URI encoding to ensure special characters cannot break URL structure or enable injection. (#5155)
28363a0 to
bd91240
Compare
Only permanently caches "no avatar" when a provider was actually consulted, preventing fallback sources from being skipped on retry when no integration or custom remote was available. (#5155)
Covers token interpolation, email parsing edge cases, and URI encoding to ensure special characters cannot break URL structure or enable injection. (#5155)
bd91240 to
13f4921
Compare
Rebase FailedError DetailsError: No merge base found between HEAD and REBASE_HEAD. Repository is a shallow clone — try running "git fetch --unshallow" Summary
Generated by Merge Mate at 2026-06-06 06:42:56 UTC |
Commit emails are attacker-controllable, so identity values interpolated
into a `gitlens.remotes` `avatar` template must not be allowed to inject
URL-structural characters. `encodeUrl` uses `encodeURI`, which preserves
`/`, `?`, `#`, `@`, and `:`, so a crafted email could previously bend
the resulting avatar URL's path, query, or fragment.
- Component-encodes `${email}`, `${emailName}`, and `${domain}` before
interpolation
- Splits on the last `@` so RFC 5322 local-parts containing `@` are
preserved (and `domain` can't be truncated by a multi-`@` email)
- Drops `getContext(...)` and the outer `encodeUrl(...)` — only the four
documented tokens are relevant for an avatar URL, and every
substituted value is now pre-encoded
(#5155)
…ate for custom remotes, detailing security measures and user approval process. (#5155)
13f4921 to
2590992
Compare
The V8 smi optimization (2^30) was applied to the avatar cache timestamp in 2975af4, replacing Number.MAX_SAFE_INTEGER. Unlike the annotation providers where the value is compared against line/column numbers, here it is compared against Date.now() (~1.75 trillion ms), so the "never expire" sentinel (~1.07 billion) always appears expired. This caused repeated async re-lookups for repos with remotes but no integration. Uses Infinity as the sentinel — makes hasAvatarExpired return false without any arithmetic, and communicates the "never expire" intent directly. (#5155)
Only permanently caches "no avatar" when a provider was actually consulted, preventing fallback sources from being skipped on retry when no integration or custom remote was available. (#5155)
Covers token interpolation, email parsing edge cases, and URI encoding to ensure special characters cannot break URL structure or enable injection. (#5155)
Commit emails are attacker-controllable, so identity values interpolated
into a `gitlens.remotes` `avatar` template must not be allowed to inject
URL-structural characters. `encodeUrl` uses `encodeURI`, which preserves
`/`, `?`, `#`, `@`, and `:`, so a crafted email could previously bend
the resulting avatar URL's path, query, or fragment.
- Component-encodes `${email}`, `${emailName}`, and `${domain}` before
interpolation
- Splits on the last `@` so RFC 5322 local-parts containing `@` are
preserved (and `domain` can't be truncated by a multi-`@` email)
- Drops `getContext(...)` and the outer `encodeUrl(...)` — only the four
documented tokens are relevant for an avatar URL, and every
substituted value is now pre-encoded
(#5155)
…ate for custom remotes, detailing security measures and user approval process. (#5155)
The V8 smi optimization (2^30) was applied to the avatar cache timestamp in 2975af4, replacing Number.MAX_SAFE_INTEGER. Unlike the annotation providers where the value is compared against line/column numbers, here it is compared against Date.now() (~1.75 trillion ms), so the "never expire" sentinel (~1.07 billion) always appears expired. This caused repeated async re-lookups for repos with remotes but no integration. Uses Infinity as the sentinel — makes hasAvatarExpired return false without any arithmetic, and communicates the "never expire" intent directly. (#5155)
Only permanently caches "no avatar" when a provider was actually consulted, preventing fallback sources from being skipped on retry when no integration or custom remote was available. (#5155)
Covers token interpolation, email parsing edge cases, and URI encoding to ensure special characters cannot break URL structure or enable injection. (#5155)
2590992 to
747cec7
Compare
Rebases and integrates #1636 (by @tmkx) on top of the current
mainafter the@gitlens/gitpackage split.Summary
Adds an optional
avatarURL template togitlens.remotes[].urls, enabling corporate / self-hosted setups to resolve commit-author avatars through a custom URL."gitlens.remotes": [ { "domain": "gitlab.intranet.com", "type": "Custom", "urls": { "repository": "https://gitlab.intranet.com/${repo}", ... + "avatar": "https://avatar.intranet.com/employee?username=${emailName}&size=${size}" } } ]Available tokens:
${email},${emailName}(email local-part),${domain},${size}.When no hosting integration resolves the commit author,
getAvatarUriFromRemoteProvidernow falls back to the best remote-with-provider; if that provider is aCustomRemoteProviderwithavatarconfigured, its interpolated URL is cached and returned.Differences vs. original PR #1636
@gitlens/gitpackage split —CustomRemotewas renamed toCustomRemoteProviderand moved topackages/git/src/remotes/custom.ts; theRemotesUrlsConfiginterface in the package needed the sameavatar?: stringaddition assrc/config.ts.${name}token renamed to${emailName}— disambiguates from the commit-author display name. The schema description already labels it as "email local-part", but the variable name itself was potentially confusing.encodeUrl(=encodeURI), which leaves/,?,#,@, and:intact — so a crafted email could bend the resulting avatar URL's path, query, or fragment.getUrlForAvatarnow component-encodes${email},${emailName}, and${domain}before interpolation, splits the email on the last@(so RFC 5322 local-parts containing@aren't truncated), and drops the now-redundant outerencodeUrl.markdownDescriptionfor theavatarfield ("a avatar url"→"an avatar URL").close #302andclose #1036. This PR closes Get GitLab avatar by email #302 only. Issue Get avatar from user #1036 asks for a standalonegitlens.avatarssetting (map ofemail→ file URI, or a folder of images) so that users without a custom remote — e.g., regular github.com users who just want local employee photos — can configure avatars. That specific mechanism is not delivered here: this PR requires agitlens.remotesentry with"type": "Custom"and anavatartemplate, so Get avatar from user #1036 should remain open.Refs #1036 · Closes #302
Test plan
gitlens.remotesentry of"type": "Custom"and anavatartemplate, commit-author avatars in the blame hover, commit details, and views resolve to the templated URL.avatartemplate, behavior is unchanged (Gravatar / hosting-integration path).${emailName},${email},${domain},${size}tokens all interpolate.avatarfield with the correct description.a/b@host) produces a URL that preserves the email verbatim via component-encoding, without bending the template's path/query.