Back to skill

Security audit

Filtrix Image Gen

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to provide the advertised Filtrix image generation/editing flow, but it needs review because its endpoint override can send the API key and user image data to an arbitrary URL.

Review before installing. Use a dedicated Filtrix API key with limited privileges or credits, do not set FILTRIX_MCP_URL unless it is a trusted HTTPS endpoint, and avoid sending private screenshots, IDs, proprietary images, or regulated data unless you accept Filtrix processing them remotely. Save outputs only to intended paths and be aware returned downloads are not size- or type-checked.

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 (2)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/generate.py:186
Finding
Unrestricted MCP Endpoint Override Exposes Credentials and User Content<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate.py:37-57, 186-194`; `scripts/edit.py:37-56, 202-209` **Vulnerability Type**: Unvalidated security-sensitive endpoint configuration **Risk Level**: High ### Complete Code Snippet From `scripts/generate.py`: ```python class McpClient: def __init__(self, endpoint: str, api_key: str): self.endpoint = endpoint self.api_key = api_key self.session_id: str | None = None self.request_id = 0 def _post(self, payload: dict[str, Any], include_id: bool = True) -> dict[str, Any]: body = json.dumps(payload).encode("utf-8") headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json", "Accept": "application/json, text/event-stream", } if self.session_id: headers["mcp-session-id"] = self.session_id req = urllib.request.Request(self.endpoint, data=body, headers=headers, method="POST") ``` ```python mcp_api_key = os.environ.get("FILTRIX_MCP_API_KEY") or os.environ.get("MCP_API_KEY") if not mcp_api_key: print("ERROR: FILTRIX_MCP_API_KEY is required", file=sys.stderr) sys.exit(1) mcp_url = os.environ.get("FILTRIX_MCP_URL", DEFAULT_MCP_URL) request_key = args.idempotency_key or f"gen-{uuid.uuid4().hex}" client = McpClient(endpoint=mcp_url, api_key=mcp_api_key) ``` The equivalent behavior appears in `scripts/edit.py`: ```python mcp_api_key = os.environ.get("FILTRIX_MCP_API_KEY") or os.environ.get("MCP_API_KEY") if not mcp_api_key: print("ERROR: FILTRIX_MCP_API_KEY is required", file=sys.stderr) sys.exit(1) mcp_url = os.environ.get("FILTRIX_MCP_URL", DEFAULT_MCP_URL) request_key = args.idempotency_key or f"edit-{uuid.uuid4().hex}" client = McpClient(endpoint=mcp_url, api_key=mcp_api_key) ``` ### Technical Analysis The scripts intentionally support `FILTRIX_MCP_URL` as an endpoint override, but they do not validate its scheme, hostna ...[truncated 2569 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require the endpoint to use HTTPS: ```python from urllib.parse import urlparse parsed = urlparse(mcp_url) if parsed.scheme != "https": raise RuntimeError("FILTRIX_MCP_URL must use HTTPS") ``` 2. Allowlist the expected service hostname, such as `mcp.filtrix.ai`, and reject unexpected ports, embedded credentials, fragments, and malformed URLs. 3. Do not automatically attach the Filtrix production credential to arbitrary custom endpoints. If custom endpoints are a required feature: - Require an explicit command-line opt-in. - Use a separate credential variable for custom endpoints. - Display the normalized destination before transmitting user content. - Require explicit user confirmation when sending local images outside the official domain. 4. Disable redirects for authenticated MCP requests or validate every redirect target before resending. Authorization headers must never be forwarded across origins. 5. Remove the generic `MCP_API_KEY` fallback unless compatibility requires it. A service-specific credential variable reduces accidental credential reuse and unintended disclosure. 6. Document clearly that prompts and source images are transmitted to a remote service and may be subject to that service's storage and privacy policies. 7. Add automated tests confirming rejection of HTTP, loopback addresses, embedded credentials, unapproved domains, unexpected ports, and cross-origin redirects. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate.py:148
Finding
Unvalidated Server-Supplied Image URL Enables Arbitrary Outbound Requests and Unbounded Downloads<![CDATA[ ## Vulnerability Details **File Location**: `scripts/generate.py:148-156, 218-236`; `scripts/edit.py:145-153, 241-259` **Vulnerability Type**: Server-side request forgery exposure and unrestricted resource consumption **Risk Level**: Medium ### Complete Code Snippet From `scripts/generate.py`: ```python def download_image(url: str) -> bytes: req = urllib.request.Request(url, method="GET") try: with urllib.request.urlopen(req, timeout=120) as resp: return resp.read() except urllib.error.HTTPError as exc: raise RuntimeError(f"Signed URL HTTP {exc.code}: {exc.read().decode('utf-8', errors='replace')}") except urllib.error.URLError as exc: raise RuntimeError(f"Signed URL network error: {exc.reason}") ``` ```python image_url = tool_payload.get("image_url") if not isinstance(image_url, str) or not image_url: print(f"ERROR: MCP did not return image_url: {json.dumps(tool_payload, ensure_ascii=False)}", file=sys.stderr) sys.exit(1) try: image_bytes = download_image(image_url) except RuntimeError as exc: print(f"ERROR: {exc}", file=sys.stderr) sys.exit(1) if not args.output: ts = datetime.now().strftime("%Y%m%d_%H%M%S") args.output = f"/tmp/filtrix_mcp_{args.mode}_{ts}.png" out_path = Path(args.output) out_path.parent.mkdir(parents=True, exist_ok=True) out_path.write_bytes(image_bytes) ``` `scripts/edit.py` uses the same unrestricted downloader: ```python def download_image(url: str) -> bytes: req = urllib.request.Request(url, method="GET") try: with urllib.request.urlopen(req, timeout=120) as resp: return resp.read() except urllib.error.HTTPError as exc: raise RuntimeError(f"Signed URL HTTP {exc.code}: {exc.read().decode('utf-8', errors='replace')}") except urllib.error.URLError as exc: raise RuntimeError(f"Signed URL network error: {exc.reason}") ``` ### Technical Analysis Both scripts trust the `image_url` returned by ...[truncated 2748 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Permit only HTTPS download URLs. 2. Restrict downloads to an allowlist of documented Filtrix storage domains. Validate the normalized hostname rather than relying on substring matching. 3. Resolve the hostname before connecting and reject loopback, private, link-local, multicast, unspecified, and reserved IP ranges for both IPv4 and IPv6. 4. Disable redirects or validate every redirect target using the same scheme, hostname, port, and resolved-address controls. 5. Stream the response in bounded chunks rather than calling `resp.read()` without a limit. Enforce a conservative maximum image size and abort if `Content-Length` or streamed bytes exceed it. 6. Require an expected image media type, such as `image/png`, `image/jpeg`, or `image/webp`. 7. Validate the downloaded file's magic bytes and decode it with a hardened image parser before writing it as a successful result. Do not trust the file extension or response header alone. 8. Write downloads to a securely created temporary file, validate them, and then atomically move them to the final destination. 9. Apply separate connection and read timeouts and return a clear security error when URL or response validation fails. 10. Add tests covering localhost URLs, private IPv4 and IPv6 addresses, DNS rebinding scenarios, cross-origin redirects, misleading content types, malformed images, missing `Content-Length`, and oversized chunked responses. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • 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
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (7)

Tp4

High
Category
MCP Tool Poisoning
Confidence
89% confidence
Finding
The skill claims image editing/generation functionality, but the documented flow includes downloading image data from signed URLs and writing it to the local filesystem without clearly stating that behavior. Hidden download-and-write behavior can lead to data persistence, overwriting files, or importing untrusted remote content in contexts where users only expected a transient image transformation request.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding
The skill claims image editing/generation functionality, but the documented flow includes downloading image data from signed URLs and writing it to the local filesystem without clearly stating that behavior. Hidden download-and-write behavior can lead to data persistence, overwriting files, or importing untrusted remote content in contexts where users only expected a transient image transformation request.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill invokes remote MCP tools, uses environment-based authentication, and documents scripts that read input images and write outputs locally, but it does not declare any explicit tool scope such as permissions or allowed-tools. This creates an avoidable over-privilege and transparency problem: an agent may grant broader capabilities than users expect, increasing the chance of unintended file, network, or secret access.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill states a third-party MCP endpoint and API key are used, but it does not clearly warn that user prompts and supplied image inputs may be transmitted off-platform to that remote service. In an image-editing context this is especially important because prompts and images may contain sensitive personal, proprietary, or regulated data.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The documentation explicitly supports sending either an external image URL or raw/base64 image content to a remote MCP endpoint, but it does not warn that user-supplied images will leave the local environment and be transmitted to a third-party service. This creates a privacy and data-handling risk because users or downstream agents may unknowingly upload sensitive images, screenshots, IDs, or internal material to Filtrix.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
When a local image is provided, the script base64-encodes it and sends the contents to a remote MCP endpoint, but the CLI does not clearly warn the user that image data will leave the local machine. This can cause unintended disclosure of sensitive images or embedded metadata, especially because the endpoint is configurable via environment variable and the tool is designed for routine use.

Description-Behavior Mismatch

Medium
Confidence
97% confidence
Finding
The manifest says the skill can both create images and refine existing ones, implying some image-editing capability. In this file, the only MCP tool invoked is `generate_image_text`, and there is no code path for accepting an existing image or performing edits/refinements on one.

Static analysis

No suspicious patterns detected.