T09 · Insecure Skill Coding Practices
- Location
scripts/douyin.js:63- Finding
OAuth State Is Generated but Not Validated
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill largely does what it claims for Douyin publishing, but it needs Review because private video audio can be sent to external transcription endpoints by default and public-account actions rely on stored credentials and configurable confirmation bypasses.
Install only if you are comfortable giving the skill access to your Douyin app credentials, local video files, and a publishing token. Prefer local Whisper mode for sensitive media, keep autoConfirm disabled for account posts, restrict ASR endpoints to trusted HTTPS services, and periodically delete transcript caches/outbox files. Verify the external Whisper dependency and model before installing them.
scripts/douyin.js:63OAuth State Is Generated but Not Validated
scripts/lib/media.js:245Extracted Audio and ASR Credentials Can Be Sent to an Arbitrary Endpoint
README.md:225Installation Instructions Use Mutable, Unverified Third-Party Artifacts
scripts/lib/media.js:210Local Whisper Leaves Plaintext Transcript Files in the System Temporary Directory
Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.
Ubuntu/Debian 快速安装指令:
sudo apt-get update && sudo apt-get install -y ffmpeg cmake build-essential curl jq xdg-utils
# 安装 whisper.cpp
git clone https://github.com/ggerganov/whisper.cpp.git
Tool calls are chained to bypass individual safety checks or escalate capabilities beyond what any single tool call would allow.
**Ubuntu/Debian 快速安装指令:**
```bash
sudo apt-get update && sudo apt-get install -y ffmpeg cmake build-essential curl jq xdg-utils
# 安装 whisper.cpp
git clone https://github.com/ggerganov/whisper.cpp.git
This code chunk is a reusable command-line utility module, not an implementation of the declared Douyin publishing workflow. While interactive prompting and confirmation could support such a tool, the actual code does not demonstrate any of the core declared capabilities. Because the described primary purpose is specific and substantial, and the code shown is only unrelated generic support infrastructure, this is a description-behavior mismatch for the supplied chunk.
The code is genuinely related to Douyin authorization and publishing, so the primary domain matches part of the description. However, this chunk only provides API-layer functionality for OAuth, token refresh, uploading a local video file, creating a video post, and classifying permission errors. It does not implement the declared local speech-to-text pipeline, automatic caption/title generation from audio, any confirmation workflow, or a fallback export mechanism when publish permissions are absent. Because several prominent declared behaviors are not represented in the supplied code chunk, the description overstates what this code actually does.
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
}
if (!tokenData.refreshToken) {
const error = new Error("Access token expired and refresh_token is missing. Re-run auth.");
error.code = "TOKEN_EXPIRED";
throw error;
}
The skill is marketed as local-first and privacy-preserving, yet later says the default ASR mode is a third-party API. In this context, the contradiction is dangerous because the tool handles potentially sensitive video/audio content, and users may unknowingly expose that content to external providers.
The README explicitly promises that audio does not need to be uploaded to any cloud ASR service, but elsewhere documents a third-party API ASR mode. This is a security/privacy documentation integrity issue because users may make deployment decisions based on a false assumption that sensitive audio never leaves the device.
The documentation introduces a third-party ASR API mode without a clear warning that user audio or derived transcript data may be sent to an external service. Because this skill processes local media files, the missing disclosure can cause unintended data exfiltration of private or regulated content.
The fallback outbox bundles video, captions, subtitles, and metadata into an export package, but the README does not warn users about the sensitivity and persistence of that bundle. In practice this can create an unprotected local archive of highly sensitive content that is easy to copy, sync, or mishandle.
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
Ubuntu/Debian 快速安装指令:
sudo apt-get update && sudo apt-get install -y ffmpeg cmake build-essential curl jq xdg-utils
# 安装 whisper.cpp
git clone https://github.com/ggerganov/whisper.cpp.git
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
ln -sf "$(pwd)/build/bin/whisper-cli" ~/.local/bin/whisper-cli
mkdir -p ~/.cache/whisper.cpp curl -L https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin -o ~/.cache/whisper.cpp/ggml-small.bin
The skill advertises and instructs use of shell execution, environment-variable access, and networked OAuth/API flows, but it does not declare any explicit tool scope such as allowed-tools or permissions. That creates an authorization and review gap: operators cannot easily constrain what the skill is allowed to access, and an agent may invoke high-risk capabilities without clear least-privilege boundaries.
The config command allows persistent storage of autoConfirm and other operational settings, which can enable future publishes or account actions with reduced user oversight. In a skill that performs OAuth, local file handling, and remote publishing, persistent autonomous settings increase the chance of accidental or unauthorized actions if reused later.
- `auth`: opens OAuth URL, accepts pasted callback URL or `code`, stores encrypted token.
- `prepare`: returns metadata, transcript, and ASR failure detail (without stopping publish flow).
- `publish`: uploads and creates video via official API. If permission-like API errors occur, writes fallback files under outbox and returns `mode: fallback`.
- `config`: stores persistent settings (`defaultPrivateStatus`, `autoConfirm`, `whisperBin`, `whisperModelPath`, `outboxDir`, etc.).
## Caption Rules
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.
" node scripts/douyin.js doctor",
" node scripts/douyin.js auth [--code <code>] [--callback-url <url>] [--scope <scope>] [--open true|false]",
" node scripts/douyin.js prepare --video <path>",
" node scripts/douyin.js publish --video <path> --text <caption> [--private-status 0|1|2] [--auto-confirm true|false]",
" node scripts/douyin.js config list",
" node scripts/douyin.js config get <key>",
" node scripts/douyin.js config set <key> <value>",
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.
" node scripts/douyin.js doctor",
" node scripts/douyin.js auth [--code <code>] [--callback-url <url>] [--scope <scope>] [--open true|false]",
" node scripts/douyin.js prepare --video <path>",
" node scripts/douyin.js publish --video <path> --text <caption> [--private-status 0|1|2] [--auto-confirm true|false]",
" node scripts/douyin.js config list",
" node scripts/douyin.js config get <key>",
" node scripts/douyin.js config set <key> <value>",
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.
" node scripts/douyin.js doctor",
" node scripts/douyin.js auth [--code <code>] [--callback-url <url>] [--scope <scope>] [--open true|false]",
" node scripts/douyin.js prepare --video <path>",
" node scripts/douyin.js publish --video <path> --text <caption> [--private-status 0|1|2] [--auto-confirm true|false]",
" node scripts/douyin.js config list",
" node scripts/douyin.js config get <key>",
" node scripts/douyin.js config set <key> <value>",
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.
" node scripts/douyin.js doctor",
" node scripts/douyin.js auth [--code <code>] [--callback-url <url>] [--scope <scope>] [--open true|false]",
" node scripts/douyin.js prepare --video <path>",
" node scripts/douyin.js publish --video <path> --text <caption> [--private-status 0|1|2] [--auto-confirm true|false]",
" node scripts/douyin.js config list",
" node scripts/douyin.js config get <key>",
" node scripts/douyin.js config set <key> <value>",
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.
" node scripts/douyin.js doctor",
" node scripts/douyin.js auth [--code <code>] [--callback-url <url>] [--scope <scope>] [--open true|false]",
" node scripts/douyin.js prepare --video <path>",
" node scripts/douyin.js publish --video <path> --text <caption> [--private-status 0|1|2] [--auto-confirm true|false]",
" node scripts/douyin.js config list",
" node scripts/douyin.js config get <key>",
" node scripts/douyin.js config set <key> <value>",
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.
" node scripts/douyin.js doctor",
" node scripts/douyin.js auth [--code <code>] [--callback-url <url>] [--scope <scope>] [--open true|false]",
" node scripts/douyin.js prepare --video <path>",
" node scripts/douyin.js publish --video <path> --text <caption> [--private-status 0|1|2] [--auto-confirm true|false]",
" node scripts/douyin.js config list",
" node scripts/douyin.js config get <key>",
" node scripts/douyin.js config set <key> <value>",
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
};
const installHints = [
"sudo apt-get update && sudo apt-get install -y ffmpeg cmake build-essential curl jq",
"git clone https://github.com/ggerganov/whisper.cpp.git && cd whisper.cpp && cmake -B build && cmake --build build -j",
"ln -sf $(pwd)/build/bin/whisper-cli ~/.local/bin/whisper-cli",
"mkdir -p ~/.cache/whisper.cpp && curl -L https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin -o ~/.cache/whisper.cpp/ggml-small.bin",
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
"sudo apt-get update && sudo apt-get install -y ffmpeg cmake build-essential curl jq",
"git clone https://github.com/ggerganov/whisper.cpp.git && cd whisper.cpp && cmake -B build && cmake --build build -j",
"ln -sf $(pwd)/build/bin/whisper-cli ~/.local/bin/whisper-cli",
"mkdir -p ~/.cache/whisper.cpp && curl -L https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin -o ~/.cache/whisper.cpp/ggml-small.bin",
"export DOUYIN_ASR_MODE=api && export DOUYIN_ASR_API_KEY=*** && export DOUYIN_ASR_API_URL=https://api.openai.com/v1/audio/transcriptions",
"or set local whisper mode: node scripts/douyin.js config set asrMode whisper-gpu",
];
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
"git clone https://github.com/ggerganov/whisper.cpp.git && cd whisper.cpp && cmake -B build && cmake --build build -j",
"ln -sf $(pwd)/build/bin/whisper-cli ~/.local/bin/whisper-cli",
"mkdir -p ~/.cache/whisper.cpp && curl -L https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin -o ~/.cache/whisper.cpp/ggml-small.bin",
"export DOUYIN_ASR_MODE=api && export DOUYIN_ASR_API_KEY=*** && export DOUYIN_ASR_API_URL=https://api.openai.com/v1/audio/transcriptions",
"or set local whisper mode: node scripts/douyin.js config set asrMode whisper-gpu",
];
This is the same functional issue as the duplicate finding at the same line: autoConfirm can suppress human review before a publish action. In a social-media publishing skill, that context makes the risk more relevant because the action has external side effects on a real account.
}
const caption = normalizeCaption(captionInput);
const autoConfirm = parseBoolean(options["auto-confirm"], config.autoConfirm);
if (!autoConfirm) {
if (!process.stdin.isTTY) {
This is the same functional issue as the duplicate finding at the same line: autoConfirm can suppress human review before a publish action. In a social-media publishing skill, that context makes the risk more relevant because the action has external side effects on a real account.
}
const caption = normalizeCaption(captionInput);
const autoConfirm = parseBoolean(options["auto-confirm"], config.autoConfirm);
if (!autoConfirm) {
if (!process.stdin.isTTY) {
This file accepts and persists a whisperLang setting from the DOUYIN_WHISPER_LANG environment variable, indicating the skill can enforce a fixed transcription language. There is no natural-language indication here that users can choose or opt in to that locale behavior, which matches the policy concern for language/locale constraints.
Detected: suspicious.dangerous_exec, suspicious.env_credential_access, suspicious.exposed_secret_literal