Skip to content

Commit 61a9bf9

Browse files
committed
Hardens custom avatar URL against email-driven URL injection
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)
1 parent 2348076 commit 61a9bf9

1 file changed

Lines changed: 16 additions & 10 deletions

File tree

packages/git/src/remotes/custom.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,22 @@ export class CustomRemoteProvider extends RemoteProvider {
3636
}
3737

3838
getUrlForAvatar(email: string, size: number): string | undefined {
39-
if (this.urls.avatar != null) {
40-
const [emailName, domain] = email.split('@');
41-
return this.encodeUrl(
42-
interpolate(
43-
this.urls.avatar,
44-
this.getContext({ emailName: emailName, domain: domain, email: email, size: String(size) }),
45-
),
46-
);
47-
}
48-
return undefined;
39+
if (this.urls.avatar == null) return undefined;
40+
41+
// Split on the last `@` so local-parts that contain `@` are preserved (per RFC 5322)
42+
const at = email.lastIndexOf('@');
43+
const emailName = at === -1 ? email : email.slice(0, at);
44+
const domain = at === -1 ? '' : email.slice(at + 1);
45+
46+
// Component-encode identity values — commit emails are attacker-controllable and
47+
// must never be able to inject URL-structural characters (`/`, `?`, `#`, ...) into the
48+
// resulting URL
49+
return interpolate(this.urls.avatar, {
50+
email: encodeURIComponent(email),
51+
emailName: encodeURIComponent(emailName),
52+
domain: encodeURIComponent(domain),
53+
size: String(size),
54+
});
4955
}
5056

5157
protected override getUrlForRepository(): string {

0 commit comments

Comments
 (0)