Back to skill

Security audit

PopAI Powerpoint Slides

Security checks for vulnerabilities and agentic risk

Overview

This skill appears to do what it says: generate presentations through PopAI, while sending prompts and optional user-selected files to PopAI/S3.

Install only if you are comfortable sending presentation prompts and any selected reference files to PopAI and its S3 storage using your POPAI_API_KEY. Avoid confidential, regulated, or secret material unless that transfer is approved, and prefer a securely created temporary output file rather than the documented predictable /tmp path.

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

T09 · Insecure Skill Coding Practices

Warning
Location
skill.md:40
Finding
Predictable Temporary File and Symlink-Following Output Write## Vulnerability Details **File Location**: `skill.md:40-41` and `generate_ppt.py:245` **Vulnerability Type**: Predictable temporary file and unsafe symlink-following file operation **Risk Level**: Medium **Complete Code Snippet**: ```bash OUTFILE="/tmp/popai_ppt_$(date +%s).jsonl" touch "$OUTFILE" ``` ```python out = open(output_file, "a") if output_file else None ``` ### Technical Analysis The documented workflow constructs an output pathname from the current timestamp in the shared `/tmp` directory. The filename is predictable, and `touch` does not ensure exclusive creation or verify that the path is a regular file owned by the invoking user. The Python implementation subsequently opens the caller-supplied path in append mode. Python's standard `open()` follows symbolic links by default. It does not use exclusive creation, `O_NOFOLLOW`, ownership validation, or file-type validation. Consequently, another local user could pre-create the predicted path as a symbolic link or race to replace the touched file before the Python process opens it. The process would then append PopAI event output to the symlink target using the invoking user's filesystem privileges. ### Attack Path 1. An attacker with local access monitors or predicts when the documented workflow will run. 2. The attacker derives the expected path, such as `/tmp/popai_ppt_<current_timestamp>.jsonl`. 3. The attacker pre-creates that path as a symbolic link, or replaces the file after `touch` and before `open()`. 4. The link points to a target file writable by the victim account or to an attacker-controlled collection file. 5. The Skill invokes `generate_ppt.py` with the predictable path through `--output`. 6. `open(output_file, "a")` follows the symbolic link. 7. Parsed API events, summaries, presentation URLs, or attacker-influenced content are appended to the target. ### Impact Assessment Exploitation does not grant privileges beyond ...[truncated 618 chars]
Remediation
## Remediation Suggestions - Replace timestamp-derived filenames with securely and atomically created temporary files: ```bash OUTFILE="$(mktemp /tmp/popai_ppt.XXXXXXXXXX.jsonl)" chmod 600 "$OUTFILE" ``` - Prefer creating the file in Python with `tempfile.NamedTemporaryFile(delete=False)` or `tempfile.mkstemp()`, which provides exclusive creation and a randomized name. - Open user-supplied output paths with operating-system flags such as `O_NOFOLLOW`, `O_CREAT`, and, when creating a new file, `O_EXCL`. - Before writing, use `os.lstat()` or equivalent checks to reject symbolic links and non-regular files. - Verify that an existing output file is owned by the effective user and has restrictive permissions. - Keep the temporary file descriptor open from creation through use instead of creating a pathname and reopening it later, eliminating the time-of-check/time-of-use race. - Store temporary output in a private directory with mode `0700` when possible.
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
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (14)

Tainted flow: 'api_key' from os.getenv (line 289, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
content_type = mimetypes.guess_type(file_path)[0] or "application/octet-stream"

    # Get presigned post
    resp = requests.post(
        PRESIGN_URL,
        headers=_headers(api_key),
        json={"md5": md5, "bucket": S3_BUCKET, "prefix": key, "contentType": content_type},
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'api_key' from os.getenv (line 289, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
payload["isUploadToEnhance"] = True

    print(f"getChannel payload: {json.dumps(payload, ensure_ascii=False)}", file=sys.stderr)
    resp = requests.post(f"{BASE_URL}/getChannel", headers=_headers(api_key), json=payload)
    resp.raise_for_status()
    channel_id = (resp.json().get("data") or {}).get("channelId")
    if not channel_id:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'api_key' from os.getenv (line 289, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
payload["imageUrls"] = []

    print(f"send payload: {json.dumps(payload, ensure_ascii=False)}", file=sys.stderr)
    resp = requests.post(
        f"{BASE_URL}/send", headers=_headers(api_key, accept="text/event-stream"),
        json=payload, stream=True,
    )
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Credential Access

High
Category
Privilege Escalation
Content
## Setup

1. Get API key from https://www.popai.pro
2. Store in environment: `export POPAI_API_KEY=...`

## Scripts
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding
Without declared permissions the skill's intent is opaque and cannot be validated.

External Transmission

Medium
Category
Data Exfiltration
Content
from concurrent.futures import ThreadPoolExecutor
import requests

BASE_URL = "https://api.popai.pro/api/v1/chat"
PRESIGN_URL = "https://api.popai.pro/py/api/v1/chat/getPresignedPost"
S3_UPLOAD_URL = "https://popai-file.s3-accelerate.amazonaws.com/"
S3_BUCKET = "popai-file"
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
from concurrent.futures import ThreadPoolExecutor
import requests

BASE_URL = "https://api.popai.pro/api/v1/chat"
PRESIGN_URL = "https://api.popai.pro/py/api/v1/chat/getPresignedPost"
S3_UPLOAD_URL = "https://popai-file.s3-accelerate.amazonaws.com/"
S3_BUCKET = "popai-file"
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Tainted flow: 'fields' from requests.post (line 66, network input) → requests.post (network output)

Medium
Category
Data Flow
Content
# Upload to S3
    with open(file_path, "rb") as f:
        upload_resp = requests.post(
            S3_UPLOAD_URL,
            files={"file": (filename, f, content_type)},
            data={
Confidence
75% confidence
Finding
The code takes presigned POST fields from the PopAI presign response and blindly forwards them into a second request to S3 without validating that the returned key/bucket constraints match the expected upload target. If the presign service is compromised or returns malformed values, the client could be induced to upload local files under unintended object keys or with attacker-controlled form fields, expanding the trust placed in a remote service beyond what is verified locally.

Tainted flow: 'payload' from requests.post (line 232, network input) → requests.post (network output)

Medium
Category
Data Flow
Content
payload["isUploadToEnhance"] = True

    print(f"getChannel payload: {json.dumps(payload, ensure_ascii=False)}", file=sys.stderr)
    resp = requests.post(f"{BASE_URL}/getChannel", headers=_headers(api_key), json=payload)
    resp.raise_for_status()
    channel_id = (resp.json().get("data") or {}).get("channelId")
    if not channel_id:
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.

Context-Inappropriate Capability

Medium
Confidence
87% confidence
Finding
This skill advertises presentation creation, but it also parses and emits web-search results from the remote agent stream. That broadens the capability surface and can disclose external browsing/search-derived content to callers who did not explicitly request or expect a search feature, which is especially risky in an agent setting where hidden retrieval may expose sensitive or policy-restricted data paths.

Context-Inappropriate Capability

Medium
Confidence
89% confidence
Finding
The --output option allows writing streamed event data to an arbitrary local path, which is not justified by the stated skill purpose and creates a local file-write capability. In an agent environment, this can be abused to overwrite or append to sensitive files, persist remote content on disk, or create unintended data exfiltration/persistence channels if the caller can influence the path.

Tainted flow: 'payload' from requests.post (line 232, network input) → requests.post (network output)

Medium
Category
Data Flow
Content
payload["imageUrls"] = []

    print(f"send payload: {json.dumps(payload, ensure_ascii=False)}", file=sys.stderr)
    resp = requests.post(
        f"{BASE_URL}/send", headers=_headers(api_key, accept="text/event-stream"),
        json=payload, stream=True,
    )
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.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The skill explicitly supports uploading user-provided local files and sending prompts and generated content to a third-party service, but it does not instruct the agent to obtain informed user consent or warn about external data transfer. This creates a real privacy and data-handling risk, especially if users provide sensitive documents, templates, or images that may be transmitted off-device to PopAI and related storage endpoints.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The agent steps prescribe fixed Chinese messages such as "PPT正在生成中,预计3-5分钟..." and "下载PPT"/"在线查看/编辑" for user communication. This imposes a specific language on users without documenting a locale-specific constraint or giving the user a choice, which violates the language/locale policy.

Static analysis

No suspicious patterns detected.