T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/apply_config_bundle.py:27
- Finding
- Archive extraction permits path traversal and unsafe link handling<![CDATA[ ## Vulnerability Details **File Location**: `scripts/apply_config_bundle.py:27-33` **Vulnerability Type**: Unsafe archive extraction **Risk Level**: High ### Vulnerable Code ```python def safe_extract_tar(archive: tarfile.TarFile, destination: Path) -> None: destination = destination.resolve() for member in archive.getmembers(): member_path = (destination / member.name).resolve() if not str(member_path).startswith(str(destination)): raise ValueError(f"Unsafe tar member path: {member.name}") archive.extractall(path=destination) ``` ### Technical Analysis The extraction guard uses a string-prefix comparison to determine whether a member remains under the extraction directory. String prefixes do not represent filesystem ancestry. For example, if the extraction directory is `/tmp/bundle`, a resolved path such as `/tmp/bundle-evil/file` still begins with the string `/tmp/bundle`. The implementation also does not reject or validate symbolic links and hard links. A crafted archive can contain a link whose target points outside the extraction directory and subsequent members that write through that link. Calling `archive.extractall()` after only validating member names does not reliably contain those link-based writes. The bundle is therefore treated as trusted filesystem input even though it can originate from another machine or an untrusted transfer channel. ### Attack Path 1. An attacker creates a tar archive containing traversal paths or archive links that resolve outside the intended temporary extraction directory. 2. The attacker supplies the archive as a migration bundle. 3. The user runs `apply_config_bundle.py --bundle <malicious-archive>`. 4. The flawed string-prefix check accepts a path that is not actually a descendant of the extraction directory, or fails to account for a malicious link target. 5. `archive.extractall()` writes an archive member outside the temporary directory. 6. Files accessible t ...[truncated 538 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Reject absolute archive member names and any member containing a `..` path component. - Replace string-prefix checks with filesystem-aware containment checks such as `resolved_path.is_relative_to(destination)`. - Reject symbolic links and hard links unless they are explicitly required. - If links must be supported, resolve and validate each link target against the extraction root before extraction. - On supported Python versions, use `tarfile` extraction filters designed to reject dangerous metadata and paths. - Extract members individually only after validation rather than passing the complete archive directly to `extractall()`. - Add regression tests for absolute paths, `../` traversal, sibling-prefix paths, symlink traversal, and hard-link traversal. ]]>
