AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Sast Pathtraversal

skill-utkusen-sast-skills-sast-pathtraversal · by utkusen

>-

No reviews yet
0 installs
4 views
0.0% view→install

Install

$ agentstack add skill-utkusen-sast-skills-sast-pathtraversal

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access Used
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-utkusen-sast-skills-sast-pathtraversal)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
4mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Sast Pathtraversal? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Path Traversal Detection

You are performing a focused security assessment to find path traversal vulnerabilities in a codebase. This skill uses a three-phase approach with subagents: recon (find file-loading sinks with dynamic paths), batched verify (trace user input and check mitigations in parallel batches of 3), and merge (consolidate batch results into one report).

Prerequisites: sast/architecture.md must exist. Run the analysis skill first if it doesn't.


What is Path Traversal

Path traversal (also called directory traversal) occurs when user-supplied input is incorporated into a file path that is then used to read, write, or serve files from the filesystem — without properly constraining the resulting path to an intended base directory. An attacker can supply sequences like ../ or encoded variants (%2e%2e%2f, ..%2f, %2e%2e/) to escape the intended directory and access arbitrary files such as /etc/passwd, application source code, credentials, or private keys.

The core pattern: unvalidated user input reaches a filesystem operation and the resolved path is not verified to remain within the intended base directory.

What Path Traversal IS

  • Serving a user-requested filename directly from a base directory without canonicalizing and checking the resulting path:

open(os.path.join(BASE_DIR, user_filename))

  • Constructing a file path from a URL parameter and passing it to a file-read function:

fs.readFile(path.join(__dirname, req.query.file), ...)

  • Template rendering or include directives driven by user input:

include($_GET['page'] . '.php')

  • Archive extraction (ZipFile, tarfile, zipslip) where entry names are used as output paths without stripping ../ components
  • Using send_file() / send_from_directory() / res.sendFile() with an unsanitized user-controlled path
  • Reading a file whose path is derived from a user-controlled database value that was stored without sanitization

What Path Traversal is NOT

Do not flag these as path traversal:

  • SSRF: Fetching a remote URL from user input — that is Server-Side Request Forgery, a separate class
  • RCE via file write: Writing attacker-controlled content to an arbitrary path — related but a different impact class (flag as RCE or File Upload)
  • Static file serving: Serving files from a path that is entirely hardcoded with no user influence
  • Safe path joins followed by realpath + prefix check: The code computes realpath() and verifies it starts with the intended base directory
  • basename() before join: Using only the filename component strips traversal sequences (though note this prevents directory selection, not just traversal)

Patterns That Prevent Path Traversal

When you see these mitigations applied before the file operation, the code is likely not vulnerable:

1. realpath / resolve followed by a base-directory prefix check (most robust fix)

# Python
import os
BASE = '/var/www/files'
safe_path = os.path.realpath(os.path.join(BASE, user_input))
if not safe_path.startswith(BASE + os.sep):
    raise PermissionError("Path escape detected")
with open(safe_path) as f:
    ...
// Node.js
const BASE = path.resolve('/var/www/files');
const resolved = path.resolve(BASE, req.query.file);
if (!resolved.startsWith(BASE + path.sep)) {
    return res.status(403).send('Forbidden');
}
fs.readFile(resolved, ...);
// Java
Path base = Paths.get("/var/www/files").toRealPath();
Path resolved = base.resolve(userInput).normalize();
if (!resolved.startsWith(base)) {
    throw new SecurityException("Path escape");
}
Files.readAllBytes(resolved);

2. basename() / path.basename() to strip directory components

# Python — strips all directory parts, only the filename remains
filename = os.path.basename(user_input)
with open(os.path.join(BASE, filename)) as f:
    ...
// PHP
$filename = basename($_GET['file']);
readfile('/var/www/uploads/' . $filename);

3. Allowlist of permitted filenames or extensions

ALLOWED = {'report.pdf', 'manual.txt', 'logo.png'}
if user_input not in ALLOWED:
    abort(400)
with open(os.path.join(BASE, user_input)) as f:
    ...

4. Framework-provided safe file serving

# Flask — send_from_directory validates the path stays within the directory
return send_from_directory('/var/www/files', filename)

# Django — FileResponse with a path that was never user-controlled

Vulnerable vs. Secure Examples

Python — Flask

# VULNERABLE: user-controlled filename joined without realpath check
@app.route('/download')
def download():
    filename = request.args.get('file')
    filepath = os.path.join('/var/www/files', filename)
    return send_file(filepath)

# SECURE: resolve and verify the path stays within the base directory
@app.route('/download')
def download():
    filename = request.args.get('file')
    base = os.path.realpath('/var/www/files')
    filepath = os.path.realpath(os.path.join(base, filename))
    if not filepath.startswith(base + os.sep):
        abort(403)
    return send_file(filepath)

Python — FastAPI

# VULNERABLE: path parameter used directly in file read
@app.get('/file/{name}')
async def get_file(name: str):
    return FileResponse(f'/app/static/{name}')

# SECURE: basename strips traversal sequences
@app.get('/file/{name}')
async def get_file(name: str):
    safe_name = os.path.basename(name)
    return FileResponse(os.path.join('/app/static', safe_name))

Node.js — Express

// VULNERABLE: req.query.file used directly in readFile
app.get('/file', (req, res) => {
  const filePath = path.join(__dirname, 'uploads', req.query.file);
  fs.readFile(filePath, (err, data) => res.send(data));
});

// SECURE: resolve and check prefix
app.get('/file', (req, res) => {
  const base = path.resolve(__dirname, 'uploads');
  const filePath = path.resolve(base, req.query.file);
  if (!filePath.startsWith(base + path.sep)) {
    return res.status(403).send('Forbidden');
  }
  fs.readFile(filePath, (err, data) => res.send(data));
});

PHP

// VULNERABLE: direct inclusion of user input
 getFile(@PathVariable String name) throws IOException {
    Path filePath = Paths.get("/var/www/files").resolve(name);
    Resource resource = new UrlResource(filePath.toUri());
    return ResponseEntity.ok(resource);
}

// SECURE: normalize and check prefix
@GetMapping("/file/{name}")
public ResponseEntity getFile(@PathVariable String name) throws IOException {
    Path base = Paths.get("/var/www/files").toRealPath();
    Path resolved = base.resolve(name).normalize();
    if (!resolved.startsWith(base)) {
        return ResponseEntity.status(403).build();
    }
    Resource resource = new UrlResource(resolved.toUri());
    return ResponseEntity.ok(resource);
}

Go

// VULNERABLE: query param joined directly to base directory
func fileHandler(w http.ResponseWriter, r *http.Request) {
    name := r.URL.Query().Get("file")
    http.ServeFile(w, r, filepath.Join("/var/www/files", name))
}

// SECURE: filepath.Clean + prefix check
func fileHandler(w http.ResponseWriter, r *http.Request) {
    name := r.URL.Query().Get("file")
    base := "/var/www/files"
    clean := filepath.Join(base, filepath.Clean("/"+name))
    if !strings.HasPrefix(clean, base+string(os.PathSeparator)) {
        http.Error(w, "Forbidden", http.StatusForbidden)
        return
    }
    http.ServeFile(w, r, clean)
}

Archive Extraction (ZipSlip)

# VULNERABLE: ZipSlip — zip entry names can contain ../
import zipfile
with zipfile.ZipFile(user_zip) as zf:
    zf.extractall('/var/www/uploads')

# SECURE: validate each entry path stays within the target directory
import zipfile, os
base = os.path.realpath('/var/www/uploads')
with zipfile.ZipFile(user_zip) as zf:
    for member in zf.namelist():
        target = os.path.realpath(os.path.join(base, member))
        if not target.startswith(base + os.sep):
            raise ValueError(f"ZipSlip detected: {member}")
    zf.extractall(base)

Execution

This skill runs in three phases using subagents. Pass the contents of sast/architecture.md to all subagents as context.

Phase 1: Find File-Loading Sinks With Dynamic Paths

Launch a subagent with the following instructions:

> Goal: Find every location in the codebase where a file is opened, read, served, or extracted using a dynamically constructed path — meaning the path (or a component of it) is stored in a variable rather than being a fully hardcoded string. Write results to sast/pathtraversal-recon.md. > > Context: You will be given the project's architecture summary. Use it to understand the tech stack, web framework, file-serving patterns, and any file upload or download features. > > What to search for — file-loading sinks with dynamic path components: > > Flag any call to a file-reading/serving function where the path argument contains a variable (regardless of where the variable comes from). You are not tracing user input in this phase — that is Phase 2's job. Just find all dynamic file access patterns. > > 1. Direct file open / read calls with a variable path: > - Python: open(var), open(os.path.join(..., var)), pathlib.Path(var).read_text(), pathlib.Path(var).read_bytes() > - Node.js: fs.readFile(var, ...), fs.readFileSync(var), fs.createReadStream(var) > - PHP: file_get_contents(var), fopen(var, ...), readfile(var), include(var), require(var), include_once(var), require_once(var) > - Ruby: File.read(var), File.open(var), IO.read(var), IO.binread(var) > - Java: new FileInputStream(var), new File(var), Files.readAllBytes(Paths.get(var)), Files.newInputStream(path) > - Go: os.Open(var), os.ReadFile(var), ioutil.ReadFile(var), os.OpenFile(var, ...) > - C#: File.ReadAllText(var), File.ReadAllBytes(var), new FileStream(var, ...), System.IO.File.Open(var, ...) > > 2. Framework file-serving calls with a variable path: > - Flask: send_file(var), send_from_directory(base, var) > - FastAPI / Starlette: FileResponse(var) > - Django: FileResponse(open(var, 'rb')), StreamingHttpResponse over an opened file > - Express: res.sendFile(var), res.download(var), express.static with dynamic root > - Spring: new UrlResource(path.toUri()), ResourceLoader.getResource(var), ClassPathResource(var) > - Rails: send_file var, render file: var > - Go: http.ServeFile(w, r, var), http.ServeContent(w, r, var, ...) > > 3. Path construction functions where at least one component is a variable: > - os.path.join(BASE, var), os.path.join(var1, var2) > - path.join(__dirname, var), path.resolve(base, var) > - Paths.get(base).resolve(var) > - filepath.Join(base, var) > - String concatenation used as a path: BASE + var, f"{BASE}/{var}", ` ${base}/${var} > > 4. **Archive extraction with user-supplied archives** (ZipSlip pattern): > - Python: zipfile.ZipFile.extractall(...), tarfile.TarFile.extractall(...) > - Java: ZipEntry.getName() used as an output path > - Node.js: unzipper, adm-zip, node-tar extraction calls > - Go: archive/zip or archive/tar extraction without entry-name validation > > **What to skip** (these have no dynamic path component — do not flag): > - File paths that are fully hardcoded string literals with no variable parts > - Paths derived entirely from server-side config / environment variables with no user-supplied component (e.g., open(settings.LOGFILE) where LOGFILE is a config value) > - Framework built-in static file middleware where the root directory is hardcoded (e.g., express.static('public') with a fixed root) > > **Output format** — write to sast/pathtraversal-recon.md: > > `markdown > # Path Traversal Recon: [Project Name] > > ## Summary > Found [N] locations where files are accessed using dynamically constructed paths. > > ## File-Loading Sinks > > ### 1. [Descriptive name — e.g., "Dynamic readFile in download endpoint"] > - **File**: path/to/file.ext (lines X-Y) > - **Function / endpoint**: [function name or route] > - **Sink**: [open / fs.readFile / send_file / include / FileInputStream / etc.] > - **Path construction**: [os.path.join / path.join / string concat / f-string / etc.] > - **Dynamic variable(s)**: var_name — [brief note on what it appears to represent, e.g., "looks like a filename from request" or "unknown origin"] > - **Code snippet**: > ` > [the path construction + file operation call] > ` > > [Repeat for each sink] > ``

After Phase 1: Check for Candidates Before Proceeding

After Phase 1 completes, read sast/pathtraversal-recon.md. If the recon found zero file-loading sinks (the summary reports "Found 0" or the "File-Loading Sinks" section is empty or absent), skip Phase 2 and Phase 3 entirely. Instead, write the following content to sast/pathtraversal-results.md, then delete sast/pathtraversal-recon.md, and stop:

# Path Traversal Analysis Results

No vulnerabilities found.

Only proceed to Phase 2 if Phase 1 found at least one file-loading sink.

Phase 2: Verify — Trace Taint and Check Mitigations (Batched)

After Phase 1 completes, read sast/pathtraversal-recon.md and split the file-loading sinks into batches of up to 3 sinks each. Launch one subagent per batch in parallel. Each subagent analyzes only its assigned sinks and writes results to its own batch file.

Batching procedure (you, the orchestrator, do this — not a subagent):

  1. Read sast/pathtraversal-recon.md and count the numbered sink sections (### 1., ### 2., etc.).
  2. Divide them into batches of up to 3. For example, 8 sinks → 3 batches (1-3, 4-6, 7-8).
  3. For each batch, extract the full text of those sink sections from the recon file.
  4. Launch all batch subagents in parallel, passing each one only its assigned sinks.
  5. Each subagent writes to sast/pathtraversal-batch-N.md where N is the 1-based batch number.
  6. Identify the project's primary language/framework from sast/architecture.md and select only the matching examples from the "Vulnerable vs. Secure Examples" section above (and "Patterns That Prevent Path Traversal" / "What Path Traversal is NOT" as reference). For example, if the project uses Node.js/Express, include the "Node.js — Express" block. Include these selected examples in each subagent's instructions where indicated by [TECH-STACK EXAMPLES] below.

Give each batch subagent the following instructions (substitute the batch-specific values):

> Goal: For each assigned file-loading sink, determine whether a user-supplied value reaches the dynamic path variable AND whether any mitigation prevents the path from escaping the intended base directory. Our goal is to find path traversal vulnerabilities. Write results to sast/pathtraversal-batch-[N].md. > > Your assigned sinks (from the recon phase): > > [Paste the full text of the assigned sink sections here, preserving the original numbering] > > Context: You will be given the project's architecture summary. Use it to understand request entry points, middleware, and how data flows through the application. > > Path traversal reference — what to look for: > > User-supplied input incorporated into a filesystem path without constraining the resolved path to an intended base directory (including ZipSlip-style archive extraction). Do not flag SSRF, pure RCE/file-write classes, fully hardcoded paths, or safe realpath/resolve + base prefix checks as path traversal (see the skill's "What Path Traversal is NOT" and "Patterns That Prevent Path Traversal" sections in the main skill document if needed). > > For each sink, perform two checks: > > **Check A — Is the path variable user-c

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.