T09 · Insecure Skill Coding Practices
- Location
scripts/extract.py:67- Finding
Unbounded Scratch Archive Extraction Can Cause Denial of Service
- Content
View full analysis
Vulnerability Details
File Location:
scripts/extract.py, lines 67–83
Vulnerability Type: Unrestricted archive expansion and resource consumption
Risk Level: MediumVulnerable Code:
python workdir = os.path.join("/tmp/scratchcode", md5hex) os.makedirs(workdir, exist_ok=True) with zipfile.ZipFile(filepath, "r") as zf: names = zf.namelist() if "project.json" in names: json_name = "project.json" elif "sprite.json" in names: json_name = "sprite.json" else: fail("neither project.json nor sprite.json found in archive") zf.extract(json_name, workdir) json_path = os.path.join(workdir, json_name) print(f"Working directory: {workdir}", file=sys.stderr) with open(json_path, "r", encoding="utf-8") as f: data = json.load(f)Technical Analysis
The extractor processes attacker-controlled
.sb3and.sprite3ZIP archives without enforcing a maximum compressed size, uncompressed size, compression ratio, or JSON complexity limit. Although extraction is restricted to a specifically named member,project.jsonorsprite.json, that member can be a highly compressed archive bomb.Calling
ZipFile.extract()expands the entire selected member into/tmp/scratchcode. The resulting file is then loaded in full withjson.load(). Consequently, one malicious upload can consume substantial disk space during expansion and substantial memory and CPU while parsing the expanded JSON.This is a resource-exhaustion flaw rather than an arbitrary file-write issue: the code selects fixed member names and constructs the destination path itself. No path traversal or privilege escalation was established from the audited code.
Attack Path
- An attacker creates a valid ZIP archive with a
.sb3or.sprite3extension. - The archive includes a member named
project.jsonorsprite.json. - That member contains valid but extremely large and high ...[truncated 929 chars]
- An attacker creates a valid ZIP archive with a
- Remediation
View remediation
Remediation Suggestions
- Retrieve the selected member with
ZipFile.getinfo()and reject it whenfile_sizeorcompress_sizeexceeds conservative limits. - Enforce a maximum compression ratio, taking care to handle zero-byte compressed sizes safely.
- Do not use unrestricted
extract(). Open the archive member as a stream and copy it into a controlled file while counting bytes; abort immediately when the configured expanded-size limit is reached. - Parse only after size validation. Where feasible, use a streaming JSON parser or enforce limits on nesting depth, object count, array length, and string length.
- Apply operating-system resource controls, including memory, CPU-time, file-size, and temporary-storage quotas.
- Write into a unique, permission-restricted temporary directory and remove partial files and directories after errors or completion.
- Validate Scratch schema and collection sizes before recursively processing blocks.
- Add regression tests using oversized members, extreme compression ratios, deeply nested JSON, and large block collections.
- Retrieve the selected member with
