Jun 19, 2026·5 min read·12 visits
A directory confinement regression in Hugo's virtual filesystem allows unauthenticated arbitrary file read during build execution via malicious symbolic links placed in templates or themes.
Hugo versions v0.123.0 through v0.163.0 are vulnerable to a directory confinement bypass. A regression in the virtual filesystem layer causes symbolic links to be followed during template execution, allowing templates to read arbitrary host files.
Hugo relies on a structured virtual filesystem to enforce directory boundaries during compilation. This mechanism prevents custom layouts, theme files, and templates from reading files outside the project repository. It serves as a security boundary to protect sensitive host configurations when rendering untrusted user inputs.
A path resolution regression was introduced in Hugo version v0.123.0 that degrades this security restriction. The vulnerability, tracked under GHSA-C3WQ-J5VH-68RC, allows templates to traverse beyond the designated directory boundaries. By utilizing symbolic links within the project directory, templates can read sensitive system files on the build host.
The vulnerability is categorized under CWE-59: Improper Link Resolution Before File Access. This flaw is highly relevant in automated CI/CD architectures where third-party templates are executed with elevated build execution credentials. Successful exploitation yields unauthorized access to system configurations, keys, and credentials stored on the build container.
Hugo utilizes the afero library to construct virtual filesystem layers. In version v0.123.0, changes to path resolution modified the behavior of RootMappingFs.statRoot. Rather than using Lstat to examine files without dereferencing paths, the application invoked standard Stat operations.
Because Stat automatically resolves symbolic links, the filesystem driver evaluates the endpoint destination instead of the link container. This processing occurs prior to executing directory boundary checks. Consequently, files referencing absolute target paths escape the sandbox validation routine.
When a template invokes file-system commands like os.ReadFile, the root-mapping layer processes the input. The underlying file wrapper follows the malicious symbolic link and returns the content of the target file to the caller. This allows attackers to access any file on the underlying operating system that is readable by the compiling process.
The remediation introduced in version v0.163.1 contains two security adjustments. The primary fix in commit cf9c8f9 introduces DropSymlinksFs, a custom filesystem wrapper designed to intercept file execution requests and drop symbolic link operations.
// NewDropSymlinksFs returns an afero.Fs wrapper that treats symlinks as non-existing files.
func NewDropSymlinksFs(base afero.Fs) *DropSymlinksFs {
return &DropSymlinksFs{base}
}
type DropSymlinksFs struct {
afero.Fs
}
func (fs *DropSymlinksFs) Open(name string) (afero.File, error) {
if _, err := fs.Stat(name); err != nil {
return nil, err
}
return fs.Fs.Open(name)
}
func (fs *DropSymlinksFs) Stat(name string) (os.FileInfo, error) {
fi, err := LstatIfPossible(fs.Fs, name)
if err != nil {
return nil, err
}
if fi.Mode()&os.ModeSymlink != 0 {
return nil, os.ErrNotExist
}
return fi, nil
}The Stat wrapper intercepts file access using LstatIfPossible. If the underlying mode indicates a symbolic link (fi.Mode()&os.ModeSymlink != 0), the wrapper returns os.ErrNotExist to simulate a missing file.
A secondary fix in commit a00b5c7 resolves an SSRF blocklist bypass vector. Previously, users could bypass IP blocks using decimal, hexadecimal, or octal IP configurations (e.g., http://2130706433/ for local addresses). The patch introduces host-name canonicalization before parsing blocklists:
func (c Config) CheckAllowedHTTPURL(u string) error {
if !c.HTTP.URLs.Accept(u) {
return deny(u)
}
if canon, ok := canonicalIPv4URL(u); ok && !c.HTTP.URLs.Accept(canon) {
return deny(u)
}
return nil
}This secondary canonicalization mitigates variations in URL encodings that previously bypassed target access validations.
To exploit this vulnerability, an attacker must have privileges to inject files into a repository compiled by Hugo. This commonly occurs when an attacker contributes a malicious theme or submits a pull request to a static site repository.
The attacker creates an absolute symbolic link within a theme directory referencing the target file:
ln -s /etc/passwd ./themes/malicious-theme/assets/exploit.txtThe attacker then adds an os.ReadFile call inside a layout template file (e.g., themes/malicious-theme/layouts/partials/header.html):
{{ if os.FileExists "themes/malicious-theme/assets/exploit.txt" }}
<div class="output">
{{ os.ReadFile "themes/malicious-theme/assets/exploit.txt" }}
</div>
{{ end }}During compilation, the Hugo template engine processes the layout commands. The virtual filesystem evaluates the symbolic link using Stat, resolving it to /etc/passwd. The engine reads the contents and appends them to the generated HTML output, making the data visible to the attacker.
The security threat posed by this bypass is high for automated deployment platforms. Organizations running continuous delivery pipelines for static sites frequently process untrusted user templates. Compromise of a build pipeline via arbitrary file read can lead to complete server environment disclosure.
The CVSS v4 score is 6.0 (Medium Severity). The score reflects a local attack vector (AV:L) because file manipulation inside the repository is required to stage the symbolic link. However, the resulting subsequent confidentiality impact is high (SC:H) due to the exposure of files residing on the build environment.
If the build user runs with administrative privileges, an attacker can access system-level secrets. This includes cloud IAM metadata tokens, continuous integration deployment credentials, and host private SSH keys.
Organizations using Hugo should immediately upgrade to version v0.163.1 or higher. The update implements the DropSymlinksFs blocklist, which renders standard symbolic links harmless inside template lookups.
For systems where immediate upgrades are not possible, administrators can audit repositories for symbolic links. Run the following command inside target codebases:
find . -type l -lsAdditionally, isolate the static rendering process within unprivileged sandbox environments. Run build tasks under low-privilege accounts, and use container configurations that lack read access to critical host files. Restricting outbound network access on build nodes also limits potential data exfiltration vectors.
CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:N/SC:H/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Hugo gohugoio | >= v0.123.0, < v0.163.1 | v0.163.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-59 |
| Attack Vector | Local |
| CVSS v4 Score | 6.0 |
| Exploit Status | PoC |
| Impact | Arbitrary File Read |
| CISA KEV Status | Not Listed |
The application attempts to access a file based on a filename, but it does not properly handle cases where that filename is a symbolic link pointing to a file outside the intended directory restriction.
An improper certificate validation vulnerability (CWE-295) in the Rust-based X.509 verification engine of python-cryptography allows wildcard Subject Alternative Names (SANs) to bypass permitted Name Constraints. This enables an attacker to construct certificates that escape the restricted scope of a subordinate Certificate Authority (CA) and successfully authenticate against vulnerable client installations. The vulnerability is tracked as CVE-2026-69248 and GHSA-m2h6-j472-rp4c, with a CVSS v4.0 base score of 6.9.
A high-severity heap-based out-of-bounds (OOB) read vulnerability exists in the Cython-based HTTP response and request parser extension of aiohttp. When processing malformed HTTP traffic, the parser fails to properly handle raw C pointers returned by the underlying llhttp library during error-message construction. This triggers an uncontrolled strlen() call on non-null-terminated network buffers, which can result in a Denial of Service (DoS) via worker process crash or the exposure of adjacent heap memory inside exception messages.
CVE-2026-69192 is a critical parser differential vulnerability in the 'ip-address' JavaScript library (versions <= 10.3.0). The library parses IPv4 octets containing leading zeros as base-10 (decimal), whereas standard system resolvers and web environments parse them as base-8 (octal). This discrepancy allows remote attackers to bypass SSRF guards and route malicious requests to internal RFC 1918 networks.
A high-severity security vulnerability has been identified within the Angular compiler's internationalization (i18n) metadata collection and translation pipeline. Angular implements strict defenses against client-side execution injection by validating standard attribute and property bindings. However, when parsing elements containing both i18n translation attributes and inline event-handler elements (such as `i18n-onerror`), the compiler failed to assert the safety of the target attribute. Consequently, compromised or untrusted localization translation source files can supply arbitrary JavaScript payloads that replace static event-handler bindings. This arbitrary code is compiled directly into the localized build bundle and executed dynamically by the web browser, bypassing runtime sanitization, security checks, and standard binding constraints.
A directory traversal and arbitrary file read vulnerability exists in PostCSS due to an incomplete fix of CVE-2026-45623. When parsing a CSS file containing a sourceMappingURL comment with the 'from' parameter unset, path traversal and absolute path validations are bypassed, enabling attackers to read arbitrary local .map files.
CVE-2026-69152 is a high-severity Denial of Service (DoS) vulnerability in brace-expansion that allows remote, unauthenticated attackers to cause a process crash or infinite thread-blocking condition. The vulnerability stems from a complete mitigation bypass of the security checks implemented for CVE-2026-14257.