Back to skill

Security audit

NSFW AI (SpicyAPI)

Security checks for vulnerabilities and agentic risk

Overview

The skill clearly acts as a SpicyAPI client for adult AI generation, with disclosed API use, local media upload behavior, price confirmation, and adult/consent rules.

Install only if you intentionally want an adult-content SpicyAPI workflow. Use it with a dedicated SPICY_API_KEY, expect prompts and selected media files to be sent to SpicyAPI, review quotes before approving billable jobs, and avoid using real people or sensitive imagery unless consent and policy requirements are satisfied.

Vulnerability Patterns
  • 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
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (15)

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

python
Idempotency-Key, so repeating it can never create a second task or charge."""
    for attempt in range(attempts):
        try:
            with urllib.request.urlopen(req, timeout=timeout) as resp:
                return json.loads(resp.read().decode("utf-8") or "{}")
        except urllib.error.HTTPError:
            raise

Tainted flow: 'put' from os.environ.get (line 171, 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/spicy.py (reported line 174)May include surrounding context.

python
put = urllib.request.Request(ticket["uploadUrl"], data=path.read_bytes(), method=ticket.get("method", "PUT"),
                                 headers={**ticket.get("headers", {}), "Content-Length": str(size)})
    try:
        with urllib.request.urlopen(put, timeout=300):
            pass
    except urllib.error.HTTPError as err:
        raise SpicyError(f"Upload PUT failed with HTTP {err.code}: {err.read()[:200]!r}") from None

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

The code downloads asset URLs returned by the remote service without validating scheme, host, or size, then writes the response to disk. If the upstream service or a configured alternate API base is malicious or compromised, this can trigger SSRF-like outbound requests and unbounded download of attacker-controlled content.

Content

Scanner excerpt · scripts/spicy.py (reported line 220)May include surrounding context.

python
ext = mimetypes.guess_extension(asset.get("mime") or "") or Path(urllib.parse.urlparse(url).path).suffix or ".bin"
        path = target / f"{task['taskId']}_{i}{ext}"
        req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
        with urllib.request.urlopen(req, timeout=300) as resp, open(path, "wb") as fh:
            fh.write(resp.read())
        saved.append(str(path))
    return saved

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill instructs the agent to use environment variables, read local files, and access external network resources, but it does not declare any explicit tool scope or allowed-tools/permissions boundary. That creates an authorization gap where the runtime may grant broader capabilities than users or reviewers expect, increasing the risk of unintended file access, API key exposure, or exfiltration through networked commands.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill enables implicit invocation without any trigger constraints, so the agent may auto-select this NSFW capability based on loose semantic matching rather than an explicit user request. In an adults-only content generation skill, that creates a meaningful risk of unintended activation, including routing ordinary image/editing requests into uncensored or sexualized generation flows without clear user intent confirmation.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
83% confidence
Finding

This markdown file describes models for adult-oriented image and video generation and mentions face/head/character swap capabilities. Although it prohibits placing a real person into sexual content without documented consent, it does not provide a broader user warning that these tools can affect privacy and sensitive personal data, which is relevant for a skill description covering such behavior.

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/models.md (reported line 71)May include surrounding context.

md
from pathlib import Path
from typing import Any

API_BASE = os.environ.get("SPICY_API_BASE_URL", "https://api.spicyapi.ai/api/v1").rstrip("/")
SERVICE_BASE = os.environ.get("SPICY_SERVICE_BASE_URL", "https://api.spicyapi.ai").rstrip("/")
CATALOG_URL = f"{SERVICE_BASE}/console/v1/catalog/models"
USER_AGENT = "nsfw-ai-skill/1.0 (+https://github.com/Spicy-API/nsfw-ai-skill)"

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

python
from pathlib import Path
from typing import Any

API_BASE = os.environ.get("SPICY_API_BASE_URL", "https://api.spicyapi.ai/api/v1").rstrip("/")
SERVICE_BASE = os.environ.get("SPICY_SERVICE_BASE_URL", "https://api.spicyapi.ai").rstrip("/")
CATALOG_URL = f"{SERVICE_BASE}/console/v1/catalog/models"
USER_AGENT = "nsfw-ai-skill/1.0 (+https://github.com/Spicy-API/nsfw-ai-skill)"

Tainted flow: 'path' from os.environ.get (line 218, credential/environment) → open (file write)

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

Content

Scanner excerpt · scripts/spicy.py (reported line 220)May include surrounding context.

python
ext = mimetypes.guess_extension(asset.get("mime") or "") or Path(urllib.parse.urlparse(url).path).suffix or ".bin"
        path = target / f"{task['taskId']}_{i}{ext}"
        req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
        with urllib.request.urlopen(req, timeout=300) as resp, open(path, "wb") as fh:
            fh.write(resp.read())
        saved.append(str(path))
    return saved

Unsafe Defaults

Medium
Category
Tool Misuse
Confidence
75% confidence
Finding

Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Content

Scanner excerpt · scripts/spicy.py (reported line 246)May include surrounding context.

python
def cmd_models(args: argparse.Namespace) -> None:
    payload = http_json("GET", f"{CATALOG_URL}?locale=en", auth=False)
    items = data_of(payload).get("items", [])
    rows = []
    for it in items:

Unsafe Defaults

Medium
Category
Tool Misuse
Confidence
75% confidence
Finding

Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Content

Scanner excerpt · scripts/spicy.py (reported line 286)May include surrounding context.

python
def cmd_models(args: argparse.Namespace) -> None:
    payload = http_json("GET", f"{CATALOG_URL}?locale=en", auth=False)
    items = data_of(payload).get("items", [])
    rows = []
    for it in items:

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
85% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

Scanner excerpt · scripts/spicy.py (reported line 397)May include surrounding context.

python
p = sub.add_parser("generate", help="quote, create, wait and download")
    add_input_flags(p)
    p.add_argument("--yes", action="store_true", help="approve the quoted price and run")
    p.add_argument("--max-cost", type=float, help="auto-approve when the max charge (USD) is at or below this")
    p.add_argument("--out", default="./spicy-output", help="download directory (default ./spicy-output)")
    p.add_argument("--timeout", type=int, default=900, help="seconds to wait for completion (default 900)")
    p.add_argument("--no-wait", action="store_true", help="return right after the task is accepted")

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/spicy.py (reported line 139)May include surrounding context.

python
if getattr(args, "prompt", None):
        payload["prompt"] = args.prompt
    for field in ("image", "last_image", "video", "audio"):
        value = getattr(args, field, None)
        if value:
            payload[f"{field}_url"] = resolve_media(value)
    if getattr(args, "images", None):

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
96% confidence
Finding

The code hard-codes locale=en when fetching model catalog data, which imposes a specific language/locale choice on users. This is a natural-language policy concern because the skill does not offer locale selection or explain why English is required.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
96% confidence
Finding

The unauthenticated schema request includes locale=en, again fixing the response language to English with no user opt-in or alternative. This violates the locale-choice criterion unless the restriction is explicitly justified.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.prompt_injection_instructions

Prompt-injection style instruction pattern detected.

Warn
Code
suspicious.prompt_injection_instructions
Location
references/prompting.md:38