T09 · Insecure Skill Coding Practices
- Location
scripts/main.ts:193- Finding
API Key Forwarded to Untrusted Image Result URLs
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This image-generation skill is mostly coherent, but it has credential-handling and runtime-execution risks that should be reviewed before installation.
Install only if you are comfortable giving this skill a WeryAI API key, letting it write local image-skill config, and running a Bun-based CLI. Prefer providing the key through a protected environment or secret store, avoid `--api-key` on the command line, avoid webhook URLs unless needed, and review or pin the runtime before using the `generate` script.
scripts/main.ts:193API Key Forwarded to Untrusted Image Result URLs
scripts/setup.mjs:119Persisted API Key File Is Created Without Explicit Owner-Only Permissions
scripts/setup.mjs:56Setup Accepts API Credentials Through Process Command-Line Arguments
scripts/vendor/weryai-image/run-generate.mjs:22Generation Launcher Executes an Unpinned Runtime Package Through npx
The declared description narrowly presents the skill as an anime illustration generator, but the code implements a broad WeryAI image-generation CLI. Anime is only one style preset among many, so the primary purpose is not limited to anime-style art. The code also supports image-to-image workflows, batch execution, status queries, downloading outputs, and extra gateway features like webhooks and use_web_search, none of which are reflected in the description. While some of these are supporting features, the broader non-anime generation capability and image-to-image functionality are material enough that the description does not accurately represent the actual behavior.
The description is too narrow and therefore misleading. The code is not an anime-specialized illustration skill; it is a broad shared image-generation client for WeryAI. It can generate many styles via presets such as photoreal, cinematic, manga, watercolor, 3d-render, poster, editorial, infographic, chalk, and ink-brush, with anime only one option among many. It also supports image-to-image generation from reference images, batch execution, status lookup, downloading outputs, and config/env loading from disk. These are substantive capabilities not reflected in the declared purpose. While all of these still relate to image generation, the primary purpose in code is a generic image generation gateway CLI rather than an anime-only illustration skill.
There is a clear mismatch between the declared purpose and the code behavior. The description says the skill generates anime-style illustrations, but the supplied code only queries a balance endpoint and returns remaining credits/balance. It does not generate images, process art prompts, or implement any anime/light-novel illustration functionality. This is a materially different primary purpose and an undeclared capability related to account quota/balance retrieval.
The declared purpose is a specialized anime illustration generator, but the supplied code is only a reusable network client. It does not contain any functionality specific to anime art, illustration generation, prompting, rendering, or model invocation semantics beyond generic HTTP transport. Its primary behavior is external API access with authentication and logging, which is a materially different purpose from the declared end-user capability. While this client could support an image-generation skill elsewhere, this code chunk itself does not implement the declared functionality and exposes undeclared generic network capabilities.
The declared purpose is an image-generation skill focused on anime-style illustrations, but the supplied code does not perform image generation, prompt handling, model invocation, or any anime-specific logic. Instead, it contains generic error-message mappings and helper functions for interpreting API and network responses from WeryAI. While such code could be a supporting utility within a larger image-generation integration, this chunk itself materially behaves as error-handling infrastructure rather than illustration generation. Therefore, the description does not accurately represent what this specific code chunk actually does.
The declared purpose is an end-user creative capability: generating anime-style illustrations. The supplied code does not perform image generation, prompt handling, art rendering, or anime-specific processing. Instead, it provides generic normalization helpers for task objects and statuses from an external service ('waiting'/'processing'/'completed'/'failed'), along with output extraction for various media fields. This is materially different from the declared purpose and indicates the description does not accurately represent the code chunk’s actual behavior.
The declared purpose says this skill generates anime-style illustrations. However, the provided code does not generate any images or perform anime-specific rendering. Its primary function is to query /v1/generation/.../status endpoints for task or batch status, interpret results, and report outputs or errors. This is a materially different purpose from the description, so the description does not accurately represent the code.
The declared description says the skill generates anime-style illustrations, implying an image-generation capability focused on anime art. However, this code chunk does not generate images or perform anime-specific rendering. Instead, it is infrastructure code for monitoring asynchronous generation jobs through remote API status endpoints (/v1/generation/.../status and /v1/generation/batch/.../status). Its primary purpose is task polling and result collection, not anime illustration generation itself. While such polling could support a generation workflow, the supplied code's actual behavior is materially different from the declared purpose and includes undeclared remote API orchestration capabilities.
The declared description says the skill generates anime-style illustrations, implying a specialized image-generation function. The supplied code chunk instead implements infrastructure for model discovery, capability lookup, parameter validation, fallback/default handling, and request body construction. It is generic across text-to-image and image-to-image modes and contains no anime-style specialization, no illustration generation logic, and no evidence of light-novel-cover or Japanese-animation-specific behavior. While this code could support an image generation system, its actual purpose is materially different from the declared end-user purpose, so this is a mismatch.
There is a clear description-behavior mismatch. The declared purpose says the skill generates anime-style illustrations, implying image creation functionality. However, this code chunk only retrieves and returns model metadata from a remote registry, filtered by mode. Its primary purpose is model discovery/listing, not generating art. While this may support an image-generation system, the supplied code itself does not perform the declared task.
The declared description says this skill generates high-quality anime style illustrations and should be used for anime/light novel/Japanese animation art. However, the code is a generic image-to-image submission wrapper: it normalizes inputs, validates them, fetches a model registry, optionally validates against model metadata, and posts to an image-to-image generation API. There is nothing in the code that specializes the behavior for anime style, light novel covers, or Japanese animation aesthetics. Its primary purpose is broader and materially different: submitting general image-to-image generation requests to a backend service. That makes the description inaccurate.
Referenced artifact was not completely inspected
| Base URL | `https://api.weryai.com` (hard-coded in `scripts/main.ts`) |
The skill is designed to locate, use, and potentially persist an API key in local .env files. Even though it emphasizes secrecy, credential handling and storage materially increase risk: a compromised skill, weak file permissions, or later workspace exposure could leak the token and permit unauthorized external API use.
3. **If `IMAGE_GEN_API_KEY` is missing**, ask the user for permission to configure it now
4. Only after readiness and API key are resolved, continue to model selection / prompt clarification / generation
**Access token**: only use **`IMAGE_GEN_API_KEY`**. It may also live in `.image-skills/anime-image-generator/.env` as `IMAGE_GEN_API_KEY=...`. After the user approves, the agent may persist it there by running `npm run setup -- --project . --workflow <workflow> --persist-api-key` when the key is already in env, or by writing the file locally on the user's behalf instead of asking the user to edit files manually.
**First-use readiness check**: before the first generation run in a new OpenClaw or local instance, the agent must run:
The manifest says this skill is for generating high-quality anime-style illustrations for anime, light novel covers, or Japanese animation style art. This file instead builds a generic visual brief across multiple non-anime workflows such as infographic, article, rednote, and general/editorial modes, and never performs image generation itself. That is a semantic mismatch between the claimed skill purpose and the implemented behavior.
Arbitrary webhook URL support can cause the external provider to send callbacks to attacker-chosen destinations, enabling SSRF-by-proxy, internal endpoint targeting, or data leakage through provider-initiated requests. In an image-generation skill, this capability is not necessary for core functionality and increases risk because user-controlled URLs are forwarded without validation or allowlisting.
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.
}
function applyStylePreset(prompt: string, style: StylePreset | null | undefined): string {
if (!style) return prompt;
return `${prompt.trim()}\n\nStyle preset (${style}): ${STYLE_PRESETS[style]}.`;
}
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.
}
function applyStylePreset(prompt: string, style: StylePreset | null | undefined): string {
if (!style) return prompt;
return `${prompt.trim()}\n\nStyle preset (${style}): ${STYLE_PRESETS[style]}.`;
}
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const projectSkillDir = path.join(projectRoot, ".image-skills", skillNamespace);
const homeSkillDir = homeRoot ? path.join(homeRoot, ".image-skills", skillNamespace) : null;
const projectEnvPath = path.join(projectRoot, ".image-skills", skillNamespace, ".env");
const projectEnvFile = pathExists(projectEnvPath);
let apiKeyInProjectEnv = false;
if (projectEnvFile) {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const projectSkillDir = path.join(projectRoot, ".image-skills", skillNamespace);
const homeSkillDir = homeRoot ? path.join(homeRoot, ".image-skills", skillNamespace) : null;
const projectEnvPath = path.join(projectRoot, ".image-skills", skillNamespace, ".env");
const projectEnvFile = pathExists(projectEnvPath);
let apiKeyInProjectEnv = false;
if (projectEnvFile) {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const projectSkillDir = path.join(projectRoot, ".image-skills", skillNamespace);
const homeSkillDir = homeRoot ? path.join(homeRoot, ".image-skills", skillNamespace) : null;
const projectEnvPath = path.join(projectRoot, ".image-skills", skillNamespace, ".env");
const projectEnvFile = pathExists(projectEnvPath);
let apiKeyInProjectEnv = false;
if (projectEnvFile) {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const projectSkillDir = path.join(projectRoot, ".image-skills", skillNamespace);
const homeSkillDir = homeRoot ? path.join(homeRoot, ".image-skills", skillNamespace) : null;
const projectEnvPath = path.join(projectRoot, ".image-skills", skillNamespace, ".env");
const projectEnvFile = pathExists(projectEnvPath);
let apiKeyInProjectEnv = false;
if (projectEnvFile) {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const projectSkillDir = path.join(projectRoot, ".image-skills", skillNamespace);
const homeSkillDir = homeRoot ? path.join(homeRoot, ".image-skills", skillNamespace) : null;
const projectEnvPath = path.join(projectRoot, ".image-skills", skillNamespace, ".env");
const projectEnvFile = pathExists(projectEnvPath);
let apiKeyInProjectEnv = false;
if (projectEnvFile) {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const projectSkillDir = path.join(projectRoot, ".image-skills", skillNamespace);
const homeSkillDir = homeRoot ? path.join(homeRoot, ".image-skills", skillNamespace) : null;
const projectEnvPath = path.join(projectRoot, ".image-skills", skillNamespace, ".env");
const projectEnvFile = pathExists(projectEnvPath);
let apiKeyInProjectEnv = false;
if (projectEnvFile) {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const projectSkillDir = path.join(projectRoot, ".image-skills", skillNamespace);
const homeSkillDir = homeRoot ? path.join(homeRoot, ".image-skills", skillNamespace) : null;
const projectEnvPath = path.join(projectRoot, ".image-skills", skillNamespace, ".env");
const projectEnvFile = pathExists(projectEnvPath);
let apiKeyInProjectEnv = false;
if (projectEnvFile) {
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
const projectSkillDir = path.join(projectRoot, ".image-skills", skillNamespace);
const homeSkillDir = homeRoot ? path.join(homeRoot, ".image-skills", skillNamespace) : null;
const projectEnvPath = path.join(projectRoot, ".image-skills", skillNamespace, ".env");
const projectEnvFile = pathExists(projectEnvPath);
let apiKeyInProjectEnv = false;
if (projectEnvFile) {