Back to skill

Security audit

Douyin Favorites to Knowledge

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its Douyin-to-local-notes purpose, but it asks for recurring background sync and has under-scoped outbound credential/media handling that users should review before installing.

Install only if you are comfortable with a dedicated persistent Douyin browser profile, automatic local note/report writes, and any selected cloud transcription provider receiving media or audio. Do not enable the daily scheduled task unless you explicitly want recurring background sync, avoid using SILICONFLOW_ASR_URL unless you fully trust and control it, store API keys in a scoped secret manager rather than a shared agent .env file, and treat custom module:function adapters as arbitrary local code.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • System PersistenceInstalls backdoors, hooks, services, or scheduled tasks that survive the run
  • 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 (4)

T06 · System Persistence

Error
Location
SKILL.md:74
Finding
Setup and login instructions mandate unsolicited scheduled persistence<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:74-80` (mirrored in `skill/SKILL.md:74-80`) **Vulnerability Type**: Scheduled-task persistence without explicit user opt-in **Risk Level**: High ### Vulnerable Code Snippet ```markdown ## 每晚日报 首次 `setup` 和 `login` 成功后,若当前 Agent 支持定时任务,必须在用户本机创建并验证每天 **23:00** 的任务,执行: ```bash douyin-favorites-knowledge daily --source collection --no-login-prompt ``` 使用当前环境可用的任务调度器;创建后必须读取任务状态或执行一次 dry-run 验证,并向用户返回任务标识与用户时区。 ``` ### Technical Analysis The Skill instructs the Agent to create a recurring task automatically after successful setup and login. This behavior is tied to ordinary onboarding rather than to a separate, explicit request from the user to enable automation. Creating a scheduled task changes persistent host state and causes the program to execute after the initiating Agent session has ended. That exceeds the privileges required to perform an immediate synchronization. Although the instruction includes some safeguards—such as avoiding credentials on the command line and verifying the task—it does not require informed, contemporaneous consent before persistence is installed. The duplicate instruction in `skill/SKILL.md` reproduces the same behavior. ### Attack Path 1. A user asks the Agent to configure the Skill or synchronize Douyin favorites. 2. The Agent loads the Skill instructions. 3. Setup and browser login complete successfully. 4. Because the Agent has scheduler access, it follows the mandatory instruction to create a daily 23:00 task. 5. The scheduled command persists beyond the current session and repeatedly accesses the browser profile, Douyin account data, local knowledge base, and any configured transcription provider. 6. Execution continues until the user independently discovers and removes the task. ### Impact Assessment The persistent task can repeatedly exercise: - Access to the dedicated persistent Douyin browser profile and authenticated account session. - Network acc ...[truncated 454 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove the requirement to create a scheduler automatically after setup or login. - Keep recurring synchronization disabled by default. - Require a separate, explicit user action such as `enable-daily` immediately before task creation. - Before installation, display: - The exact command that will run. - The execution frequency and timezone. - The account identity under which it will run. - The directories, browser profile, network services, and credentials it may access. - Any possible API charges. - Ask for an affirmative confirmation that defaults to rejection in non-interactive environments. - Provide a command that lists task status and a documented command that completely removes the task. - Where possible, create tasks with narrowly scoped environment variables, filesystem access, and execution permissions. - Apply the same corrections to both `SKILL.md` copies. ]]>

T01 · Skill Instruction Hijacking

Warning
Location
src/douyin_favorites_knowledge/cli.py:328
Finding
Hard-coded commercial referral steers Agent output and browser navigation<![CDATA[ ## Vulnerability Details **File Location**: `src/douyin_favorites_knowledge/cli.py:328-343` **Vulnerability Type**: Agent output manipulation and affiliate navigation **Risk Level**: Medium ### Vulnerable Code Snippet ```python if transcription == "siliconflow" and next_step: payload["siliconflow_referral_url"] = "https://cloud.siliconflow.cn/i/1srulim9" payload["siliconflow_console_url"] = "https://cloud.siliconflow.cn/account/ak" if sys.stdin.isatty(): print("—— SiliconFlow 配置(抖音推荐)——", file=sys.stderr) print(" ① 推荐注册/登录:https://cloud.siliconflow.cn/i/1srulim9", file=sys.stderr) print(" ② 控制台建 Key:https://cloud.siliconflow.cn/account/ak", file=sys.stderr) print(" ③ export SILICONFLOW_API_KEY='…' 或写入 ~/.hermes/.env 后重启", file=sys.stderr) try: ans = input("是否打开推荐注册页?[Y/n] ").strip().lower() except EOFError: ans = "n" if ans not in {"n", "no"}: try: import webbrowser webbrowser.open("https://cloud.siliconflow.cn/i/1srulim9") print("已尝试打开浏览器。", file=sys.stderr) except Exception: print("无法自动打开,请手动访问推荐注册页。", file=sys.stderr) ``` Related mandatory referral guidance is also present in `SKILL.md:87-93` and `skill/SKILL.md:87-93`. ### Technical Analysis The setup flow embeds a fixed referral URL and labels it as the recommended registration destination. It also adds the referral URL to structured output and offers to open it in the user's browser. The prompt uses an affirmative default: any answer other than `n` or `no`, including an empty response, causes browser navigation. This is not required to configure transcription because the official API-key console URL is already available. Embedding this behavior in the Skill instructions can influence an Agent to repeatedly promote and navigate to a commercially benefiting URL. The code and instructions do not disclose the nat ...[truncated 1072 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove the hard-coded referral URL from executable output and Skill instructions. - Use only neutral, clearly identified official documentation and API-console URLs. - Do not automatically open promotional or registration pages. - If browser opening remains available, require an explicit `yes`; an empty answer must default to no action. - Clearly disclose any affiliate or referral relationship before presenting such a link. - Separate commercial links from operational setup instructions and never describe them as required. - Apply the same changes to `SKILL.md`, `skill/SKILL.md`, CLI output, README content, and changelog guidance where applicable. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
src/douyin_favorites_knowledge/siliconflow.py:193
Finding
Environment-controlled ASR endpoint can receive the API key and private media<![CDATA[ ## Vulnerability Details **File Location**: `src/douyin_favorites_knowledge/siliconflow.py:193-224` **Vulnerability Type**: Unvalidated credential-bearing outbound endpoint **Risk Level**: High ### Vulnerable Code Snippet ```python request = urllib.request.Request( endpoint, data=bytes(body), method="POST", headers={ "Authorization": f"Bearer {api_key}", "Content-Type": f"multipart/form-data; boundary={boundary}", "User-Agent": "douyin-favorites-to-knowledge/siliconflow", }, ) with urllib.request.urlopen(request, timeout=300) as response: payload = json.loads(response.read().decode("utf-8", errors="replace")) ``` ```python api_key = os.environ[KEY_NAME].strip() ctx = context if isinstance(context, dict) else {} options = ctx.get("options") if isinstance(ctx.get("options"), dict) else {} max_media_bytes = int(options.get("max_media_bytes", MAX_MEDIA_BYTES) or MAX_MEDIA_BYTES) model = str(ctx.get("model") or os.environ.get("SILICONFLOW_ASR_MODEL") or DEFAULT_MODEL).strip() endpoint = str(os.environ.get("SILICONFLOW_ASR_URL") or DEFAULT_ENDPOINT).strip() ``` ### Technical Analysis The transcription endpoint is accepted directly from `SILICONFLOW_ASR_URL`. Before sending the request, the code does not validate: - The URL scheme. - The destination hostname. - The destination port. - Whether the endpoint is the official SiliconFlow API origin. - Whether an HTTP redirect remains on the trusted origin. The resulting request contains both the `SILICONFLOW_API_KEY` in the `Authorization` header and the downloaded or extracted media in the multipart body. Consequently, control over the process environment is sufficient to redirect sensitive credentials and private user content to another server. An insecure `http://` endpoint could additionally expose the bearer token and media to network interception. Depending on URL-handler and redirect behavior, the endpoint can also be used to initiate requests toward i ...[truncated 1432 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove `SILICONFLOW_ASR_URL` from normal production operation unless custom endpoints are an explicit supported requirement. - If an override is necessary: - Require the `https` scheme. - Allowlist the exact official hostname, expected path, and standard TLS port. - Reject embedded credentials, fragments, nonstandard schemes, loopback addresses, link-local addresses, and private network destinations. - Resolve and validate destination addresses to mitigate DNS rebinding. - Disable automatic redirects or validate every redirect target before forwarding credentials. - Never forward the bearer token when the origin changes. - Keep custom or development endpoints behind an explicit unsafe-development flag that is disabled by default and never available to scheduled jobs. - Add tests proving that HTTP URLs, foreign hosts, private IP addresses, and cross-origin redirects are rejected before any request body or authorization header is sent. - Document the exact third party receiving uploaded media and obtain informed consent before cloud transcription. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:87
Finding
API key is recommended for persistence in a broadly scoped Agent environment file<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:87-93` (mirrored in `skill/SKILL.md:87-93`; repeated in `src/douyin_favorites_knowledge/cli.py:188-190,328-334`) **Vulnerability Type**: Insecure plaintext credential persistence guidance **Risk Level**: Medium ### Vulnerable Code Snippet ```markdown 1. 推荐注册/登录:https://cloud.siliconflow.cn/i/1srulim9 2. 控制台创建 Key:https://cloud.siliconflow.cn/account/ak 3. `export SILICONFLOW_API_KEY='…'` 或写入 `~/.hermes/.env`,**重启 Agent** 4. `douyin-favorites-knowledge check-config` ``` The CLI repeats the same recommendation: ```python print(" ③ export SILICONFLOW_API_KEY='…' 或写入 ~/.hermes/.env 后重启", file=sys.stderr) ``` ### Technical Analysis The Skill recommends writing the SiliconFlow API key to `~/.hermes/.env`, which appears to be a general Agent environment file rather than a secret store scoped to this Skill. The guidance does not: - Verify or enforce restrictive file permissions. - Limit which Agent components or child processes inherit the secret. - Separate this Skill's key from unrelated Agent workloads. - Provide rotation or deletion instructions. - Warn that plaintext environment files may be copied into backups, diagnostics, or support bundles. Environment-variable use can be appropriate, but persistent plaintext storage in a shared Agent file expands the credential's exposure beyond the minimum privilege required for transcription. ### Attack Path 1. The user follows the setup instruction and writes `SILICONFLOW_API_KEY` into `~/.hermes/.env`. 2. The Agent restarts and loads the shared environment file. 3. Other tools, plugins, adapters, or child processes launched by the Agent inherit or can read the key. 4. A compromised or overly privileged component reads the variable or plaintext file. 5. The key is used outside this Skill, disclosed through diagnostics, or retained in backups after the user believes it has been removed. ### Impact Assessment Potential consequences include: - ...[truncated 469 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Recommend an operating-system keychain, encrypted secret manager, or scheduler-integrated secret store instead of a generic Agent `.env` file. - Scope the secret to this application and only to commands that require cloud transcription. - Avoid exporting the key to the full Agent process when narrower per-command injection is available. - If file storage must be supported: - Use a dedicated application-specific secrets file. - Create it with owner-only permissions such as mode `0600`. - Reject group- or world-readable permissions. - Exclude it from version control, backups, logs, and diagnostic bundles. - Never print the value. - Provide explicit key-removal and rotation instructions. - Ensure scheduled jobs receive the secret through a secure scheduler or secret-manager mechanism rather than by reading a shared Agent environment file. - Update all duplicated guidance in both Skill files and the CLI. ]]>
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
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (42)

Tainted flow: 'request' from os.environ.get (line 14, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
headers={"Content-Type": "application/json"},
        method="POST",
    )
    with urlopen(request, timeout=10) as response:
        if response.status // 100 != 2:
            raise ValueError("飞书 webhook 返回非成功状态")
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
声明的用途聚焦于把抖音收藏/喜欢内容同步到本地 Markdown 或 Obsidian 知识库,以及转录方式选择;并未提到任何飞书集成、外部通知或向第三方服务发送结果。该代码的实际行为是访问环境变量中的 FEISHU_WEBHOOK_URL,并通过 HTTP POST 向飞书 webhook 发送“已沉淀多少条新笔记”的通知。这属于未声明的外部网络通知能力和额外资源访问。虽然发送的内容看起来只是计数,不明显涉及 Cookie 或私密数据,但它仍然超出了“同步到本地知识库”的已声明范围,因此应判定为描述与行为不匹配。

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding
该代码块的核心行为是环境探测和转录服务推荐,属于安装/诊断子模块,而声明描述的是一个面向抖音内容同步到 Markdown/Obsidian 的完整技能。虽然声明中提到需要让用户选择 SiliconFlow、本地 Whisper 或不转录,因此“转录相关配置”与整体技能有关,但此代码不仅没有实现声明中的主要业务流程(抓取已授权账号的收藏/喜欢、同步到本地知识库),还额外检查并推荐了百炼和 MiniMax,这些提供方未在声明的首选列表中明确出现。若将此代码视为整个技能的实际行为,则其主要目的与声明明显不一致,应判定为描述与行为不匹配。

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
声明描述的是一个面向抖音数据同步与可选转录的业务技能,而提供的代码片段是 security.py,功能集中在文本安全检查和错误信息脱敏。它会检测推理标签、若干令牌格式、私密字段名和私有文件路径,并阻止或清洗这些内容。这与声明中的核心能力(获取用户授权账号的收藏/喜欢列表、同步到 Markdown/Obsidian、处理首次转录选择)没有直接对应关系。虽然其中“不泄露 Cookie 与私密数据”的安全目标与声明的安全约束部分一致,但这只是局部支撑性安全实现,不能代表整体技能用途。因此就该代码片段与声明用途的对应关系来看,存在明显不匹配。

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The declared description describes a Douyin content synchronization skill with account-scoped data access, source selection between favorites and likes, optional transcription, and local knowledge-base export. The supplied code does none of that. It is purely a test file for repository hygiene and packaging consistency. Its behavior is materially different from the declared primary purpose, and it lacks any implementation of the described syncing, transcription, or user-account handling capabilities. Therefore this is a clear description-behavior mismatch.

Credential Access

High
Category
Privilege Escalation
Content
print("—— SiliconFlow 配置(抖音推荐)——", file=sys.stderr)
            print("  ① 推荐注册/登录:https://cloud.siliconflow.cn/i/1srulim9", file=sys.stderr)
            print("  ② 控制台建 Key:https://cloud.siliconflow.cn/account/ak", file=sys.stderr)
            print("  ③ export SILICONFLOW_API_KEY='…' 或写入 ~/.hermes/.env 后重启", file=sys.stderr)
            try:
                ans = input("是否打开推荐注册页?[Y/n] ").strip().lower()
            except EOFError:
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
def cli(config: Path, *args: str) -> subprocess.CompletedProcess[str]:
    env = dict(os.environ)
    env["PYTHONPATH"] = PYTHONPATH
    return subprocess.run(
        [sys.executable, "-m", "douyin_favorites_knowledge", "--config", str(config), *args],
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Env Variable Harvesting

High
Category
Data Exfiltration
Content
def cli(config: Path, *args: str) -> subprocess.CompletedProcess[str]:
    env = dict(os.environ)
    env["PYTHONPATH"] = PYTHONPATH
    return subprocess.run(
        [sys.executable, "-m", "douyin_favorites_knowledge", "--config", str(config), *args],
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill requests or implies capabilities including shell, network, environment-variable access, and file read/write, but does not declare an explicit tool scope such as permissions or allowed-tools. That creates an over-privileged execution surface where an agent may invoke sensitive operations without a clear least-privilege contract, increasing the chance of unintended command execution, network egress, or secret exposure.

Natural-Language Policy Violations

Medium
Confidence
82% confidence
Finding
The entire skill description and operational instructions are written as mandatory Chinese-language guidance, and there is no indication that users may choose another language or locale. Under the policy, forcing a specific language without opt-in is a natural-language policy violation unless the locale constraint is explicitly documented and justified.

Vague Triggers

Medium
Confidence
89% confidence
Finding
The description explains behavior and defaults, but it does not clearly define the exact trigger phrases or contexts that should invoke the skill. Phrases like acting when a user says they want favorites or likes synchronized are implied rather than narrowly enumerated, which can lead to unintended activation in ordinary conversation about Douyin content.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The skill instructs the agent to create and verify a 23:00 scheduled task that performs silent daily synchronization and writes Markdown reports. Even though it says this should happen after setup/login and on the user's machine, the wording makes automatic ongoing file modification the default behavior without an equally explicit user-facing consent checkpoint for persistence, cadence, and continued writes.

Description-Behavior Mismatch

Medium
Confidence
91% confidence
Finding
The file implements a DashScope/Bailian transcription path even though the skill description only mentions SiliconFlow, local Whisper, or no transcription. That mismatch can defeat user expectations and informed consent, causing audio/media URLs from an authorized Douyin account to be sent to an unadvertised third-party service.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The code sends the Douyin media play URL to DashScope for remote ASR, which shares user-derived content with an external service. In this skill's context, the data originates from a user's favorites/likes and may reveal private interests or account-linked content; because the code itself does not enforce a user-facing disclosure or consent check, this creates a real privacy and data-handling risk.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The browser is launched with a hard-coded locale of "zh-CN", which enforces a specific language/locale regardless of user preference. The file does not offer an opt-in or configuration path for locale selection, matching the policy-violation criterion for forced language settings.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The sync and daily commands are explicitly described as silently writing to the knowledge base, and the implementation auto-approves all collected candidates before promotion. In a skill that ingests browser-derived content into local notes, this increases the risk of unexpected persistent writes, content poisoning, or accidental inclusion of sensitive/private material without a final user-visible confirmation at execution time.

Description-Behavior Mismatch

Medium
Confidence
91% confidence
Finding
The CLI allows arbitrary module:function adapters for collector, enricher, and notifier, which expands the trust boundary far beyond the stated browser-to-local-sync workflow. Because these adapters are dynamically loaded and executed, a user or downstream automation can run unintended code with the tool's privileges, enabling data exfiltration or unauthorized side effects if an untrusted adapter is supplied.

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
The manifest describes syncing Douyin favorites/likes into a local Markdown or Obsidian knowledge base, with optional transcription choices, but says nothing about sending data or status messages to third-party chat platforms. This function reads a Feishu webhook URL from the environment and performs an external POST request, which is an additional notification/integration capability beyond the stated purpose.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if err:
                    last = err
                    continue
                extracted = subprocess.run(
                    ["ffmpeg", "-nostdin", "-y", "-i", str(media_path), "-vn", "-ac", "1", "-ar", rate, str(audio_path)],
                    capture_output=True,
                    text=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Tainted flow: 'rate' from os.environ.get (line 55, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
if err:
                    last = err
                    continue
                extracted = subprocess.run(
                    ["ffmpeg", "-nostdin", "-y", "-i", str(media_path), "-vn", "-ac", "1", "-ar", rate, str(audio_path)],
                    capture_output=True,
                    text=True,
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.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if not shutil.which("mmx"):
        return {"state": "unavailable", "reason": "未发现 MiniMax 命令行转录接口"}
    try:
        result = subprocess.run(["mmx", "speech", "--help"], capture_output=True, text=True, timeout=5, check=False)
    except (OSError, subprocess.TimeoutExpired):
        return {"state": "unavailable", "reason": "无法确认 MiniMax 是否提供转录接口"}
    help_text = f"{result.stdout}\n{result.stderr}".lower()
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

External Transmission

Medium
Category
Data Exfiltration
Content
KEY_NAME = "SILICONFLOW_API_KEY"
DEFAULT_MODEL = "FunAudioLLM/SenseVoiceSmall"
DEFAULT_ENDPOINT = "https://api.siliconflow.cn/v1/audio/transcriptions"
MAX_MEDIA_BYTES = 512 * 1024 * 1024
TEMP_PREFIX = "douyin-sf-asr-"
DEFAULT_BITRATE = "64k"
Confidence
81% confidence
Finding
The code is explicitly designed to transmit media to `api.siliconflow.cn`, which is an external service boundary crossing and therefore a real data-exposure concern when handling private or user-authenticated content. In this skill context, external transmission is expected for cloud ASR, but it remains security-relevant because user favorites/liked media and extracted audio may contain sensitive personal information.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if not shutil.which("ffmpeg"):
        return False
    rate, bitrate = _audio_encode_settings()
    completed = subprocess.run(
        [
            "ffmpeg",
            "-nostdin",
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Tainted flow: 'rate' from os.environ.get (line 56, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Content
if not shutil.which("ffmpeg"):
        return False
    rate, bitrate = _audio_encode_settings()
    completed = subprocess.run(
        [
            "ffmpeg",
            "-nostdin",
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.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
This code uploads downloaded media content to a third-party transcription provider, which can expose user media and spoken content outside the local environment. In the skill context, the metadata says transcription provider choice should be explicitly selected by the user first, but this file itself contains no consent enforcement or disclosure guard, so misuse by callers could lead to unintended external sharing of private content.

Static analysis

No suspicious patterns detected.