Back to skill

Security audit

Lineage Skill

Security checks across malware telemetry and agentic risk

Overview

The skill’s course-processing behavior is mostly coherent, but its installer handles existing API-key .env files through an unsafe predictable /tmp backup path, so it needs review before installation.

Install only if you are comfortable sending selected course materials to the transcription, vision, text, and OCR providers you configure. Before installing, patch or avoid the installer’s /tmp .env backup behavior, keep API keys in agent-managed secrets or a private 0600 file, use trusted ffmpeg/ffprobe binaries, and review/pin dependencies in a virtual environment.

SkillSpector

By NVIDIA
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (22)

Tainted flow: 'cmd' from os.getenv (line 268, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
def get_video_duration(video_path: str) -> float:
    cmd = [FFPROBE, "-v", "error", "-show_entries", "format=duration",
           "-of", "default=noprint_wrappers=1:nokey=1", video_path]
    r = subprocess.run(cmd, capture_output=True, text=True)
    return float(r.stdout.strip()) if r.returncode == 0 else 0
Confidence
87% confidence
Finding
r = subprocess.run(cmd, capture_output=True, text=True)

Tainted flow: 'cmd' from os.getenv (line 126, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
"-y", output_path,
    ]
    try:
        subprocess.run(cmd, capture_output=True, text=True, timeout=1800)
        return os.path.exists(output_path) and os.path.getsize(output_path) > 0
    except Exception as e:
        print(f"  压缩失败: {e}")
Confidence
88% confidence
Finding
subprocess.run(cmd, capture_output=True, text=True, timeout=1800)

Tainted flow: 'cmd' from os.getenv (line 126, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
"-y", output_path,
    ]
    try:
        subprocess.run(cmd, capture_output=True, text=True, timeout=1800)
        return os.path.exists(output_path) and os.path.getsize(output_path) > 0
    except Exception as e:
        print(f"  480p 压缩失败: {e}")
Confidence
88% confidence
Finding
subprocess.run(cmd, capture_output=True, text=True, timeout=1800)

Tainted flow: 'cmd' from os.getenv (line 126, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
cmd = [FFMPEG, "-y", "-ss", str(time_sec), "-i", video_path,
           "-vframes", "1", "-q:v", "2", output_path]
    try:
        subprocess.run(cmd, capture_output=True, text=True, timeout=60)
        return os.path.exists(output_path) and os.path.getsize(output_path) > 0
    except Exception:
        return False
Confidence
88% confidence
Finding
subprocess.run(cmd, capture_output=True, text=True, timeout=60)

Tainted flow: 'cmd' from os.getenv (line 126, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
"-t", str(dur), "-c", "copy",
               "-avoid_negative_ts", "make_zero", cp]
        try:
            subprocess.run(cmd, capture_output=True, text=True, timeout=300)
            if os.path.exists(cp) and os.path.getsize(cp) > 0:
                chunks.append((cp, start))
                print(f"  片段 {i+1}/{num}: offset={start/60:.0f}min, {os.path.getsize(cp)/(1024*1024):.0f}MB")
Confidence
88% confidence
Finding
subprocess.run(cmd, capture_output=True, text=True, timeout=300)

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

Critical
Category
Data Flow
Content
last_error: Exception | None = None
    for model in models:
        try:
            response = requests.post(
                url,
                headers={
                    "Authorization": f"Bearer {api_key}",
Confidence
95% confidence
Finding
response = requests.post( url, headers={ "Authorization": f"Bearer {api_key}", "Content-Type": "application/json",

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

Critical
Category
Data Flow
Content
for model in models:
        try:
            print(f"  🚀 图像模型: {model}, images={len(image_paths)}", flush=True)
            response = requests.post(
                url,
                headers={
                    "Authorization": f"Bearer {api_key}",
Confidence
95% confidence
Finding
response = requests.post( url, headers={ "Authorization": f"Bearer {api_key}", "Content-Type": "application/json",

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

Medium
Category
Data Flow
Content
"-q:a", "2", "-y", audio_path,
    ]
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=600)
        return result.returncode == 0 and os.path.exists(audio_path)
    except Exception as e:
        print(f"  音频提取异常: {e}")
Confidence
81% confidence
Finding
result = subprocess.run(cmd, capture_output=True, text=True, timeout=600)

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

Medium
Category
Data Flow
Content
audio_path,
    ]
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
        return float(result.stdout.strip()) if result.returncode == 0 else 0
    except Exception:
        return 0
Confidence
81% confidence
Finding
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)

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

Medium
Category
Data Flow
Content
output_path,
    ]
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
        return result.returncode == 0 and os.path.exists(output_path) and os.path.getsize(output_path) > 0
    except Exception as e:
        print(f"  分段音频提取异常: {e}")
Confidence
81% confidence
Finding
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill instructs the agent to use shell commands, access environment variables for API keys, read and write files, and potentially use network-backed providers, but it does not declare permissions or capability boundaries. This creates a trust and containment problem: a host may expose more capability than the user expects, and the skill can drive sensitive operations such as reading local materials, writing generated artifacts, or invoking external services with secret-backed credentials.

External Transmission

Medium
Category
Data Exfiltration
Content
BASE_DIR = str(Path(__file__).resolve().parents[1])

AUDIO_TRANSCRIBE_API_KEY = os.getenv("AUDIO_TRANSCRIBE_API_KEY", "")
AUDIO_TRANSCRIBE_BASE_URL = os.getenv("AUDIO_TRANSCRIBE_BASE_URL", "https://api.openai.com/v1")
AUDIO_TRANSCRIBE_MODEL = os.getenv("AUDIO_TRANSCRIBE_MODEL", "whisper-1")
FFMPEG = os.getenv("FFMPEG") or shutil.which("ffmpeg") or "ffmpeg"
FFPROBE = os.getenv("FFPROBE") or shutil.which("ffprobe") or "ffprobe"
Confidence
98% confidence
Finding
https://api.openai.com/

Credential Access

High
Category
Privilege Escalation
Content
print_info "Cancelled"
            exit 0
        fi
        if [ -f "$SKILL_DIR/.env" ]; then
            cp "$SKILL_DIR/.env" "/tmp/lineage-skill.env.bak"
            print_info "Backed up existing .env to /tmp/lineage-skill.env.bak"
        fi
Confidence
92% confidence
Finding
.env"

Credential Access

High
Category
Privilege Escalation
Content
exit 0
        fi
        if [ -f "$SKILL_DIR/.env" ]; then
            cp "$SKILL_DIR/.env" "/tmp/lineage-skill.env.bak"
            print_info "Backed up existing .env to /tmp/lineage-skill.env.bak"
        fi
        rm -rf "$SKILL_DIR"
Confidence
90% confidence
Finding
.env"

Credential Access

High
Category
Privilege Escalation
Content
print_success "Files copied"

    if [ -f "/tmp/lineage-skill.env.bak" ]; then
        mv "/tmp/lineage-skill.env.bak" "$SKILL_DIR/.env"
        print_success "Restored existing .env"
    fi
Confidence
89% confidence
Finding
.env"

Unpinned Dependencies

Low
Category
Supply Chain
Content
openai>=1.0.0
python-dotenv>=1.0.0
requests>=2.31.0
pillow>=10.0.0
Confidence
96% confidence
Finding
openai>=1.0.0

Unpinned Dependencies

Low
Category
Supply Chain
Content
openai>=1.0.0
python-dotenv>=1.0.0
requests>=2.31.0
pillow>=10.0.0
Confidence
97% confidence
Finding
python-dotenv>=1.0.0

Unpinned Dependencies

Low
Category
Supply Chain
Content
openai>=1.0.0
python-dotenv>=1.0.0
requests>=2.31.0
pillow>=10.0.0
Confidence
97% confidence
Finding
requests>=2.31.0

Unpinned Dependencies

Low
Category
Supply Chain
Content
openai>=1.0.0
python-dotenv>=1.0.0
requests>=2.31.0
pillow>=10.0.0
Confidence
99% confidence
Finding
pillow>=10.0.0

Known Vulnerable Dependency: python-dotenv==1.0.0 — 2 advisory(ies): CVE-2026-28684 (python-dotenv: Symlink following in set_key allows arbitrary file overwrite via ); CVE-2026-28684 (python-dotenv reads key-value pairs from a .env file and can set them as environ)

Medium
Category
Supply Chain
Confidence
88% confidence
Finding
python-dotenv==1.0.0

Known Vulnerable Dependency: requests==2.31.0 — 6 advisory(ies): CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2024-35195 (Requests `Session` object does not verify requests after making first request wi); CVE-2026-25645 (Requests has Insecure Temp File Reuse in its extract_zipped_paths() utility func) +3 more

Medium
Category
Supply Chain
Confidence
90% confidence
Finding
requests==2.31.0

Known Vulnerable Dependency: pillow==10.0.0 — 10 advisory(ies): CVE-2023-50447 (Arbitrary Code Execution in Pillow); CVE-2024-28219 (Pillow buffer overflow vulnerability); CVE-2026-55379 (Pillow `BdfFontFile`: `Image.new()` called without `_decompression_bomb_check()`) +7 more

Critical
Category
Supply Chain
Confidence
98% confidence
Finding
pillow==10.0.0

VirusTotal

VirusTotal findings are pending for this skill version.

View on VirusTotal

Static analysis

Detected: suspicious.dynamic_code_execution, suspicious.potential_exfiltration

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
tests/test_select_video_keyframes.py:14

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
tests/test_transcribe_video_segmenting.py:10

Python code base64-encodes a local file and sends it over the network.

Critical
Code
suspicious.potential_exfiltration
Location
scripts/llm_client.py:173