Back to skill

Security audit

AI Image Generation

Security checks for vulnerabilities and agentic risk

Overview

The skill is an image tool, but its edit and variation functions can upload any local file path the agent can read to OpenAI without clear limits or confirmation.

Review before installing. Use this only in an environment where the agent cannot read sensitive files, and do not pass arbitrary or untrusted file paths to the edit or variation tools. The skill should ideally be updated to disclose uploads, require explicit confirmation, restrict inputs to approved image files, validate file contents, and fix multipart authentication.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
ai_image.py:33
Finding
Unrestricted Local File Read and External Upload via Image Tools## Vulnerability Details **File Location**: `ai_image.py`, lines 33–59 **Vulnerability Type**: Arbitrary local file disclosure through unrestricted path input **Risk Level**: High ### Vulnerable Code ```python async def edit_image(self, image_path: str, prompt: str) -> Dict[str, Any]: import requests if not requests: return {"success": False, "error": "requests not available"} url = "https://api.openai.com/v1/images/edits" with open(image_path, "rb") as img: files = {"image": img} data = {"prompt": prompt} try: loop = asyncio.get_event_loop() resp = await loop.run_in_executor(None, lambda: requests.post(url, files=files, data=data, timeout=60)) if resp.status_code == 200: return {"success": True, "url": resp.json().get("data", [{}])[0].get("url")} return {"success": False, "error": "Error"} except Exception as e: return {"success": False, "error": str(e)} async def variations(self, image_path: str, n: int = 1) -> Dict[str, Any]: import requests if not requests: return {"success": False, "error": "requests not available"} url = "https://api.openai.com/v1/images/variations" with open(image_path, "rb") as img: files = {"image": img} data = {"n": n} try: loop = asyncio.get_event_loop() resp = await loop.run_in_executor(None, lambda: requests.post(url, files=files, data=data, timeout=60)) ``` ### Technical Analysis The public `ai_edit_image` and `ai_image_variations` tools accept an arbitrary filesystem path from the caller. That path is passed directly to `open(image_path, "rb")`, and the resulting content is attached to an outbound HTTP request. The implementation does not: - Restrict access to a user-approved media directory. - Canonicalize the path and verify that it ...[truncated 1814 chars]
Remediation
## Remediation Suggestions 1. Restrict file inputs to an explicitly configured, user-approved image directory. 2. Resolve the requested path with `Path.resolve()` and verify that the resolved path is a descendant of the approved directory. 3. Reject symbolic links, non-regular files, device files, and paths containing traversal outside the approved root. 4. Validate content by decoding it with a trusted image library rather than relying on the filename extension or MIME type. 5. Enforce conservative file-size and image-dimension limits before loading or uploading content. 6. Require explicit user confirmation identifying the resolved file and external destination before transmission. 7. Prefer opaque file handles or platform-provided attachment identifiers over caller-controlled filesystem paths. 8. Run the Skill under a sandboxed account with access only to the workspace and approved media files. 9. Add tests covering absolute paths, `..` traversal, symlinks, non-image files, oversized files, and paths outside the permitted directory.

T09 · Insecure Skill Coding Practices

Warning
Location
ai_image.py:42
Finding
Authentication Header Omitted from Image Edit and Variation Requests## Vulnerability Details **File Location**: `ai_image.py`, lines 42 and 59 **Vulnerability Type**: Incorrect authentication handling for sensitive outbound requests **Risk Level**: Medium ### Vulnerable Code ```python resp = await loop.run_in_executor( None, lambda: requests.post( url, files=files, data=data, timeout=60 ) ) ``` The same unauthenticated request pattern is used by both `edit_image` and `variations`. By contrast, the generation operation supplies the client's configured headers: ```python resp = await loop.run_in_executor( None, lambda: requests.post( url, headers=self.headers, json=payload, timeout=60 ) ) ``` ### Technical Analysis `AIImageClient` constructs an authorization header containing the configured OpenAI API key: ```python self.headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json" } ``` However, the edit and variation methods do not pass an authorization header to `requests.post`. These calls are therefore expected to be rejected by the OpenAI API while still initiating an outbound request containing the selected local file. This is primarily an insecure implementation and reliability defect. It also aggravates the file-disclosure issue because sensitive file content may be transmitted in requests that cannot complete their declared operation. Multipart requests must not reuse the hardcoded JSON `Content-Type` header, because `requests` needs to generate a multipart boundary. Authorization should therefore be separated from content-type handling. ### Attack Path 1. A user or attacker invokes `ai_edit_image` or `ai_image_variations` with a local file. 2. The Skill opens the file and constructs a multipart upload. 3. It sends the request to the declared OpenAI endpoint without the required bearer token. 4. The request ...[truncated 674 chars]
Remediation
## Remediation Suggestions 1. Pass an authorization-only header to multipart requests: ```python auth_headers = {"Authorization": f"Bearer {self.api_key}"} requests.post( url, headers=auth_headers, files=files, data=data, timeout=60 ) ``` 2. Do not manually set `Content-Type: application/json` for multipart uploads; allow `requests` to generate the correct multipart content type and boundary. 3. Apply the path restrictions, image validation, size limits, and user-confirmation controls described for the arbitrary file upload vulnerability before enabling authenticated uploads. 4. Use `resp.raise_for_status()` or structured status handling and preserve safe API error details for diagnosis. 5. Add integration tests verifying that all API operations authenticate correctly and that rejected requests do not retry or upload files unnecessarily.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (12)

Lp3

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding
The skill declares use of an environment variable and, by description, performs outbound API calls, but it does not declare any explicit tool scope such as permissions or allowed-tools. That mismatch weakens reviewability and enforcement, making it easier for a skill to access secrets and the network without clear operator consent or policy restriction.

External Transmission

Medium
Category
Data Exfiltration
Content
if not requests:
            return {"success": False, "error": "requests not available"}
        
        url = "https://api.openai.com/v1/images/generations"
        payload = {"prompt": prompt, "model": model, "size": size, "n": 1}
        try:
            loop = asyncio.get_event_loop()
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
payload = {"prompt": prompt, "model": model, "size": size, "n": 1}
        try:
            loop = asyncio.get_event_loop()
            resp = await loop.run_in_executor(None, lambda: requests.post(url, headers=self.headers, json=payload, timeout=60))
            data = resp.json()
            if resp.status_code == 200:
                return {"success": True, "url": data.get("data", [{}])[0].get("url")}
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
The skill manifest describes image generation, but the implementation also reads arbitrary local files from user-supplied paths for edit/variation operations. In an agent environment, this expands capability from prompt submission to local file access and can expose sensitive local images or other reachable files to external services if the path is attacker-influenced.

External Transmission

Medium
Category
Data Exfiltration
Content
if not requests:
            return {"success": False, "error": "requests not available"}
        
        url = "https://api.openai.com/v1/images/edits"
        with open(image_path, "rb") as img:
            files = {"image": img}
            data = {"prompt": prompt}
Confidence
90% confidence
Finding
This external transmission is more sensitive because it involves uploading local file contents, not just prompts, to a third-party service. Combined with arbitrary path input and limited disclosure, this increases the risk of unintended data exfiltration from the host environment.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The edit function uploads a locally read file to an external API without any built-in notice, confirmation, or disclosure. In agent contexts, users may believe they are performing a local transformation, while the code actually transmits file contents off-system, creating privacy and data-handling risk.

Tainted flow: 'data' from requests.post (line 24, network input) → requests.post (network output)

Medium
Category
Data Flow
Content
data = {"prompt": prompt}
            try:
                loop = asyncio.get_event_loop()
                resp = await loop.run_in_executor(None, lambda: requests.post(url, files=files, data=data, timeout=60))
                if resp.status_code == 200:
                    return {"success": True, "url": resp.json().get("data", [{}])[0].get("url")}
                return {"success": False, "error": "Error"}
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Tainted flow: 'data' from requests.post (line 24, network input) → requests.post (network output)

Medium
Category
Data Flow
Content
data = {"prompt": prompt}
            try:
                loop = asyncio.get_event_loop()
                resp = await loop.run_in_executor(None, lambda: requests.post(url, files=files, data=data, timeout=60))
                if resp.status_code == 200:
                    return {"success": True, "url": resp.json().get("data", [{}])[0].get("url")}
                return {"success": False, "error": "Error"}
Confidence
65% confidence
Finding
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

External Transmission

Medium
Category
Data Exfiltration
Content
if not requests:
            return {"success": False, "error": "requests not available"}
        
        url = "https://api.openai.com/v1/images/variations"
        with open(image_path, "rb") as img:
            files = {"image": img}
            data = {"n": n}
Confidence
90% confidence
Finding
As with the edit endpoint, this call sends local image data to an external provider. In the context of an agent skill, the danger is elevated because the manifest emphasizes image generation, while the code can also export local files, creating a meaningful privacy boundary crossing.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The variation function similarly reads a local file and sends it to a third-party API with no user-facing warning in code or tool schema. This can lead to unintended exfiltration of sensitive images when the caller does not understand that a local path causes remote upload.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The tool descriptions are generic and provide no clear constraints on when the agent should invoke them, which increases the risk of inappropriate or overly eager tool use. In an image-generation skill, unconstrained activation can cause unintended API calls, prompt forwarding of sensitive user content, or editing/variation actions on arbitrary local image paths without sufficient user confirmation.

Missing User Warnings

Low
Confidence
78% confidence
Finding
The file tells users to set the OPENAI_API_KEY environment variable but does not include any warning about handling credentials carefully or the privacy implications of transmitting prompts/images to an external AI service. For markdown files, safety-relevant behaviors that could affect privacy or system integrity should be disclosed to the user.

Static analysis

No suspicious patterns detected.