Back to skill

Security audit

aim-blog-write

Security checks for vulnerabilities and agentic risk

Overview

This is a real blog-to-Word skill, but it asks users to expose an API key in chat and sends that key over plaintext HTTP, so it should be reviewed before installation.

Review this skill before installing. Do not paste a real aim-secret-key into chat for it; use it only if the provider moves credential entry to a safer secret store, switches service calls to HTTPS, narrows activation, and restricts image downloads to trusted HTTPS hosts with size and content checks.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/common.sh:23
Finding

Authentication Secret and Blog Data Transmitted Over Plaintext HTTP

Content
View full analysis

Vulnerability Details

File Location: scripts/env_guard.sh:16-17, scripts/common.sh:23-32
Vulnerability Type: Cleartext transmission of sensitive information
Risk Level: High

Complete Code Snippet

bash
AIM_APPLY_URL="http://192.168.25.10:30775/aim-skills"
AEP_BASE_URL_FIXED="http://aep.vemic.com/aim_mentaassistant_2024"
bash
local url="${AEP_BASE_URL}${path}"
local timeout="${AEP_TIMEOUT:-600}"

echo ">>> AEP sync request -> ${url}" >&2

curl -s -N --noproxy '*' --max-time "${timeout}" -X POST "${url}" \
    -H "Content-Type: application/json" \
    -H "X-AEP-CONSUMER-SECRET: ${AEP_CONSUMER_SECRET}" \
    -d "${body}"

Technical Analysis

The AEP endpoint is hardcoded with the http:// scheme. The request includes the user's aim-secret-key in the X-AEP-CONSUMER-SECRET header and sends the complete blog request body over the same unencrypted connection.

HTTP provides neither confidentiality nor authenticated server identity. Any party capable of observing or modifying traffic between the host and the endpoint—including a local-network attacker, compromised gateway, malicious DNS resolver, or upstream network operator—can capture the authentication secret and submitted content. An active attacker can also alter the SSE response consumed by the scripts.

The explicit --noproxy '*' option bypasses configured HTTP proxies, including potentially mandatory organizational security proxies. It does not protect the connection from interception and may circumvent expected egress inspection.

Attack Path

  1. A user places a valid aim-secret-key in the skill's .env file.
  2. The user or agent invokes scripts/seo_agent.sh or scripts/blog_to_word.py.
  3. ensure_aep_env exports the key as AEP_CONSUMER_SECRET.
  4. run_skill_sync connects to the hardcoded AEP endpoint over plaintext HTTP.
  5. The key is transmitted in an HTTP header, while the ...[truncated 989 chars]
Remediation
View remediation

Remediation Suggestions

  • Replace the endpoint with an https:// URL and reject non-HTTPS endpoint configurations.
  • Configure curl to fail on TLS and HTTP errors, for example with --fail-with-body --show-error.
  • Do not disable certificate verification. Where appropriate, pin the organization's CA or server public key.
  • Remove --noproxy '*' unless direct routing is an explicitly reviewed requirement. Use a narrowly scoped NO_PROXY configuration for approved internal hosts instead.
  • Rotate all keys that may previously have traversed an untrusted network over HTTP.
  • Prefer short-lived, narrowly scoped credentials over reusable static secrets.
  • Ensure authentication failures and malformed responses cause a fail-closed result rather than proceeding with generated output.
  • Serve the registration URL over HTTPS as well, and avoid directing users to plaintext credential-related services.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/blog_to_word.py:99
Finding

Server-Side Request Forgery Through Unvalidated Image URLs

Content
View full analysis

Vulnerability Details

File Location: scripts/blog_to_word.py:99-135
Vulnerability Type: Unrestricted outbound request / SSRF
Risk Level: High

Complete Code Snippet

python
def download_image(entry, dest_dir: Path, idx: int) -> tuple[Path, str] | None:
    url, caption = _coerce_image_entry(entry)
    if not url:
        print(f"[image] Item {idx} has no URL; skipping: {entry!r:.120}",
              file=sys.stderr)
        return None

    try:
        r = requests.get(
            url, timeout=60, stream=False,
            proxies={"http": None, "https": None},
        )
    except Exception as e:
        print(f"[image] Download failed {url}: {e}", file=sys.stderr)
        return None

    if r.status_code != 200:
        print(f"[image] {url} returned HTTP {r.status_code}; skipping",
              file=sys.stderr)
        return None

    ext = os.path.splitext(urlparse(url).path)[1].lower()
    if ext not in _VALID_EXTS:
        ct = r.headers.get("Content-Type", "").split(";")[0].strip().lower()
        ext = _EXT_FROM_CT.get(ct, ".jpg")

    dest = dest_dir / f"image_{idx:02d}{ext}"
    try:
        dest.write_bytes(r.content)

The downloaded entries originate from the remote result:

python
images = data.get("images") or []

for idx, entry in enumerate(images, 1):
    result_item = download_image(entry, img_dir, idx)

Technical Analysis

The program performs an HTTP request to every image_url returned by the remote SEO service without validating the URL scheme, destination hostname, resolved IP address, or redirect target. Proxy use is explicitly disabled.

Consequently, anyone who can control or alter the AEP response can cause the machine running the skill to send requests to arbitrary destinations reachable from that machine. This includes loopback services, private-network services, link-local addresses, and cloud instance metad ...[truncated 2235 chars]

Remediation
View remediation

Remediation Suggestions

  • Do not fetch arbitrary URLs supplied by the generation service. Prefer image identifiers resolved through a trusted, authenticated image API.
  • Implement an explicit allowlist of expected schemes, hosts, ports, and path prefixes. Permit only https.
  • Resolve hostnames before connecting and reject loopback, private, link-local, multicast, reserved, and unspecified IPv4 and IPv6 ranges.
  • Prevent DNS rebinding by connecting to the validated resolved address while preserving and validating the intended TLS hostname.
  • Disable redirects or validate the scheme, host, port, and resolved address at every redirect hop.
  • If private image storage is required, allowlist exact storage hosts instead of allowing all private addresses.
  • Enforce egress restrictions outside the application so the process cannot access metadata, loopback, or unrelated internal services.
  • Validate Content-Type and image magic bytes before saving or parsing the response.
  • Avoid including complete sensitive URLs in logs, because query strings may contain credentials.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/blog_to_word.py:117
Finding

Unbounded Image Response Buffering Enables Memory and Disk Exhaustion

Content
View full analysis

Vulnerability Details

File Location: scripts/blog_to_word.py:117-135
Vulnerability Type: Uncontrolled resource consumption
Risk Level: Medium

Complete Code Snippet

python
r = requests.get(
    url, timeout=60, stream=False,
    proxies={"http": None, "https": None},
)

if r.status_code != 200:
    print(f"[image] {url} returned HTTP {r.status_code}; skipping",
          file=sys.stderr)
    return None

ext = os.path.splitext(urlparse(url).path)[1].lower()
if ext not in _VALID_EXTS:
    ct = r.headers.get("Content-Type", "").split(";")[0].strip().lower()
    ext = _EXT_FROM_CT.get(ct, ".jpg")

dest = dest_dir / f"image_{idx:02d}{ext}"
try:
    dest.write_bytes(r.content)

The caller also imposes no limit on the number of returned image entries:

python
image_files: list[tuple[Path, str]] = []
for idx, entry in enumerate(images, 1):
    result_item = download_image(entry, img_dir, idx)
    if result_item:
        image_files.append(result_item)

Technical Analysis

Each response is downloaded with stream=False, causing the complete response body to be buffered in memory. The code does not enforce a maximum Content-Length, streamed-byte limit, image dimension limit, aggregate download limit, or maximum number of images.

The 60-second timeout does not provide a response-size limit. A server can send a very large response within that period, and multiple image entries can repeat the operation. Each accepted response is then copied to disk and may be decoded by python-docx and its image-processing dependencies, adding further memory and CPU pressure. A compressed image with extreme declared dimensions can also produce disproportionate processing costs.

Attack Path

  1. An attacker controls an image URL in the remote SEO response or modifies the response in transit.
  2. The URL points to a server that returns HTTP 200 with a very large body, or the ...[truncated 858 chars]
Remediation
View remediation

Remediation Suggestions

  • Set stream=True and read responses in fixed-size chunks.
  • Reject responses whose declared Content-Length exceeds a conservative per-image limit.
  • Stop reading and delete the partial file when the actual streamed byte count exceeds that limit.
  • Enforce maximum image count and aggregate bytes per document.
  • Validate image signatures and dimensions before document embedding, including a maximum pixel count.
  • Apply filesystem quotas and execute document generation in a resource-limited sandbox.
  • Use separate connection and read timeouts and impose an overall generation deadline.
  • Clean up partial and rejected files on every error path.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (34)

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · .gitignore (reported line 13)May include surrounding context.

text
*.pyc

# Environment (contains secrets)
.env

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · README.md (reported line 23)May include surrounding context.

  1. 复制配置模板并填入密钥:
bash
cp .env.example .env

编辑 .env,将密钥填入:

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

The skill description implies a writing workflow, but the instructions also direct the agent to inspect configuration, parse secrets, and manage credential setup. Credential collection and local configuration management materially expand the attack surface beyond content generation and can expose sensitive data if mishandled.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The skill description implies a writing workflow, but the instructions also direct the agent to inspect configuration, parse secrets, and manage credential setup. Credential collection and local configuration management materially expand the attack surface beyond content generation and can expose sensitive data if mishandled.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The skill description implies a writing workflow, but the instructions also direct the agent to inspect configuration, parse secrets, and manage credential setup. Credential collection and local configuration management materially expand the attack surface beyond content generation and can expose sensitive data if mishandled.

Content

No source excerpt is available for this finding.

Vague Triggers

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The trigger phrases are extremely broad and map many ordinary writing requests to this skill automatically. In context, that is risky because activation can lead to shell execution, network calls, and credential-setup prompts for users who only asked for simple writing help, expanding exposure well beyond user intent.

Content

No source excerpt is available for this finding.

Missing User Warnings

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The skill tells the agent to have the user paste a secret into chat and persist it to .env without a prominent user-facing warning about transcript retention, storage risks, or safer alternatives. This encourages insecure credential handling and increases the chance of accidental secret disclosure or long-term exposure in logs and local files.

Content

No source excerpt is available for this finding.

Ssd 3

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

Persisting a user-supplied secret from chat into a local configuration file is a direct sensitive-data handling flaw. It creates multiple exposure points—chat logs, model context, local filesystem, backups, and downstream tooling—and is especially dangerous because the skill is framed as routine blog generation, not credential administration.

Content

No source excerpt is available for this finding.

Ssd 3

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The repeated credential-handling section reinforces an unsafe pattern of asking for a secret in chat and storing it locally. Repetition increases the likelihood the agent will follow the insecure flow, making accidental exposure more probable and harder to treat as incidental documentation error.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · README.md (reported line 125)May include surrounding context.

md
# ============================================================
# 公共函数库 —— 校验密钥、发送 AEP 同步请求
#
# 密钥来源:<skill_root>/.env 的 aim-secret-key(与数字人 skill 一致)
# ============================================================

_COMMON_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/common.sh (reported line 5)May include surrounding context.

sh
# ============================================================
# 公共函数库 —— 校验密钥、发送 AEP 同步请求
#
# 密钥来源:<skill_root>/.env 的 aim-secret-key(与数字人 skill 一致)
# ============================================================

_COMMON_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/env_guard.sh (reported line 6)May include surrounding context.

sh
# ============================================================
# 公共函数库 —— 校验密钥、发送 AEP 同步请求
#
# 密钥来源:<skill_root>/.env 的 aim-secret-key(与数字人 skill 一致)
# ============================================================

_COMMON_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/env_guard.sh (reported line 20)May include surrounding context.

sh
# ============================================================
# 公共函数库 —— 校验密钥、发送 AEP 同步请求
#
# 密钥来源:<skill_root>/.env 的 aim-secret-key(与数字人 skill 一致)
# ============================================================

_COMMON_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

Credential Access

High
Category
Privilege Escalation
Confidence
71% confidence
Finding

This function hardcodes the secret location to a plaintext .env file within the skill directory. While local file-based secrets can be acceptable in limited environments, in this context it contributes to insecure secret handling because the skill also instructs the user to provide the secret to the agent for storage there, increasing the chance of accidental disclosure or improper file permissions.

Content

Scanner excerpt · scripts/env_guard.sh (reported line 28)May include surrounding context.

sh
}

_guard_env_file() {
    echo "$(_guard_skill_root)/.env"
}

# ---------- 从 <skill>/.env 解析 aim-secret-key ----------

Credential Access

High
Category
Privilege Escalation
Confidence
88% confidence
Finding

This routine parses the local .env file to resolve the secret value, which is a real credential-access operation. In isolation it may be operationally legitimate, but within a blog-routing skill and combined with user-to-agent secret onboarding, it creates an unnecessary credential access path unrelated to the declared function, making the context more suspicious and risky.

Content

Scanner excerpt · scripts/env_guard.sh (reported line 31)May include surrounding context.

sh
echo "$(_guard_skill_root)/.env"
}

# ---------- 从 <skill>/.env 解析 aim-secret-key ----------
# 输出: <value>\t<source>;未命中则空输出、退出码 1
_guard_resolve_secret() {
    local file

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The markdown explains that the skill auto-generates blog content, downloads images, embeds them into a Word document, and outputs files locally, while later requiring an aim-secret-key. However, it does not clearly disclose up front that user-supplied inputs are sent to an external gateway/service and that the skill will create local output directories and files. For a markdown skill description, these user-impacting behaviors should be explicitly warned about.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill declares broad operational behavior—shell execution, outbound network access, and local file writes—but provides no explicit tool scope or permission boundaries. That makes it easy for an agent runtime to invoke higher-risk capabilities than users would reasonably expect from a blog-writing skill, increasing the chance of unintended command execution, data persistence, or network egress.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The skill explicitly instructs the agent to ask the user to paste a secret key into chat and then write it into a local .env file. Collecting credentials through chat is unsafe because transcripts may be logged, retained, exposed to other tools, or mishandled by the agent, turning a simple content skill into a secret-handling workflow.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The documentation states the skill does not touch internal storage and only needs AEP credentials, yet elsewhere it directs the agent to modify .env and write output files and downloaded images to disk. This inconsistency can mislead reviewers and users about persistence and data handling, weakening security review and consent.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

The module docstring and user-facing CLI/help strings are entirely in Chinese, which imposes a fixed language for instructions and status output. The policy allows locale constraints only when users are offered a choice or the restriction is clearly justified, neither of which is present here.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
83% confidence
Finding

Executing a shell script gives the skill a broader execution capability than implied by its declared content-routing purpose, increasing the attack surface and trust placed in local companion code. Even without immediate injection in this call site, hidden or later-modified shell logic can perform arbitrary commands, network access, or data exfiltration under the agent's privileges.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/blog_to_word.py (reported line 52)May include surrounding context.

python
if not seo.is_file():
        raise FileNotFoundError(f"seo_agent.sh 不存在: {seo}")

    proc = subprocess.run(
        ["bash", str(seo), json.dumps(payload, ensure_ascii=False)],
        capture_output=True, text=True, timeout=timeout,
    )

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script fetches attacker-controlled remote image URLs returned by the upstream agent and writes the responses to disk, creating an SSRF-style outbound request surface and untrusted file ingestion path. In skill context, this exceeds the declared blog-routing/generation purpose and introduces undisclosed network and file-write side effects that could reach internal endpoints or store malicious content.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The function performs silent outbound HTTP requests to arbitrary URLs and writes the responses to disk without any user-facing disclosure or consent. In this skill context, that is dangerous because upstream-generated image URLs may be attacker influenced, enabling SSRF, unexpected data transfer, internal network probing, or storage of hostile content.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The code creates directories and persists generated blog content, downloaded images, and raw JSON to disk even though the skill description only frames the capability as routing blog-generation requests to seo_agent. This mismatch matters because stored artifacts may contain sensitive prompts, model output, or third-party content and create unexpected retention and disclosure risks.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.