Back to skill

Security audit

Visual References

Security checks for vulnerabilities and agentic risk

Overview

The skill’s main Pexels reference-image workflow is coherent, but its helper script can delete matching files in any caller-selected output folder before downloading results.

Review this skill before installing. It appears intended to fetch Pexels reference images, but only run it with a dedicated temporary output directory such as /tmp/visual-refs, do not point --output at a working project or home directory, and consider removing the unused unpinned requests dependency.

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/visual_ref.py:69
Finding

Caller-Controlled Output Directory Enables Destructive File Cleanup

Content
View full analysis

Vulnerability Details

File Location: scripts/visual_ref.py, lines 69-85
Vulnerability Type: Arbitrary-directory destructive cleanup
Risk Level: Medium

Vulnerable Code:

python
parser.add_argument("--output", default="/tmp/visual-refs", help="Output folder")
parser.add_argument("--orientation", choices=["landscape", "portrait", "square"], help="Orientation (optional)")
parser.add_argument("--list-only", action="store_true", help="List URLs only, no download")
args = parser.parse_args()

if not PEXELS_API_KEY:
    print("Error: PEXELS_API_KEY not found in environment.", file=sys.stderr)
    print("  Set it with: export PEXELS_API_KEY=your_api_key", file=sys.stderr)
    sys.exit(1)

output_dir = Path(args.output)
# Clean previous references to avoid accumulation across searches
if output_dir.exists():
    for old_file in output_dir.glob("ref_*"):
        old_file.unlink()
    meta = output_dir / "refs_meta.json"
    if meta.exists():
        meta.unlink()

Technical Analysis

The --output argument accepts an unrestricted filesystem path. Before performing the search or downloading images, the script deletes every entry matching ref_* and the file refs_meta.json from the selected directory.

The cleanup operation does not verify that the directory is the documented /tmp/visual-refs directory, that it was created by this script, or that the matching files belong to a previous invocation. It also lacks confirmation, canonical-path validation, and a directory ownership marker. Although Path.glob() limits deletion to matching entries directly inside the selected directory, unrelated files with those names can still be removed.

The documented mandatory output path reduces accidental exposure when instructions are followed, but the implementation does not enforce that rule. The behavior therefore exceeds the minimum filesystem privileges needed for the declared functionality.

...[truncated 1071 chars]

Remediation
View remediation

Remediation Suggestions

  • Enforce the documented /tmp/visual-refs directory instead of accepting arbitrary output paths.
  • If custom directories are required, resolve them with Path.resolve() and require them to be descendants of a dedicated, application-controlled base directory.
  • Create a marker file when initializing an output directory and refuse cleanup unless the marker proves that the directory is managed by this Skill.
  • Track files created by the script and delete only those exact files rather than deleting every entry matching a broad filename pattern.
  • Skip all cleanup in --list-only mode because that mode does not need an output directory for downloaded images.
  • Refuse dangerous locations such as filesystem roots, user home directories, and existing directories not created by the Skill.
  • Handle directories and symbolic links explicitly so cleanup cannot unexpectedly target or fail against non-regular entries.

T08 · Insecure Dependencies

Note
Location
SKILL.md:4
Finding

Unnecessary and Unpinned Third-Party Dependency Declaration

Content
View full analysis

Vulnerability Details

File Location: SKILL.md, line 4
Vulnerability Type: Unnecessary unpinned dependency
Risk Level: Low

Vulnerable Code:

yaml
metadata: {"clawdbot":{"emoji":"🖼️","requires":{"bins":["python3"],"pip":["requests"],"env":["PEXELS_API_KEY"]}}}

Technical Analysis

The Skill declares the third-party requests package without a version or integrity constraint. The audited script does not import or use requests; all network operations use Python's standard-library urllib modules.

Consequently, the dependency is not required for the declared functionality. If the Skill installation process automatically installs declared pip packages, this metadata introduces unnecessary package-index access and third-party code into the environment. Because no version is pinned, dependency resolution may select different package and transitive dependency versions over time.

No evidence was found that the declared package name is a typosquat or that the current package is malicious. The issue is the avoidable and unconstrained supply-chain surface.

Attack Path

  1. A Skill manager reads the pip requirements from SKILL.md.
  2. The manager contacts a configured Python package index and resolves the unpinned requests dependency and its transitive dependencies.
  3. External package artifacts are downloaded and installed despite not being needed by the script.
  4. A compromised package release, package index, dependency, or installation path could execute or introduce untrusted code during installation or later use.

Impact Assessment

The declaration may produce unnecessary network access, package installation, and third-party code exposure. Any resulting privileges are limited to those of the package installer or Skill runtime, but installations performed in a shared or privileged environment could have broader impact.

This finding does not demonstrate active compromise; it identif ...[truncated 78 chars]

Remediation
View remediation

Remediation Suggestions

  • Remove requests from the Skill metadata because the implementation uses only Python standard-library networking modules.
  • If requests becomes necessary in a future implementation, pin it to an audited version or constrained version range.
  • Use a lock file with cryptographic hashes where the deployment system supports integrity-verified installations.
  • Regularly audit direct and transitive dependencies and avoid declaring packages that are not imported or used.
  • Ensure dependency installation runs without elevated privileges and in an isolated environment.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (9)

Tainted flow: 'req' from os.environ.get (line 34, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/visual_ref.py (reported line 40)May include surrounding context.

python
})

    try:
        with urllib.request.urlopen(req, timeout=15) as resp:
            data = json.loads(resp.read().decode())
            return data.get("photos", [])
    except urllib.error.HTTPError as e:

Tainted flow: 'req' from os.environ.get (line 34, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/visual_ref.py (reported line 58)May include surrounding context.

python
filename = output_dir / f"ref_{index:02d}_{photo_id}.{ext}"

    req = urllib.request.Request(img_url, headers={"User-Agent": "visual-ref-skill/2.0"})
    with urllib.request.urlopen(req, timeout=30) as resp:
        filename.write_bytes(resp.read())

    return filename, photographer

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill invokes a Python script that uses an API key, performs network access to Pexels, and writes files to /tmp, but the manifest does not declare any explicit tool scope such as allowed tools or permissions. This creates an authorization gap: an agent may execute code with broader-than-expected capabilities, making review and policy enforcement harder.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The markdown explicitly instructs the agent to translate the user's query to English because Pexels works best in English. This imposes a language choice on the workflow without offering the user an option or documenting consent, which matches the language/locale policy violation criteria.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The line 'Always in English' is a blanket instruction that enforces a specific language for operation. Because the skill does not present this as a user choice or justified regional constraint, it violates the natural-language policy rule for forced language selection.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/visual_ref.py (reported line 33)May include surrounding context.

python
if orientation:
        params["orientation"] = orientation  # landscape | portrait | square

    url = "https://api.pexels.com/v1/search?" + urllib.parse.urlencode(params)
    req = urllib.request.Request(url, headers={
        "Authorization": PEXELS_API_KEY,
        "User-Agent": "visual-ref-skill/2.0",

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The script unconditionally deletes existing ref_* files and refs_meta.json in the chosen output directory before downloading new results. This can destroy user data unexpectedly, especially if --output points to a shared or previously used directory, and exceeds a narrowly scoped search/download action by performing cleanup without confirmation.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This is a true safety issue: prior files are removed without any prompt, dry-run, or explicit user opt-in. In agent contexts, where output paths may be supplied automatically, silent deletion makes accidental data loss more likely and harder for a user to anticipate.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
94% confidence
Finding

The documentation says to run one search with --count 5, then 'pass ALL 3 references directly to generate_image', while the example immediately below hard-codes exactly three file paths. Elsewhere, the skill describes downloading five images by default, so these instructions actively conflict about the intended behavior.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.