Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases
The English summary is being prepared. Showing the original description.
Use when the diff builds a filesystem path from input, extracts an archive, accepts an upload, serves a file or writes a temp file - traversal, symlinks, zip-slip, upload type and served content-type
The English summary is being prepared. Showing the original description.
See how to installReview the instructions the agent will receive before you install.
Join, normalize, resolve and clean are lexical: they rewrite a string, they do not ask the filesystem where it points. A path check that runs on the string and an open that runs on the filesystem disagree whenever there is a .., an absolute path, or a symlink.
Core principle: resolve the real path first, then check containment by path components, then open what you checked — or use an API that confines the open itself.
// ❌ HasPrefix on a cleaned string: "/srv/files-private/x" passes for base "/srv/files",
// and a symlink inside base escapes entirely
p := filepath.Join(base, name)
if !strings.HasPrefix(filepath.Clean(p), base) { return errBadPath }
f, _ := os.Open(p)
// ✅ Go 1.24+: the root confines every lookup, symlinks included
root, _ := os.OpenRoot(base)
f, err := root.Open(name)
// older Go: reject non-local names first — filepath.IsLocal(name) (Go 1.20+)
// ❌ normalize() is lexical; a symlink under base still escapes
Path p = base.resolve(name).normalize();
if (!p.startsWith(base)) throw new SecurityException();
// ✅ compare real paths (Path.startsWith is component-wise, String.startsWith is not)
Path real = base.resolve(name).toRealPath();
if (!real.startsWith(base.toRealPath())) throw new SecurityException();
# ❌ os.path.join drops base entirely when name is absolute ("/etc/passwd")
path = os.path.join(UPLOAD_DIR, name)
# ✅
root = Path(UPLOAD_DIR).resolve()
path = (root / name).resolve()
if not path.is_relative_to(root): # Python 3.9+
abort(400)
// ❌ startsWith without a trailing separator; "/data/app-secrets" passes for "/data/app"
const p = path.resolve(base, name); if (!p.startsWith(base)) throw ...
// ✅
const p = path.resolve(base, name); if (!p.startsWith(base + path.sep)) throw ...
// and for symlinks: fs.realpathSync(p) before the check
Flask: send_from_directory. Express: res.sendFile(name, { root: base }) (rejects escaping paths) rather than res.sendFile(path.join(base, name)).
Lexical checks never see a symlink. It matters when the attacker can create files inside the base (an upload directory, an extracted archive, a cloned repository, a shared volume). Resolve with realpath/EvalSymlinks/toRealPath, or open with O_NOFOLLOW / os.Root. A check-then-open gap on a directory the attacker writes to is a race (CWE-367) — report it only when the attacker demonstrably controls that directory.
Archive entry names are attacker input: ../../.ssh/authorized_keys, absolute paths, and symlink entries that point outside and are then written through.
// ❌
File out = new File(destDir, entry.getName());
Files.copy(zis, out.toPath());
// ✅
Path out = destDir.resolve(entry.getName()).normalize();
if (!out.startsWith(destDir)) throw new IOException("bad entry: " + entry.getName());
tarfile.extractall() without filter="data" (the safe default only from Python 3.14); shutil.unpack_archive on tar. zipfile.extractall sanitises .. and absolute names — not a finding by itself.archive/zip and archive/tar: check filepath.IsLocal(hdr.Name) and skip or reject symlink entries.adm-zip/unzipper/yauzl joining entry.path.Decompression bombs are a resource issue — excluded, except a bomb expanded before any size check on a shared worker by an unauthenticated or low-trust sender (security-hard-exclusions-and-precedents §4).
.html or .svg served inline from the app's own origin is stored XSS. Serve user files with Content-Disposition: attachment, a fixed safe Content-Type, X-Content-Type-Options: nosniff, or from a separate cookieless domain.Content-Type and the extension are claims. Allowlist extensions and verify the magic bytes when the type decides how the file is processed (image libraries, document parsers)..env, .git/, source and backups.tenants/B/report.pdf?Predictable names in a shared temp directory (/tmp/export- + userId) let a local attacker pre-create or symlink the file. Use os.CreateTemp, tempfile.mkstemp/NamedTemporaryFile, Files.createTempFile, fs.mkdtemp. Only a finding when other local users or tenants share the machine.
Reading arbitrary server files or writing outside the intended directory → HIGH; CRITICAL when the write reaches code, config or authorized_keys, or the read reaches credentials. Inline-served HTML/SVG upload → HIGH (stored XSS).
filepath.Clean, path.normalize or os.path.normpath as a containment check.<name> converter rejects /, while <path:name> accepts it.Join/resolve followed by a string startsWith/HasPrefix check.entry.getName(), hdr.Name, member.name used to build an output path.extractall( with no filter= on Python < 3.14.file.originalname / filename into the path.Content-Type taken from the upload when serving it back.CWE-22, CWE-59, CWE-367, CWE-377, CWE-434 at cwe.mitre.org · OWASP Top 10:2025 A01 Broken Access Control · ASVS 5.0 V5 File Handling
No reviews yet. Tell others what you thought.
No login needed. Share how it worked for you, ask a question, or note where it did not run.
No comments yet. Be the first to write one.
Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases
The English summary is being prepared. Showing the original description.
Use when the diff adds or changes an endpoint, resolver, RPC, job or query that takes an object id, a role check, a request binding or a tenant filter - BOLA/IDOR, function-level authorization, mass assignment and tenant scoping
The English summary is being prepared. Showing the original description.
Use on every UI change - semantic HTML, labels for controls, keyboard-navigable dialogs/menus, visible focus, and never color as the only signal
The English summary is being prepared. Showing the original description.
Use when a task changes any screen, form, dialog, menu or control - Lighthouse/axe scan of the changed screens, a keyboard walk, and the thresholds that fail a task
The English summary is being prepared. Showing the original description.
How to work a task returned with review, QA or UAT findings. Use when a task is in need_revision or PR review comments are in your context.
The English summary is being prepared. Showing the original description.
Use when the repository keeps its alerting in code, or a person asks for alerts outside TaskTrooper - PrometheusRule YAML, a Cloud Monitoring alert policy in Terraform, a Datadog monitor, each routed to TaskTrooper's alert ingest URL so it opens an incident here
The English summary is being prepared. Showing the original description.