Back to skill

Security audit

Filtrix AI Image Generation

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its image generation/editing purpose, but it needs review because fal.ai editing silently disables a safety check and the docs understate external sharing of prompts and images.

Install only if you are comfortable with prompts and selected images or masks being sent to OpenAI, Google, or fal.ai under those providers' terms. Be especially cautious with the fal.ai edit provider because this version disables a provider-side safety checker without a user-facing option or disclosure.

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/edit.py:220
Finding
fal.ai Safety Checker Explicitly Disabled During Image Editing## Vulnerability Details **File Location**: `scripts/edit.py`, lines 220–244 **Vulnerability Type**: Provider-side safety control disabled by default **Risk Level**: Medium ```python arguments = { "prompt": prompt, "image_size": {"width": dims["width"], "height": dims["height"]}, "num_images": 1, "max_images": 1, "enable_safety_checker": False, "enhance_prompt_mode": "standard", "sync_mode": True, "image_urls": [data_uri], } if seed is not None: arguments["seed"] = seed url = f"https://fal.run/{model_id}" payload = json.dumps(arguments).encode() req = urllib.request.Request( url, data=payload, headers={ "Authorization": f"Key {api_key}", "Content-Type": "application/json", }, ) ``` ### Technical Analysis Every fal.ai image-editing request explicitly sets `enable_safety_checker` to `False`. Disabling this safeguard is not necessary for the declared image-editing functionality and is not disclosed in `SKILL.md`, `README.md`, or `references/fal.md`. The setting is unconditional and cannot be overridden through the command-line interface. Consequently, even ordinary invocations silently request that fal.ai process the supplied prompt and image without its optional content-safety checker. This differs from the OpenAI and Gemini paths, which retain provider filtering and handle safety-related rejection responses. Base64 encoding of the selected image and its transmission to fal.ai are functionally necessary for image-to-image editing and do not, by themselves, constitute covert exfiltration. The confirmed issue is the unnecessary disabling of the provider-side safeguard. ### Attack Path 1. An attacker or user supplies an image and an unsafe editing instruction. 2. The Skill is invoked with `python scripts/edit.py --provider fal --image INPUT --prompt PROMPT`. 3. The script reads and Base64-encodes the explicitly selected image. ...[truncated 840 chars]
Remediation
## Remediation Suggestions 1. Remove the `enable_safety_checker` field so the provider's secure default applies, or explicitly set it to `True`. 2. If disabling the checker is an essential supported capability, require a dedicated opt-in command-line flag rather than disabling it unconditionally. 3. Require explicit user confirmation before honoring such an opt-in and clearly explain the policy and safety implications. 4. Document the setting in `SKILL.md`, `README.md`, and `references/fal.md`. 5. Add tests verifying that normal fal.ai edit requests enable or preserve safety filtering. 6. Reject unsafe combinations by default and avoid automatically retrying requests in ways intended to bypass provider safety decisions.
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 (20)

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

Critical
Category
Data Flow
Content
)

    try:
        with urllib.request.urlopen(req) as resp:
            result = json.loads(resp.read())
    except urllib.error.HTTPError as e:
        err_body = e.read().decode()
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
)

    try:
        with urllib.request.urlopen(req) as resp:
            result = json.loads(resp.read())
    except urllib.error.HTTPError as e:
        err_body = e.read().decode()
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
if not b64:
        url = result.get("data", [{}])[0].get("url")
        if url:
            with urllib.request.urlopen(url) as img_resp:
                return img_resp.read()
        raise RuntimeError("No image data in OpenAI edit response")
Confidence
90% confidence
Finding
The code downloads an arbitrary URL returned by the OpenAI API response without validating the scheme, host, or content type. If that response is malformed, compromised, or unexpectedly influenced, this creates an SSRF-style outbound fetch path and could allow retrieval of unintended internal or malicious resources.

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

Critical
Category
Data Flow
Content
)

    try:
        with urllib.request.urlopen(req, timeout=180) as resp:
            body = json.loads(resp.read())
    except urllib.error.HTTPError as e:
        err_body = e.read().decode()
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
)

    try:
        with urllib.request.urlopen(req) as resp:
            body = json.loads(resp.read())
    except urllib.error.HTTPError as e:
        err_body = e.read().decode()
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
)

    try:
        with urllib.request.urlopen(req) as resp:
            body = json.loads(resp.read())
    except urllib.error.HTTPError as e:
        err_body = e.read().decode()
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
)

    try:
        with urllib.request.urlopen(req) as resp:
            body = json.loads(resp.read())
    except urllib.error.HTTPError as e:
        err_body = e.read().decode()
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
)

    try:
        with urllib.request.urlopen(req) as resp:
            body = json.loads(resp.read())
    except urllib.error.HTTPError as e:
        err_body = e.read().decode()
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
# Try URL fallback
        url = body.get("data", [{}])[0].get("url")
        if url:
            with urllib.request.urlopen(url) as img_resp:
                return img_resp.read()
        raise RuntimeError("No image data in OpenAI response")
Confidence
90% confidence
Finding
The code blindly fetches a URL returned by the OpenAI API without validating scheme, host, or content type. If that response is ever malformed, compromised, or redirected to an unexpected location, the skill could be induced to perform unintended outbound requests or download arbitrary content, creating an SSRF-style trust-boundary issue.

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

Critical
Category
Data Flow
Content
)

    try:
        with urllib.request.urlopen(req, timeout=180) as resp:
            body = json.loads(resp.read())
    except urllib.error.HTTPError as e:
        err_body = e.read().decode()
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The description says the skill generates images, but the documented behavior also includes image-to-image editing, uploading an existing local image, and optional masking. That mismatch can mislead users and reviewers about what data leaves the system; in practice, a user may trigger the skill expecting prompt-only generation while the skill can process and transmit sensitive local images to third-party providers.

Missing User Warnings

High
Confidence
98% confidence
Finding
The fal.ai request explicitly sets enable_safety_checker to false, disabling a built-in content safety control without warning the user. In an image-generation/editing skill, this materially increases the chance of producing disallowed, abusive, or otherwise risky content and weakens defense-in-depth.

External Model or Provider Selection

High
Category
Excessive Agency
Content
if not api_key:
        raise RuntimeError("GOOGLE_API_KEY not set")

    # Default to Flash (cheaper). Use --model gemini-3-pro-image-preview for higher quality.
    model = model or "gemini-2.5-flash-image"
    aspect = GEMINI_ASPECT_MAP.get(size, "1:1")
Confidence
90% confidence
Finding
Skill selects an external model or provider that may use a different account or billing plan than the operator expects. Undisclosed model switches can cause unexpected cost or quota consumption.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill advertises capabilities that require environment access, filesystem reads/writes, and outbound network calls, but it does not declare any tool scope or permissions boundaries. This weakens reviewability and increases the chance the skill is invoked with broader access than users or the platform expect, especially since it handles API keys and image files.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The trigger language is very broad: it activates on common requests to create or make any image, picture, artwork, or visual content. Overbroad routing can cause unintended invocation of a networked skill that accesses API keys and files, increasing the chance that user prompts or images are sent externally when the user did not intend to use this specific provider-backed capability.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The skill description explains setup and provider choices but does not warn users that prompts and, for edit mode, uploaded images and masks are transmitted to external AI providers. This omission creates a real privacy and data-handling risk because users may unknowingly send sensitive text or images to third parties with separate retention and policy regimes.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
This skill sends user-provided images and prompts to third-party AI services but provides no explicit notice or consent mechanism around external data sharing. In an agent-skill context, users may assume processing is local, so sensitive images or embedded metadata could be transmitted off-platform unexpectedly.

External Transmission

Medium
Category
Data Exfiltration
Content
}).encode()

    req = urllib.request.Request(
        "https://api.openai.com/v1/images/generations",
        data=payload,
        headers={
            "Authorization": f"Bearer {api_key}",
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
}).encode()

    req = urllib.request.Request(
        "https://api.openai.com/v1/images/generations",
        data=payload,
        headers={
            "Authorization": f"Bearer {api_key}",
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
}).encode()

    req = urllib.request.Request(
        "https://api.openai.com/v1/images/generations",
        data=payload,
        headers={
            "Authorization": f"Bearer {api_key}",
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Static analysis

No suspicious patterns detected.