Typorithm · easy

Portable report names and filesystem boundaries

pythonapi-clientsautomationindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

Portable report names and filesystem boundaries

0%

Concept and contract

Validate a portable relative report name without confusing lexical validation with a filesystem security boundary.

A path string, a normalized path and a file you can actually open are different things. This lesson validates a portable relative report name in a deliberately small format: forward slashes, no parent traversal and a JSON extension.

PurePosixPath performs lexical operations without touching disk. Its result is useful for a report manifest shared between operating systems, but it does not inspect permissions, symlinks or the real filesystem.

Mechanism and worked example

The function first rejects missing text, surrounding whitespace and backslashes, then inspects normalized path components.

The function first rejects missing text, surrounding whitespace and backslashes, then inspects normalized path components. A relative name can contain subdirectories, and redundant current-directory components normalize away.

An absolute path and any surviving parent component are rejected. Returning the normalized POSIX spelling gives later layers one stable representation. The caller still chooses the actual output directory and performs the eventual filesystem operation under its own permissions.

Complete worked example

python

from pathlib import PurePosixPath

def report_name(value):
    if not isinstance(value, str) or not value or value != value.strip() or "\\" in value:
        raise ValueError("portable relative name required")
    path = PurePosixPath(value)
    if path.is_absolute() or ".." in path.parts or not path.parts or path.suffix != ".json":
        raise ValueError("relative JSON report required")
    return path.as_posix()
Reasoning
Validate a portable relative report name without confusing lexical validation with a filesystem security boundary. Compare the stated values and failure boundary before changing the implementation.

Counterexamples and common mistakes

The counterexample checks whether the spelling starts with a slash but forgets parent traversal.

The counterexample checks whether the spelling starts with a slash but forgets parent traversal. A name such as reports/../private.json then crosses the intended lexical boundary.

String prefix tests also confuse siblings such as reports and reports-old. Even a correct lexical check is not a secure directory sandbox: symlinks and concurrent directory changes require a separate operating-system-level design when paths are untrusted.

Counterexample: a runnable policy defect

python

from pathlib import PurePosixPath

def report_name(value):
    if not isinstance(value, str) or not value or value != value.strip() or "\\" in value:
        raise ValueError("portable relative name required")
    path = PurePosixPath(value)
    if path.is_absolute() or not path.parts or path.suffix != ".json":
        raise ValueError("relative JSON report required")
    return path.as_posix()
Reasoning
This runnable counterexample violates the declared contract; the companion lab requires it to fail.

Independent practice: API client task

Allow either .json or .csv while keeping every other rule intact.

Allow either .json or .csv while keeping every other rule intact. Start with a valid CSV report in a subdirectory, then reject an absolute CSV path and a parent-traversing CSV path.

Preserve normalization of a current-directory component. This exercise changes one allowlist instead of asking you to build a cross-platform path library or handle untrusted multi-user directory races.

Step 1: Allow JSON and CSV report extensions. Step 2: Preserve rejection of absolute names and parent components.

Practice starter: solve before reading the solution

python

from pathlib import PurePosixPath

def report_name(value):
    # Remove this placeholder, then adapt the retained baseline:
    # Allow JSON and CSV report extensions.
    raise NotImplementedError
    if not isinstance(value, str) or not value or value != value.strip() or "\\" in value:
        raise ValueError("portable relative name required")
    path = PurePosixPath(value)
    if path.is_absolute() or ".." in path.parts or not path.parts or path.suffix != ".json":
        raise ValueError("relative JSON report required")
    return path.as_posix()
Reasoning
Allow JSON and CSV report extensions. Preserve rejection of absolute names and parent components.
  • Hint 1: Change the extension comparison to a small allowed set.
  • Hint 2: Return path.as_posix(), not the original spelling.

Solution, tests and trade-offs

The reference cases include an empty string, a boolean, a JSON name, a normalized nested name and rejected traversal.

The reference cases include an empty string, a boolean, a JSON name, a normalized nested name and rejected traversal. The practice adds CSV without weakening those boundaries.

Exact returned strings reveal an implementation that validates the normalized path but returns a different raw spelling. File writing is deferred to a later disposable-directory lesson; no fixture here reads or deletes an existing user file.

Practice solution and acceptance checks

python

from pathlib import PurePosixPath

def report_name(value):
    if not isinstance(value, str) or not value or value != value.strip() or "\\" in value:
        raise ValueError("portable relative name required")
    path = PurePosixPath(value)
    if path.is_absolute() or ".." in path.parts or not path.parts or path.suffix not in {".json", ".csv"}:
        raise ValueError("relative JSON report required")
    return path.as_posix()
Reasoning
Change the extension comparison to a small allowed set. Return path.as_posix(), not the original spelling.

Interview reasoning and next step

Explain what PurePath can prove and what it cannot.

Explain what PurePath can prove and what it cannot. It can help describe components and extensions without I/O, while Path.resolve and real file opening involve filesystem state.

If the client later writes reports, use a trusted dedicated output directory and a clearly stated ownership assumption. The atomic-file lesson addresses partial writes, which is a different concern from preventing an unauthorized path from being opened.

Optional stretch — after the core task works: Describe how symlink-aware opening would differ under a multi-user threat model.

Computer practice: https://applydjinn.com/typorithm/python-automation-lab-20261105.zip. Run verify.py using your selected Python interpreter, then adapt the retained exercise baseline and check it with --practice followed by the lesson id. The standard-library lab uses owned temporary 127.0.0.1 loopback servers, fictional pages and disposable files; it never scans production feeds or opens the working ApplyDjinn database.

Validate a portable relative report name without confusing lexical validation with a filesystem security boundary.