Back to skill

Security audit

🎬 观影小管家

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent movie assistant, but it mishandles media-server credentials and stores personal viewing context with weak controls.

Install only after reviewing and fixing credential handling: remove hard-coded API keys, rotate exposed keys, require user-provided secrets, separate public API requests from Emby-authenticated requests, avoid sending Emby tokens in URLs, and prefer HTTPS. Users should also understand that the skill keeps local records of viewing history, ratings, mood, work status, and interests, and should have clear ways to review, delete, or disable that memory.

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

T09 · Insecure Skill Coding Practices

Error
Location
index.js:29
Finding
Emby API Token Disclosed to Unrelated TMDB Endpoints<![CDATA[ ## Vulnerability Details **File Location**: `index.js:29-45`; additional affected callers appear at `index.js:49-56`, `index.js:87-101`, and `index.js:986-1027` **Vulnerability Type**: Cross-origin credential disclosure caused by a shared authenticated HTTP helper **Risk Level**: High ### Vulnerable Code ```js async function fetchJson(url, options = {}) { const headers = { 'Content-Type': 'application/json', 'X-Emby-Token': EMBY_API_KEY, 'X-Emby-Authorization': 'MediaBrowser Client="MovieButler",Device="PC",DeviceId="1",Version="1.0"', ...options.headers }; const res = await fetch(url, { headers, ...options }); if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); } // ==================== TMDB 功能 ==================== async function searchMovie(query) { const url = `${TMDB_BASE_URL}/search/movie?api_key=${TMDB_API_KEY}&query=${encodeURIComponent(query)}&language=zh-CN`; const data = await fetchJson(url); return data.results || []; } ``` Other TMDB requests use the same helper: ```js async function getMovieDetails(movieId) { const url = `${TMDB_BASE_URL}/movie/${movieId}?api_key=${TMDB_API_KEY}&language=zh-CN`; return await fetchJson(url); } async function getMovieCredits(movieId) { const url = `${TMDB_BASE_URL}/movie/${movieId}/credits?api_key=${TMDB_API_KEY}&language=zh-CN`; return await fetchJson(url); } ``` ### Technical Analysis `fetchJson()` unconditionally adds `X-Emby-Token` and `X-Emby-Authorization` to every request. The helper is used for both the private Emby server and the unrelated public TMDB service. Consequently, an ordinary TMDB movie search sends the configured Emby API token to `api.themoviedb.org`. TMDB does not require this token, so this behavior exceeds the minimum privileges needed for movie metadata retrieval and violates credential isolation between service origins. The risk applies when `EMBY_API_KEY` or the higher-pr ...[truncated 1461 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create separate request functions or clients for TMDB, OMDb, and Emby. 2. Attach `X-Emby-Token` only in the Emby-specific client. 3. Parse and validate the destination URL before adding credentials, requiring its origin to exactly match the configured Emby origin. 4. Do not rely on substring or suffix checks for host validation. 5. Ensure caller-provided headers cannot accidentally propagate credentials across origins. 6. Rotate the current Emby token because it may already have been disclosed through normal TMDB requests. 7. Add automated tests asserting that TMDB and OMDb requests never contain Emby headers. A safer design is: ```js async function fetchPublicJson(url, options = {}) { const res = await fetch(url, { ...options, headers: { 'Content-Type': 'application/json', ...options.headers } }); if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); } async function fetchEmbyJson(resource, options = {}) { const base = new URL(EMBY_URL); const url = new URL(resource, base); if (url.origin !== base.origin) { throw new Error('Refusing to send Emby credentials to another origin'); } const res = await fetch(url, { ...options, headers: { 'Content-Type': 'application/json', 'X-Emby-Token': EMBY_API_KEY, 'X-Emby-Authorization': 'MediaBrowser Client="MovieButler",Device="PC",DeviceId="1",Version="1.0"', ...options.headers } }); if (!res.ok) throw new Error(`HTTP ${res.status}`); return res.json(); } ``` ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
index.js:15
Finding
API Credentials Hard-Coded in Source Code and Documentation<![CDATA[ ## Vulnerability Details **File Location**: `index.js:15-17`, `index.js:59-67`, `README.md:40-43`, and `SKILL.md:109-112` **Vulnerability Type**: Hard-coded third-party API credentials **Risk Level**: Medium ### Vulnerable Code ```js const TMDB_API_KEY = process.env.TMDB_API_KEY || 'bd1ba3aa647fbaa7b35e93db5164a53f'; const TMDB_BASE_URL = 'https://api.themoviedb.org/3'; const OMDb_API_KEY = process.env.OMDB_API_KEY || '9e58a5e3'; // OMDb API (IMDb 数据) ``` ```js async function getIMDbRating(imdbId) { if (!imdbId) return null; // 尝试多个 OMDb API Key const apiKeys = ['9e58a5e3', '23967f54', '79e28a36']; for (const key of apiKeys) { try { const url = `https://www.omdbapi.com/?i=${imdbId}&apikey=${key}`; ``` The TMDB credential is also published in the documentation: ```text TMDB_API_KEY=bd1ba3aa647fbaa7b35e93db5164a53f ``` ### Technical Analysis The package distributes one real-looking TMDB key and three OMDb keys. If environment configuration is absent, the application silently uses the embedded TMDB key. The OMDb rating function always uses its own hard-coded key array and does not use the declared `OMDb_API_KEY` constant. Secrets embedded in source code and documentation cannot be restricted to an authorized deployment. Anyone who can read or download the Skill can extract and reuse them independently. Publishing multiple fallback keys also makes credential rotation and usage attribution more difficult. ### Attack Path 1. An attacker obtains the publicly distributed Skill files. 2. The attacker reads `index.js`, `README.md`, or `SKILL.md`. 3. The attacker extracts the TMDB and OMDb API keys. 4. The attacker sends direct requests to the corresponding third-party APIs using those keys. 5. The attacker consumes available quotas or causes usage that appears to originate from the credential owner. 6. The provider may throttle, suspend, or revoke the exposed credentials, disrupting legitimate Skill functio ...[truncated 580 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Revoke and rotate all TMDB and OMDb keys found in the repository. 2. Remove every fallback credential from source code. 3. Replace documentation credentials with unambiguous placeholders such as `TMDB_API_KEY=your_key_here`. 4. Require credentials through environment variables or an approved secret manager. 5. Fail with a clear configuration error when a required credential is absent. 6. Change `getIMDbRating()` to use the configured `OMDB_API_KEY` instead of an embedded key array. 7. Add secret-scanning checks to continuous integration and pre-commit workflows. 8. Review version-control history because deleting a secret from the current revision does not remove it from prior commits. For example: ```js const TMDB_API_KEY = process.env.TMDB_API_KEY; const OMDB_API_KEY = process.env.OMDB_API_KEY; if (!TMDB_API_KEY) { throw new Error('TMDB_API_KEY is required'); } async function getIMDbRating(imdbId) { if (!imdbId || !OMDB_API_KEY) return null; const url = new URL('https://www.omdbapi.com/'); url.searchParams.set('i', imdbId); url.searchParams.set('apikey', OMDB_API_KEY); const res = await fetch(url); const data = await res.json(); // Validate and return the response as appropriate. } ``` ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
index.js:113
Finding
Emby Credential Exposed in Query Strings over a Default Plaintext Connection<![CDATA[ ## Vulnerability Details **File Location**: `index.js:18`, `index.js:113-117`, and `index.js:237-245`; the insecure default is repeated in `README.md:47-50` and `SKILL.md:115-118` **Vulnerability Type**: Sensitive token in URL combined with insecure transport configuration **Risk Level**: Medium ### Vulnerable Code ```js const EMBY_URL = process.env.EMBY_URL || 'http://192.168.0.151:8096'; ``` ```js async function getEmbyMediaFolders() { // 获取指定的媒体库(电影、剧集、演唱会) const url = `${EMBY_URL}/Library/MediaFolders?api_key=${EMBY_API_KEY}`; try { const data = await fetchJson(url); ``` The pre-scan location contains the same issue: ```js async function getEmbyLibraryStats() { const movies = await getEmbyMovies(); const folders = await getEmbyMediaFolders(); // 获取媒体库详细信息 const folderDetails = await Promise.all( folders.map(async id => { try { const res = await fetchJson(`${EMBY_URL}/Items/${id}?api_key=${EMBY_API_KEY}`); return { name: res.Name, type: res.CollectionType }; ``` ### Technical Analysis The Emby API token is placed in the URL query string even though the shared HTTP helper also sends it in the `X-Emby-Token` header. Query-string credentials can be retained in server access logs, reverse-proxy logs, monitoring systems, browser or debugging histories, and error reports. The default Emby URL uses unencrypted HTTP. When this default or another HTTP endpoint is used, both authentication material and private media-library traffic are transmitted without transport confidentiality or server authentication. A network-positioned attacker could observe the request or modify its response. Sending the token in both the header and URL is unnecessary. The query-string copy exceeds the minimum authentication data needed for the declared Emby functionality. ### Attack Path 1. A user configures an Emby API token but leaves the default HTTP server URL or configures an ...[truncated 1281 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove `api_key=${EMBY_API_KEY}` from all Emby URLs. 2. Send the token only through the Emby-specific authentication header. 3. Require an HTTPS Emby URL for normal operation. 4. If plaintext HTTP must be supported for isolated local development, require an explicit opt-in and display a prominent warning. 5. Validate the configured URL with the standard `URL` parser and reject unexpected schemes. 6. Use a least-privileged, dedicated Emby token rather than an administrator credential. 7. Rotate the existing token because it may already appear in traffic or logs. 8. Scrub tokens from application, proxy, and server logs. 9. Avoid including authenticated URLs in exceptions or diagnostic output. For example: ```js function validateEmbyUrl(value) { const url = new URL(value); if (url.protocol !== 'https:') { throw new Error('EMBY_URL must use HTTPS'); } return url.toString().replace(/\/$/, ''); } const EMBY_URL = validateEmbyUrl(process.env.EMBY_URL); async function getEmbyMediaFolders() { const url = `${EMBY_URL}/Library/MediaFolders`; const data = await fetchEmbyJson(url); return data.Items || []; } ``` ]]>
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
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
Findings (34)

Ae1

High
Category
analysis-evasion
Content
- `SKILL.md` - 本说明文档
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `index.js` - 主程序
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Credential Access

High
Category
Privilege Escalation
Content
*/

const path = require('path');
require('dotenv').config({ path: path.join(__dirname, '../../../.env') });

const EMBY_URL = process.env.EMBY_URL || 'http://192.168.0.151:8096';
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
*/

const path = require('path');
require('dotenv').config({ path: path.join(__dirname, '../../../.env') });

const EMBY_URL = process.env.EMBY_URL || 'http://192.168.0.151:8096';
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

YARA rule 'agent_skill_mcp_tool_poisoning_metadata': MCP/tool metadata poisoning indicators in tool schemas or skill manifests [agent_skills]

High
Category
YARA Match
Content
{
  "name": "movie-butler",
  "displayName": "🎬 观影小管家",
  "version": "1.0.0",
  "description": "杜老师的专属观影助手,整合 TMDB + Emby,提供电影查询、媒体库管理、个性化推荐、每周观影计划等服务",
  "author": "小暖阳",
  "license": "MIT",
  "tags": ["电影", "Emby", "TMDB", "推荐", "媒体库"],
  "category": "娱乐",
  "requirements": {
    "env": [
      "TMDB_API_KEY",
      "EMBY_URL",
      "EMBY_API_KEY",
      "EMBY_USER_ID"
    ]
  },
  "features": [
    "🌐 双源查询(TMDB + Emby)",
    "🔗 一键播放链接",
    "📊 精确年份匹配",
    "🎯 智能推荐算法",
    "👨‍👩‍👦 家庭电�
Confidence
80% confidence
Finding
YARA rule matched a hack tool or exploit indicator (offensive tools, reconnaissance, privilege escalation, or exploit frameworks).

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill explicitly states it will record the user's mood, work status, interests, and later use that information for recommendations, but provides no notice about storage, retention, sharing, or user control. This creates a privacy risk because it encourages collection of behavioral and potentially sensitive personal data without informed consent or clear limits.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The demo shows persistent storage of viewing history, ratings, and subjective feedback, but does not warn users that these become lasting personal records. Viewing habits and ratings can reveal preferences, routines, and potentially sensitive inferences, so silent persistence increases privacy and trust risks.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
Referencing an Emby API key without warning about the scope of access understates the privacy impact of connecting to a media server. An API key may allow broad access to server content and metadata, so users should be informed that the skill may inspect their library and potentially infer household viewing behavior.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill advertises recording movie impressions, ratings, and background information, but it does not prominently warn users that these preferences and status signals will be persistently stored. Because the stored data includes mood, work state, interests, and media habits, inadequate disclosure creates a meaningful privacy and consent issue.

Context-Inappropriate Capability

Medium
Confidence
99% confidence
Finding
The README discloses a real TMDB API key and internal Emby server details including a private IP address, username, and service endpoint. Even if some values are incomplete, publishing live-looking credentials and internal infrastructure information can enable unauthorized API usage, service abuse, reconnaissance, and targeted attacks against the media server environment.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The example trigger phrases are very broad natural-language commands that can easily overlap with ordinary conversation, causing the skill to activate unintentionally. In a skill that queries external services and may reveal server availability or generate downstream actions like download suggestions, accidental invocation increases privacy and safety risk.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The background-recording examples use everyday statements like mood and work status without clear boundaries indicating that these will be stored as persistent memory. This can cause users to unknowingly save sensitive personal context, creating privacy risk and potentially influencing future recommendations in ways they did not intend.

Vague Triggers

Medium
Confidence
95% confidence
Finding
Examples like '本周推荐什么电影?', '这周有什么好电影推荐', and similar everyday phrases are broad enough to be said in normal conversation and may invoke the skill unintentionally. Because the skill can query external services and access personal media-server data, accidental activation could disclose preferences or trigger unwanted network requests.

Vague Triggers

Medium
Confidence
96% confidence
Finding
The background-recording examples are ordinary personal statements like mood or work status, but the documentation frames them as inputs that improve recommendations without strong opt-in boundaries. This creates risk that sensitive behavioral data is captured or persisted when the user does not clearly understand they are authorizing storage and profiling.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The file structure notes that 'movie-memory.md' is auto-created for viewing records and preferences, but the skill does not clearly warn users that reactions, ratings, and inferred tastes will be stored persistently. Persistent storage of entertainment history and emotional reactions can reveal personal preferences and habits, especially on shared systems.

Context-Inappropriate Capability

Medium
Confidence
99% confidence
Finding
The documentation exposes a real-looking TMDB API key and internal service configuration details, including a private LAN Emby URL and username. Hard-coded secrets and infrastructure identifiers can enable unauthorized API use, credential stuffing against related services, or reconnaissance of the user's environment, and this is unrelated to what end users need to invoke the skill safely.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The documentation describes configuration for TMDB, Emby, and Plex but does not clearly tell users that their search queries and media-related requests may be sent to those third-party or self-hosted services using configured credentials. That omission weakens informed consent and can surprise users who may not expect external transmission of interests, watch history context, or server queries.

External Transmission

Medium
Category
Data Exfiltration
Content
// 配置
const TMDB_API_KEY = process.env.TMDB_API_KEY || 'bd1ba3aa647fbaa7b35e93db5164a53f';
const TMDB_BASE_URL = 'https://api.themoviedb.org/3';
const OMDb_API_KEY = process.env.OMDB_API_KEY || '9e58a5e3'; // OMDb API (IMDb 数据)
const EMBY_URL = process.env.EMBY_URL || 'http://192.168.0.151:8096';
const EMBY_USERNAME = process.env.EMBY_USERNAME || '喜悦影音';
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The inline documentation says the skill will only search movie and series libraries, but the subsequent Emby code explicitly includes `musicvideos` folders and `MusicVideo` item types in multiple queries. This is an active contradiction between stated intent in comments and implemented scope.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
The skill hardcodes zh-CN in external API requests and uses only Chinese trigger terms, examples, and output text throughout the file. This enforces a specific language/locale without any opt-in or alternate language selection, which matches the stated policy-violation category.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
The function comment states it retrieves specified libraries for movies, series, and concerts, and the following comment narrows this to 'movie, series, concert' libraries, but the implemented filter actually uses Emby's `musicvideos` collection type. That behavior is materially different from the natural-language description and broadens the content category accessed.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The code reads Emby watch history and persists it into a local markdown file without any consent, retention control, or disclosure in the code flow. Viewing history is personal behavioral data, and silently storing it increases privacy risk if the host is multi-user, backed up elsewhere, or later exposed through logs or file access.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill saves user ratings and free-form notes to disk without notifying the user that the information will be persisted locally. Because the notes can contain personal opinions or sensitive details, undisclosed storage creates a privacy issue and can lead to unintended disclosure on shared systems.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
This code stores sensitive personal context such as mood, work status, and interests to a local file without explicit warning or consent. Such contextual data is more privacy-sensitive than movie metadata and could be harmful if accessed by other local users, backup systems, or later-integrated tooling.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
All user-facing instructions and examples are presented only in Chinese, and the skill does not offer an alternative language or indicate that Chinese is an intentional region-specific requirement. Under the policy, forcing a specific language without user opt-in is a natural-language policy concern.

Static analysis

Detected: suspicious.env_credential_access, suspicious.exposed_secret_literal

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
index.js:15

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
README.md:42

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
SKILL.md:111