Back to skill

Security audit

Discord Purge Bot

Security checks for vulnerabilities and agentic risk

Overview

This skill is openly designed for Discord cleanup, but its destructive Discord deletion features have under-scoped confirmation checks that could allow broader deletion than the preview implied.

Review before installing. Use a least-privileged Discord bot token, prefer DISCORD_BOT_TOKEN over --token, test with dry runs, and do not rely on the provided confirmation codes as strong proof that the exact previewed deletion scope is what will run. Avoid nuke mode with --delete-old unless you are prepared for irreversible loss of the original channel.

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/job-code.mjs:5
Finding
Purge confirmation code does not bind the complete deletion scope<![CDATA[ ## Vulnerability Details **File Location**: `scripts/job-code.mjs:5-25`, used by `scripts/purge-preview.mjs:49-58` and `scripts/purge-runner.mjs:137-153` **Vulnerability Type**: Incomplete authorization-scope binding **Risk Level**: High ### Vulnerable Code ```javascript export function buildConfirmCode({ channelId, authorId, contains, regex, after, before, includePinned, }) { const raw = [ normalizeValue(channelId), normalizeValue(authorId), normalizeValue(contains), normalizeValue(regex), normalizeValue(after), normalizeValue(before), normalizeValue(includePinned), ].join('|'); const hash = crypto.createHash('sha1').update(raw).digest('hex').slice(0, 8).toUpperCase(); return `PURGE-${hash}`; } ``` The runner accepts additional parameters that affect the deletion scope, but they are not included in the confirmation code: ```javascript const filters = normalizeFilters(args); const expectedCode = buildConfirmCode({ channelId, authorId: args['author-id'], contains: args.contains, regex: args.regex, after: args.after, before: args.before, includePinned: filters.includePinned, }); const providedCode = String(args.confirm ?? ''); if (!providedCode) throw new Error('Missing required argument --confirm'); if (providedCode !== expectedCode) { throw new Error(`Confirmation mismatch. Expected ${expectedCode}`); } ``` ### Technical Analysis The confirmation code binds the channel and several content filters, but it omits deletion-relevant parameters including: - `maxScan` - `maxMatches` - `regexFlags` Consequently, a code generated from a limited preview remains valid when the runner is invoked with a much larger scan or match limit. For example, a preview limited to 10 messages can produce the same code as a destructive run scanning 5,000 messages, provided the fields included in `buildConfirmCode` are unchanged. Changing `regexFlags` can also change regular-expression matching behavio ...[truncated 1534 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Bind every behavior- or scope-affecting field into a canonical confirmation manifest, including: - Channel ID - Author ID - Content and regular-expression filters - Regular-expression flags - Time boundaries - Pinned-message handling - Maximum scan count - Maximum match count - Deletion mode and other destructive options - Generate and persist a preview manifest containing the exact matched message IDs or a cryptographic digest of the ordered ID set. - Require the runner to load that manifest and delete only the reviewed message IDs. - Use a cryptographically random, single-use confirmation nonce rather than a deterministic truncated SHA-1 value. - Store the nonce with an expiration time, consumption status, and digest of the complete preview manifest. - Do not reveal a valid expected confirmation code in mismatch errors. - Reject execution if any command-line filter differs from the stored preview manifest. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/purge-nuke.mjs:57
Finding
Nuke confirmation does not distinguish cloning from deletion of the original channel<![CDATA[ ## Vulnerability Details **File Location**: `scripts/purge-nuke.mjs:57-80`; confirmation generation in `scripts/job-code.mjs:27-30` **Vulnerability Type**: Destructive option omitted from confirmation scope **Risk Level**: High ### Vulnerable Code ```javascript export function buildNukeCode({ channelId }) { const hash = crypto.createHash('sha1').update(normalizeValue(channelId)).digest('hex').slice(0, 8).toUpperCase(); return `NUKE-${hash}`; } ``` ```javascript const token = resolveToken(args); const expectedCode = buildNukeCode({ channelId }); const providedCode = String(args.confirm ?? ''); if (!providedCode) throw new Error('Missing required argument --confirm'); if (providedCode !== expectedCode) { throw new Error(`Confirmation mismatch. Expected ${expectedCode}`); } const channel = await getChannel({ token, channelId }); if (!channel.guild_id) { throw new Error('Target channel is not in a guild. Nuke flow supports guild channels only.'); } const clonePayload = buildClonePayload(channel); const reason = args.reason ? String(args.reason) : undefined; const newChannel = await createGuildChannel({ token, guildId: String(channel.guild_id), body: clonePayload, reason, }); let deletedOriginal = false; if (args['delete-old']) { await deleteChannel({ token, channelId, reason }); deletedOriginal = true; } ``` ### Technical Analysis The same confirmation code is accepted for two materially different operations: 1. Clone the channel while preserving the original. 2. Clone the channel and permanently delete the original with `--delete-old`. `buildNukeCode` only includes `channelId`; it does not include `delete-old`. A confirmation obtained for the non-destructive clone operation can therefore be reused for deletion of the original channel. The code is deterministic, unkeyed, reusable, and disclosed by the mismatch error. It is not evidence that an operator explicitly approved original-channel deletion. ### Attack Path 1. An op ...[truncated 1036 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Include `deleteOld` in the canonical nuke-operation manifest and confirmation validation. - Use separate operation names and confirmation prompts, such as `CLONE` and `DELETE-ORIGINAL`. - Require a second, explicit confirmation immediately before deleting the original channel. - Use a random, single-use, expiring nonce associated with the channel ID, guild ID, operation type, and `deleteOld` value. - Do not return the valid expected code in confirmation-mismatch errors. - Consider requiring an independently generated preview artifact that records the original channel and proposed replacement before allowing deletion. - Preserve and report a rollback plan where Discord capabilities permit it; otherwise, clearly state that original-channel deletion is irreversible. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/purge-runner.mjs:123
Finding
Message purge runner does not enforce the documented guild-channel restriction<![CDATA[ ## Vulnerability Details **File Location**: `scripts/purge-runner.mjs:123-174` **Vulnerability Type**: Missing target-type validation **Risk Level**: Medium ### Vulnerable Code ```javascript const channelId = String(args['channel-id'] ?? ''); if (!channelId) throw new Error('Missing required argument --channel-id'); const token = resolveToken(args); const filters = normalizeFilters(args); const dryRun = asBoolean(args['dry-run'], false); const singleOnly = asBoolean(args['single-only'], false); const deleteDelayMs = Number.parseInt(String(args['delete-delay-ms'] ?? '0'), 10) || 0; const expectedCode = buildConfirmCode({ channelId, authorId: args['author-id'], contains: args.contains, regex: args.regex, after: args.after, before: args.before, includePinned: filters.includePinned, }); const providedCode = String(args.confirm ?? ''); if (!providedCode) throw new Error('Missing required argument --confirm'); if (providedCode !== expectedCode) { throw new Error(`Confirmation mismatch. Expected ${expectedCode}`); } const stateFile = args['state-file'] ? String(args['state-file']) : undefined; const result = { jobId: buildJobId('run'), startedAt: new Date().toISOString(), phase: 'scanning', channelId, filters: summarizeFilters(args, filters), dryRun, singleOnly, scannedCount: 0, matchedCount: 0, recentCount: 0, oldCount: 0, deletedCount: 0, failedCount: 0, expectedConfirmCode: expectedCode, }; await writeState(stateFile, result); const scanResult = await scanMessages({ token, channelId, filters, onPage: async (progress) => { result.scannedCount = progress.scannedCount; result.matchedCount = progress.matchedCount; await writeState(stateFile, result); }, }); ``` ### Technical Analysis The skill's safety contract states that destructive operations must abort if the target is not a guild channel. The nuke flow calls `getChannel` and verifies `channel.guild_id`, but the ordinary purge runn ...[truncated 1471 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Call `getChannel({ token, channelId })` before scanning. - Require a valid `guild_id` and reject DM, group-DM, and unsupported channel types. - Maintain an explicit allowlist of channel types supported by message purge operations. - Perform target validation before creating state files or retrieving message history. - Record the validated guild ID and channel type in preview and execution manifests. - Bind the validated guild ID and channel type into the confirmation artifact. - Apply the same validation logic in both preview and runner scripts to prevent differences between reviewed and executed targets. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/common.mjs:68
Finding
Bot token command-line option can expose credentials to local process inspection and shell history<![CDATA[ ## Vulnerability Details **File Location**: `scripts/common.mjs:68-73`; option advertised by executable help text, including `scripts/purge-preview.mjs:10`, `scripts/purge-runner.mjs:19`, and `scripts/purge-nuke.mjs:10` **Vulnerability Type**: Sensitive credential exposure through command-line arguments **Risk Level**: Medium ### Vulnerable Code ```javascript export function resolveToken(args) { const value = args.token || process.env.DISCORD_BOT_TOKEN; if (!value) { throw new Error('Missing Discord token. Provide --token or DISCORD_BOT_TOKEN.'); } return String(value).trim(); } ``` The scripts explicitly advertise the command-line form: ```text --token <token> Discord bot token (or use DISCORD_BOT_TOKEN) ``` ### Technical Analysis Passing a secret using `--token <token>` places the Discord bot token in the process argument vector. Depending on the operating system and process isolation settings, command-line arguments may be visible through process-monitoring tools, `/proc`, job supervisors, telemetry, crash reports, or audit systems. Interactive shell history may also persist the complete command. The token is subsequently sent only to the fixed Discord API endpoint as an `Authorization` header, which is necessary for the declared functionality. The identified problem is the optional command-line secret ingestion path, not the Discord API authentication itself. ### Attack Path 1. An operator follows the documented `--token` option and launches a preview, purge, or nuke command. 2. The plaintext token is stored in the process command line and potentially in shell history. 3. Another local user, monitoring agent, process supervisor, or log collector reads the command arguments or history. 4. The observer extracts the bot token. 5. The token is used directly against Discord's API until revoked. 6. The attacker obtains all capabilities granted to that bot role, potentially including message or channel management. ### Impa ...[truncated 505 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove or strongly deprecate the `--token` command-line option. - Prefer `DISCORD_BOT_TOKEN` supplied by a protected process environment or secrets manager. - Support reading the token from a file descriptor, standard input, or a permission-restricted secret file. - If a token file is supported, verify restrictive ownership and permissions before reading it. - Update help text and examples to warn against placing tokens in shell commands. - Ensure logs, state files, errors, and telemetry never include the token or full request headers. - Run the bot with the minimum Discord permissions needed for the selected operation. - Rotate the token immediately if it has previously appeared in shell history, process logs, or monitoring records. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (18)

Ae1

High
Category
analysis-evasion
Content
- Run `purge-preview.mjs` first for every destructive request.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- Run `purge-preview.mjs` first for every destructive request.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- Run `purge-preview.mjs` first for every destructive request.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- Run `purge-preview.mjs` first for every destructive request.
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

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

Ae1

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

Ae1

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

Ae1

High
Category
analysis-evasion
Content
node scripts/purge-nuke.mjs --channel-id 123456789012345678 --confirm "NUKE-XXXXXXXX" --out ./tmp/nuke.json
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/purge-nuke.mjs --channel-id 123456789012345678 --confirm "NUKE-XXXXXXXX" --out ./tmp/nuke.json
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
node scripts/purge-nuke.mjs --channel-id 123456789012345678 --confirm "NUKE-XXXXXXXX" --out ./tmp/nuke.json
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Tool Parameter Abuse

High
Category
Tool Misuse
Content
## Core Endpoints

- List messages: `GET /channels/{channel.id}/messages`
- Delete one message: `DELETE /channels/{channel.id}/messages/{message.id}`
- Bulk delete: `POST /channels/{channel.id}/messages/bulk-delete`
- Get channel: `GET /channels/{channel.id}`
- Create guild channel: `POST /guilds/{guild.id}/channels`
Confidence
80% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
- Bulk delete: `POST /channels/{channel.id}/messages/bulk-delete`
- Get channel: `GET /channels/{channel.id}`
- Create guild channel: `POST /guilds/{guild.id}/channels`
- Delete channel: `DELETE /channels/{channel.id}`

## Bulk Delete Rules
Confidence
80% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Missing User Warnings

High
Confidence
93% confidence
Finding
The bulkDeleteMessages and deleteMessage helpers perform irreversible deletion of Discord messages, yet there is no confirmation prompt, user-facing notice, or inline documentation warning about the destructive effect. Code-file guidance calls for disclosure around destructive or irreversible operations when no other warning is present.

Missing User Warnings

High
Confidence
94% confidence
Finding
The deleteChannel function issues a DELETE request that can permanently remove a Discord channel, but the file contains no confirmation prompt, user-facing log, or explanatory comment/docstring disclosing that destructive action. This matches the missing-warning criterion for destructive or irreversible operations in code files.

Missing User Warnings

Medium
Confidence
85% confidence
Finding
This code performs authenticated HTTP requests to the Discord API using a token in the Authorization header and may send request bodies, but the file provides no confirmation prompt, user-facing log, or explanatory comment/docstring warning that external network transmission occurs. For a code file, network calls that transmit user or system data should have some visible disclosure unless clearly documented elsewhere.

Missing User Warnings

Low
Confidence
85% confidence
Finding
resolveToken reads DISCORD_BOT_TOKEN from the environment, which is access to sensitive credential material. This file does not include any user-facing notice, comment, or docstring explaining that the skill may obtain authentication data from environment variables.

Missing User Warnings

Low
Confidence
82% confidence
Finding
The writeJson helper creates directories and writes JSON to an arbitrary resolved path, which is a safety-relevant file modification operation. In this file there is no confirmation prompt, user-facing log/print, or explanatory comment/docstring disclosing that behavior.

Missing User Warnings

Low
Confidence
78% confidence
Finding
This function calls `fetchMessagesPage` with a token and channel ID to retrieve message data from an external service, which is a network operation involving potentially sensitive user or system data. In this file there is no confirmation prompt, logging, comment, or docstring disclosing that message content will be fetched and scanned.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/discord-api.mjs:15