Recent chats
ASKGEEZ Q&A GUIDETechnology & devices

How can I check that a relative input path points inside my sample folder?

Quick answer

Resolve the sample folder, join it with the user’s relative input, and resolve the result. Then use relative_to() to check whether the resolved candidate is inside the resolved folder; reject it if that raises ValueError. For an input that must already exist, use strict resolution. This helps catch .. traversal and symlinks pointing outside, but the check alone is not a complete security boundary.

LET’S WORK THROUGH IT

The question, the details, the next step.

Question

I have a folder of sample files and ask a script user for a relative filename. How do I check the path stays inside that folder before I read it?

Geez

Use pathlib.Path to resolve both the intended folder and the candidate, then check whether the candidate can be expressed relative to the folder. Resolving matters: it makes paths absolute, resolves symlinks, and removes .. components. A plain is_relative_to() check on unresolved paths is not enough for this purpose because that comparison does not access the filesystem or treat .. specially. [S1]

For an input file that must already exist, this pattern accepts an in-folder relative path and rejects an input that escapes:

from pathlib import Path

sample_dir = Path("samples").resolve(strict=True)
user_input = Path(input("Sample file: "))

if user_input.is_absolute():
    raise SystemExit("Enter a relative path inside the sample folder")

try:
    candidate = (sample_dir / user_input).resolve(strict=True)
    candidate.relative_to(sample_dir)
except (OSError, ValueError):
    raise SystemExit("Choose an existing path inside the sample folder")

text = candidate.read_text(encoding="utf-8")

For example, notes.txt can resolve inside samples; ../private.txt resolves outside and fails the containment check. Resolution errors, such as a missing input, are also rejected. This checks location, not whether the resolved path is a regular file. If that distinction matters for your script, check the file type separately before reading.

Follow-up question

What happens if someone types an absolute path, or if there is a symlink inside samples?

Geez

The example rejects an absolute input outright because it asks for a relative path. For a relative input, the containment check still tests the resolved candidate location. If that location is outside, relative_to(sample_dir) raises ValueError, and the example rejects it. [S1]

Resolution also follows symlinks. So if samples/shortcut.txt points to a file outside samples, strict resolution produces the target path, and the containment check rejects that target. [S1] This is why the check uses resolved paths rather than comparing the input text or its spelling. You can give users a clearer error by distinguishing an outside path from a missing or unreadable one, but keep the final decision based on the resolved candidate.

Follow-up question

Can I rely on this to make reading files secure?

Geez

No. It is a useful path-validation check, but it does not guarantee race-free access. A filesystem entry could change between the time the script checks the resolved path and the time it opens the file; the documentation describes path operations, not a complete access-control mechanism. [S1]

For a local sample-processing script, this pattern may be a sensible way to reject straightforward path escapes. If files or the filesystem can be changed by an untrusted party while the program runs, do not treat a check followed by a normal open as a security boundary. The example also assumes that the sample directory and input already exist. If you intend to create a new output file instead, strict resolution for an existing input is not directly applicable; choose and review a different design for that task.

Follow-up question

What should I verify before adapting this code to my project?

Geez

Start by deciding whether the input must already exist, as the example assumes. Then try an ordinary in-folder name, a .. path, an absolute path, and—if your sample folder contains symlinks—a symlink whose target is outside. Confirm that each result matches your intended behavior before connecting validation to file reading.

The example uses Path.resolve(strict=True) and Path.relative_to(). Check that these methods are available in the Python version you target; the cited documentation is for Python 3.15 and advises confirming method availability for the target version. [S1] Avoid replacing the path relationship check with a string-prefix check: textual spelling alone does not establish that one resolved path is inside another.

A practical checklist

  • Resolve the intended sample directory and confirm it exists.

  • Join the relative user input to that directory, then resolve the candidate before checking containment.

  • Reject absolute inputs if the interface requires relative paths.

  • Reject the candidate when relative_to(sample_dir) raises ValueError.

  • Use strict resolution when the requested input must already exist, and handle resolution errors.

  • Do not treat check-then-open as race-free protection if filesystem contents may change.

A few more useful answers

Why not check whether the input string starts with the sample folder name?

String prefixes compare spelling, not resolved filesystem locations. Resolve the paths and check their path relationship instead. [S1]

What does `strict=True` do in this example?

It makes resolution require the path to exist, which fits an existing input. If it does not exist, resolution fails, so the example rejects it. [S1]

Does this validate that the candidate is a file?

No. It checks whether the resolved candidate is inside the sample directory. Add a separate file-type check if your task requires a regular file.

Sources

References used for this guide.

  1. [S1] Python 3.15 pathlib: resolving and comparing filesystem paths
    docs.python.org · Checked

YOUR QUESTION. YOUR DETAILS.

Let’s work through your situation.

Get a tailored answer or help with your next task. Starts at $1, with a quote before you pay.

Ask your own question
← Back to Geez at Work