T09 · Insecure Skill Coding Practices
- Location
scripts/news-digest.mjs:75- Finding
Shell Command Injection in News Orchestration
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill does what it claims, but its implementation exposes users to command-injection and credential-handling risks when generating and sending news briefings.
Review before installing. Use this only with trusted operators and trusted input values, avoid untrusted topics/titles/target-user values, and do not provide production Feishu credentials until the scripts are changed to use fetch or execFile/spawn with argument arrays, validate inputs and links, keep secrets out of command strings, and require confirmation before sending real Feishu messages.
scripts/news-digest.mjs:75Shell Command Injection in News Orchestration
scripts/fetch-news.mjs:41Shell Command Injection in External API Requests
scripts/news-digest.mjs:112Feishu Application Secret Exposed in Child Process Command Line
The skill advertises and operationalizes shell execution, network access, and use of environment-backed secrets, but it does not declare any tool scope such as permissions or allowed-tools. That creates an authorization gap: a broad-trigger skill can cause web requests, CLI execution, and secret-backed actions without explicit least-privilege boundaries or reviewer-visible constraints.
The file states that the agent summarizes top stories "in Chinese," and later emphasizes "全程中文输出" as a fixed behavior. This appears to force a specific language/locale without documenting user choice, opt-in, or a region-specific justification.
The trigger phrases are very broad and map to common conversational requests like asking for news or updates, making accidental invocation likely. In this skill's context, accidental invocation is more concerning because activation can lead to live web access and outbound Feishu delivery to a target user, potentially causing unintended data retrieval or message sending.
The code builds a shell command string and passes it to execSync, incorporating dynamic values such as headers, URL, body, and proxy. Because proxy comes from environment variables and header values include secrets, this creates command-injection risk and expands the skill's capability from simple HTTP requests to arbitrary shell execution if any interpolated value is attacker-controlled.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
const headerArgs = Object.entries(headers).map(([k, v]) => `-H "${k}: ${v}"`).join(' ');
const proxyArg = proxy ? `--proxy ${proxy}` : '';
const escaped = JSON.stringify(body).replace(/'/g, "'\\''");
const cmd = `curl -s --max-time ${Math.floor(timeoutMs / 1000)} ${proxyArg} -X POST "${url}" ${headerArgs} -d '${escaped}'`;
return JSON.parse(execSync(cmd, { timeout: timeoutMs + 2000 }).toString());
}
The prompt explicitly requires that titles and summaries 'must be in Chinese' and also says non-Chinese sources must be translated into Chinese. This imposes a fixed language policy in the skill's natural-language behavior without offering the user a language choice or documenting an opt-in.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
for (let attempt = 1; attempt <= 2; attempt++) {
try {
const data = curlPost(
'https://api.perplexity.ai/chat/completions',
{ 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
{ model: 'sonar', messages: [{ role: 'user', content: prompt }], max_tokens: 1500 },
proxy,
The insight-generation path repeats the same unsafe pattern by constructing a curl command with interpolated proxy and API key data and executing it via execSync. This unnecessarily grants shell execution in a feature that only needs outbound HTTPS, so compromise of environment-controlled inputs could lead to arbitrary command execution and secret exposure.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
max_tokens: 400,
}).replace(/'/g, "'\\''");
const proxyArg = proxy ? `--proxy ${proxy}` : '';
const cmd = `curl -s --max-time 20 ${proxyArg} -X POST "https://api.ppinfra.com/v3/openai/chat/completions" -H "Content-Type: application/json" -H "Authorization: Bearer ${ppioKey}" -d '${escaped}'`;
const data = JSON.parse(execSync(cmd, { timeout: 22000 }).toString());
return data.choices?.[0]?.message?.content?.trim() || '';
} catch (e) {
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
max_tokens: 400,
}).replace(/'/g, "'\\''");
const proxyArg = proxy ? `--proxy ${proxy}` : '';
const cmd = `curl -s --max-time 20 ${proxyArg} -X POST "https://api.ppinfra.com/v3/openai/chat/completions" -H "Content-Type: application/json" -H "Authorization: Bearer ${ppioKey}" -d '${escaped}'`;
const data = JSON.parse(execSync(cmd, { timeout: 22000 }).toString());
return data.choices?.[0]?.message?.content?.trim() || '';
} catch (e) {
The embedded text and comments describe the skill as producing Chinese content, including 50-60字,中文 and Chinese cover copy, which suggests a language-specific behavior. Under the policy rule, forcing a specific language without opt-in can be a locale-policy violation unless the regional constraint is clearly justified.
The manifest describes a news briefing workflow, where network retrieval and message delivery are expected. However, this file implements that workflow by spawning subprocesses via execSync and interpolating user-controlled values such as topics, title, and target user into shell command strings, which is a broader and more dangerous capability than the stated purpose requires.
The script builds a shell command string with user-controlled values such as topic, date, count/category-derived fields, and optional flags, then executes it with execSync. Quoting is incomplete and brittle for shell metacharacters like embedded double quotes or command substitutions, so an attacker can potentially achieve command injection and run arbitrary commands in the agent environment, with access to environment secrets included in the same execution context.
This command passes FEISHU_APP_ID and FEISHU_APP_SECRET into a child process and sends the assembled news digest to a target user, which is a network/data-transmission action. The code logs that it is building a card, but it does not explicitly warn that content will be delivered externally via Feishu using configured credentials.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
}
async function fetchFeishuToken(appId, appSecret) {
const res = await fetch('https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ app_id: appId, app_secret: appSecret }),
This code transmits fully attacker-influenced content (title, summary, insight, and url from the input JSON) to an external messaging platform and ultimately to an end user without validation or sanitization. In this skill context, that can enable phishing or social-engineering delivery through trusted Feishu cards, especially because arbitrary URLs are embedded as '查看原文' buttons and rich markdown content is forwarded as-is.
}
async function sendCard(token, userId, card) {
const res = await fetch('https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=open_id', {
method: 'POST',
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
body: JSON.stringify({
This code writes a temporary JavaScript file and then invokes node -e through execSync, which are safety-relevant operations for a code file. Although the header comment states the output path, there is no confirmation prompt and no user-facing warning around the temporary file creation or subprocess execution itself.
The date formatting is hard-coded to zh-CN and Asia/Shanghai, and the surrounding usage/comments are also Chinese-only, with no option for users to choose another locale. This can violate language/locale policy when a skill imposes a specific locale without explicit opt-in or documented regional justification.