Back to skill

Security audit

lovart-api

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it claims, but it has broad external-transfer, download, TLS, and cross-session reuse behavior that users should review before installing.

Install only if you are comfortable sending prompts and selected files to Lovart, storing project/thread history under ~/.lovart, and having the agent reuse recent Lovart threads by default. Avoid enabling LOVART_INSECURE_SSL, keep LOVART_BASE_URL pointed at the official Lovart endpoint, and do not use upload or download commands with sensitive local paths or untrusted URLs.

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/agent_skill.py:19
Finding
Configurable Authenticated Endpoint and Optional TLS Verification Bypass<![CDATA[ ## Vulnerability Details **File Location**: `scripts/agent_skill.py`, lines 19-24, 95-117, and 662-669 **Vulnerability Type**: Unrestricted authentication endpoint combined with optional TLS verification bypass **Risk Level**: High ### Complete Code Snippet ```python # SSL context — verification ON by default. Opt-out via LOVART_INSECURE_SSL=1 # for users behind corporate proxies/VPNs that do TLS interception. _ssl_ctx = ssl.create_default_context() if os.environ.get("LOVART_INSECURE_SSL") == "1": _ssl_ctx.check_hostname = False _ssl_ctx.verify_mode = ssl.CERT_NONE ``` ```python def _request(self, method: str, path: str, body=None, params=None, retries: int = None) -> dict: if retries is None: retries = 3 if method == "GET" else 1 url = f"{self.base_url}{path}" if params: url += "?" + urllib.parse.urlencode(params) data = json.dumps(body).encode() if body is not None else None last_err = None idempotency_key = uuid.uuid4().hex if method == "POST" else None for attempt in range(retries): # Re-sign on each attempt (timestamp freshness) headers = self._sign(method, path) headers["Content-Type"] = "application/json" headers["User-Agent"] = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) LovartAgentSkill/1.0" if idempotency_key: headers["Idempotency-Key"] = idempotency_key req = urllib.request.Request(url, data=data, headers=headers, method=method) try: with urllib.request.urlopen(req, timeout=self.timeout, context=_ssl_ctx) as resp: result = json.loads(resp.read().decode()) break ``` ```python # Read from env vars, fall back to CLI args env_base_url = os.environ.get("LOVART_BASE_URL", "https://lgw.lovart.ai") env_ak = os.environ.get("LOVART_ACCESS_KEY", "") env_sk = os.environ.get("LOVART_SECRET_KEY", "") parser = argparse.ArgumentParser(description="Lovart Agent OpenAPI Skill") parse ...[truncated 3396 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Pin the production API origin** - Permit only `https://lgw.lovart.ai`. - Reject HTTP, unexpected ports, user-information components, malformed hostnames, and unapproved subdomains. - If development endpoints are necessary, require an explicit development build or separately protected configuration. 2. **Remove the insecure TLS mode** - Remove `LOVART_INSECURE_SSL`. - For enterprise TLS inspection, support a user-provided CA bundle while retaining certificate and hostname verification. - Never use `ssl.CERT_NONE` in production. 3. **Control redirects** - Disable redirects for authenticated API calls unless required. - If redirects are supported, validate every target against the same HTTPS origin allowlist. - Ensure authentication headers are never forwarded to a different origin. 4. **Protect credentials** - Remove or discourage `--ak` and `--sk`. - Read credentials from a protected environment, operating-system credential store, or file with restrictive permissions. - Avoid printing credentials or including them in exception messages. 5. **Separate network trust contexts** - Use distinct verified SSL contexts for API requests and artifact retrieval. - Do not allow one environment option to disable verification for every outbound connection. 6. **Add startup validation** - Parse the configured URL with `urllib.parse.urlsplit`. - Fail closed before constructing any authenticated request if the origin is not explicitly approved. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/agent_skill.py:242
Finding
Unrestricted Artifact URL Fetching Enables Server-Side Request Forgery and Unsafe File Downloads<![CDATA[ ## Vulnerability Details **File Location**: `scripts/agent_skill.py`, lines 242-280 and 753-756 **Vulnerability Type**: Unvalidated remote URL retrieval and unrestricted download destination **Risk Level**: High ### Complete Code Snippet ```python @staticmethod def download_artifacts(result: dict, output_dir: str = "/tmp/lovart", prefix: str = "lovart") -> list: """Download all artifacts from a result dict to local files. Idempotent: file names are derived from URL hash, and existing files are skipped. Returns list of {"type", "url", "local_path", "new": bool}.""" import os os.makedirs(output_dir, exist_ok=True) downloaded = [] seen_urls = set() for item in result.get("items", []): for artifact in item.get("artifacts", []): url = artifact.get("content", "") atype = artifact.get("type", "unknown") if not url or url in seen_urls: continue seen_urls.add(url) ext = os.path.splitext(url.split("?")[0])[-1] or ( ".mp4" if atype == "video" else ".png" ) url_hash = hashlib.sha1(url.encode()).hexdigest()[:12] local_path = os.path.join(output_dir, f"{prefix}_{url_hash}{ext}") if os.path.exists(local_path) and os.path.getsize(local_path) > 0: downloaded.append({"type": atype, "url": url, "local_path": local_path, "new": False}) continue try: req = urllib.request.Request(url, headers={ "User-Agent": "Mozilla/5.0", "Referer": "https://www.lovart.ai/", }) with urllib.request.urlopen(req, timeout=60, context=_ssl_ctx) as resp: with open(local_path, "wb") as f: f.write(resp.read()) downloaded.append({"type": atype, "url": url, "local_path": local_path, "new": True}) except Exception: ...[truncated 4104 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Allowlist artifact origins** - Accept only HTTPS URLs from explicitly approved Lovart asset domains. - Compare normalized hostnames exactly rather than relying on substring or suffix checks. - Reject URL user information, nonstandard ports, and malformed hostnames. 2. **Prevent SSRF** - Resolve the hostname before connecting. - Reject loopback, private, link-local, multicast, reserved, unspecified, and carrier-grade NAT address ranges for both IPv4 and IPv6. - Revalidate the connected address to reduce DNS-rebinding risk. - Apply the same validation to every redirect target, or disable redirects entirely. 3. **Bound resource usage** - Check `Content-Length` when present. - Enforce a strict maximum download size while streaming in fixed-size chunks. - Abort and delete partial files when the limit is exceeded. - Configure separate connection and read timeouts. 4. **Validate downloaded content** - Require an approved media MIME type. - Verify file signatures where practical. - Derive the extension from validated content rather than the untrusted URL. - Reject executable, script, archive, and HTML content unless specifically required. 5. **Restrict local writes** - Use a fixed, dedicated download root with restrictive permissions. - Resolve the final path and verify that it remains under that root. - Remove or tightly constrain caller-controlled output directories and prefixes. - Create files atomically with exclusive creation and safe permissions. 6. **Improve error handling** - Log a sanitized reason when a URL is rejected. - Avoid catching every exception without distinction. - Remove partial files after network or validation failures. 7. **Validate API responses** - Treat artifact records as untrusted input even when received from Lovart. - Require expected artifact structure, approved type values, and approved origins before retrieval. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (12)

Lp3

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding
The skill declares no explicit permissions while its documented behavior clearly requires environment-variable access, local file read/write, and network operations. This weakens platform-level consent and review because users and orchestrators are not accurately informed about the capabilities the skill will exercise.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The skill can open any local path supplied to the upload command and send its raw contents to the remote Lovart service. In an agent context, that creates a data-exfiltration path outside the manifest’s stated API operations, especially if an upstream model or prompt can influence the file path.

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
The skill fetches arbitrary artifact URLs and writes the response bodies to local disk under /tmp without strong origin validation or manifest disclosure. In an agent setting, this expands the skill from API usage into local filesystem modification and remote content retrieval, which can be abused for SSRF-like access patterns or dropping untrusted files on disk.

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
The skill persists project IDs, names, and thread history in a state file under the user’s home directory, but this local storage is not disclosed in the manifest. Conversation metadata can contain sensitive prompts or identifiers, so silent persistence increases privacy and data-retention risk.

Vague Triggers

High
Confidence
91% confidence
Finding
The trigger conditions are extremely broad, covering common verbs like 'create', 'make', and multilingual equivalents plus generic project/history terms. This can cause the skill to activate for ordinary conversation or unrelated requests, leading to unnecessary external API calls and unintended data disclosure to the Lovart backend.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
Setting LOVART_INSECURE_SSL=1 disables certificate validation and hostname checks for all requests, enabling man-in-the-middle interception of credentials, prompts, uploaded files, and downloaded artifacts. Because this applies globally and there is no strong user-facing warning at point of use, it materially weakens transport security.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The upload path reads a local file and sends it to a remote server with no runtime disclosure in that code path. In a tool-using agent environment, users may not realize that specifying a path results in full file exfiltration to a third party.

Missing User Warnings

Low
Confidence
88% confidence
Finding
Artifacts are automatically fetched and written to local storage without an inline warning in the execution path. While less severe than credential theft, this still creates unannounced local side effects and may store unsafe or unwanted content on disk.

Missing User Warnings

Low
Confidence
86% confidence
Finding
The skill silently writes persistent state into ~/.lovart/state.json without explicit warning when saving metadata. Hidden persistence can surprise users and retain sensitive usage history longer than expected.

Ssd 3

Medium
Confidence
97% confidence
Finding
The skill mandates reading ~/.lovart/state.json and reusing active projects and recent threads by default, including when the user was not asked in the current conversation. That creates a real cross-session context leakage risk: a new request may inherit prior project IDs, thread history, and related metadata from earlier user activity, exposing or transmitting prior context to the backend.

Ssd 3

Medium
Confidence
94% confidence
Finding
The instruction to pass the user's description verbatim to the backend, combined with preserving multi-turn thread memory, increases the chance that sensitive data, secrets, or unrelated prior context are forwarded externally without minimization. In a generation skill, this is particularly dangerous because ordinary user prompts may contain personal, proprietary, or regulated content that should be filtered or summarized before transmission.

Session Persistence

Medium
Category
Rogue Agent
Content
**Step 1: `config --json`**
- Check local state (`~/.lovart/state.json`) for `active_project`
- If `active_project` is set → proceed to Step 2. Do NOT create a new project. Do NOT ask the user.
- If `active_project` is missing → ask the user: "Do you have an existing Lovart project ID, or should I create a new one?" **WAIT for their answer.**
- Save with: `project-add --project-id PID --name "name"`
Confidence
90% confidence
Finding
The skill persists and reuses project/thread state across sessions, and the instructions explicitly bias the agent toward silent reuse rather than fresh context. This creates retention and session-boundary confusion risks, where later requests can be associated with earlier workspaces or histories without clear user awareness.

Static analysis

Detected: suspicious.insecure_tls_verification

HTTPS certificate verification is disabled.

Warn
Code
suspicious.insecure_tls_verification
Location
scripts/agent_skill.py:23