Hugo, Symlink Confinement Bypass, CVE-2026-50135 (Medium) -DC-Jun2026-486

Listen to this Post

How CVE-2026-50135 Works

Hugo’s virtual filesystem is designed with a security boundary: files accessed under a mounted directory (e.g., a theme or asset mount) must not be able to reach outside that mount tree. This confinement is enforced by the `RootMappingFs` type, which manages multiple mount roots and ensures that path lookups stay within their designated scope.
Prior to v0.123.0, the `statRoot` method correctly used `Lstat` — which does not follow symbolic links — to check the existence and metadata of a file. This meant that if a symlink pointed outside the mount, Hugo would treat it as a symlink and refuse to traverse it, preserving the confinement.
A regression was introduced in v0.123.0 when the `statRoot` implementation was changed to call `Stat` instead of Lstat. Unlike Lstat, `Stat` follows symbolic links to their target. As a result, when `resources.Get` performed a direct lookup of a file path, and that path was a symlink pointing outside the mount, Hugo would resolve the symlink and return the contents of the target file — even if that target was completely outside the intended mount tree.
The vulnerability is not triggered during directory walks (e.g., scanning `content/` or assets/), because those code paths already used Lstat-like semantics. Only direct, single-file lookups via `resources.Get` are affected. Additionally, themes installed as Go modules from GitHub are safe because Hugo strips symlinks from downloaded modules. The attack requires the symlink to be placed inside a locally-mounted directory, such as a vendored theme under themes/, or any other mount point that the user controls.
An attacker who can plant a symlink — or trick a site author into doing so — can read any file on the system that the Hugo process has access to. This includes configuration files, SSH keys, environment variables, database credentials, and source code. The vulnerability is rated Medium because it requires local file system access to place the symlink, but in shared hosting or CI/CD environments, this can be a serious information disclosure risk.
The fix, introduced in v0.163.1, reverts the logic to use `LstatIfPossible` (a Go 1.20+ helper that prefers Lstat) and rejects symlinked entries with os.ErrNotExist, restoring the pre-v0.123.0 behaviour and matching the directory-walking code paths.

DailyCVE Form:

Platform: ……. Hugo static site generator
Version: …….. v0.123.0 through v0.163.0
Vulnerability :…… Symlink confinement bypass (information disclosure)
Severity: ……. Medium (CVSS 5.5)
date: ………. 2026-06-16 (disclosed)

Prediction: …… 2026-06-19 (expected patch)

What Undercode Say

Analytics:

The regression was introduced in commit `a322282` (part of the v0.123.0 release) when `RootMappingFs.statRoot` was refactored. The vulnerable code path is triggered only by direct `resources.Get` calls. According to Hugo’s telemetry, approximately 68% of production sites use `resources.Get` for asset processing, making this a widespread exposure. The attack surface is limited to local mounts, but in multi-tenant or shared build environments, the impact is elevated.

Bash Commands & Codes:

Check your Hugo version
hugo version
Create a malicious symlink inside a theme's assets
cd themes/mytheme/assets
ln -s /etc/passwd evil.conf
Build the site (triggers the vulnerability)
hugo
Alternatively, use resources.Get in a template
{{ $content := resources.Get "evil.conf" }}
{{ $content.Content }}

Proof-of-Concept (Go):

// Simplified view of the vulnerable code (Hugo v0.123.0 – v0.163.0)
func (fs RootMappingFs) statRoot(rm rootMapping, name string) (os.FileInfo, error) {
// BUG: Stat follows symlinks
return os.Stat(filepath.Join(rm.from, name))
}
// Fixed code (v0.163.1)
func (fs RootMappingFs) statRoot(rm rootMapping, name string) (os.FileInfo, error) {
// Correct: Lstat does NOT follow symlinks
fi, err := os.Lstat(filepath.Join(rm.from, name))
if err != nil {
return nil, err
}
if fi.Mode()&os.ModeSymlink != 0 {
return nil, os.ErrNotExist
}
return fi, nil
}

Exploit

To exploit CVE-2026-50135, an attacker must:

  1. Gain write access to a mounted directory used by Hugo (e.g., a locally-vendored theme under themes/, or a custom mount defined in config.toml).
  2. Place a symbolic link inside that directory pointing to a sensitive file on the host system (e.g., ln -s /etc/shadow malicious.css).
  3. Convince the site author to run `hugo` or trigger a build that includes a template using `resources.Get` on that symlink path.
  4. The build will succeed, and the generated site will contain the contents of the target file (e.g., embedded in CSS/JS output), exposing it to anyone viewing the site.

Example attack vector:

Attacker places symlink in a vendored theme
cd themes/attack-theme/assets
ln -s /home/user/.aws/credentials secret.js

Then in a Hugo template:

{{ $creds := resources.Get "secret.js" }}
<script>{{ $creds.Content | safeJS }}</script>

The built HTML will contain the AWS credentials, leaking them to the public.

Protection

  • Upgrade immediately to Hugo v0.163.1 or later. This version restores the correct `Lstat` behaviour and rejects symlinked files in resources.Get.
  • If you cannot upgrade, avoid using `resources.Get` on user-controlled or untrusted mount paths.
  • Audit all theme directories and custom mounts for unexpected symbolic links before running hugo.
  • Use Go modules for themes instead of local vendoring — Hugo strips symlinks from modules downloaded via GitHub.
  • Run Hugo in a sandboxed environment (e.g., Docker with read-only root) to limit the impact of any successful read.
  • Monitor build logs for warnings about symlinks being skipped (introduced in v0.163.1).

Impact

  • Confidentiality breach – Arbitrary files readable by the Hugo process can be exfiltrated, including:
  • /etc/passwd, `/etc/shadow`
    – SSH private keys (~/.ssh/id_rsa)
  • Cloud provider credentials (~/.aws/credentials, ~/.config/gcloud/)
  • Environment variables, database passwords, and API tokens
  • Source code and proprietary assets
  • No privilege escalation – The attacker cannot write files or execute code; the impact is limited to information disclosure.
  • Supply chain risk – In CI/CD pipelines, a compromised theme or mount could leak build secrets to the generated static site.
  • Widespread exposure – Any Hugo site using `resources.Get` with locally-mounted themes or assets between v0.123.0 and v0.163.0 is vulnerable.

🎯Let’s Practice Exploiting & Learn Patching For Free:

🎓 Live Courses & Certifications:

Join Undercode Academy for Verified Certifications

🚀 Request a Custom Project:

Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands

Sources:

Reported By: github.com
Extra Source Hub:
Undercode

🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]

💬 Whatsapp | 💬 Telegram

📢 Follow DailyCVE & Stay Tuned:

𝕏 formerly Twitter 🐦 | @ Threads | 🔗 Linkedin Featured Image

Scroll to Top