Back to skill

Security audit

douyin-creator-insight

Security checks for vulnerabilities and agentic risk

Overview

The skill has a coherent public Douyin analysis purpose, but it can forward ASR API keys and media to unvalidated endpoints and download unvalidated media URLs from the local network context.

Review before installing. Use only from trusted working directories, avoid untrusted .env or .env.local files, do not set custom ASR endpoint variables unless you control and trust the host, and prefer running with explicit provider configuration. Treat generated Markdown as untrusted public content, and be aware the SiliconFlow signup path is a referral link.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (4)

T01 · Skill Instruction Hijacking

Note
Location
scripts/setup_config.py:20
Finding

Referral-based provider onboarding steers agent and user behavior

Content
View full analysis
bool: """Interactive helper: open the referral signup page in the default browser.""" import sys import webbrowser if not sys.stdin.isatty(): return False if ask: answer = input("是否打开 SiliconFlow 推荐注册页?[Y/n] ").strip().lower() if answer in {"n", "no"}: return False try: return bool(webbrowser.open(SILICONFLOW_REFERRAL_URL)) except Exception: return False ``` The Skill instructions additionally require new users to visit the same referral URL before opening the provider console and use directive language that discourages finding an alternative registration route. ### Technical Analysis The Skill repeatedly embeds and promotes an invitation-specific SiliconFlow registration URL instead of using only neutral official documentation or console URLs. The setup helper can open that referral URL in the user's default browser, and its prompt defaults to acceptance unless the user explicitly answers negatively. Selecting a cloud ASR provider can be relevant to the declared transcription functionality. Requiring a particular referral route, however, is not necessary to perform creator analysis or cloud transcription. It alters onboarding behavior for a potentially commercial purpose and therefore exceeds the minimum instructions needed by the Skill. This is not evidence of cred ...[truncated 944 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/asr.py:197
Finding

Cloud ASR credentials can be forwarded to arbitrary endpoint overrides

Content
View full analysis
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
scripts/asr.py:390
Finding

Unvalidated media URLs permit server-side request forgery

Content
View full analysis
list[tuple[str, str]]: """Ordered media URLs: video stream only (no audio_url / BGM branch).""" out: list[tuple[str, str]] = [] seen: set[str] = set() for kind, url in (("video", video.video_url),): u = (url or "").strip() if u and u not in seen: seen.add(u) out.append((kind, u)) return out ``` ```python def _download_media(url: str, destination: Path) -> None: # Douyin CDN (*.douyinvod.com) returns 403 without browser-like Referer/Origin. # DashScope URL-mode still fails server-side (Aliyun cannot fetch CDN); upload/local paths need this. request = Request( url, headers={ "User-Agent": ( "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/120.0.0.0 Safari/537.36" ), "Referer": "https://www.douyin.com/", "Origin": "https://www.douyin.com", "Accept": "*/*", }, ) with urlopen(request, timeout=180) as response, destination.open("wb") as output: while chunk := response.read(1024 * 1024): output.write(chunk) ``` The unvalidated downloader is reached from both cloud-upload transcription and local Whisper transcription: ```python source_path = temp_dir / f"source-{kind}.{_media_format(media_url)}" audio_path = temp_dir / f"audio-{kind}.mp3" _download_media(media_url, source_path) _extract_cloud_audio(source_path, audio_path) ``` ### Technical Analysis Media URLs originate from browser collection records or explicitly injected collection adapters. The URL is norm ...[truncated 2540 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/report_builder.py:267
Finding

Creator-controlled fields are emitted as unsanitized Markdown

Content
View full analysis
str: """Generate a Markdown report.""" creator = report.creator nickname = creator.nickname or creator.douyin_id or creator.creator_query valid_transcripts = [t for t in report.transcripts if t.status == TranscriptStatus.SUCCESS and t.text] lines = [ f"# {nickname} · Douyin Content Insight Report", "", f"**Data timestamp:** {report.generated_at.strftime('%Y-%m-%d %H:%M:%S')}", f"**Research purpose:** {report.research_goal}", f"**Data collection:** {report.data_source}", f"**Speech transcription:** {report.transcript_source}", f"**Transcription quality:** {report.transcript_quality.get('message') or 'Not recorded'}", f"**Transcription candidates:** {_selection_summary(report.transcript_selection)}", f"**Video collection:** {_collection_summary(report.collection)}", "", "## Account Overview", "", "| Metric | Value |", "|---|---|", f"| Followers | {creator.followers_count or '—'} |", f"| Total likes | {creator.heart_count or '—'} |", f"| Videos | {len(report.videos)} |", f"| Transcripts collected | {len(valid_transcripts)} |", "", ] if creator.signature: lines.extend([f"> {creator.signature}", ""]) if report.categories: lines.append("## Topic Categories") lines.append("") video_map = {v.aweme_id: v for v in report.videos} for cat, ids in sorted(report.categories.items(), key=lambda x: -len(x[1])): lines.append(f"### {cat} ({len(ids)} items)") for vid in ids[:5]: v = video_map.get(vid) if v: ...[truncated 3345 chars]
Remediation
View remediation
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (81)

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

The cloud ASR endpoint is overridable through the DOUYIN_CLOUD_ASR_URL environment variable, and the code sends media URLs plus the ASR API token to whatever endpoint is configured. In untrusted deployment environments, this enables exfiltration of sensitive request data and credentials to an attacker-controlled server.

Content

Scanner excerpt · scripts/asr.py (reported line 226)May include surrounding context.

python
method="POST",
    )
    try:
        with urlopen(request, timeout=120) as response:
            response_payload = json.loads(response.read().decode("utf-8"))
    except HTTPError as exc:
        detail = exc.read().decode("utf-8", errors="replace")[:500]

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

The function downloads arbitrary URLs from video metadata with urlopen and writes the response to local disk, but it does not validate scheme, host, IP range, redirect target, or content length. If an attacker can influence video.video_url or upstream metadata, this can be used for SSRF against internal services or for resource-exhaustion downloads.

Content

Scanner excerpt · scripts/asr.py (reported line 406)May include surrounding context.

python
"Accept": "*/*",
        },
    )
    with urlopen(request, timeout=180) as response, destination.open("wb") as output:
        while chunk := response.read(1024 * 1024):
            output.write(chunk)

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

The multipart upload destination is controlled by the SILICONFLOW_ASR_URL environment variable, and the request includes both the bearer token and uploaded audio derived from downloaded media. If that endpoint is changed to an attacker-controlled host, the skill will exfiltrate potentially sensitive content and credentials directly to the attacker.

Content

Scanner excerpt · scripts/asr.py (reported line 466)May include surrounding context.

python
method="POST",
    )
    try:
        with urlopen(request, timeout=180) as response:
            return json.loads(response.read().decode("utf-8"))
    except HTTPError as exc:
        detail = exc.read().decode("utf-8", errors="replace")[:500]

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

Reusing a separate favorites-related skill installation/configuration and browser profile introduces a cross-context trust issue that is not fully reflected in the declared purpose. Even if the skill says it will not read favorites records, shared profile and config access can expose session state, local paths, or unintended coupling between tools, making misuse or accidental data leakage more plausible.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

Reusing a separate favorites-related skill installation/configuration and browser profile introduces a cross-context trust issue that is not fully reflected in the declared purpose. Even if the skill says it will not read favorites records, shared profile and config access can expose session state, local paths, or unintended coupling between tools, making misuse or accidental data leakage more plausible.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

Reusing a separate favorites-related skill installation/configuration and browser profile introduces a cross-context trust issue that is not fully reflected in the declared purpose. Even if the skill says it will not read favorites records, shared profile and config access can expose session state, local paths, or unintended coupling between tools, making misuse or accidental data leakage more plausible.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

Reusing a separate favorites-related skill installation/configuration and browser profile introduces a cross-context trust issue that is not fully reflected in the declared purpose. Even if the skill says it will not read favorites records, shared profile and config access can expose session state, local paths, or unintended coupling between tools, making misuse or accidental data leakage more plausible.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding

Reusing a separate favorites-related skill installation/configuration and browser profile introduces a cross-context trust issue that is not fully reflected in the declared purpose. Even if the skill says it will not read favorites records, shared profile and config access can expose session state, local paths, or unintended coupling between tools, making misuse or accidental data leakage more plausible.

Content

No source excerpt is available for this finding.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · scripts/categorizer.py (reported line 93)May include surrounding context.

python
"insights": "..."
}}
"""
    return prompt


def categorize_videos(videos: List[Video]) -> Dict[str, List[str]]:

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/runtime_env.py (reported line 36)May include surrounding context.

python
home = Path.home()
    hermes_home = Path(os.environ.get("HERMES_HOME", home / ".hermes")).expanduser()
    candidates = (
        Path.cwd() / ".env",
        Path.cwd() / ".env.local",
        skill_root / ".env",
        skill_root / ".env.local",

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/runtime_env.py (reported line 38)May include surrounding context.

python
home = Path.home()
    hermes_home = Path(os.environ.get("HERMES_HOME", home / ".hermes")).expanduser()
    candidates = (
        Path.cwd() / ".env",
        Path.cwd() / ".env.local",
        skill_root / ".env",
        skill_root / ".env.local",

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · tests/test_runtime_env.py (reported line 48)May include surrounding context.

python
home = Path.home()
    hermes_home = Path(os.environ.get("HERMES_HOME", home / ".hermes")).expanduser()
    candidates = (
        Path.cwd() / ".env",
        Path.cwd() / ".env.local",
        skill_root / ".env",
        skill_root / ".env.local",

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · tests/test_runtime_env.py (reported line 64)May include surrounding context.

python
home = Path.home()
    hermes_home = Path(os.environ.get("HERMES_HOME", home / ".hermes")).expanduser()
    candidates = (
        Path.cwd() / ".env",
        Path.cwd() / ".env.local",
        skill_root / ".env",
        skill_root / ".env.local",

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · tests/test_runtime_env.py (reported line 75)May include surrounding context.

python
home = Path.home()
    hermes_home = Path(os.environ.get("HERMES_HOME", home / ".hermes")).expanduser()
    candidates = (
        Path.cwd() / ".env",
        Path.cwd() / ".env.local",
        skill_root / ".env",
        skill_root / ".env.local",

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · tests/test_runtime_env.py (reported line 88)May include surrounding context.

python
home = Path.home()
    hermes_home = Path(os.environ.get("HERMES_HOME", home / ".hermes")).expanduser()
    candidates = (
        Path.cwd() / ".env",
        Path.cwd() / ".env.local",
        skill_root / ".env",
        skill_root / ".env.local",

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · tests/test_runtime_env.py (reported line 113)May include surrounding context.

python
home = Path.home()
    hermes_home = Path(os.environ.get("HERMES_HOME", home / ".hermes")).expanduser()
    candidates = (
        Path.cwd() / ".env",
        Path.cwd() / ".env.local",
        skill_root / ".env",
        skill_root / ".env.local",

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · tests/test_runtime_env.py (reported line 128)May include surrounding context.

python
home = Path.home()
    hermes_home = Path(os.environ.get("HERMES_HOME", home / ".hermes")).expanduser()
    candidates = (
        Path.cwd() / ".env",
        Path.cwd() / ".env.local",
        skill_root / ".env",
        skill_root / ".env.local",

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · tests/test_runtime_env.py (reported line 143)May include surrounding context.

python
home = Path.home()
    hermes_home = Path(os.environ.get("HERMES_HOME", home / ".hermes")).expanduser()
    candidates = (
        Path.cwd() / ".env",
        Path.cwd() / ".env.local",
        skill_root / ".env",
        skill_root / ".env.local",

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · tests/test_runtime_env.py (reported line 157)May include surrounding context.

python
home = Path.home()
    hermes_home = Path(os.environ.get("HERMES_HOME", home / ".hermes")).expanduser()
    candidates = (
        Path.cwd() / ".env",
        Path.cwd() / ".env.local",
        skill_root / ".env",
        skill_root / ".env.local",

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/runtime_env.py (reported line 37)May include surrounding context.

python
hermes_home = Path(os.environ.get("HERMES_HOME", home / ".hermes")).expanduser()
    candidates = (
        Path.cwd() / ".env",
        Path.cwd() / ".env.local",
        skill_root / ".env",
        skill_root / ".env.local",
        hermes_home / ".env",

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/runtime_env.py (reported line 39)May include surrounding context.

python
hermes_home = Path(os.environ.get("HERMES_HOME", home / ".hermes")).expanduser()
    candidates = (
        Path.cwd() / ".env",
        Path.cwd() / ".env.local",
        skill_root / ".env",
        skill_root / ".env.local",
        hermes_home / ".env",

Credential Access

High
Category
Privilege Escalation
Confidence
90% confidence
Finding

Reading hermes_home / '.env' pulls configuration from a shared launcher-level location that may contain credentials unrelated to this skill. That broadens the skill's access boundary and can silently import sensitive values into the process without a per-skill consent step.

Content

Scanner excerpt · scripts/runtime_env.py (reported line 40)May include surrounding context.

python
Path.cwd() / ".env.local",
        skill_root / ".env",
        skill_root / ".env.local",
        hermes_home / ".env",
        home / ".hermes" / ".env",
        home / ".config" / "douyin-creator-insight.env",
        home / ".config" / "douyin-creator-insight" / ".env",

Credential Access

High
Category
Privilege Escalation
Confidence
90% confidence
Finding

Reading ~/.hermes/.env similarly accesses a cross-skill shared configuration area that may hold secrets for other tools or workflows. In the context of a public-content analysis skill, that creates unnecessary exposure to unrelated credentials and local profile paths.

Content

Scanner excerpt · scripts/runtime_env.py (reported line 41)May include surrounding context.

python
skill_root / ".env",
        skill_root / ".env.local",
        hermes_home / ".env",
        home / ".hermes" / ".env",
        home / ".config" / "douyin-creator-insight.env",
        home / ".config" / "douyin-creator-insight" / ".env",
    )

Credential Access

High
Category
Privilege Escalation
Confidence
87% confidence
Finding

Reading ~/.config/douyin-creator-insight/.env from the user's home directory is less severe than generic shared env scanning but still silently imports locally stored secrets and profile settings into the skill process. If an attacker can influence downstream skill behavior, these values become available without the user explicitly supplying them at run time.

Content

Scanner excerpt · scripts/runtime_env.py (reported line 43)May include surrounding context.

python
hermes_home / ".env",
        home / ".hermes" / ".env",
        home / ".config" / "douyin-creator-insight.env",
        home / ".config" / "douyin-creator-insight" / ".env",
    )
    seen: set[str] = set()
    ordered: list[Path] = []

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/run_pipeline.py (reported line 433)May include surrounding context.

python
print(f"    ② 登录后到控制台创建 API Key:{SILICONFLOW_CONSOLE_URL}")
    print("    ③ 本机写入环境变量(不要发聊天、不要写进仓库):")
    print("       export SILICONFLOW_API_KEY='你的 Key'")
    print("       # 或写入 ~/.hermes/.env / 本仓库 .env.local 后重启 Agent")
    print(f"    ④ 价格参考:{SILICONFLOW_PRICING_URL}(SenseVoiceSmall 页上标注可能为免费,以账单为准)")
    print(f"    ⑤ 接口文档:{SILICONFLOW_DOCS_URL}")
    print("  【可选】DASHSCOPE_API_KEY — 仅第三方可公网直链;抖音 douyinvod 常失败,不替代上面")