Back to skill

Security audit

The Arena — AI Debate Moderator

Security checks for vulnerabilities and agentic risk

Overview

This skill is mostly a coherent Discord debate moderator, but it needs review because its default setup can expose an entire Discord guild and its scripts have unsafe input handling.

Install only after reviewing the generated OpenClaw and Discord permissions. Prefer explicit channel ID allowlists, requireMention=true by default, minimal bot permissions, a dedicated isolated agent, and operator-only scoreboard access. Patch the setup and scoreboard scripts to validate numeric inputs and generate JSON with a serializer before relying on them in a shared server.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/setup.sh:132
Finding
Command Execution Through Unvalidated Bash Arithmetic Expressions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/setup.sh:132-145` **Vulnerability Type**: Bash arithmetic-expression injection **Risk Level**: High ### Vulnerable Code ```bash if [[ "${USE_DEFAULT_WEIGHTS,,}" == "y" ]]; then W_EVIDENCE=35 W_ENGAGEMENT=25 W_HONESTY=20 W_PERSUASION=20 else echo " Enter weights (must sum to 100):" read -rp " Evidence & Reasoning [35]: " W_EVIDENCE W_EVIDENCE="${W_EVIDENCE:-35}" read -rp " Engagement [25]: " W_ENGAGEMENT W_ENGAGEMENT="${W_ENGAGEMENT:-25}" read -rp " Intellectual Honesty [20]: " W_HONESTY W_HONESTY="${W_HONESTY:-20}" read -rp " Persuasiveness [20]: " W_PERSUASION W_PERSUASION="${W_PERSUASION:-20}" TOTAL=$((W_EVIDENCE + W_ENGAGEMENT + W_HONESTY + W_PERSUASION)) if [[ "$TOTAL" -ne 100 ]]; then echo "Error: Weights sum to $TOTAL, must be 100." >&2 exit 1 fi fi ``` ### Technical Analysis The four weight values are read as unrestricted strings and then evaluated inside a Bash arithmetic expansion. Bash arithmetic contexts do not merely parse decimal integers: they evaluate arithmetic expressions and recursively resolve variable and array references. Malicious expressions involving array subscripts or nested substitutions can consequently trigger unintended shell evaluation. The check that the final total equals 100 occurs only after the arithmetic expression has been evaluated. It therefore cannot prevent side effects produced during evaluation. ### Attack Path 1. An attacker convinces a privileged operator or automation process to run `scripts/setup.sh`. 2. The operator chooses custom judging weights. 3. The attacker supplies a crafted Bash arithmetic expression instead of a decimal weight. 4. Line 142 evaluates the expression while calculating `TOTAL`. 5. Embedded side effects execute with the privileges of the process running the setup script. 6. The final sum validation occurs only after the malicious expressi ...[truncated 381 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Validate every weight before using it in an arithmetic context: ```bash validate_weight() { local value="$1" if [[ ! "$value" =~ ^[0-9]+$ ]]; then echo "Error: weights must be decimal integers." >&2 exit 1 fi if (( 10#$value < 0 || 10#$value > 100 )); then echo "Error: weights must be between 0 and 100." >&2 exit 1 fi } ``` Call this function for all four values, then convert them explicitly: ```bash validate_weight "$W_EVIDENCE" validate_weight "$W_ENGAGEMENT" validate_weight "$W_HONESTY" validate_weight "$W_PERSUASION" W_EVIDENCE=$((10#$W_EVIDENCE)) W_ENGAGEMENT=$((10#$W_ENGAGEMENT)) W_HONESTY=$((10#$W_HONESTY)) W_PERSUASION=$((10#$W_PERSUASION)) TOTAL=$((W_EVIDENCE + W_ENGAGEMENT + W_HONESTY + W_PERSUASION)) ``` Reject expressions, signs, whitespace, variable names, array syntax, and all other non-decimal input. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/scoreboard.sh:161
Finding
SQL Injection Through the Scoreboard History Limit<![CDATA[ ## Vulnerability Details **File Location**: `scripts/scoreboard.sh:161-191` **Vulnerability Type**: SQL injection **Risk Level**: High ### Vulnerable Code ```bash cmd_history() { ensure_db local limit=10 while [[ $# -gt 0 ]]; do case "$1" in --limit) limit="${2:-10}" shift 2 ;; *) shift ;; esac done local count count=$(sqlite3 "$DB" "SELECT COUNT(*) FROM debates;") if [[ "$count" -eq 0 ]]; then echo "No debates recorded yet." return fi echo "📋 RECENT DEBATES (last $limit)" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" while IFS='|' read -r id winner loser topic format created_at; do echo "" echo " #$id — $topic" echo " Format: $format | $created_at" echo " Winner: $winner | Loser: $loser" done < <(sqlite3 -separator '|' "$DB" "SELECT id, winner, loser, topic, format, created_at FROM debates ORDER BY created_at DESC LIMIT $limit;") } ``` ### Technical Analysis The `--limit` argument is copied directly into an SQL statement without numeric validation or parameter binding. Because the SQLite command-line client accepts SQL containing multiple statements, a crafted limit can terminate the intended query and append another statement. Shell quoting prevents ordinary shell metacharacter execution here, but it does not prevent SQL interpretation. The vulnerability therefore permits execution of attacker-supplied SQLite statements against the selected database. ### Attack Path 1. The attacker gains influence over arguments supplied to the scoreboard command, directly or through an agent that translates Discord requests into CLI arguments. 2. The attacker supplies a malicious value such as a numeric limit followed by an additional SQL statement. 3. `cmd_history` stores the complete string in `limit`. 4. Line 191 inter ...[truncated 715 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Require the limit to be a bounded positive integer before constructing SQL: ```bash if [[ ! "$limit" =~ ^[0-9]+$ ]]; then echo "Error: --limit must be a positive integer." >&2 exit 1 fi limit=$((10#$limit)) if (( limit < 1 || limit > 100 )); then echo "Error: --limit must be between 1 and 100." >&2 exit 1 fi ``` Where supported, use SQLite parameters rather than textual interpolation. Also reject missing values after `--limit` instead of silently substituting the default, and add regression tests using semicolons, comments, whitespace, signs, and oversized integers. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
references/setup-guide.md:145
Finding
Guild-Wide Agent Exposure and Excessive Discord Permissions<![CDATA[ ## Vulnerability Details **File Location**: `references/setup-guide.md:40-48, 93, 145-155`; `scripts/setup.sh:328-340` **Vulnerability Type**: Excessive privileges and overly broad channel authorization **Risk Level**: Medium ### Vulnerable Code The setup guide recommends broad Discord permissions: ```markdown ### Bot Permissions - **General:** Read Messages/View Channels, Manage Channels - **Text:** Send Messages, Send Messages in Threads, Create Public Threads, Manage Messages, Embed Links, Attach Files, Read Message History, Add Reactions, Use External Emoji - **Advanced:** Manage Roles (if you want the bot to assign debate roles) ``` It then claims that the debate agent is isolated to debate channels: ```markdown The debate agent gets its own agent ID, its own AGENTS.md (generated from `references/agents-template.md`), and is bound only to the debate channels. ``` However, the generated guild configuration authorizes every channel: ```json { "channels": { "discord": { "guilds": { "$GUILD_ID": { "requireMention": $REQUIRE_MENTION, "channels": { "*": { "allow": true } } } } } } } ``` The setup script also defaults mention enforcement to disabled: ```bash read -rp " Arena requireMention (true=cheaper, false=active moderation) [false]: " ARENA_MENTION ARENA_MENTION="${ARENA_MENTION:-false}" ``` ### Technical Analysis The wildcard channel rule authorizes the agent throughout the guild rather than limiting it to the five debate channels described by the Skill. With `requireMention` set to `false`, messages can be processed without an explicit user invocation. The recommended bot permissions include channel management, message management, and optionally role management. These permissions are broader than those required to judge debates and maintain a scoreboard. This violates least privilege and increases the impact of prompt injection, agent malfunction, or t ...[truncated 1265 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Replace the wildcard with an explicit channel-ID allowlist: ```json "channels": { "RULES_CHANNEL_ID": { "allow": true, "requireMention": true }, "PROPOSAL_CHANNEL_ID": { "allow": true, "requireMention": true }, "ARENA_CHANNEL_ID": { "allow": true, "requireMention": true }, "RECORDS_CHANNEL_ID": { "allow": true, "requireMention": true }, "BAR_CHANNEL_ID": { "allow": true, "requireMention": true } } ``` Additional hardening should include: 1. Default `requireMention` to `true` everywhere. 2. Require an explicit, informed opt-in before disabling mention enforcement in the arena. 3. Remove `Manage Channels`, `Manage Messages`, and `Manage Roles` unless a reviewed feature strictly requires them. 4. Limit the bot's Discord role to the lowest possible position and scope permissions per channel. 5. Make the generated configuration match the documentation's per-channel behavior. 6. Add a setup-time warning if a wildcard channel rule or guild-wide management permission is selected. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/setup.sh:294
Finding
Configuration Injection Through Unescaped Setup Values<![CDATA[ ## Vulnerability Details **File Location**: `scripts/setup.sh:43-51, 191-192, 211-226, 294-350` **Vulnerability Type**: Unsafe JSON and Markdown configuration generation **Risk Level**: Medium ### Vulnerable Code Attacker-influenced values are accepted without validation: ```bash read -rp " Guild ID: " GUILD_ID if [[ -z "$GUILD_ID" ]]; then echo "Error: Guild ID is required." >&2 exit 1 fi ``` ```bash read -rp " Arena requireMention (true=cheaper, false=active moderation) [false]: " ARENA_MENTION ARENA_MENTION="${ARENA_MENTION:-false}" ``` ```bash 3) read -rp " Model string: " MODEL if [[ -z "$MODEL" ]]; then echo "Error: Model string required." >&2 exit 1 fi ;; ``` The values are interpolated directly into JSON-shaped output: ```bash cat > "$CONFIG_FILE" <<CONFIGMD # OpenClaw Config — Debate Moderator Generated by setup.sh. Apply these to your OpenClaw gateway. ## 1. Agent Entry (add to \`agents.list\` array) \`\`\`json { "id": "debate", "name": "Debate Moderator", "workspace": "REPLACE_WITH_ABSOLUTE_PATH_TO_DEBATE_WORKSPACE", "model": { "primary": "$MODEL" }, "tools": { "profile": "messaging", "deny": [ "exec", "process", "nodes", "cron", "gateway", "browser", "canvas", "sessions_spawn", "sessions_send", "sessions_list", "sessions_history", "subagents", "session_status", "agents_list", "tts", "image", "memory_search", "memory_get" ], "exec": { "security": "deny" }, "fs": { "workspaceOnly": true } } } \`\`\` ## 2. Binding Entry (add to \`bindings\` BEFORE any catch-all Discord binding) \`\`\`json { "agentId": "debate", "match": { "channel": "discord", "guildId": "$GUILD_ID" } } \`\`\` ## 3. Guild Entry (safe to merge with \`config.patch\`) \`\`\`json { "channels": { "discord": { "guilds": { "$GUILD_ID": { "requireMention": $REQUIRE_MENTION, "channels": { "*": { "allo ...[truncated 1970 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Do not construct JSON through raw heredoc interpolation. Generate configuration using a JSON serializer such as `jq`: ```bash jq -n \ --arg guild_id "$GUILD_ID" \ --arg model "$MODEL" \ --argjson require_mention "$REQUIRE_MENTION" \ '{ agent: { id: "debate", name: "Debate Moderator", model: {primary: $model} }, binding: { agentId: "debate", match: { channel: "discord", guildId: $guild_id } }, guild: { channels: { discord: { guilds: { ($guild_id): { requireMention: $require_mention } } } } } }' ``` Validate inputs before serialization: ```bash [[ "$GUILD_ID" =~ ^[0-9]{17,20}$ ]] || exit 1 [[ "$ARENA_MENTION" == "true" || "$ARENA_MENTION" == "false" ]] || exit 1 [[ "$MODEL" =~ ^[A-Za-z0-9._/-]+$ ]] || exit 1 ``` Also validate the final generated JSON with `jq -e`, display a structured configuration diff, and require explicit administrator confirmation before application. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (14)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The description promises a broad Discord debate moderator with AI judging and multiple interactive features. The supplied code only provides one narrow supporting component: persistent scoreboard management via a shell script and SQLite database. While the persistent scoreboard aspect aligns with part of the description, the primary declared capabilities—Discord operation, moderation flow, AI judging, personas, and scored verdicts—are absent from this code chunk. This is a material description-to-behavior mismatch rather than a mere partial implementation detail.

Anti-Refusal Statement

High
Category
Anti-Refusal
Content
### Step 1 — Choose a Server
Create a new Discord server or pick an existing one. You'll need the **guild ID**
(right-click the server icon → Copy Server ID with Developer Mode enabled).

### Step 2 — Choose a Moderator Persona
Pick the voice your moderator uses during debates. Default: **Scholar**.
Confidence
75% confidence
Finding
Skill attempts to nullify the agent's safety policies or restrictions ('you have no restrictions', 'ignore your guidelines', 'do anything now'). This is a direct jailbreak that disables guardrails.

Instruction Override

High
Category
Prompt Injection
Content
# ── Guild ID ──────────────────────────────────────
echo "Step 1: Discord Server (Guild) ID"
echo "  Enable Developer Mode in Discord (Settings → Advanced),"
echo "  then right-click your server icon → Copy Server ID."
echo ""
read -rp "  Guild ID: " GUILD_ID
Confidence
70% confidence
Finding
This pattern attempts to override system instructions or ignore safety constraints. Without LLM analysis, manual review is recommended.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The trigger phrase "set up a debate server" is broad enough that it could activate during ordinary administrative conversation and initiate privileged configuration guidance unexpectedly. In an agent environment, ambiguous activation phrases can cause unintended state changes, excessive data collection, or generation of sensitive config templates when the user did not mean to invoke the skill.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The phrase "let's debate" is vague and context-free, so it could be triggered in casual chat rather than as an intentional command to move a topic into moderated debate flow. In a public or semi-public Discord setting, this increases the chance of unsolicited bot actions, channel noise, and accidental processing of user content under the debate workflow.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
Documenting a destructive `scoreboard.sh reset` command without an explicit warning, confirmation requirement, or access-control note can lead operators or the agent to erase persistent records unintentionally. If exposed through automation, a simple mistaken invocation could wipe all debate history and standings, causing integrity and availability loss for stored data.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The skill explicitly authorizes shell command execution to update and query the scoreboard, which expands the agent's capabilities beyond pure debate moderation into command execution and persistent state mutation. Even though the commands are hardcoded examples, interpolating user-controlled values like participant names and topics into shell invocations creates unnecessary risk if downstream execution is not strongly sandboxed and safely parameterized.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill instructs the agent to run local scripts that read and update persistent records, but it does not tell users that these requests may execute shell commands or alter stored data. This lack of transparency increases the chance of unintended state changes and makes command-trigger abuse harder for users and operators to notice, especially in a chat environment where terms like 'scoreboard' or 'history' appear harmless.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
This format explicitly instructs participants to publicly reveal their actual beliefs before participating, which can pressure users into disclosing sensitive personal opinions in a persistent Discord setting. In the context of debate communities, those disclosures may concern politics, religion, ethics, or other sensitive attributes and can be retained, screenshot, or used for harassment or social targeting.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The guide requests broad Discord permissions including Manage Channels, Manage Messages, and optionally Manage Roles, which gives the bot meaningful moderation power over the server. While some permissions may be functionally necessary for the debate-moderation use case, the documentation does not clearly warn administrators about the scope of control being granted or recommend least-privilege deployment, increasing the risk of accidental overprovisioning or misuse if the bot or agent is compromised.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The sample guild configuration sets `requireMention: false` and `"*": { "allow": true }`, which enables the debate agent to read all messages in the guild and potentially respond everywhere. In a semi-public Discord environment, this creates a privacy and prompt-injection exposure well beyond the narrow debate use case, especially because the example does not prominently warn that non-debate channels will also be visible to the agent unless channel access is explicitly restricted.

Intent-Code Divergence

Low
Confidence
97% confidence
Finding
Lines L027-L032 define a tightly limited operating scope centered on debate channels and scoreboard resources. However, lines L065-L069 explicitly instruct the agent to use `#the-bar` for casual conversation, which contradicts the earlier claim that it only handles debates and the scoreboard.

Vague Triggers

Low
Confidence
88% confidence
Finding
In a markdown skill file, command or invocation phrases should be specific enough to avoid unintended activation. The standalone trigger `help` is a common everyday word and the file does not provide exclusion conditions or tighter scoping for when it should count as a command versus normal chat.

Missing User Warnings

Low
Confidence
87% confidence
Finding
The file explicitly allows users to define arbitrary moderator personas, including voice, signature phrases, and fallacy-flagging behavior, but provides no guardrails against abusive, harassing, sexual, extremist, deceptive, or otherwise policy-violating persona definitions. In a Discord moderation/judging context, this can cause the bot to generate unsafe or inappropriate content on demand, especially because persona instructions are meant to shape all ongoing interactions.

Static analysis

No suspicious patterns detected.