Back to skill

Security audit

Remotion + Excalidraw + TTS

Security checks for vulnerabilities and agentic risk

Overview

This skill appears to be a legitimate video-generation tool, but it has review-worthy risks around API key exposure, file deletion, cloud TTS data sharing, and runtime npm installs.

Review this skill before installing. Use local macOS say or a prebuilt MP3 for sensitive narration unless you are comfortable sending the text to OpenAI or ElevenLabs. Avoid passing a workdir that contains anything valuable under a remotion-excalidraw-tts subdirectory, and do not run it with elevated privileges. If you use cloud TTS, assume API keys may be exposed in logs until the command logging and curl invocation are fixed.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (3)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/make_video.py:12
Finding

API Credentials Exposed Through Command Logging and Process Arguments

Content
View full analysis

Vulnerability Details

File Location: scripts/make_video.py:12-14, scripts/make_video.py:70-100, and scripts/make_video.py:112-143
Vulnerability Type: Sensitive credential exposure
Risk Level: Medium

Vulnerable Code

python
def run(cmd, cwd=None):
    print(f"+ {' '.join(cmd)}", flush=True)
    subprocess.check_call(cmd, cwd=cwd)
python
cmd = [
    "curl",
    "-sS",
    "-X",
    "POST",
    "https://api.openai.com/v1/audio/speech",
    "-H",
    f"Authorization: Bearer {api_key}",
    "-H",
    "Content-Type: application/json",
    "-d",
    "{}",
    "--output",
    str(out_mp3),
]

payload = {
    "model": model,
    "voice": voice,
    "format": "mp3",
    "input": text,
}

import json as _json

cmd[cmd.index("{}") if "{}" in cmd else -3] = _json.dumps(payload, ensure_ascii=False)
run(cmd)
python
run([
    "curl",
    "-sS",
    "-X",
    "POST",
    url,
    "-H",
    f"xi-api-key: {api_key}",
    "-H",
    "Content-Type: application/json",
    "-H",
    "Accept: audio/mpeg",
    "-d",
    _json.dumps(payload, ensure_ascii=False),
    "--output",
    str(out_mp3),
])

Technical Analysis

The common run() helper prints every command-line argument before execution. The OpenAI and ElevenLabs implementations place API keys directly inside HTTP header arguments passed to curl. Consequently, the complete credentials are printed in plaintext to the terminal and may be retained in shell captures, CI logs, agent execution logs, or centralized logging systems.

The credentials are also present in the curl process argument vector while the request is running. Depending on operating-system process visibility and isolation settings, another local process or same-user process monitor may be able to read them.

Sending narration to the explicitly selected remote TTS provider is necessary for those op ...[truncated 1216 chars]

Remediation
View remediation

Remediation Suggestions

  1. Change command logging so sensitive headers and credentials are replaced with fixed redaction markers before output.
  2. Avoid placing credentials in process arguments. Prefer an in-process HTTPS client that sets authorization headers through an API rather than through an external command line.
  3. If curl must remain in use, supply a restrictive temporary configuration through standard input or another mechanism that does not expose secrets in the argument vector.
  4. Ensure any temporary credential-bearing data is created with owner-only permissions and removed immediately after use.
  5. Add automated tests confirming that OpenAI and ElevenLabs keys never appear in stdout or stderr.
  6. Rotate any API credentials previously used in environments where execution output was retained.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/make_video.py:198
Finding

User-Selected Work Directory Can Trigger Unconditional Recursive Deletion

Content
View full analysis

Vulnerability Details

File Location: scripts/make_video.py:198-203
Vulnerability Type: Unsafe recursive file deletion
Risk Level: Medium

Vulnerable Code

python
if args.workdir:
    workdir = Path(args.workdir).expanduser().resolve()
    workdir.mkdir(parents=True, exist_ok=True)
    project_dir = workdir / "remotion-excalidraw-tts"
    if project_dir.exists():
        shutil.rmtree(project_dir)

Technical Analysis

When --workdir is supplied, the script derives a fixed child path named remotion-excalidraw-tts. If that path already exists, it is recursively deleted without confirmation, without checking whether it was created by this Skill, and without requiring an explicit overwrite option.

The operation follows from a user-controlled base path and can remove an unrelated directory that happens to have the fixed child name. The documented interface describes --workdir as an optional working directory but does not warn that existing content beneath it will be permanently erased.

Although the deletion is constrained to a fixed child name after path resolution, recursive deletion remains broader than the minimum file-system access needed to create a temporary rendering project.

Attack Path

  1. A directory containing valuable files exists at /chosen/path/remotion-excalidraw-tts.
  2. A user, automation wrapper, or misleading invocation supplies --workdir /chosen/path.
  3. The script resolves the path and detects the existing fixed child directory.
  4. shutil.rmtree(project_dir) recursively deletes that directory and all of its contents.
  5. The template is copied into a newly created directory at the same path, potentially obscuring that unrelated content was removed.

Impact Assessment

Exploitation can cause permanent loss of files writable by the account running the Skill. The scope is limited by the invoking user's file-system permissions and to the derived `remot ...[truncated 196 chars]

Remediation
View remediation

Remediation Suggestions

  1. Create a unique project directory with tempfile.mkdtemp(dir=workdir, prefix="remotion-excalidraw-tts-") instead of reusing and deleting a fixed path.
  2. By default, fail safely if a requested destination already exists.
  3. If replacement is required, introduce an explicit --force option and clearly document that it deletes existing content.
  4. Before deleting, require a marker file generated by the Skill and verify that the resolved deletion target is a direct child of the intended work directory.
  5. Reject symlinks and unexpected file types at the target path.
  6. Display the exact deletion target and obtain confirmation for interactive use.
  7. Run rendering as an unprivileged user so accidental deletion remains constrained by least privilege.

T08 · Insecure Dependencies

Note
Location
scripts/make_video.py:246
Finding

Runtime Dependency Installation Executes Third-Party Lifecycle Code

Content
View full analysis

Vulnerability Details

File Location: scripts/make_video.py:246-247, assets/template/remotion-project/package.json:7-15, assets/template/remotion-project/package-lock.json:5076-5080, and assets/template/remotion-project/package-lock.json:5546-5549
Vulnerability Type: Runtime supply-chain exposure
Risk Level: Low

Vulnerable Code

python
# install deps
run(["npm", "i"], cwd=str(project_dir))
json
"dependencies": {
  "@excalidraw/excalidraw": "^0.18.0",
  "@remotion/cli": "4.0.419",
  "@remotion/tailwind-v4": "4.0.419",
  "react": "19.2.3",
  "react-dom": "19.2.3",
  "remotion": "4.0.419",
  "tailwindcss": "4.0.0"
}
json
"node_modules/esbuild": {
  "version": "0.25.0",
  "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
  "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==",
  "hasInstallScript": true
}
json
"node_modules/fsevents": {
  "version": "2.3.3",
  "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
  "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoeTtP4r0lN9z7AAawCXgqea7bDii0AYNVbuw==",
  "hasInstallScript": true
}

Technical Analysis

Every video render invokes network-backed npm i, which downloads third-party packages and permits npm lifecycle scripts to execute with the privileges of the invoking user. The lockfile contains integrity hashes and the install-script declarations identified during the audit belong to expected platform tooling, namely esbuild and optional macOS package fsevents. No malicious dependency or lifecycle payload was confirmed.

However, runtime installation expands the Skill's execution surface beyond the local rendering operation. The direct @excalidraw/excalidraw dependency also uses a caret range rather than an exact version. Using `npm ...[truncated 1560 chars]

Remediation
View remediation

Remediation Suggestions

  1. Replace npm i with npm ci so installation strictly follows the committed lockfile and fails if the manifest and lockfile differ.
  2. Pin every direct dependency to an exact version, including @excalidraw/excalidraw.
  3. Explicitly configure and allowlist the trusted npm registry used by the Skill.
  4. Preinstall and audit dependencies in a controlled build environment rather than downloading them during each render.
  5. Use npm ci --ignore-scripts if Remotion and required native tooling can operate without lifecycle scripts; otherwise, explicitly review and allow only necessary install scripts.
  6. Run installation and rendering in a sandbox with minimal file-system access, no unnecessary credentials, and restricted outbound networking.
  7. Regularly scan the locked dependency tree for known vulnerabilities and verify lockfile integrity during release.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • 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
Findings (36)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The manifest presents the skill as a local Excalidraw-to-video workflow using macOS say, but the documented behavior includes cloud TTS providers, API-key usage, MP3 ingestion, and storyboard-driven scene control. This mismatch is dangerous because users may trust the skill as fully local and non-networked, leading to uninformed disclosure of script content to third parties or unexpected capability use.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: nanoid==4.0.2 — 4 advisory(ies): CVE-2026-67214 (nanoid: non-secure generators can loop indefinitely with negative size); CVE-2026-67213 (nanoid: custom generators can loop indefinitely when size is zero); CVE-2024-55565 (Predictable results in nanoid generation when given non-integer values) +1 more

High
Category
Supply Chain
Confidence
80% confidence
Finding

nanoid 4.0.2 has published issues including predictable output for some misuse cases and loop/hang behavior in edge-case generator usage. In this lockfile it appears as a transitive utility, and there is no evidence here that the affected non-default APIs are used for security-sensitive token generation, so practical impact in this skill is limited.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: brace-expansion==2.0.2 — 4 advisory(ies): CVE-2026-13149 (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding); CVE-2026-33750 (brace-expansion: Zero-step sequence causes process hang and memory exhaustion); CVE-2026-14257 (brace-expansion: DoS via unbounded expansion length causing an out-of-memory pro) +1 more

High
Category
Supply Chain
Confidence
86% confidence
Finding

brace-expansion 2.0.2 has multiple DoS-style advisories involving pathological pattern expansion. In this case it is nested under dev tooling, so the main risk is process hang or memory exhaustion when tooling evaluates attacker-controlled glob patterns rather than direct compromise.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: minimatch==9.0.5 — 3 advisory(ies): CVE-2026-27904 (minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regu); CVE-2026-26996 (minimatch has a ReDoS via repeated wildcards with non-matching literal in patter); CVE-2026-27903 (minimatch has ReDoS: matchOne() combinatorial backtracking via multiple non-adja)

High
Category
Supply Chain
Confidence
87% confidence
Finding

minimatch 9.0.5 is affected by ReDoS/backtracking issues that can be triggered by crafted patterns. Because it appears under lint/config tooling here, the likely impact is denial of service in CI or local developer workflows if untrusted patterns are evaluated.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: brace-expansion==1.1.12 — 4 advisory(ies): CVE-2026-13149 (brace-expansion: DoS via exponential-time expansion of consecutive non-expanding); CVE-2026-33750 (brace-expansion: Zero-step sequence causes process hang and memory exhaustion); CVE-2026-14257 (brace-expansion: DoS via unbounded expansion length causing an out-of-memory pro) +1 more

High
Category
Supply Chain
Confidence
86% confidence
Finding

brace-expansion 1.1.12 has known expansion-based DoS issues similar to later branches. In this project it is used through development dependencies, so the main consequence is potential CPU or memory exhaustion if an attacker can influence brace patterns evaluated in tooling.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: browserslist==4.28.1 — 2 advisory(ies): CVE-2026-73088 (Browserslist: Uncaught crash / prototype write via untrusted browserslist-stats.); CVE-2026-73089 (Browserslist: Unbounded memory growth (no cache eviction) via distinct query res)

High
Category
Supply Chain
Confidence
80% confidence
Finding

browserslist 4.28.1 has advisories for crashes and memory growth when handling untrusted stats or distinct query results. In this skill it is part of the front-end/build toolchain, so exploitation would most likely affect build or rendering availability rather than confidentiality or integrity.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill documents shell execution, file reads/writes, and environment-variable use, but declares no explicit tool scope or permissions boundary. This is dangerous because consumers cannot reliably understand or restrict what the skill may access, increasing the chance of over-broad execution and accidental exposure of local files or secrets during use.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The top-level description emphasizes local macOS say TTS, yet the skill also supports OpenAI and ElevenLabs cloud TTS. That inconsistency can mislead users about where their voiceover text is processed, which matters because scripts may contain sensitive or proprietary information.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
88% confidence
Finding

Using npx remotion render without a pinned version allows the toolchain to resolve and execute whatever package version is current at runtime. That creates a supply-chain risk: a malicious or compromised upstream release could execute arbitrary code on the host during rendering.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The inclusion of network-dependent OpenAI and ElevenLabs TTS expands the skill beyond its stated local rendering purpose and introduces external data flows and credential handling. In this context, the extra capability is more dangerous because the skill is framed as a local media pipeline, so users may not expect outbound requests or API-key use.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The documentation does not warn that selecting OpenAI or ElevenLabs sends the voiceover text to third-party services. This is dangerous because users may unknowingly upload confidential scripts, internal diagrams' narration, or regulated content to external vendors without consent or review.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
88% confidence
Finding

The README instructs users to run npx remotion render without pinning a specific package version. npx may resolve and execute whatever version is current or otherwise available at execution time, which can introduce supply-chain risk if a malicious or compromised upstream package version is published. In this skill context, the command is part of normal developer workflow, so the intent appears benign, but users may copy-paste it directly, increasing practical exposure.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
86% confidence
Finding

The README recommends npx remotion upgrade without a pinned version, which can cause execution of an unpinned package from the registry. Because upgrade commands intentionally modify dependencies, an attacker controlling or compromising the package distribution channel could gain code execution or alter project dependencies unexpectedly. The surrounding context is a template README, so this is likely unintentional rather than malicious, but still a legitimate supply-chain exposure.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The file states that prior memories are automatically recalled and injected into prompt context before a conversation, without warning that previous user data will be reused in future interactions. This is dangerous because previously stored sensitive or incorrect information can silently influence model behavior, leak private context into later tasks, or violate user expectations around data separation and transparency.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The text explicitly describes automatically extracting conversation content, embedding it, and storing it in a long-term memory database, but provides no notice, consent flow, retention limits, or guidance on handling sensitive data. This creates a real privacy and data-governance risk because users may unknowingly have personal, confidential, or regulated information persisted for future use.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The configuration unconditionally enables Config.setOverwriteOutput(true), which causes Remotion renders to replace existing output files without prompting. In a skill that generates MP4s as part of an automated workflow, this can destroy prior artifacts or overwrite user-selected targets if paths are reused, leading to data loss and confusion even though it is not a code-execution issue.

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/make_video.py (reported line 15)May include surrounding context.

python
def run(cmd, cwd=None):
    print(f"+ {' '.join(cmd)}", flush=True)
    subprocess.check_call(cmd, cwd=cwd)


def check_bin(name):

Tainted flow: 'cmd' from os.environ.get (line 86, credential/environment) → subprocess.check_call (code execution)

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 · scripts/make_video.py (reported line 15)May include surrounding context.

python
def run(cmd, cwd=None):
    print(f"+ {' '.join(cmd)}", flush=True)
    subprocess.check_call(cmd, cwd=cwd)


def check_bin(name):

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/make_video.py (reported line 24)May include surrounding context.

python
def ffprobe_duration_seconds(path: Path) -> float:
    out = subprocess.check_output(
        [
            "ffprobe",
            "-v",

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill description emphasizes local macOS say TTS, but the code also supports remote OpenAI and ElevenLabs synthesis. That mismatch can mislead users into providing sensitive voiceover text under the assumption processing is local, when the script can transmit it to third-party services.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The script contains outbound network TTS capability beyond the prominently described local workflow, increasing privacy and supply-chain exposure in a context that appears primarily local/rendering-focused. In agent environments, undeclared external transmission is materially more dangerous because users may not expect their narration text to leave the machine.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

When the OpenAI backend is selected, the full voiceover text is sent to an external API without any explicit warning, consent checkpoint, or sensitivity screening. Voiceover scripts may contain proprietary, personal, or confidential content, so silent exfiltration to a third party creates a real privacy risk.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
96% confidence
Finding

This code performs outbound transmission of voiceover content to the OpenAI speech API. External transmission is expected only if the user knowingly chose that backend, but because the skill framing emphasizes local TTS, the data egress is insufficiently disclosed and therefore a genuine privacy concern.

Content

Scanner excerpt · scripts/make_video.py (reported line 91)May include surrounding context.

python
"-sS",
        "-X",
        "POST",
        "https://api.openai.com/v1/audio/speech",
        "-H",
        f"Authorization: Bearer {api_key}",
        "-H",

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The ElevenLabs path similarly uploads user-provided voiceover text to an external service with no explicit warning or confirmation. In this skill context, users may reasonably expect a local-only media pipeline, making undisclosed transmission more sensitive.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
96% confidence
Finding

This endpoint sends user voiceover text to ElevenLabs over the network, creating third-party data exposure. In a skill marketed around local Remotion and macOS say generation, that hidden egress is more dangerous because it violates likely user expectations.

Content

Scanner excerpt · scripts/make_video.py (reported line 126)May include surrounding context.

python
if out_mp3.exists():
        out_mp3.unlink()

    url = f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}"

    payload = {
        "text": text,

Static analysis

No suspicious patterns detected.