Skip to content

Discord channel: signature validation missing; capabilities header mismatch breaks discovery #148

@jamjahal

Description

@jamjahal

Description

Summary

Two separate bugs prevent the Discord channel from working properly:

  1. Capabilities JSON schema mismatch – Discovery fails because of a wrong field name.
  2. Missing Discord signature validation – The interactions endpoint cannot be verified by Discord because the host never checks X-Signature-Ed25519 and X-Signature-Timestamp.

Bug 1: Capabilities header field name mismatch

Location: channels-src/discord/discord.capabilities.json

The Discord capabilities file uses header_name for the credential location:

"location":{"type":"header","header_name":"Authorization","prefix":"Bot "}

CredentialLocationSchema in src/tools/wasm/capabilities_schema.rs expects name:

Header {
    name: String,
    #[serde(default)]
    prefix: Option<String>,
},

Impact: ChannelCapabilitiesFile::from_bytes() fails during discovery, the channel is skipped, and "Discord (WASM)" never shows up in the onboarding wizard.

Workaround: Use name instead of header_name in discord.capabilities.json.


Bug 2: Discord signature validation not implemented

Location: src/channels/wasm/router.rs (or equivalent webhook handler)

According to Discord's "Preparing for Interactions" docs, the endpoint must:

  1. Return 200 with {"type":1} for valid PING requests.
  2. Validate X-Signature-Ed25519 and X-Signature-Timestamp and return 401 for invalid signatures.

The docs state:

"If either of these are not complete, your Interactions Endpoint URL will not be validated."

"Discord will also perform automated, routine security checks against your endpoint, including purposefully sending you invalid signatures."

Current behavior: The host only performs generic webhook secret checks. There is no Discord-specific Ed25519 validation. All requests (including invalid signatures) receive 200 OK with the correct PONG body.

Impact: Discord’s verification fails with: "The specified interactions endpoint url could not be verified."

Expected behavior:
Before forwarding to the WASM channel, validate the Ed25519 signature using discord_public_key:

  • If invalid → return 401 Unauthorized.
  • If valid → forward to the WASM channel.

Environment

  • IronClaw v0.5.0
  • Discord channel from channels-src/discord
  • Cloudflare Tunnel for webhook endpoint
  • PostgreSQL database

References

Proposed fix

Bug 2: Discord signature validation

Add host-side validation in the webhook path before calling the WASM channel. Rough steps:

  1. Detect Discord webhooks – Treat requests to /webhook/discord or channels whose capabilities include require_signature_verification: true.

  2. Load discord_public_key – Read from the secrets store, similar to discord_bot_token. If not configured, either:

    • return 401 for all Discord requests, or
    • log a warning and skip validation (weaker, but keeps current behavior).
  3. Validate signature before WASM call – In webhook_handler (or equivalent), for Discord:

    • Read X-Signature-Ed25519 and X-Signature-Timestamp.
    • Use the raw request body (bytes) and public key to verify:
      • signature = hex_decode(X-Signature-Ed25519)
      • message = X-Signature-Timestamp + body
      • verify(message, signature, public_key) (e.g. ed25519-dalek or tweetnacl).
    • If invalid or missing headers → return 401.
    • If valid → continue as now and call the WASM channel.
  4. Dependencies – Add something like ed25519-dalek or tweetnacl for Ed25519 verification.

  5. Setup wizard – Ensure discord_public_key is configured during onboarding (e.g. in setup.required_secrets or equivalent for Discord).

Pseudocode:

// In webhook_handler, before call_on_http_request:
if channel_name == "discord" {
    if let Some(public_key) = state.router.get_discord_public_key().await {
        let sig = headers.get("X-Signature-Ed25519").and_then(|v| v.to_str().ok());
        let ts = headers.get("X-Signature-Timestamp").and_then(|v| v.to_str().ok());
        match (sig, ts) {
            (Some(s), Some(t)) => {
                if !verify_discord_signature(public_key, t, &body, s) {
                    return (StatusCode::UNAUTHORIZED, Json(json!({"error": "invalid request signature"})));
                }
            }
            _ => return (StatusCode::UNAUTHORIZED, Json(json!({"error": "missing signature headers"}))),
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions