T05 · Unauthorized Access and Privilege Escalation
- Location
scripts/capture_browser_artifacts.py:74- Finding
Symlink-Based Disclosure of Files Outside the Project Root
- Content
View full analysis
Vulnerability Details
File Location:
scripts/capture_browser_artifacts.py, lines 74–90
Vulnerability Type: Symlink traversal and unauthorized local file collection
Risk Level: MediumVulnerable Code
python for path in candidates: if not path.is_file(): continue full = path.resolve() if str(full) in seen: continue seen.add(str(full)) unique_files.append(full) if len(unique_files) >= args.max_files: break artifacts: list[Artifact] = [] for src in unique_files: rel = src.relative_to(root) if src.is_relative_to(root) else Path(src.name) target = out_dir / rel target.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(src, target)Technical Analysis
Artifact candidates are collected from repository-controlled directories such as
test-results,playwright-report,.debug, andcypress. The call topath.is_file()follows symbolic links, whilepath.resolve()converts a symlink into the path of its target.The script does not reject a resolved path that falls outside
--project-root. Instead, whensrc.is_relative_to(root)is false, it usesPath(src.name)and copies the external file into the output directory under its basename. Consequently, a malicious or untrusted project can provide a matching symlink that points to any file readable by the account running the script.The default patterns include broad artifact names such as
**/*playwright*.log, image files, trace archives, HAR files, and videos. An attacker can choose a symlink name matching one of these patterns without requiring a custom--patternargument.Attack Path
- An attacker prepares an untrusted repository containing a symlink in one of the default search directories, for example:
test-results/system-playwright.logpointing to a sensitive file outside the repository. - A user or autonomous agent debugs that repos ...[truncated 1389 chars]
- An attacker prepares an untrusted repository containing a symlink in one of the default search directories, for example:
- Remediation
View remediation
Remediation Suggestions
Reject symbolic links and all resolved paths outside the project root before adding candidates to the copy list:
python for path in candidates: if path.is_symlink() or not path.is_file(): continue full = path.resolve(strict=True) if not full.is_relative_to(root): continue if full in seen: continue seen.add(full) unique_files.append(full)Additional hardening should include:
- Revalidate that every source remains inside
rootimmediately before opening or copying it, reducing time-of-check to time-of-use risk. - Use file-descriptor-based operations with no-follow semantics where supported to prevent symlink replacement between validation and copying.
- Reject an output directory located inside any searched source directory, preventing recursive collection of previously generated bundles.
- Emit a warning or fail securely when a candidate resolves outside the project root rather than silently copying it under a basename.
- Add regression tests covering direct symlinks, nested symlinks, broken links, directory symlinks, and links replaced during capture.
- Require users to inspect the manifest and redact secrets before sharing an artifact bundle.
- Revalidate that every source remains inside
