T06 · System Persistence
- Location
SKILL.md:74- Finding
Setup and login instructions mandate unsolicited scheduled persistence
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
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.
SKILL.md:74Setup and login instructions mandate unsolicited scheduled persistence
src/douyin_favorites_knowledge/cli.py:328Hard-coded commercial referral steers Agent output and browser navigation
src/douyin_favorites_knowledge/siliconflow.py:193Environment-controlled ASR endpoint can receive the API key and private media
SKILL.md:87API key is recommended for persistence in a broadly scoped Agent environment file
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
headers={"Content-Type": "application/json"},
method="POST",
)
with urlopen(request, timeout=10) as response:
if response.status // 100 != 2:
raise ValueError("飞书 webhook 返回非成功状态")
声明的用途聚焦于把抖音收藏/喜欢内容同步到本地 Markdown 或 Obsidian 知识库,以及转录方式选择;并未提到任何飞书集成、外部通知或向第三方服务发送结果。该代码的实际行为是访问环境变量中的 FEISHU_WEBHOOK_URL,并通过 HTTP POST 向飞书 webhook 发送“已沉淀多少条新笔记”的通知。这属于未声明的外部网络通知能力和额外资源访问。虽然发送的内容看起来只是计数,不明显涉及 Cookie 或私密数据,但它仍然超出了“同步到本地知识库”的已声明范围,因此应判定为描述与行为不匹配。
该代码块的核心行为是环境探测和转录服务推荐,属于安装/诊断子模块,而声明描述的是一个面向抖音内容同步到 Markdown/Obsidian 的完整技能。虽然声明中提到需要让用户选择 SiliconFlow、本地 Whisper 或不转录,因此“转录相关配置”与整体技能有关,但此代码不仅没有实现声明中的主要业务流程(抓取已授权账号的收藏/喜欢、同步到本地知识库),还额外检查并推荐了百炼和 MiniMax,这些提供方未在声明的首选列表中明确出现。若将此代码视为整个技能的实际行为,则其主要目的与声明明显不一致,应判定为描述与行为不匹配。
声明描述的是一个面向抖音数据同步与可选转录的业务技能,而提供的代码片段是 security.py,功能集中在文本安全检查和错误信息脱敏。它会检测推理标签、若干令牌格式、私密字段名和私有文件路径,并阻止或清洗这些内容。这与声明中的核心能力(获取用户授权账号的收藏/喜欢列表、同步到 Markdown/Obsidian、处理首次转录选择)没有直接对应关系。虽然其中“不泄露 Cookie 与私密数据”的安全目标与声明的安全约束部分一致,但这只是局部支撑性安全实现,不能代表整体技能用途。因此就该代码片段与声明用途的对应关系来看,存在明显不匹配。
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.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
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:
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.
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],
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.
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],
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 calls execute external commands. Without careful input validation, this enables command injection.
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,
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
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,
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
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()
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.
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"
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
if not shutil.which("ffmpeg"):
return False
rate, bitrate = _audio_encode_settings()
completed = subprocess.run(
[
"ffmpeg",
"-nostdin",
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
if not shutil.which("ffmpeg"):
return False
rate, bitrate = _audio_encode_settings()
completed = subprocess.run(
[
"ffmpeg",
"-nostdin",
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.