Back to skill

Security audit

Scratch Blocks

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent Scratch visualization helper, but its upload extractor can process untrusted Scratch archives without size limits, which could exhaust local disk, memory, or CPU.

Review this skill before installing if it may process files from untrusted users. It does not show evidence of exfiltration, deception, credential access, or persistence, but oversized or malicious Scratch archives could consume local resources; use it only in a constrained environment or after adding archive and JSON size limits.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

T09 · Insecure Skill Coding Practices

Warning
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: Medium

Vulnerable 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 .sb3 and .sprite3 ZIP 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.json or sprite.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 with json.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

  1. An attacker creates a valid ZIP archive with a .sb3 or .sprite3 extension.
  2. The archive includes a member named project.json or sprite.json.
  3. That member contains valid but extremely large and high ...[truncated 929 chars]
Remediation
View remediation

Remediation Suggestions

  • Retrieve the selected member with ZipFile.getinfo() and reject it when file_size or compress_size exceeds 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.
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (4)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The core behavior is Scratch-related visualization, which aligns partially with the description's general intent. However, the declared purpose specifically says to use the skill when the user uploads a .sb3 or .sprite3 file. This code does not open or extract those archive formats at all. Instead, it expects a custom top-level list format called 'scratch-json' and explicitly errors on raw Scratch project/sprite JSON, instructing the caller to run extract.py first. That is a material description-behavior mismatch in primary input handling. No suspicious undeclared exfiltration or unrelated capabilities are present; the mismatch is that the implementation is narrower and different from the declared file-handling purpose.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill instructs the agent to read from references/ and invoke local scripts, and it also suggests writing temporary JSON files, but it declares no explicit tool scope or permissions. In an agent framework, undeclared file read/write capability weakens least-privilege controls and can let a broadly empowered runtime access or create files beyond what the skill actually needs.

Content

No source excerpt is available for this finding.

Vague Triggers

Low
Category
Not specified by scanner
Confidence
78% confidence
Finding

This JSON file is a manifest-like block catalog, so SQP-1 applies. The block text "go to" is highly generic and, without any negative examples or scope constraints in this file, could be interpreted as an overly broad activation phrase that overlaps with everyday speech.

Content

No source excerpt is available for this finding.

Vague Triggers

Low
Category
Not specified by scanner
Confidence
72% confidence
Finding

The text "start sound" is a broad natural-language phrase in a JSON manifest-style file. Because this file provides no explicit trigger scope, exclusions, or invocation constraints, the phrase can collide with ordinary user language under SQP-1's vague-trigger criteria.

Content

No source excerpt is available for this finding.