Back to Blog
critical SEVERITY7 min read

Path Traversal Vulnerability Fixed in Hatch-Pet Scripts: A Deep Dive

A high-severity path traversal vulnerability was discovered and patched in the hatch-pet script suite, where unsanitized user input could allow attackers to read or overwrite sensitive files anywhere on the filesystem. The fix ensures that file paths are properly validated before use, preventing attackers from escaping the intended working directory. Understanding this class of vulnerability is essential for any developer working with file I/O and user-supplied input.

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

Answer Summary

This is a critical path traversal vulnerability (CWE-22) in the hatch-pet shell script suite, where unsanitized user input was passed directly into file path construction logic, allowing attackers to use sequences like `../../../etc/passwd` to escape the intended directory and access or overwrite arbitrary files on the filesystem. The fix resolves this by canonicalizing all user-supplied paths and verifying they remain within the permitted base directory before any file operation is performed. Developers can prevent this class of vulnerability by always resolving absolute paths and asserting the resolved path starts with the expected base directory prefix.

Vulnerability at a Glance

cweCWE-22
fixCanonicalize paths and verify they remain within the intended base directory before performing any file operation
riskAttackers can read or overwrite arbitrary files on the filesystem, including sensitive configuration and credential files
languageShell Script
root causeUser-supplied input used to construct file paths without canonicalization or boundary validation
vulnerabilityPath Traversal

Introduction

Imagine handing a stranger the keys to your house and saying, "You can only go in the kitchen." Now imagine they discover the keys also open every other room — and you never knew. That's essentially what a path traversal vulnerability does to your application's filesystem.

A path traversal vulnerability (also known as a directory traversal attack) allows an attacker to manipulate file path references in your application to access files and directories stored outside the intended folder. In the worst cases, this means reading sensitive configuration files, credentials, or even overwriting critical system files.

This post breaks down a recently patched high-severity path traversal vulnerability found in skills/hatch-pet/scripts/generate_pet_images.py, explains how it could have been exploited, and walks through the best practices you should adopt to prevent similar issues in your own code.


The Vulnerability Explained

What Happened?

The vulnerability resided in how the hatch-pet script constructed file paths. Specifically, the code joined a run_dir variable with a hardcoded filename (pet_request.json) to build a full file path — something like this:

# Vulnerable pattern
import os

run_dir = get_run_dir()  # Could come from CLI args, API params, or env vars
request_file = os.path.join(run_dir, 'pet_request.json')

with open(request_file, 'r') as f:
    data = f.read()

This looks innocent at first glance. The problem? run_dir was derived from user-controlled input without any validation.

The Anatomy of a Path Traversal Attack

The ../ sequence (or ..\ on Windows) is the key weapon in a path traversal attack. When you join an unvalidated user input with a base path, an attacker can supply sequences like:

../../etc/passwd
../../../var/secrets/.env
../../../../home/user/.ssh/id_rsa

So if an attacker passes run_dir as ../../etc/, the resulting path becomes:

os.path.join("../../etc/", "pet_request.json")
# Resolves to: ../../etc/pet_request.json

This effectively points the file operation outside the intended working directory, potentially exposing or corrupting sensitive system files.

Real-World Impact

Depending on how the vulnerable code is used, an attacker could:

  • Read sensitive files: /etc/passwd, .env files containing API keys, database credentials, private SSH keys
  • Overwrite critical files: Configuration files, log files, or even executable scripts
  • Achieve privilege escalation: By overwriting scripts run by higher-privileged processes
  • Exfiltrate application secrets: Internal configuration, tokens, or user data

Attack Scenario: Step by Step

Let's walk through a concrete attack scenario:

Setup: The hatch-pet script accepts a --run-dir CLI argument or reads RUN_DIR from an environment variable.

Step 1 — Attacker identifies the input vector:

python generate_pet_images.py --run-dir /tmp/myrun

Step 2 — Attacker crafts a malicious path:

python generate_pet_images.py --run-dir "../../etc/"

Step 3 — Script attempts to read:

# Internally resolves to:
open("../../etc/pet_request.json", 'r')
# Or in a write scenario:
open("../../etc/pet_request.json", 'w')

Step 4 — Data exfiltration or corruption occurs silently, with no error raised by the application.

This is classified as CWE-22: Improper Limitation of a Pathname to a Restricted Directory and is consistently listed in the OWASP Top 10 under A01:2021 – Broken Access Control.


The Fix

What Changed?

The fix introduces proper path validation before the run_dir variable is used to construct any file path. The core of the solution is to:

  1. Resolve the absolute, canonical path of the constructed file path
  2. Verify it falls within the expected base directory
  3. Reject any path that escapes the intended directory

Here's what a secure implementation looks like:

# BEFORE (Vulnerable)
import os

def get_request_file(run_dir):
    return os.path.join(run_dir, 'pet_request.json')

run_dir = args.run_dir  # Unvalidated user input
request_file = get_request_file(run_dir)

with open(request_file, 'r') as f:
    data = f.read()
# AFTER (Secure)
import os

# Define a strict base directory for all run operations
BASE_RUNS_DIR = os.path.abspath('/app/runs')

def get_safe_request_file(run_dir: str) -> str:
    """
    Safely construct a file path, ensuring it stays within BASE_RUNS_DIR.
    Raises ValueError if the resolved path escapes the base directory.
    """
    # Resolve the absolute path (resolves all ../ sequences)
    abs_run_dir = os.path.realpath(os.path.abspath(run_dir))

    # Ensure the resolved path starts with our allowed base directory
    if not abs_run_dir.startswith(BASE_RUNS_DIR + os.sep):
        raise ValueError(
            f"Invalid run_dir: path '{run_dir}' escapes the allowed directory."
        )

    return os.path.join(abs_run_dir, 'pet_request.json')

# Usage
try:
    request_file = get_safe_request_file(args.run_dir)
    with open(request_file, 'r') as f:
        data = f.read()
except ValueError as e:
    print(f"Security error: {e}")
    sys.exit(1)

Why This Fix Works

The critical line is:

abs_run_dir = os.path.realpath(os.path.abspath(run_dir))
  • os.path.abspath() converts the path to an absolute path based on the current working directory
  • os.path.realpath() resolves all symbolic links and normalizes ../ sequences

After this resolution, ../../etc/ becomes /etc/ — and the subsequent check:

if not abs_run_dir.startswith(BASE_RUNS_DIR + os.sep):

...immediately catches that /etc/ is not within /app/runs/ and raises an error before any file operation occurs.

Note: Always append os.sep when checking startswith() to avoid false positives where a directory like /app/runs-extra might incorrectly match a check for /app/runs.


Conclusion

Path traversal vulnerabilities are a classic yet persistently dangerous class of security flaw. They're often introduced through seemingly harmless code — a simple os.path.join() call that doesn't account for what a malicious actor might supply as input.

The key lessons from this vulnerability and its fix are:

  1. Never trust user input — validate and canonicalize all file paths before use
  2. Use os.path.realpath() + boundary checks to enforce directory restrictions in Python
  3. Allowlists beat blocklists — define what's permitted, reject everything else
  4. Least privilege matters — limit what your process can access at the OS level
  5. Automate detection — integrate SAST tools and write security-specific tests

Security vulnerabilities like this one are fixed one patch at a time, but the real win comes from building a culture where developers understand why these patterns are dangerous and proactively avoid them. The next time you write code that touches the filesystem with user-supplied input, take a moment to ask: "What happens if someone passes ../../etc/ here?"

That moment of reflection might just save your application — and your users.


This vulnerability was identified and patched as part of an automated security scanning process. Automated tools are a valuable layer of defense, but they work best when paired with developer education and secure-by-default coding practices.

Prevention and further reading

Related Articles

medium

path-to-regexp 0.1.12 DoS: Catastrophic Backtracking on Malformed URL

path-to-regexp version 0.1.12 contains a regular expression engine vulnerability where maliciously crafted URL parameters trigger exponential backtracking, causing CPU exhaustion and service unavailability. Upgrading to version 0.1.13 eliminates the vulnerable pattern from the parsing logic.

critical

CVE-2026-59873: node-tar 7.5.11 DoS via Crafted Gzip Bomb

node-tar versions 7.5.11 through 7.5.18 are vulnerable to a denial-of-service attack through maliciously crafted gzip archives that decompress to disproportionately large sizes. An attacker can exploit this to exhaust memory and CPU resources by submitting a small, highly compressed archive that expands beyond configured limits during extraction.

high

MapManager.get() Race Condition Duplicates API Requests

The MapManager's `get(mapUid, cache)` method used a check-then-act pattern that permitted multiple concurrent requests to pass the cache miss check simultaneously, triggering redundant API calls and risking cache corruption. The fix introduces a `_pending` promise map to deduplicate in-flight fetches for identical map UIDs.

critical

Lampa Desktop Auto-Update Heuristic Bypass: Execution of Unverified

Lampa Desktop's auto-update mechanism downloaded JavaScript and CSS from `raw.githubusercontent.com` using only heuristic validation—file size thresholds and string pattern matching—that attackers could trivially satisfy. The fix introduces cryptographic integrity verification by cross-referencing Git blob hashes from the GitHub Contents API, ensuring downloaded code matches the repository's authoritative state before execution.

high

adm-zip 0.6.0 Preserves SUID Bits From ZIPs: CVE-2026-102282

The `adm-zip` dependency resolved to 0.6.0 in this project's dependency tree, a version affected by CVE-2026-102282: during extraction it applies the Unix permission bits stored in each ZIP entry's external file attributes verbatim, including the setuid (`04000`), setgid (`02000`), and sticky bits. An attacker who controls an archive passed to `extractAllTo()` or `extractEntryTo()` can therefore have the extractor create a setuid binary owned by whatever user the extraction process runs as. The

high

requestInput() Type Confusion: NaN and Object Bypass in JavaScript

The `requestInput()` utility function lacked validation on its `type` parameter and failed to handle `NaN` results from float conversions, creating a type confusion weakness. An attacker could supply malformed inputs that propagate unhandled `NaN` values or unexpected object types through the type system. The fix adds explicit guards against `NaN` type parameters and rejects non-primitive type values.