Back to skill

Security audit

Recursive maths animator

Security checks for vulnerabilities and agentic risk

Overview

This is mostly a Manim animation helper, but it includes purpose-mismatched audit guidance and an unsafe deletion helper that could remove files outside the intended project area.

Install only if you are comfortable reviewing the helper code first. Avoid using untrusted scene or branch names with ManimProject, do not enable remote TTS for confidential narration unless you accept sending text to the provider, and prefer pinned/hash-locked dependencies before using it in a sensitive environment.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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)

T01 · Skill Instruction Hijacking

Error
Location
SKILL.md:487
Finding

Security-analysis instruction hijacking

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
references/manim_versioning.py:527
Finding

Path traversal enables recursive deletion outside branch storage

Content
View full analysis
None: """Delete a provisional branch""" branch_path = self.scenes_path / scene_name / "branches" / branch_name if branch_path.exists(): shutil.rmtree(branch_path) # Update config config = self._load_config() if branch_name in config["scenes"][scene_name]["branches"]: config["scenes"][scene_name]["branches"].remove(branch_name) self._save_config(config) print(f"✓ Branch '{branch_name}' deleted") ``` The same unsafe path construction is centralized in this helper: ```python def _get_scene_path(self, scene_name: str, branch: Optional[str] = None) -> Path: """Get path to scene (main or branch)""" if branch: return self.scenes_path / scene_name / "branches" / branch return self.scenes_path / scene_name ``` ### Technical Analysis Both `scene_name` and `branch_name` are accepted as arbitrary strings and concatenated into filesystem paths without: - restricting them to safe identifier characters; - rejecting absolute paths or `..` components; - resolving the final path and checking that it remains under the intended root; - checking whether the deletion target is the branch root itself; or - protecting against symlink-based redirection. `Path` composition does not neutralize traversal components. For example, a branch name containing repeated `../` segments can resolve outside `scenes//branches/`. `delete_branch()` passes that path directly to `shutil.rmtree()`, which recursively removes the selected directory. The unsafe helper also affects scene file operations elsewhere in `ManimProject`, including writes, copies, rende ...[truncated 1537 chars]
Remediation
View remediation
str: if not _SAFE_ID.fullmatch(value): raise ValueError(f"Invalid {field}: {value!r}") return value ``` 2. Resolve and enforce containment before every read, write, copy, symlink, or deletion: ```python def _contained_path(root: Path, *parts: str) -> Path: root = root.resolve() candidate = root.joinpath(*parts).resolve() if candidate == root or not candidate.is_relative_to(root): raise ValueError("Path escapes the permitted root") return candidate ``` 3. For branch deletion, use the branch directory as the containment root and explicitly prohibit deleting that root: ```python branches_root = ( self.scenes_path / safe_scene_name / "branches" ).resolve() branch_path = _contained_path(branches_root, safe_branch_name) ``` 4. Reject absolute path components and any component equal to `.` or `..`, even if an allowlist is also present. 5. Before recursive deletion, use `lstat()` and reject symlink targets. Define an explicit policy for symlinks throughout the project. 6. Apply the same validation to `__init__`, `create_scene`, `update_scene`, `branch`, `merge`, `rollback`, `render`, `tag`, and `_get_scene_path`. 7. Add tests for absolute paths, nested traversal, mixed separators, symlinks, empty identifiers, Unicode separator-like characters, and attempts to target the project or branch root. ]]>

T08 · Insecure Dependencies

Warning
Location
requirements.txt:9
Finding

Mutable dependencies and downloaded font artifacts lack immutable integrity verification

Content
View full analysis
=0.18.0 manim-voiceover[gtts]>=0.3.1 # Optional: Gemini TTS (set GEMINI_API_KEY). Uncomment if you use gemini_tts_service.py. # google-genai>=1.0.0 ``` Font archives are downloaded and consumed without a checksum or signature check: ```python def _download_zip(url: str, dest: Path) -> Path: """Download a zip file from Google Fonts to ``dest``. Returns path.""" dest.parent.mkdir(parents=True, exist_ok=True) cmd = ["curl", "-L", "-o", str(dest), url] result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode != 0: raise RuntimeError(f"Failed to download font from {url}: {result.stderr}") return dest def _extract_needed(ttf_names: List[str], zip_path: Path, out_dir: Path) -> List[Path]: """Extract only the listed TTF files from a Google Fonts zip.""" out_dir.mkdir(parents=True, exist_ok=True) extracted = [] with zipfile.ZipFile(zip_path, "r") as zf: for item in zf.namelist(): # Google Fonts zips sometimes nest under a folder basename = Path(item).name if basename in ttf_names: data = zf.read(item) target = out_dir / basename target.write_bytes(data) extracted.append(target) return extracted def install_fonts(scheme_key: str, target_dir: str | Path) -> List[Path]: """ Download and install all fonts needed for a design system. """ scheme_key = scheme_key.lower().strip() if scheme_key not in _SCHEME_FONTS: available = ", ".join(_SCHEME_FONTS.keys()) raise KeyError(f"Unknown ...[truncated 3166 chars]
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
Findings (66)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

Scanner excerpt · SKILL.md (reported line 53)May include surrounding context.

md
The installable skill is the **directory that contains `SKILL.md`** (often `.../recursive-maths-animator/` inside a Git clone), not the repository root above it

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
85% confidence
Finding

Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Content

Scanner excerpt · references/manim_guide.md (reported line 141)May include surrounding context.

md
manim-voiceover caches generated TTS audio in the `media/voiceovers/` directory:
- Identical text will not be regenerated
- Modified text triggers automatic regeneration
- Clear cache: `rm -rf media/voiceovers/`

### SRT Subtitle Auto-Generation

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The README states that voiceover is generated automatically via GTTSService but does not warn users that narration text may be transmitted to an external third-party text-to-speech provider. In a skill that may be used with proprietary, personal, or confidential content, this omission creates a real data exposure risk because users may unknowingly send sensitive text off-box.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

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

Setup

bash
# Create virtual environment
python3.12 -m venv .venv
source .venv/bin/activate

Skill Enumeration

Medium
Category
Agent Snooping
Confidence
85% confidence
Finding

Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Content

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

Skill Installation (Optional)

For system-wide access:

bash
# Copy to Claude Code skills
cp -r references/pattern_library ~/.claude/skills/recursive-maths-animator/references/

Skill Enumeration

Medium
Category
Agent Snooping
Confidence
85% confidence
Finding

Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Content

Scanner excerpt · WHAT_YOU_CAN_DO.md (reported line 225)May include surrounding context.

Skill Installation (Optional)

For system-wide access:

bash
# Copy to Claude Code skills
cp -r references/pattern_library ~/.claude/skills/recursive-maths-animator/references/

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · SKILL.md (reported line 25)May include surrounding context.

md
- **F — Soft enterprise** (Roboto, warm cream — existing default)

   Also offer **aspect ratio** (16:9, 1:1, 9:16) and **tone** (calm / punchy). Let the user pick or mix.
3. **Wait for approval** — Only after the user confirms (or says “use A + 1:1 + calm”) do you: write **`ANIMATION_BRIEF.md`** (filled) + **`DESIGN_THEME.md`** (locked), then implement the scene.
4. **Use the maths engine** — Prefer Manim-native motion: `MathTex` / `Tex`, `NumberPlane`, `ParametricFunction`, `Transform` / `ReplacementTransform`, `Indicate`, `ShowPassingFlash`, `LaggedStart`, updaters. Avoid “generic UI explainer” unless that is what they asked for. See **`references/manim_guide.md`** for patterns.
5. **Shareable quality** — **MP4 at `-qh` / `--quality h`** is the default deliverable for “looks good.” GIF is for **layout checks** only; re-encoding with aggressive `ffmpeg` **crushes** gradients and dark minimal palettes. If they need a small GIF, render a **short** clip, limit colors in the scene, or share **MP4** / link instead.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The template hard-codes GTTSService(lang='en', slow=True), which instructs generated projects to use English voiceover by default. This is a natural-language locale constraint presented as the standard template behavior, and the file does not pair it with a user choice or opt-in at that point.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The legacy template repeats the same forced English setting with GTTSService(lang='en', slow=True). Because this applies across generated scenes unless manually edited, it violates the language/locale policy criterion requiring user choice or clear justification.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The document states that the skill does not call cloud LLM APIs from Python, yet elsewhere it explicitly documents optional Gemini integration requiring google-genai and GEMINI_API_KEY. That inconsistency can mislead operators into underestimating network/data-exfiltration risk and approving execution in environments where outbound API access is restricted.

Content

No source excerpt is available for this finding.

Skill Enumeration

Medium
Category
Agent Snooping
Confidence
80% confidence
Finding

Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Content

Scanner excerpt · SYSTEM_OVERVIEW.md (reported line 35)May include surrounding context.

md
| Document | Purpose | Location |
|----------|---------|----------|
| **README.md** | Main project overview with video gallery | `/README.md` |
| **SKILL.md** | Complete skill documentation | `~/.claude/skills/recursive-maths-animator/SKILL.md` |
| **QUICK_REFERENCE.md** | Pattern usage guide | `references/pattern_library/QUICK_REFERENCE.md` |
| **FINAL_RELENTLESS_REPORT.md** | Testing evidence | `.training/FINAL_RELENTLESS_REPORT.md` |
| **COMPREHENSIVE_PATTERN_LEARNINGS.json** | Machine-readable learnings | `.training/learnings/` |

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · WHAT_YOU_CAN_DO.md (reported line 166)May include surrounding context.

text

### 2. Educational Series
Create a series of math/statistics videos:
- Episode 1: Normal Distribution (DistributionAnimation)
- Episode 2: Sampling (SamplingVisualization)
- Episode 3: Correlation (TableToScatterPlotPattern)

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Confidence
75% confidence
Finding

Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Content

Scanner excerpt · references/design_systems/__init__.py (reported line 140)May include surrounding context.

python
"""Import the module for a given scheme key."""
    meta = get_scheme(key)
    mod_name = meta["module"]
    __import__(mod_name)
    return sys.modules[mod_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 · references/design_systems/font_catalog.py (reported line 132)May include surrounding context.

python
"""Download a zip file from Google Fonts to ``dest``. Returns path."""
    dest.parent.mkdir(parents=True, exist_ok=True)
    cmd = ["curl", "-L", "-o", str(dest), url]
    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode != 0:
        raise RuntimeError(f"Failed to download font from {url}: {result.stderr}")
    return dest

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 · references/manim_versioning.py (reported line 615)May include surrounding context.

python
"""Download a zip file from Google Fonts to ``dest``. Returns path."""
    dest.parent.mkdir(parents=True, exist_ok=True)
    cmd = ["curl", "-L", "-o", str(dest), url]
    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode != 0:
        raise RuntimeError(f"Failed to download font from {url}: {result.stderr}")
    return dest

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The example scene demonstrates SwissContainers.pill_label("v1.0", position=RIGHT * 3 + UP * 2), implying that pill_label accepts a position parameter. However, the actual function signature at L243-L244 defines only text, bg_color, text_color, and border_color, so the documented example contradicts the implementation and would fail at runtime.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The service sends arbitrary user-supplied text to Google's Gemini API for TTS generation, but the public interface and top-level documentation do not clearly warn that input content is transmitted to a third-party service. In contexts where the text may contain secrets, proprietary material, or sensitive personal data, this can lead to unintended data disclosure across trust boundaries.

Content

No source excerpt is available for this finding.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · references/manim_guide.md (reported line 399)May include surrounding context.

Solution:

bash
# Linux
sudo apt install fonts-noto-cjk

# macOS (built-in PingFang SC, usually OK)

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The brief template states 'Use English only,' which imposes a language constraint regardless of user preference. The policy allows locale constraints only when the user is offered a choice or when the constraint is clearly justified; neither is present here.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The module-level description at L002-L004 presents this as a Git-based version control system for Manim projects. The install_scheme_fonts path adds a distinct capability to fetch/install external font assets via a referenced font catalog, which is not an obvious requirement of version control itself.

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 · references/manim_versioning.py (reported line 358)May include surrounding context.

python
cli_format,
            "--disable_caching",
        ]
        result = subprocess.run(cmd, capture_output=True, text=True, cwd=self.project_path)
        
        if result.returncode != 0:
            print(f"Render error: {result.stderr}")

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The code deletes a branch directory with shutil.rmtree, which is a destructive operation, but this method provides no confirmation prompt, warning print, or explanatory comment/docstring communicating that local branch contents will be permanently removed. Under the code-file criteria, destructive operations should have some visible disclosure unless already clearly warned elsewhere in the file.

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 · references/manim_versioning.py (reported line 570)May include surrounding context.

python
file1 = scene_path / "versions" / f"v{v1}.py"
        file2 = scene_path / "versions" / f"v{v2}.py"
        
        result = subprocess.run(
            ["diff", "-u", str(file1), str(file2)],
            capture_output=True, text=True
        )

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 · references/manim_versioning.py (reported line 595)May include surrounding context.

python
def _git(self, *args) -> subprocess.CompletedProcess:
        """Run git command in project directory"""
        return subprocess.run(
            ["git"] + list(args),
            cwd=self.project_path,
            capture_output=True

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The code explicitly configures the speech service with GTTSService(lang='en'), which hard-codes English output. This is a natural-language locale constraint and there is no indication in the file that users can choose another language or that the English-only behavior is justified.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.