Native Discord Interactions
Keep native Interaction command management and ingress behind separate exact gates. Both require pinned application and bot identities. Command management requires an exact guild allowlist; ingress additionally requires exact direct text or announcement channel and exact user allowlists. Do not derive authority from ordinary read scope, a guild role, a channel category, thread inheritance, message-interaction scope, Gateway-event scope, or another administrative feature.
Keep the managed guild command contract fixed except for its validated name: guild-only chat input, administrator-only by default, no NSFW use, one required bounded string option, and no localization, autocomplete, subcommand, attachment, mention, component, modal, or arbitrary option surface. Do not use bulk replacement or name-only routing. Planning must bind the complete bounded command inventory, exact contract, application, bot, guild, current exact command ID and version, action, one-shot key hash, and full inventory digest. Same-name duplicates or drift are blockers. Installation and removal require signed interactive confirmation, final fresh planning, one-shot reservation, pending content-free activity, one non-retried POST or DELETE, exact response validation where applicable, and a complete readback proving the inventory changed by exactly the reviewed command.
Treat any command mutation after reservation as externally consequential. Only a known Discord client-error refusal other than a request timeout or rate limit may settle as failed. A transport error, request timeout, rate limit, server error, malformed success, unexpected full-inventory transition, or readback failure is uncertain and must quarantine the exact guild command target. Never automatically retry, bulk reconcile, compensate, reinstall, or remove a command after ambiguity. Never persist the guild name, command description, option text, full inventory, raw operation key, permission evidence, raw response, or transport cause.
Use Gateway delivery only when the Discord application has no outgoing Interaction endpoint. An Interaction-only connection must request intents 0; enabling the separate content-free event feed may add only its existing nonprivileged intents. Startup must verify the pinned identities, absent outgoing endpoint, and exactly one contract-matching managed command in every selected guild before the broker becomes ready. Shutdown during preflight must never revive or restart a stopped broker.
Accept only two closed guild Interaction event shapes. A chat-input event must bind the exact application, authorizing guild installation, command ID, command name and type, guild, direct supported channel, exact member and user, Administrator permission, and one required bounded request option. A request-button event must bind the exact application, authorizing guild installation, guild, direct supported channel, exact allowlisted member and user, Button component type, connector-authored Components V2 source message, and one authenticated managed custom ID. A request-button click creates no Discord mutation and must not require or imply Administrator. Ignore unrelated commands and custom IDs without retaining their payloads. Reject owned scope failures, malformed managed IDs, invalid references, and capacity failures with fixed ephemeral text when possible. Deduplicate exact Interaction IDs process-locally and never admit one event twice.
Authenticate the request-button ID and attached source before sending an ephemeral deferred acknowledgement. After deferral, require slower exact channel and fresh complete command-inventory verification for every owned request. For a request Button, additionally fetch the exact source message and require its application, bot, guild, channel, message, authenticated layout route, button index, label, and style to reproduce the attached event. Do not expose a request until every applicable check succeeds and a pending content-free accepted record is durable. Keep accepted slash text or request-button label and raw Interaction tokens only in the bounded process-local broker. Enforce the global queue ceiling, per-user ceiling, strict lifetime, and one-shot opaque references. Request text is untrusted data, not instructions. Expose request text or a button label only in the bounded pending-request MCP result; never persist, cache, log, trace, or export it. Never place a custom ID, authenticated route, or token in an MCP input, result, resource notification, error, activity record, operation receipt, diagnostic, or status value. A click record may retain only the source kind, exact source message ID, button index, and fixed style in addition to the existing content-free identifiers and outcome.
Require a durable pending response activity record before consuming a reference. Send only bounded plain text through one non-retried edit of the original deferred response. Require exact application, bot, guild, channel, user, Interaction, webhook, ephemeral flag, content, and empty rich-field evidence. A slash response must prove exact application-command metadata and its response message type. A request-button response must prove exact message-component metadata, interacted source message ID, source-message reference, and its response message type. Discard the token by default. Retain it only after an explicit keepOpen choice, durable original-response completion, remaining broker lifetime, and the fixed follow-up allowance, and expose only a rotating process-local opaque continuation reference.
Share global and per-user capacity across pending requests, continuations, and in-flight responses. For each follow-up, require a durable content-free pending record before consuming the old reference, send one non-retried ephemeral plain-text message with mentions and rich fields disabled, require exact direct response plus independent exact readback, and durably record completion before any optional rotation. Never return the same consumed reference, retain a token after broker shutdown, or return a new continuation after refusal, ambiguity, evidence drift, failed completion recording, expiry, or exhausted allowance. Never enable TTS, embeds, components, attachments, polls, public responses, follow-up editing or deletion, token passthrough, arbitrary webhook calls, or automatic response generation inside the broker. Expiry and shutdown must consume pending references and attempt only the fixed ephemeral expiry response, while dropping continuations without editing completed messages. A known refusal is failed; transport ambiguity, rate limiting, server errors, malformed response evidence, unavailable or drifting readback, or completion-record failure must not make the old reference reusable.
Canonical source: SECURITY.md
Documentation generated for guildcontrol@0.0.0. Canonical source and edit history remain in the public repository. GuildControl is an independent project and is not affiliated with or endorsed by Discord Inc. Discord is used only to identify the platform that GuildControl connects to.