T09 · Insecure Skill Coding Practices
- Location
scripts/codex_image_adapter.py:747- Finding
Worker-reported artifact path permits copying arbitrary readable local images
- Content
View full analysis
Vulnerability Details
File Location:
scripts/codex_image_adapter.py, lines 747–753 and 1018–1028
Vulnerability Type: Improper path authorization / arbitrary local image disclosure
Risk Level: MediumVulnerable Code
python candidate = Path(os.path.expanduser(cleaned)) if not candidate.is_absolute(): candidate = images_root / cleaned candidate = Path(os.path.normpath(str(candidate))) if candidate.is_file() and candidate.suffix.lower() in ALLOWED_IMAGE_SUFFIXES: return ArtifactDiscovery(candidate, "explicit_reported_path", [str(candidate)])The accepted source is subsequently copied into the workspace:
python fd, tmp_name = tempfile.mkstemp(dir=str(target.parent), suffix=target.suffix) os.close(fd) tmp_path = Path(tmp_name) try: shutil.copyfile(source, tmp_path) os.replace(tmp_path, target) finally: if tmp_path.exists(): try: tmp_path.unlink() except OSError: # pragma: no cover - defensive passTechnical Analysis
discover_artifact()treats paths extracted from Codex-generated agent text as artifact candidates. If a reported path is absolute, the adapter uses it directly. Acceptance requires only that the path identify an existing file with an allowed image suffix.Although the function documentation states that an explicitly reported artifact must live under
$CODEX_HOME/generated_images, the explicit-path branch does not enforce that containment requirement. It applies lexical normalization withnormpath(), but does not resolve the path and verify that it remains beneath the trusted image-generation directory.The accepted path is later passed to
shutil.copyfile(), which reads the selected local file and places a copy at the user-authorized workspace output path. This turns worker-controlled text into a local file-selection capability.The normal output-path validation protects the desti ...[truncated 2044 chars]
- Remediation
View remediation
Remediation Suggestions
- Canonicalize both the generated-images root and the reported candidate using
Path.resolve(strict=True). - Reject the candidate unless it is a regular file beneath the canonical
$CODEX_HOME/generated_imagesdirectory. - When a Codex thread ID is available, narrow authorization further by requiring the artifact to reside beneath that thread’s generated-image directory.
- Reject symbolic links, or open the source with platform-appropriate no-follow semantics and verify the opened file’s identity before copying.
- Repeat the containment and file-type checks immediately before the copy to reduce time-of-check/time-of-use risk.
- Prefer selecting artifacts from a before/after filesystem snapshot rather than trusting paths reported in model-generated text.
- Add regression tests covering absolute paths outside the generated-images root,
..traversal, symlink escapes, and paths belonging to a different thread.
Example containment logic:
python trusted_root = images_root.resolve(strict=True) candidate = candidate.resolve(strict=True) if not candidate.is_file() or candidate.suffix.lower() not in ALLOWED_IMAGE_SUFFIXES: continue try: candidate.relative_to(trusted_root) except ValueError: continue- Canonicalize both the generated-images root and the reported candidate using
