T09 · Insecure Skill Coding Practices
- Location
src/voice-connection.ts:886- Finding
Speaker Authorization Fails Open When the Primary User Cannot Be Resolved
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This Discord voice skill largely does what it claims, but it needs review because unintended speakers may be captured, transcripts are logged, and voice prompts can reach the agent's full toolset.
Review this before installing on any server with sensitive conversations or powerful agent tools. Prefer Discord user IDs over names, set an explicit allowlist, disable auto-join and voice speaker switching unless needed, isolate the voice agent from high-impact tools, and remove or tightly restrict transcript logging.
src/voice-connection.ts:886Speaker Authorization Fails Open When the Primary User Cannot Be Resolved
src/voice-connection.ts:541Discord Voice Transcripts and User Identifiers Are Written to Application Logs
The finding indicates the skill may dynamically load core agent modules, manage sessions/workspaces, access persistent session stores, and support embedded agent execution—capabilities far beyond a voice plugin's declared role. Such hidden breadth materially increases risk because a plugin installed for speech features may also gain privileged access to agent state, persistence, and execution pathways that could be abused for lateral movement or data exposure.
The finding indicates the skill may dynamically load core agent modules, manage sessions/workspaces, access persistent session stores, and support embedded agent execution—capabilities far beyond a voice plugin's declared role. Such hidden breadth materially increases risk because a plugin installed for speech features may also gain privileged access to agent state, persistence, and execution pathways that could be abused for lateral movement or data exposure.
The finding indicates the skill may dynamically load core agent modules, manage sessions/workspaces, access persistent session stores, and support embedded agent execution—capabilities far beyond a voice plugin's declared role. Such hidden breadth materially increases risk because a plugin installed for speech features may also gain privileged access to agent state, persistence, and execution pathways that could be abused for lateral movement or data exposure.
The skill declares required environment variables and clearly describes networked Discord/Deepgram behavior, but it does not declare any explicit tool scope such as permissions or allowed-tools. That mismatch weakens policy enforcement and review because the runtime may grant broader env and network access than is transparently documented, increasing the chance of unintended token exposure or outbound communication abuse.
The wake word and example commands indicate that speaker-switching can be controlled by spoken phrases, but the schema text does not define safeguards such as exact speaker verification, confirmation flows, or anti-spoofing constraints. In a Discord voice channel, this can let normal conversation, imitation, replayed audio, or ambiguous name matching trigger authorization changes, causing the bot to listen to or respond to unintended users.
The plugin logs raw transcribed speech content together with the user ID, which can capture sensitive conversations, credentials, or personal data spoken in the channel. Voice-chat context makes this especially risky because participants may not expect full-text retention in logs, and logs are often broadly accessible and long-lived.
The transcript handler explicitly tells the embedded agent it has access to all normal tools and skills, and the commented-out lane restriction suggests prior scoping was removed. In a voice-channel context, any user who can speak to the bot may be able to indirectly trigger unrelated powerful capabilities, creating privilege expansion far beyond speech transcription and TTS.
The inline comment indicates a deliberate decision to remove a lane restriction because it might limit tool access, which weakens an apparent security boundary. In combination with untrusted voice transcripts being sent to the agent, this increases the chance that spoken prompts can reach sensitive capabilities that should not be available from a Discord voice session.
The code sends audio buffers to an external Deepgram service via a WebSocket connection, which is a network operation that transmits user data. While the file has technical comments and some console logging, it does not include any user-facing warning, confirmation, or explicit disclosure that captured audio is being sent off-system.
The function sends the provided text to Deepgram over HTTPS, which is a network operation transmitting user-supplied content to a third party. In this file there is no confirmation prompt, user-facing log/print, or comment/docstring warning that input text will be sent to an external service.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
}
async transcribe(audioBuffer: Buffer, sampleRate: number): Promise<STTResult> {
const url = new URL("https://api.deepgram.com/v1/listen");
url.searchParams.set("model", this.model);
url.searchParams.set("encoding", "linear16");
url.searchParams.set("sample_rate", String(sampleRate));
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
}
async transcribe(audioBuffer: Buffer, sampleRate: number): Promise<STTResult> {
const url = new URL("https://api.deepgram.com/v1/listen");
url.searchParams.set("model", this.model);
url.searchParams.set("encoding", "linear16");
url.searchParams.set("sample_rate", String(sampleRate));
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
}
async transcribe(audioBuffer: Buffer, sampleRate: number): Promise<STTResult> {
const url = new URL("https://api.deepgram.com/v1/listen");
url.searchParams.set("model", this.model);
url.searchParams.set("encoding", "linear16");
url.searchParams.set("sample_rate", String(sampleRate));
The function sends the provided audio buffer to Deepgram over the network for transcription, which is a privacy-relevant operation involving user/system data. While the file has a brief class comment, it does not provide any user-facing disclosure, confirmation, logging, or warning about external transmission of audio content.
The synthesize method sends the provided text to Deepgram's remote API, which can expose user content to a third party. While the code is functionally clear, there is no confirmation prompt, user-facing log, or inline warning indicating that input text leaves the local system.
This component records Discord voice audio, transcribes it via external STT providers, and generates replies via external TTS providers, but this file contains no mechanism to notify or obtain consent from channel participants before their voice data is sent to third-party services. In a voice-chat context, that creates a real privacy and compliance risk because users may be unknowingly recorded and their speech shared outside Discord, potentially violating server expectations, platform policies, or legal consent requirements.
This manifest file describes the skill as a 'Discord voice channel plugin' with STT and TTS capabilities, but it does not indicate what specific commands, contexts, or conditions activate the skill. In a manifest file, the lack of explicit trigger scope can make invocation behavior ambiguous and increase the chance of unintended activation.
Using a caret range for a production dependency allows future installs to resolve to different package versions than were originally tested. That increases supply-chain risk because a compromised or breaking upstream minor/patch release could be pulled into deployments without explicit review.
"deepgram"
],
"dependencies": {
"@discordjs/voice": "^0.18.0",
"discord.js": "^14.16.3",
"libsodium-wrappers": "^0.7.15",
"opusscript": "^0.0.8",
Using a caret range for a production dependency allows future installs to resolve to different package versions than were originally tested. That increases supply-chain risk because a compromised or breaking upstream minor/patch release could be pulled into deployments without explicit review.
],
"dependencies": {
"@discordjs/voice": "^0.18.0",
"discord.js": "^14.16.3",
"libsodium-wrappers": "^0.7.15",
"opusscript": "^0.0.8",
"prism-media": "^1.3.5",
Using a caret range for a production dependency allows future installs to resolve to different package versions than were originally tested. That increases supply-chain risk because a compromised or breaking upstream minor/patch release could be pulled into deployments without explicit review.
"dependencies": {
"@discordjs/voice": "^0.18.0",
"discord.js": "^14.16.3",
"libsodium-wrappers": "^0.7.15",
"opusscript": "^0.0.8",
"prism-media": "^1.3.5",
"ws": "^8.18.0"
Using a caret range for a production dependency allows future installs to resolve to different package versions than were originally tested. That increases supply-chain risk because a compromised or breaking upstream minor/patch release could be pulled into deployments without explicit review.
"@discordjs/voice": "^0.18.0",
"discord.js": "^14.16.3",
"libsodium-wrappers": "^0.7.15",
"opusscript": "^0.0.8",
"prism-media": "^1.3.5",
"ws": "^8.18.0"
},
Using a caret range for a production dependency allows future installs to resolve to different package versions than were originally tested. That increases supply-chain risk because a compromised or breaking upstream minor/patch release could be pulled into deployments without explicit review.
"discord.js": "^14.16.3",
"libsodium-wrappers": "^0.7.15",
"opusscript": "^0.0.8",
"prism-media": "^1.3.5",
"ws": "^8.18.0"
},
"clawdbot": {
Using an unpinned range for the production WebSocket library is more concerning because network-facing packages are common attack surfaces and may receive security-relevant updates or, conversely, introduce vulnerable versions into fresh installs. In a Discord voice plugin that relies on real-time network communication, dependency drift in this component can directly affect exposed runtime behavior.
"libsodium-wrappers": "^0.7.15",
"opusscript": "^0.0.8",
"prism-media": "^1.3.5",
"ws": "^8.18.0"
},
"clawdbot": {
"extensions": [
The manifest references ws without pinning an exact version, and the package family has multiple published advisories, so there is no reliable way to determine from this file whether installs will avoid affected releases. In a network-facing Discord voice plugin, an unresolved advisory state on a WebSocket stack can expose the service to denial of service or information disclosure depending on the resolved version.
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.
]
},
"devDependencies": {
"@sinclair/typebox": "^0.34.48",
"@types/node": "^25.0.10",
"@types/ws": "^8.18.0",
"typescript": "^5.9.3"