Back to skill

Security audit

Multi-panel Figure Assembler

Security checks for vulnerabilities and agentic risk

Overview

This skill appears purpose-built for assembling figures, but it needs review because its dependency list is unsafe and its script does not enforce the workspace path limits it documents.

Review before installing. Use an isolated environment, remove 'pil', pin reviewed dependency versions, and only run the script on trusted image paths until workspace-boundary checks are enforced in code.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/main.py:119
Finding

Documented Workspace Path Restrictions Are Not Enforced

Content
View full analysis

Vulnerability Details

File Location: scripts/main.py:119-125 and scripts/main.py:276-280
Related Security Contract: SKILL.md:104
Vulnerability Type: Missing path traversal and workspace-boundary validation
Risk Level: Medium

Vulnerable Code

The input-loading logic checks only whether a path exists. It does not reject traversal components, absolute paths, or resolved paths outside the permitted workspace:

python
def _load_image(self, path: str) -> Image.Image:
    """Load an image from file."""
    img_path = Path(path)
    if not img_path.exists():
        raise FileNotFoundError(f"Image not found: {path}")
    
    img = Image.open(path)

The output logic likewise accepts any path available to the current process, creates its parent directories, and saves the result without a workspace-boundary check:

python
# Save with DPI info
output_path = Path(output)
output_path.parent.mkdir(parents=True, exist_ok=True)

# Save as PNG with DPI
composite.save(output_path, dpi=(self.dpi, self.dpi))

This contradicts the documented restriction in SKILL.md:104:

text
- If any input file path contains `../` or points outside the workspace, reject with a path traversal warning.

Technical Analysis

Filesystem access should be constrained by resolving each user-controlled path and verifying that the canonical path remains beneath a trusted workspace root. The implementation performs no such verification.

Path.exists() establishes only that the input exists. It does not establish that the path is authorized. Consequently, absolute paths, paths containing ../, and symlinks resolving outside the workspace are accepted. The same issue applies to the output path, where mkdir() and Image.save() operate on a caller-selected location.

Merely searching for the literal string ../ would not be sufficient because absolute paths, platform-specific separator ...[truncated 1524 chars]

Remediation
View remediation

Remediation Suggestions

  1. Require the application to receive or define an explicit trusted workspace root.
  2. Canonicalize the workspace, every input path, and the output path with Path.resolve().
  3. Verify containment using Path.relative_to() rather than string-prefix comparisons.
  4. Apply the same policy to both input and output paths before opening files or creating directories.
  5. Reject paths whose symlink-resolved targets leave the workspace.
  6. Optionally reject symlink inputs entirely if they are unnecessary.
  7. Validate output extensions against the explicitly supported formats.
  8. Add automated tests for absolute paths, ../ traversal, nested traversal, sibling-prefix paths, and symlinks escaping the workspace.

Example containment helper:

python
def resolve_in_workspace(path: str, workspace: Path) -> Path:
    root = workspace.resolve(strict=True)
    candidate = Path(path).resolve(strict=False)
    try:
        candidate.relative_to(root)
    except ValueError:
        raise ValueError(f"Path is outside the workspace: {path}")
    return candidate

For inputs, additionally require candidate.is_file() and resolve with strict existence checks. For output, validate containment before calling mkdir() or save().

T08 · Insecure Dependencies

Warning
Location
requirements.txt:1
Finding

Ambiguous and Unpinned Third-Party Dependencies

Content
View full analysis

Vulnerability Details

File Location: requirements.txt:1-3
Vulnerability Type: Unsafe and non-reproducible dependency declaration
Risk Level: Medium

Vulnerable Code

text
numpy
pil
pillow

Technical Analysis

The source imports Pillow through the PIL namespace, but the manifest declares both pil and pillow. pil is not needed to satisfy the code's from PIL import ... imports when Pillow is installed. Requesting an unnecessary package with a similar name increases dependency-confusion and package-selection risk.

In addition, none of the dependencies is version-pinned or hash-verified. A fresh installation can therefore resolve to package versions different from those reviewed or tested. This weakens build reproducibility and allows future upstream changes to enter the execution environment without a corresponding project review.

NumPy is imported by scripts/main.py but is not used by the observed implementation. Unnecessary dependencies increase the supply-chain attack surface without providing functionality.

Attack Path

  1. A user or automated deployment follows the project manifest and runs pip install -r requirements.txt.
  2. The package manager requests the ambiguous pil distribution in addition to Pillow and resolves all packages without version constraints.
  3. Installation may fail, select unintended package content, or install future package versions that were not reviewed with this project.
  4. Package installation hooks or subsequently imported dependency code execute with the privileges of the installation or application process.

The audit did not establish that any currently resolved package is malicious. The vulnerability is the unnecessary ambiguous package request and the absence of controls ensuring that reviewed dependency artifacts are installed.

Impact Assessment

The maximum impact depends on the privileges used for package installation and appli ...[truncated 468 chars]

Remediation
View remediation

Remediation Suggestions

  1. Remove the ambiguous pil entry and use Pillow as the sole provider of the PIL import namespace.
  2. Remove NumPy from both requirements.txt and scripts/main.py unless it becomes necessary.
  3. Pin dependencies to reviewed, compatible versions.
  4. Generate and verify cryptographic hashes for deployment artifacts, for example with a lock-file workflow or pip --require-hashes.
  5. Use a controlled package index and disable untrusted supplemental indexes in production builds.
  6. Add dependency vulnerability scanning and scheduled update review to CI.
  7. Install dependencies in an isolated virtual environment under a non-privileged account.

A minimal manifest should contain only a reviewed Pillow release, for example:

text
Pillow==<reviewed-version>

The selected version should be tested with the supported Python runtime and accompanied by an artifact hash in production.

Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (7)

Possible Typosquatting: 'pil' resembles popular package 'pip'

High
Category
Supply Chain
Confidence
98% confidence
Finding

The package name 'pil' is a strong red flag: the maintained library for Python imaging is Pillow, and 'pil' may represent an obsolete, unofficial, or typo-confusable dependency. If an installer resolves and fetches this package, an attacker-controlled or unsafe package could be introduced into the environment, leading to arbitrary code execution at install time or runtime.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

This JSON audit result explicitly states that the skill description does not mention the 6-panel constraint and could lead to false triggers for 4-panel or 9-panel requests. For a JSON file, this falls under vague trigger scope because the activation/description is described as insufficiently specific.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
94% confidence
Finding

The dependency list does not pin a specific numpy version, which makes builds non-reproducible and can pull in a vulnerable or incompatible release over time. In a skill that processes files and images, uncontrolled dependency resolution increases supply-chain risk even if it is not an immediate exploit by itself.

Content

Scanner excerpt · requirements.txt (reported line 1)May include surrounding context.

text
numpy
pil
pillow

Unverifiable Dependency: numpy has 16 known advisory(ies) (CVE-2014-1859 (Numpy arbitrary file write via symlink attack); CVE-2021-41495 (NumPy NULL Pointer Dereference); CVE-2021-33430 (NumPy Buffer Overflow (Disputed)) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
88% confidence
Finding

Numpy has known historical advisories, and because no version is pinned there is no way to verify whether the installed release is affected. This is primarily a supply-chain hygiene issue rather than proof of an active vulnerable install, but it still leaves consumers exposed to avoidable risk.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
99% confidence
Finding

The entry 'pil' is unpinned and additionally suspicious because the canonical modern imaging package is typically Pillow, while PIL itself is obsolete and the name could resolve unexpectedly in some ecosystems or internal mirrors. This creates both reproducibility and dependency-confusion/typosquatting risk, which is more dangerous here because the skill is explicitly image-processing related and likely to install this package path during setup.

Content

Scanner excerpt · requirements.txt (reported line 2)May include surrounding context.

text
numpy
pil
pillow

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
95% confidence
Finding

Pillow is included without a version pin, so deployments may silently install different releases with different security properties. Because Pillow parses untrusted image formats, leaving the version unconstrained increases exposure to known parser bugs and future regressions.

Content

Scanner excerpt · requirements.txt (reported line 3)May include surrounding context.

text
numpy
pil
pillow

Unverifiable Dependency: pillow has 16 known advisory(ies) (CVE-2016-2533 (Pillow buffer overflow in ImagingPcdDecode); CVE-2023-50447 (Arbitrary Code Execution in Pillow); CVE-2021-27922 (Pillow Uncontrolled Resource Consumption) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
93% confidence
Finding

Pillow has multiple published advisories, including issues in image parsing, and the unpinned requirement prevents verifying whether a safe version will be installed. In the context of a figure-assembly skill that will likely open user-supplied images, this raises the practical risk because the library directly handles potentially attacker-controlled files.

Content

No source excerpt is available for this finding.