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

Content
View full analysis

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
View 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
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · generate_ppt.py (reported line 60)May include surrounding context.

python
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},

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · generate_ppt.py (reported line 106)May include surrounding context.

python
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:

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · generate_ppt.py (reported line 239)May include surrounding context.

python
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,
    )

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

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

md
## Setup

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

## Scripts

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · generate_ppt.py (reported line 17)May include surrounding context.

python
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"

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · generate_ppt.py (reported line 18)May include surrounding context.

python
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"

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

Medium
Category
Data Flow
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.

Content

Scanner excerpt · generate_ppt.py (reported line 72)May include surrounding context.

python
# Upload to S3
    with open(file_path, "rb") as f:
        upload_resp = requests.post(
            S3_UPLOAD_URL,
            files={"file": (filename, f, content_type)},
            data={

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

Medium
Category
Data Flow
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.

Content

Scanner excerpt · generate_ppt.py (reported line 106)May include surrounding context.

python
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:

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

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

Medium
Category
Data Flow
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.

Content

Scanner excerpt · generate_ppt.py (reported line 239)May include surrounding context.

python
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,
    )

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.