T09 · Insecure Skill Coding Practices
- Location
skillguard/orchestrator.py:25- Finding
Unbounded Extraction and Memory Loading of Untrusted ZIP Archives
- Content
View full analysis
Vulnerability Details
File Location:
skillguard/orchestrator.py, lines 25–43
Vulnerability Type: Uncontrolled resource consumption through archive extraction
Risk Level: HighVulnerable Code
python with tempfile.TemporaryDirectory() as tmpdir: try: with zipfile.ZipFile(zip_path, 'r') as zf: zf.extractall(tmpdir) except (zipfile.BadZipFile, FileNotFoundError) as e: ctx.skill_name = os.path.basename(zip_path) ctx.skill_description = f"ZIP extraction failed: {e}" return ctx # Walk extracted directory for root, dirs, files in os.walk(tmpdir): for filename in files: full_path = os.path.join(root, filename) rel_path = os.path.relpath(full_path, tmpdir) try: with open(full_path, 'rb') as f: raw = f.read() ctx.all_files[rel_path] = raw except: continueTechnical Analysis
The scanner accepts an untrusted ZIP package and calls
extractall()without first enforcing limits on the number of entries, individual uncompressed file size, aggregate uncompressed size, or compression ratio. A small, highly compressed archive can therefore expand into a volume of data that exhausts available disk space.After extraction, every file is read in full through
f.read()and retained inctx.all_files. This duplicates the resource risk at the memory layer. Retaining all binary files is unnecessary for the declared static-analysis functionality because the detectors primarily use decoded Python files,SKILL.md, andrequirements.txt.The temporary directory provides cleanup after normal completion, but it does not prevent disk, memory, or CPU exhaustion while extraction and loading are in progress.
Attack Path
- An attacker prepares a ZIP bomb or an archive containing numerous large files with a ...[truncated 1478 chars]
- Remediation
View remediation
Remediation Suggestions
-
Inspect every
ZipInfoentry before extraction and reject archives exceeding explicit limits for:- Total entry count.
- Maximum uncompressed size per entry.
- Maximum aggregate uncompressed size.
- Maximum compression ratio.
- Maximum path depth and filename length.
-
Extract entries individually instead of using unrestricted
extractall(). Track the number of bytes written and abort immediately when a configured quota is exceeded. -
Stream supported files with bounded reads rather than calling unrestricted
f.read(). Reject or truncate files exceeding the scanner's per-file analysis limit. -
Do not retain every extracted binary in
ctx.all_files. Store only files required by enabled detectors, or retain metadata and bounded content for supported file types. -
Apply process-level memory, disk, CPU, and execution-time limits when scanning attacker-controlled packages.
-
Return a clear scan failure when any quota is exceeded, and ensure partially extracted temporary content is cleaned up.
-
Add regression tests using high-compression archives, oversized entries, excessive entry counts, deeply nested archives, and large collections of small files.
-
