Back to skill

Security audit

CPA GPT Image 2

Security checks for vulnerabilities and agentic risk

Overview

The skill matches its image-generation purpose, but its API-key handling can send credentials to the wrong or unencrypted endpoint.

Review before installing. Use only a trusted HTTPS endpoint, set the matching base URL and API key together, avoid relying on fallback credentials from models.json, and do not include secrets or sensitive data in image prompts unless the endpoint and output path are approved.

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/generate_image.py:161
Finding
Independent Endpoint and Credential Resolution Can Disclose API Keys## Vulnerability Details **File Location**: `scripts/generate_image.py:161-200` **Vulnerability Type**: Credential-to-endpoint confusion **Risk Level**: High ### Vulnerable Code ```python provider_defaults = load_openclaw_provider_defaults() base_url = (os.getenv("IMAGE_GEN_BASE_URL") or os.getenv("OTCBOT_BASE_URL") or os.getenv("CPA_BASE_URL") or os.getenv("OPENAI_BASE_URL") or provider_defaults["base_url"] or "").rstrip("/") api_key = os.getenv("IMAGE_GEN_KEY") or os.getenv("OTCBOT_API_KEY") or os.getenv("CPA_API_KEY") or os.getenv("OPENAI_API_KEY") or provider_defaults["api_key"] or "" user_agent = os.getenv("CPA_USER_AGENT", "codex-tui/0.122.0 (Manjaro 26.1.0-pre; x86_64) vscode/3.0.12 (codex-tui; 0.122.0)") version = os.getenv("CPA_VERSION", "0.122.0") originator = os.getenv("CPA_ORIGINATOR", "codex_cli_rs") if not base_url: fail("Missing OTCBOT_BASE_URL / CPA_BASE_URL / OPENAI_BASE_URL and no otcbot baseUrl found in models.json") if not api_key: fail("Missing OTCBOT_API_KEY / CPA_API_KEY / OPENAI_API_KEY and no otcbot apiKey found in models.json") url = f"{base_url}/responses" if base_url.endswith("/v1") else f"{base_url}/v1/responses" payload = { "model": args.model, "input": args.prompt, "tools": [ { "type": "image_generation", "output_format": args.format, } ], "instructions": args.instructions, "tool_choice": "auto", "stream": args.stream, "store": False, } last_raw = "" last_parsed = None for attempt in range(args.retries + 1): data = json.dumps(payload).encode("utf-8") req = urllib.request.Request( url, data=data, headers={ "Authorization": f"Bearer {api_key}", "user-agent": user_agent, "version": version, "originator": originator, "session_id": args.session_id, "accept": "text/ ...[truncated 2054 chars]
Remediation
## Remediation Suggestions - Resolve endpoint and credential values as atomic provider configurations rather than through independent fallback chains. - If `IMAGE_GEN_BASE_URL` is set, require `IMAGE_GEN_KEY` explicitly and refuse to fall back to another provider's credential. - Apply the same pairing rule to `OTCBOT_BASE_URL` and `OTCBOT_API_KEY`, `CPA_BASE_URL` and `CPA_API_KEY`, and `OPENAI_BASE_URL` and `OPENAI_API_KEY`. - Require HTTPS for non-loopback endpoints. - Optionally maintain an allowlist of trusted endpoint origins and require explicit confirmation before sending credentials to a new origin. - Do not automatically send a credential read from `models.json` when any environment variable overrides the configured provider URL. - Add tests covering mixed configurations to verify that a URL from one provider can never receive another provider's credential.

T09 · Insecure Skill Coding Practices

Error
Location
SKILL.md:80
Finding
Documentation Recommends Sending Bearer Credentials over Plaintext HTTP## Vulnerability Details **File Location**: `SKILL.md:80-82` **Vulnerability Type**: Plaintext transmission of sensitive information **Risk Level**: High ### Vulnerable Code ```bash export IMAGE_GEN_BASE_URL='http://192.168.10.8:8317/v1' export IMAGE_GEN_KEY='sk-xxxx' export IMAGE_GEN_MODEL='gpt-5.4' ``` ### Technical Analysis The recommended environment configuration uses an unencrypted HTTP endpoint together with an API bearer key. The implementation sends that key in the `Authorization` header and does not reject plaintext HTTP URLs. HTTP provides neither transport confidentiality nor server authentication. Any party able to observe or manipulate traffic between the client and the documented private-network endpoint may recover the API key, read prompts, alter requests, or substitute responses. ### Attack Path 1. A user follows the documented configuration and supplies a real API key. 2. The script sends an HTTP request to the private-network endpoint. 3. An attacker on the same network path observes traffic through a compromised router, wireless network, proxy, host, or local-network interception technique. 4. The attacker extracts the bearer credential from the plaintext `Authorization` header. 5. The attacker reuses the credential against the service or modifies API responses in transit. ### Impact Assessment A network-positioned attacker can obtain the transmitted API credential and all request content, including user prompts, instructions, model selection, and session metadata. The attacker may consume paid quota or exercise any API permissions attached to the stolen credential. Because transport integrity is absent, the attacker may also alter generated-image responses or inject misleading server errors. The scope is limited to information and privileges available through the affected API credential; no direct local privilege escalation is demonstrated.
Remediation
## Remediation Suggestions - Replace the documented HTTP endpoint with an HTTPS endpoint using a valid, trusted certificate. - Add runtime validation that rejects non-HTTPS URLs unless the destination is an explicitly permitted loopback development endpoint. - Do not treat private-network addressing as a substitute for authenticated encryption. - If the local service does not currently support TLS, place it behind a properly configured TLS reverse proxy or use an authenticated encrypted tunnel. - Document certificate verification requirements and prohibit disabling TLS verification. - Rotate any real credential that has already been transmitted over plaintext HTTP.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (11)

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

Critical
Category
Data Flow
Content
)

    try:
        with urllib.request.urlopen(req, timeout=120) as resp:
            raw = resp.read().decode("utf-8", errors="replace")
            status = resp.status
    except urllib.error.HTTPError as e:
Confidence
93% confidence
Finding
The request destination and authorization secret are taken from environment variables and local provider config, then sent to whatever `base_url` resolves to. In a skill context, this creates an SSRF/exfiltration primitive: a malicious or compromised environment/config can redirect requests and bearer credentials, prompts, and session metadata to an attacker-controlled endpoint.

os.system() or os exec-family call

High
Category
Dangerous Code Execution
Content
env_model = os.getenv("OTCBOT_IMAGE_MODEL") or os.getenv("CPA_MODEL")
    if env_model:
        return env_model
    status_json = os.popen("openclaw models status --json 2>/dev/null").read().strip()
    if status_json:
        try:
            status = json.loads(status_json)
Confidence
85% confidence
Finding
os.system() and os exec-family calls run shell commands with the process's full privileges, enabling arbitrary command execution.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill clearly describes capabilities involving environment access, file writes, shell execution, and network transmission, but it does not declare any explicit tool scope or permission boundaries. In an agent setting, that omission increases the chance the skill is invoked with broader privileges than necessary, making unintended outbound requests or file operations harder to constrain and audit.

External Transmission

Medium
Category
Data Exfiltration
Content
8. extracts the first base64 image from the response
9. writes the file to the requested output path

## Fallback curl patterns

Preferred non-streaming version:
Confidence
84% confidence
Finding
This skill is explicitly designed to transmit user-supplied prompts and metadata to an external `/v1/responses` endpoint, which is a real data egress path. In context this is expected functionality rather than overtly malicious behavior, but it still carries security risk because sensitive prompt contents, headers, and possibly environment-derived configuration are sent off-host to whatever base URL is configured.

Context-Inappropriate Capability

Medium
Confidence
92% confidence
Finding
The manifest describes using an OpenAI-compatible /v1/responses endpoint for image generation, but this code additionally inspects a local models.json file and invokes `openclaw models status --json` via `os.popen` to infer provider and model defaults. Reading local configuration may be arguable as setup, but spawning a shell command is a broader capability not justified by the stated purpose of simply calling the endpoint.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
This code accesses sensitive credentials from multiple environment variables and from a local models.json file, then proceeds unless values are present. While failures mention missing keys, there is no comment, docstring, or user-facing notice explaining that the script will read stored API keys or local provider configuration.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The script makes an HTTP POST to a configured API endpoint and transmits the user's prompt along with session_id and client metadata headers. Although network access is central to image generation, the code lacks any visible print, comment, or docstring warning users that their prompt and metadata will be sent to a remote service.

Missing User Warnings

Low
Confidence
84% confidence
Finding
The skill states that it sends prompts to an external endpoint and writes generated files locally, but it does not present a clear user-facing privacy warning about prompt data leaving the local environment or files being created on disk. This can lead users to unknowingly transmit sensitive content or store generated artifacts in unsafe locations.

Natural-Language Policy Violations

Low
Confidence
72% confidence
Finding
The example usage hard-codes a Chinese prompt, and the curl examples repeat the same locale choice, which can imply a language preference in the skill guidance. Because the file does not say the skill is region-specific or that users may choose their own language, this may conflict with the language/locale policy guidance.

Natural-Language Policy Violations

Low
Confidence
72% confidence
Finding
This example request uses Chinese input text and does not clarify that language selection is optional. In the absence of a documented locale-specific purpose, repeated single-locale examples can be interpreted as steering users toward a fixed language.

Natural-Language Policy Violations

Low
Confidence
72% confidence
Finding
The streaming example repeats Chinese-only input while the skill does not state that it is intended for a Chinese-language workflow. That creates the same locale-policy concern as the other examples because no opt-in or justification is provided.

Static analysis

No suspicious patterns detected.