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

Content
View full analysis

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
View 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
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/edit.py (reported line 118)May include surrounding context.

python
)

    try:
        with urllib.request.urlopen(req) as resp:
            result = json.loads(resp.read())
    except urllib.error.HTTPError as e:
        err_body = e.read().decode()

Tainted flow: 'req' from os.environ.get (line 237, 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/edit.py (reported line 179)May include surrounding context.

python
)

    try:
        with urllib.request.urlopen(req) as resp:
            result = json.loads(resp.read())
    except urllib.error.HTTPError as e:
        err_body = e.read().decode()

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

Critical
Category
Data Flow
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.

Content

Scanner excerpt · scripts/edit.py (reported line 128)May include surrounding context.

python
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")

Tainted flow: 'req' from os.environ.get (line 237, 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/edit.py (reported line 246)May include surrounding context.

python
)

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

Tainted flow: 'req' from os.environ.get (line 229, 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/generate.py (reported line 81)May include surrounding context.

python
)

    try:
        with urllib.request.urlopen(req) as resp:
            body = json.loads(resp.read())
    except urllib.error.HTTPError as e:
        err_body = e.read().decode()

Tainted flow: 'req' from os.environ.get (line 229, 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/generate.py (reported line 129)May include surrounding context.

python
)

    try:
        with urllib.request.urlopen(req) as resp:
            body = json.loads(resp.read())
    except urllib.error.HTTPError as e:
        err_body = e.read().decode()

Tainted flow: 'req' from os.environ.get (line 229, 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/generate.py (reported line 225)May include surrounding context.

python
)

    try:
        with urllib.request.urlopen(req) as resp:
            body = json.loads(resp.read())
    except urllib.error.HTTPError as e:
        err_body = e.read().decode()

Tainted flow: 'req' from os.environ.get (line 229, 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/generate.py (reported line 230)May include surrounding context.

python
)

    try:
        with urllib.request.urlopen(req) as resp:
            body = json.loads(resp.read())
    except urllib.error.HTTPError as e:
        err_body = e.read().decode()

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

Critical
Category
Data Flow
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.

Content

Scanner excerpt · scripts/generate.py (reported line 92)May include surrounding context.

python
# 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")

Tainted flow: 'req' from os.environ.get (line 229, 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/generate.py (reported line 198)May include surrounding context.

python
)

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

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.

Content

No source excerpt is available for this finding.

Missing User Warnings

High
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

External Model or Provider Selection

High
Category
Excessive Agency
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.

Content

Scanner excerpt · scripts/generate.py (reported line 108)May include surrounding context.

python
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")

Undeclared Tool Scope

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.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

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 · references/openai.md (reported line 5)May include surrounding context.

md
}).encode()

    req = urllib.request.Request(
        "https://api.openai.com/v1/images/generations",
        data=payload,
        headers={
            "Authorization": f"Bearer {api_key}",

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/edit.py (reported line 109)May include surrounding context.

python
}).encode()

    req = urllib.request.Request(
        "https://api.openai.com/v1/images/generations",
        data=payload,
        headers={
            "Authorization": f"Bearer {api_key}",

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/generate.py (reported line 72)May include surrounding context.

python
}).encode()

    req = urllib.request.Request(
        "https://api.openai.com/v1/images/generations",
        data=payload,
        headers={
            "Authorization": f"Bearer {api_key}",

Static analysis

No suspicious patterns detected.