Back to skill

Security audit

Discord Connect Wizard

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent Discord setup helper, but it handles bot credentials and local OpenClaw state with several real safety gaps that merit Review before installation.

Install only if you are comfortable with a local wizard that will handle a Discord bot token, change OpenClaw Discord configuration, restart the gateway, and approve pairing. Avoid pasting bot tokens into chat; prefer a secure local input path and rotate the token if it was exposed. Review existing OpenClaw Discord accounts first because this version may overwrite a colliding account ID.

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 (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/wizard.mjs:286
Finding
Unauthenticated Localhost API Permits Cross-Site State-Changing Requests<![CDATA[ ## Vulnerability Details **File Location**: `scripts/wizard.mjs:286-296, 373-400` **Vulnerability Type**: Missing authentication and CSRF protection on privileged localhost endpoints **Risk Level**: High ### Complete Code Snippet ```js const server = http.createServer(async (req, res) => { try { const u = new URL(req.url, `http://${req.headers.host}`); if (req.method === 'GET' && u.pathname === '/') { return text(res, 200, htmlPage(), 'text/html; charset=utf-8'); } if (u.pathname.startsWith('/api/')) { let body = ''; req.on('data', c => (body += c)); await new Promise(r => req.on('end', r)); const payload = body ? JSON.parse(body) : {}; ``` ```js if (req.method === 'POST' && u.pathname === '/api/finalize') { if (!state.accountId) return json(res, 400, { ok: false, error: 'set accountId first' }); if (!state.guildId) return json(res, 400, { ok: false, error: 'pick a guild first' }); if (!state.userId) return json(res, 400, { ok: false, error: 'pick your user first' }); await writeBaselineConfig(); await openclaw(['gateway', 'restart']); // Wait for pairing request for THIS account. const deadline = Date.now() + 5 * 60 * 1000; while (Date.now() < deadline) { const { stdout } = await openclaw(['pairing', 'list', 'discord', '--account', state.accountId, '--json']); let data; try { data = JSON.parse(stdout); } catch { data = null; } const reqs = data?.requests || data || []; if (Array.isArray(reqs) && reqs.length) { const code = reqs[0]?.code; if (code) { state.pairingCode = code; await openclaw(['pairing', 'approve', 'discord', code]); return json(res, 200, { ok: true, pairingApproved: true }); } } await wait(2000); } return json(res, 408, { ok: false, error: 'Timeout waiting for pairing. Ensure Discord allows DMs from server members, then DM the bot and retry.' }); } ``` ### Technical Analysis The service is ...[truncated 2130 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Generate a cryptographically random secret at startup using `crypto.randomBytes()`. - Require that secret on every API request, preferably through a SameSite-protected session cookie and an additional CSRF token for state-changing operations. - Validate `Origin` against the exact expected localhost origin. - Validate the `Host` header against an explicit allowlist such as `127.0.0.1:8787` and reject unexpected values. - Require `Content-Type: application/json` for JSON API requests. - Apply strict request-body size limits. - Separate read-only and state-changing routes and require explicit user confirmation immediately before configuration writes, restarts, and pairing approvals. - Consider shutting down the wizard automatically after successful completion or a short inactivity timeout. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/wizard.mjs:262
Finding
Deterministic Account Identifier Can Overwrite Existing OpenClaw Accounts<![CDATA[ ## Vulnerability Details **File Location**: `scripts/wizard.mjs:262-269, 310-317` **Vulnerability Type**: Unsafe identifier collision and missing existing-account check **Risk Level**: High ### Complete Code Snippet ```js const acct = `channels.discord.accounts.${state.accountId}`; await openclaw(['config', 'set', `${acct}.enabled`, 'true', '--json']); if (state.accountName) { await openclaw(['config', 'set', `${acct}.name`, JSON.stringify(state.accountName), '--json']); } await openclaw(['config', 'set', `${acct}.token`, JSON.stringify(state.token), '--json']); await openclaw(['config', 'set', `${acct}.groupPolicy`, JSON.stringify('allowlist'), '--json']); await openclaw(['config', 'set', `${acct}.guilds.${state.guildId}.requireMention`, 'false', '--json']); await openclaw(['config', 'set', `${acct}.guilds.${state.guildId}.users`, JSON.stringify([state.userId]), '--json']); ``` ```js const raw = appName || botUser.username || 'wizardbot'; const accountId = raw.toLowerCase().replace(/[^a-z0-9]+/g,'_').replace(/^_+|_+$/g,'').slice(0,32) || 'wizardbot'; state.accountId = accountId; state.accountName = raw; return json(res, 200, { ok: true, bot: { id: botUser.id, username: botUser.username }, accountId, appName: raw }); ``` ### Technical Analysis The account identifier is derived through lossy normalization of the Discord application name. Punctuation and whitespace collapse into underscores, unsupported characters are removed, and the result is truncated to 32 characters. Distinct application names can therefore produce the same identifier. No check is performed to determine whether `channels.discord.accounts.<accountId>` already exists before multiple `openclaw config set` commands modify it. This contradicts the project's documented guarantee that the wizard creates a new account and “never overwrites existing bots.” This is a destructive configuration-collision flaw rather than command injection: `execFile` avoids shell interpretation, but ...[truncated 1074 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Query the current OpenClaw configuration before writing any account settings. - If the derived account identifier already exists, abort by default and explain the collision. - Generate a unique identifier using a stable Discord application ID or a cryptographically random suffix rather than relying only on normalized display names. - Treat partial matches and case-normalized matches as collisions. - Require explicit user confirmation before modifying any existing account. - Perform the configuration update transactionally so that a failure partway through does not leave mixed old and new settings. - Add automated tests covering punctuation collisions, Unicode-only names, truncation collisions, and the `wizardbot` fallback. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/wizard.mjs:250
Finding
Discord Bot Token Is Exposed in Child-Process Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/wizard.mjs:250-252, 267` **Vulnerability Type**: Sensitive credential passed through process command-line arguments **Risk Level**: Medium ### Complete Code Snippet ```js async function openclaw(args) { const { stdout, stderr } = await execFileAsync('openclaw', args, { env: process.env }); return { stdout: stdout?.toString() ?? '', stderr: stderr?.toString() ?? '' }; } ``` ```js await openclaw(['config', 'set', `${acct}.token`, JSON.stringify(state.token), '--json']); ``` ### Technical Analysis The implementation correctly uses `execFile` instead of a shell, so the token is not subject to shell interpolation. However, the complete Discord bot token is still placed in the child process's argument vector. Command-line arguments may be visible to same-host process inspection, endpoint monitoring, diagnostic collection, process accounting, crash tooling, or audit systems. The exact visibility depends on the operating system and its process-isolation configuration, but the token's confidentiality should not rely on process-list restrictions. The source comment stating that the token is “never logged” does not address disclosure through process metadata. ### Attack Path 1. The user enters a valid Discord bot token into the wizard. 2. During finalization, the wizard invokes `openclaw config set` with the token as a command-line argument. 3. A local process observer, monitoring agent, diagnostic utility, or audit subsystem captures the child process argument vector while the command runs. 4. The observer extracts the token and uses it to authenticate to Discord as the bot. This path requires local process-observation capability or access to tooling that records process arguments. ### Impact Assessment Disclosure of the token permits impersonation of the Discord bot and use of every Discord API permission granted to that bot. Depending on the bot's guild role and configured intents, this can ...[truncated 231 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Use a supported stdin-based secret input so the token is not present in the argument vector. - Prefer an OpenClaw secret-store or credential API designed for sensitive values. - If OpenClaw lacks a safe secret-input mechanism, add one rather than passing secrets on the command line. - Ensure any stored token is protected by restrictive filesystem permissions and is excluded from logs, diagnostics, backups, and error responses where feasible. - Clear the in-memory token after configuration has completed and shut down the wizard promptly. - Document the local process-observation risk until a secure input channel is implemented. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/wizard.mjs:387
Finding
Pairing Approval Omits the Selected Account Scope<![CDATA[ ## Vulnerability Details **File Location**: `scripts/wizard.mjs:387-396` **Vulnerability Type**: Insufficient account binding during privileged pairing approval **Risk Level**: Medium ### Complete Code Snippet ```js const { stdout } = await openclaw(['pairing', 'list', 'discord', '--account', state.accountId, '--json']); let data; try { data = JSON.parse(stdout); } catch { data = null; } const reqs = data?.requests || data || []; if (Array.isArray(reqs) && reqs.length) { const code = reqs[0]?.code; if (code) { state.pairingCode = code; await openclaw(['pairing', 'approve', 'discord', code]); return json(res, 200, { ok: true, pairingApproved: true }); } } ``` ### Technical Analysis The wizard scopes the pairing-list query to `state.accountId`, but the subsequent approval command omits the account selector. The project documentation explicitly requires approval to be filtered to the selected account to avoid cross-bot mixing. Whether the wrong request can actually be approved depends on OpenClaw's pairing-code uniqueness and command-resolution behavior. Nevertheless, omitting the account context removes the defense-in-depth binding between the request that was inspected and the account for which approval is authorized. The code also approves the first returned request without confirming that its requesting Discord user matches `state.userId`. ### Attack Path 1. Multiple Discord accounts have pending pairing activity, or an unexpected request is pending for the selected account. 2. The wizard lists requests using `--account state.accountId`. 3. It chooses the first request without checking its requester against the selected user ID. 4. It calls the generic approval command without passing the selected account. 5. If OpenClaw resolves the code outside the intended account context, or if an unexpected request occupied the first position, an unintended pairing may be approved. Exploitability is dependent on OpenClaw's pairing-cod ...[truncated 531 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Pass the selected account identifier to the approval operation if supported: ```js await openclaw([ 'pairing', 'approve', 'discord', code, '--account', state.accountId, ]); ``` - Confirm the exact supported CLI syntax against the installed OpenClaw version and fail closed if account-scoped approval is unavailable. - Validate that the pairing request's Discord user ID matches `state.userId`. - Do not automatically approve the first request merely because it exists. - Re-query the pairing state after approval and verify both the account ID and requester identity. - If request identity cannot be verified programmatically, display non-sensitive request metadata and require explicit local confirmation before approval. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (13)

Ae1

High
Category
analysis-evasion
Content
node scripts/wizard.mjs
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/wizard.mjs
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ssd 3

High
Confidence
98% confidence
Finding
The skill explicitly allows a fallback where the user pastes a Discord bot token into chat, creating a direct natural-language exfiltration path for a high-value secret. Even if the text says to avoid logging the token, sending it through chat exposes it to the agent context, transcripts, integrations, retention systems, or accidental disclosure, and a bot token can grant full bot impersonation until rotated.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The README explicitly advertises automated config writes and gateway restarts but does not clearly warn the user that the skill will make local system and service changes. In a one-machine onboarding flow, this can cause unexpected modification of OpenClaw configuration or interruption of running services, especially if the operator assumes the skill is advisory rather than state-changing.

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill describes behavior requiring browser, network, and likely environment/config access, but it does not declare explicit tool scope or permissions boundaries. That mismatch can cause an agent runtime to execute sensitive actions with broader-than-expected capabilities, reducing transparency and informed consent for operations involving Discord setup, token handling, and local configuration changes.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
### Conversation mode (recommended)
Use browser automation + chat prompts. No localhost UI required.

Hard requirement: **agent opens and drives the Developer Portal via browser tool** (do not ask user to open pages or click around).
Resilience rule: if the browser tool times out / disconnects, the agent must **self-recover** (restart gateway/browser as needed) and retry. Only ask the user to click if recovery is impossible.
UX rule: whenever the user must act (login/CAPTCHA/MFA/OAuth authorize), send a screenshot + **ONE** instruction line.
If you need a deterministic step list, run:
Confidence
80% confidence
Finding
The skill directs the agent to autonomously drive the Developer Portal, self-recover, and minimize user involvement, which increases the chance of the agent taking sensitive external actions without granular confirmation. In context, this includes creating apps, configuring bot settings, generating invite flows, and potentially restarting local services, all of which are meaningful account and system changes.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill instructs the wizard to store a Discord bot token in OpenClaw configuration and restart the gateway, but the description does not prominently warn about these sensitive side effects. This is dangerous because bot tokens are secrets whose storage location and persistence matter, and automatic service restarts can disrupt running systems or apply unreviewed configuration immediately.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The instruction explicitly requires a specific Chinese phrasing and prohibits an alternative question form, which imposes a language choice on the interaction. This is a natural-language policy concern because the file does not indicate user opt-in or a justified locale-specific scope.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
## Flow

1) Agent opens https://discord.com/developers/applications (agent must do this; do NOT ask user to open it)
2) Agent creates new application (name auto-generated, must NOT contain "discord")
3) Agent navigates to Bot page, enables intents, saves
4) Agent triggers Reset Token (and clicks confirm)
Confidence
80% confidence
Finding
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.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
## Flow

1) Agent opens https://discord.com/developers/applications (agent must do this; do NOT ask user to open it)
2) Agent creates new application (name auto-generated, must NOT contain "discord")
3) Agent navigates to Bot page, enables intents, saves
4) Agent triggers Reset Token (and clicks confirm)
Confidence
80% confidence
Finding
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.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The checklist correctly instructs the agent not to log or echo the Discord bot token, but it also says that if the user pastes the token in chat the agent should proceed without first warning that chat may expose secrets to logs, transcripts, or other tooling. In a skill specifically handling bot credential setup, omission of an explicit 'do not paste secrets into chat' warning materially increases the chance of credential leakage through the agent interface.

Natural-Language Policy Violations

Medium
Confidence
94% confidence
Finding
This code serves a user-facing HTML wizard whose headings, instructions, and status text are written in Chinese, but there is no indication that the skill is China-specific or that users can opt into another language. The policy explicitly flags skills that force a specific language or locale without user choice or justified documentation.

Natural-Language Policy Violations

Low
Confidence
97% confidence
Finding
The UX rule gives a fixed example of wording to avoid using Chinese text, which implies a language-specific interaction policy embedded in the skill. Because the file does not offer a language choice or document a justified locale constraint, this can violate the language/locale policy requirement.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/wizard.mjs:18