Launch Week Day 1: Announcing Security Design Review
MEDIUM 4.8 Maven

Emissary has Stored XSS via Navigation Template Link Injection

GHSA-cpm7-cfpx-3hvp · CVE-2026-35571

Published · Modified

Description

Summary

Mustache navigation templates interpolated configuration-controlled link values
directly into href attributes without URL scheme validation. An administrator
who could modify the navItems configuration could inject javascript: URIs,
enabling stored cross-site scripting (XSS) against other authenticated users
viewing the Emissary web interface.

Details

Vulnerable code — nav.mustache (line 10)

{{#navItems}}
<li class="nav-item">
  <a class="nav-link" href="{{link}}">{{display}}</a>
</li>
{{/navItems}}

The {{link}} value was rendered without any scheme validation. Mustache's
default HTML escaping protects against injection of new HTML tags but does
not prevent javascript: URIs in href attributes, since javascript:
contains no characters that HTML-escaping would alter.

Attack vector

An administrator sets a navigation item's link to:

javascript:alert(document.cookie)

Any authenticated user who clicks the navigation link executes the script in
their browser context.

Impact

  • Session hijacking via cookie theft
  • Actions performed on behalf of the victim user
  • Requires administrative access to modify navigation configuration
  • Requires user interaction (clicking the malicious link)

Mitigating factors

  • Exploitation requires administrative access to modify the navItems
    configuration
  • User interaction (clicking the link) is required
  • The Emissary web interface is typically accessed only by authenticated
    operators within a trusted network

Remediation

Fixed in PR #1293,
merged into release 8.39.0.

Server-side link validation — NavAction.java

An allowlist regex was added that only permits http://, https://, or
site-relative (/) URLs:

private static final Pattern VALID_LINK = Pattern.compile("^(https?:/)?/.*");

private static boolean isValidLink(String link) {
    if (!VALID_LINK.matcher(link).matches()) {
        logger.warn("Skipping invalid navigation link '{}'", link);
        return false;
    }
    return true;
}

Invalid links are logged and silently dropped from the rendered navigation.

Template hardening — nav.mustache

Added rel="noopener noreferrer" to all navigation link anchor tags as a
defense-in-depth measure:

<a class="nav-link" href="{{link}}" rel="noopener noreferrer">{{display}}</a>

Tests were added to verify that javascript: and ftp:// URIs are rejected
while http://, https://, and site-relative (/path) links are accepted.

Workarounds

If upgrading is not immediately possible, audit the navigation configuration
to ensure all navItems link values use only http://, https://, or
relative (/) URL schemes.

References

Ready to move

Start Securing

Free, no credit card | First findings in minutes