Back to skill

Security audit

Feishu Voice Assistant

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it claims, but its file handling could let a manipulated TTS component cause local files to be uploaded to Feishu.

Install only if you trust the sibling Duby and Feishu helper modules and the runtime environment. Avoid sending secrets or regulated content, because the input text is logged and sent to Duby, and the resulting audio is posted to the chosen Feishu target. The publisher should constrain TTS output to a dedicated directory, validate generated audio files before upload, stop logging full message text, document required permissions, and update dependency hygiene before broad use.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • 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 (2)

T09 · Insecure Skill Coding Practices

Error
Location
index.js:95
Finding

Unrestricted Local File Upload Through Untrusted TTS Output Path

Content
View full analysis

Vulnerability Details

File Location: index.js, lines 95-111
Vulnerability Type: Arbitrary local file disclosure caused by insufficient path validation
Risk Level: High

Vulnerable Code

js
const mediaPath = await duby.duby_tts({ 
    text: options.text, 
    voice_id: options.voice 
});

if (!mediaPath || !mediaPath.startsWith('MEDIA:')) {
    throw new Error(`Invalid TTS response: ${mediaPath}`);
}

const filePath = mediaPath.replace('MEDIA:', '').trim();
const absolutePath = path.resolve(process.cwd(), filePath);

if (!fs.existsSync(absolutePath)) {
    throw new Error(`Generated audio file not found at: ${absolutePath}`);
}

console.log(`📤 Uploading audio: ${filePath}...`);

// Step 2: Upload to Feishu
const token = await getToken();
const fileKey = await uploadAudio(token, absolutePath);

The selected file is subsequently read and transmitted by uploadAudio():

js
const fileBuffer = fs.readFileSync(filePath);
const blob = new Blob([fileBuffer]);

const formData = new FormData();
formData.append('file_type', 'stream');
formData.append('file_name', fileName);
formData.append('file', blob, fileName);
formData.append('duration', '10000');

const response = await fetchWithRetry('https://open.feishu.cn/open-apis/im/v1/files', {
    method: 'POST',
    headers: {
        'Authorization': `Bearer ${token}`
    },
    body: formData
});

Technical Analysis

The Skill trusts the MEDIA: path returned by the external sibling module ../duby/index.js. It only verifies that the returned string begins with MEDIA: and that the resolved path exists. It does not ensure that the file:

  • Is inside a dedicated TTS output directory.
  • Was created by the current TTS operation.
  • Is a regular, non-symbolic-link audio file.
  • Has an approved extension or media type.
  • Is within an acceptable size limit.

path.resolve() accepts absolute paths and traversal sequences. Consequently, a compromised, replaced, or otherwise manipulated Duby ...[truncated 1820 chars]

Remediation
View remediation

Remediation Suggestions

  1. Create a dedicated private directory for generated TTS files and accept output only from that directory.
  2. Canonicalize both the trusted directory and returned file with fs.realpathSync() before comparing them.
  3. Verify that the canonical file path remains beneath the trusted directory using a path-separator-aware containment check.
  4. Reject symbolic links and require the result to be a regular file through fs.lstatSync() and fs.statSync().
  5. Validate the file extension, detected media type, and audio format rather than trusting the filename.
  6. Enforce a conservative maximum file size before reading or uploading the file.
  7. Prefer an interface in which the TTS component returns audio bytes or a controlled file descriptor instead of an arbitrary filesystem path.
  8. Pin and verify the integrity of sibling components and ensure that untrusted users cannot modify them.
  9. Remove the generated file securely after transmission unless retention is explicitly required.

T09 · Insecure Skill Coding Practices

Note
Location
index.js:91
Finding

Caller-Supplied Message Text Is Written Verbatim to Process Logs

Content
View full analysis

Vulnerability Details

File Location: index.js, line 91
Vulnerability Type: Sensitive information exposure through logging
Risk Level: Low

Vulnerable Code

js
console.log(`🎤 Generating audio for: "${options.text}"...`);

Technical Analysis

The complete value supplied through --text is written to standard output. Voice-message content may contain confidential business information, personal data, credentials, or other sensitive material.

Standard output is frequently captured by shell history tooling, CI/CD systems, process supervisors, containers, orchestration platforms, and centralized logging services. These systems may retain logs longer than the Feishu message itself and expose them to operators or service accounts that are not authorized to read the message content.

Logging the full text is not necessary to perform TTS generation or deliver the resulting audio and therefore exceeds the minimum data exposure required by the declared functionality.

Attack Path

  1. A user invokes the Skill with confidential information in the --text argument.
  2. The Skill interpolates the complete message into a console.log() call.
  3. The execution environment captures standard output.
  4. The message remains stored in terminal recordings, job output, container logs, or centralized logging infrastructure.
  5. A user or service with access to those logs reads the confidential message content.

Impact Assessment

The issue can disclose the complete voice-message text to parties with access to process logs. It does not grant additional system privileges, but it broadens access to potentially sensitive content beyond the intended Feishu recipient. The practical scope depends on the deployment environment's logging, retention, and access-control policies.

Remediation
View remediation

Remediation Suggestions

  1. Do not log the caller-provided text by default.
  2. Replace the message with non-sensitive operational metadata, such as a request identifier or character count.
  3. If diagnostic logging is required, make it explicitly opt-in and clearly warn users that message content may be recorded.
  4. Apply structured redaction before logging and prohibit logging credentials, tokens, personal data, or message bodies.
  5. Configure production logging with appropriate access controls, retention limits, and encryption.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (9)

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 · index.js (reported line 9)May include surrounding context.

js
const duby = require('../duby/index.js');
const { Blob } = require('buffer'); // Use native Blob for FormData

// Ensure .env is loaded
require('dotenv').config({ path: path.resolve(__dirname, '../../.env') });

async function uploadAudio(token, filePath) {

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 · index.js (reported line 10)May include surrounding context.

js
const { Blob } = require('buffer'); // Use native Blob for FormData

// Ensure .env is loaded
require('dotenv').config({ path: path.resolve(__dirname, '../../.env') });

async function uploadAudio(token, filePath) {
    const fileName = path.basename(filePath);

Known Vulnerable Dependency: form-data==4.0.5 — 1 advisory(ies): CVE-2026-12143 (form-data: CRLF injection in form-data via unescaped multipart field names and f)

High
Category
Supply Chain
Confidence
95% confidence
Finding

The lockfile pins form-data to version 4.0.5, which the provided advisory identifies as vulnerable to CRLF injection via unescaped multipart field names/files. In a skill that sends audio and is likely to construct multipart HTTP requests to external APIs, this dependency is security-relevant because attacker-controlled field metadata could potentially corrupt request boundaries or inject unintended headers/content.

Content

No source excerpt is available for this finding.

Known Vulnerable Dependency: form-data==4.0.5 — 1 advisory(ies): CVE-2026-12143 (form-data: CRLF injection in form-data via unescaped multipart field names and f)

High
Category
Supply Chain
Confidence
96% confidence
Finding

The package can resolve to form-data 4.0.5, which is flagged with a high-severity CRLF injection advisory. In a skill that sends audio/messages and likely constructs multipart HTTP requests to external APIs, unsafe multipart field-name handling can enable request/header manipulation if attacker-controlled input reaches form construction, making the context more dangerous than a purely local utility.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill declares no explicit tool scope or permissions despite requiring access to environment variables for API keys and credentials. This weakens reviewability and containment, because users and policy engines cannot easily tell that the skill depends on sensitive secrets and external service access.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill description and usage omit a clear warning that user-provided text is transmitted to an external TTS provider and then sent onward to Feishu recipients. This can lead users to unknowingly disclose sensitive or regulated information to third parties and unintended audiences.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Content

Scanner excerpt · package.json (reported line 7)May include surrounding context.

json
"description": "Sends voice messages (audio) to Feishu using Duby TTS",
  "main": "index.js",
  "dependencies": {
    "commander": "^9.0.0",
    "form-data": "^4.0.0",
    "dotenv": "^16.0.0"
  },

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Content

Scanner excerpt · package.json (reported line 8)May include surrounding context.

json
"main": "index.js",
  "dependencies": {
    "commander": "^9.0.0",
    "form-data": "^4.0.0",
    "dotenv": "^16.0.0"
  },
  "scripts": {

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
40% confidence
Finding

Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Content

Scanner excerpt · package.json (reported line 9)May include surrounding context.

json
"dependencies": {
    "commander": "^9.0.0",
    "form-data": "^4.0.0",
    "dotenv": "^16.0.0"
  },
  "scripts": {
    "test": "node index.js --help"