Skip to main content

Free 30-min security demo Book Now

Offensive360 Offensive360
ZeroDays CVE-2026-70617
High CVE-2026-70617 CVSS 8.1 Spacebar Server TypeScript

Missing AuthZ in Spacebar Group DM Endpoint

CVE-2026-70617: Missing authorization in Spacebar Server allows any authenticated user to join arbitrary group DMs, read history, and post messages.

Offensive360 Research Team
Affects: < commit dcfd910
Source Code View Patch

Overview

CVE-2026-70617 is a missing authorization vulnerability in Spacebar Server, an open-source, self-hostable messaging platform compatible with the Discord API. The flaw resides in the PUT /channels/{channel_id}/recipients/{user_id} endpoint, which is intended to manage membership in group direct-message (DM) channels. Prior to the patching commit dcfd910, this handler performed no verification that the requesting user was already a member of the target channel — or had any right to modify its membership — before processing the request. Any authenticated user on the instance could exploit this to silently add themselves to any private group DM, gaining full read and write access to that channel’s history.

The vulnerability was identified through inspection of the server’s REST API route handlers and reported through the project’s security advisory process. It affects all Spacebar Server deployments running code prior to commit dcfd91035e3da42abf5f32d8d86a35219225b3d4. Because Spacebar is specifically designed for self-hosted deployments — often used by communities that expect the same privacy guarantees as commercial platforms — the practical blast radius extends to any installation where users are not all mutually trusted.

This is a textbook Broken Access Control issue (OWASP A01:2021). Despite the simplicity of the underlying mistake, the consequences are significant: private conversation history, participant identities, and the ability to inject messages into established conversations are all exposed to any credentialed attacker. The CVSS 8.1 HIGH score reflects the low attack complexity and the breadth of data accessible post-exploitation.

Technical Analysis

The root cause is an absent membership check in the group DM recipient handler. In the pre-patch codebase, the route handler for PUT /channels/:channel_id/recipients/:user_id retrieved the channel record and appended the target user to the recipients list without first asserting that the authenticated caller (req.user_id) was a current member of that channel.

A simplified but representative version of the vulnerable handler looks like this:

// VULNERABLE — pre-patch handler (illustrative)
router.put(
  "/channels/:channel_id/recipients/:user_id",
  route({ permission: "MANAGE_CHANNELS" }),  // permission guard checks a generic flag,
                                              // but NOT channel membership
  async (req: Request, res: Response) => {
    const { channel_id, user_id } = req.params;

    const channel = await Channel.findOneOrFail({ where: { id: channel_id } });

    if (channel.type !== ChannelType.GROUP_DM) {
      throw new HTTPError("Channel is not a group DM", 400);
    }

    // ❌ No check: is req.user_id already a recipient of channel_id?
    // ❌ No check: does req.user_id own or administer this channel?

    await Channel.addRecipient(channel_id, user_id);

    res.sendStatus(204);
  }
);

The route({ permission: "MANAGE_CHANNELS" }) middleware validates that the authenticated user holds a server-level permission flag, but group DMs exist outside of guilds — they have no guild-scoped permission context. As a result, the permission check either passes trivially or is not meaningfully enforced, and execution falls through directly to the addRecipient call.

The exploit is correspondingly trivial. An attacker who knows (or enumerates) a target channel_id — which, in Discord-compatible APIs, is a Snowflake that can sometimes be inferred from timing or leaked in shared servers — issues a single authenticated request:

PUT /channels/1234567890123456789/recipients/9876543210987654321 HTTP/1.1
Host: spacebar.example.com
Authorization: Bearer <attacker_token>
Content-Type: application/json

On success the server responds 204 No Content, and the attacker is now a full participant in the group DM. They can immediately GET /channels/{channel_id}/messages to retrieve complete message history, send new messages, and add additional third-party users — all without any notification mechanism that would alert the channel’s legitimate owners.

The secondary impact — force-adding arbitrary third-party users — compounds the severity. An attacker can abuse the same endpoint to add victims to channels containing offensive or harmful content, potentially exposing those users to harassment or impersonation scenarios.

Impact

An authenticated attacker exploiting CVE-2026-70617 can:

  • Read complete message history of any private group DM channel on the instance, including messages sent before the attacker joined.
  • Post messages as a legitimate participant, enabling impersonation attacks, social engineering of other channel members, or injection of malicious links.
  • Force-add third-party users to arbitrary channels without their knowledge or consent, which may be used for harassment, spam, or to expose users to controlled attacker environments.
  • Enumerate private channel membership by observing recipient lists after joining.

The CVSS 3.1 vector AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N reflects that exploitation requires only a low-privilege authenticated account (standard user registration), no user interaction, and no complex preconditions. The confidentiality and integrity impacts are both rated High because complete conversation contents and participant control are affected. Availability is not directly impacted.

For enterprise or community Spacebar deployments this translates to a full breach of the private messaging boundary — the core privacy guarantee of any messaging platform.

How to Fix It

The correct remediation is to assert channel membership for the requesting user before processing any recipient mutation. The patch introduced at commit dcfd910 adds this guard:

// FIXED — post-patch handler pattern
router.put(
  "/channels/:channel_id/recipients/:user_id",
  route({ permission: "MANAGE_CHANNELS" }),
  async (req: Request, res: Response) => {
    const { channel_id, user_id } = req.params;
    const caller_id = req.user_id;

    const channel = await Channel.findOneOrFail({ where: { id: channel_id } });

    if (channel.type !== ChannelType.GROUP_DM) {
      throw new HTTPError("Channel is not a group DM", 400);
    }

    // ✅ Verify the requesting user is already a recipient of this channel
    const isCallerMember = channel.recipients?.some(
      (r) => r.id === caller_id
    );

    if (!isCallerMember) {
      throw new HTTPError("Missing Permissions", 403);
    }

    // Optional: enforce group DM size cap before adding
    if ((channel.recipients?.length ?? 0) >= 10) {
      throw new HTTPError("Group DM is full", 400);
    }

    await Channel.addRecipient(channel_id, user_id);

    res.sendStatus(204);
  }
);

Upgrade steps for self-hosted operators:

# Pull the latest code including the security fix
git -C /path/to/spacebar-server pull origin master

# Verify you are at or past the fix commit
git log --oneline | grep dcfd910

# Reinstall dependencies and rebuild
npm install
npm run build

# Restart the service
systemctl restart spacebar-server   # adjust to your process manager

There is no semver release tag to pin to at this time; operators should ensure their deployment is at or past commit dcfd91035e3da42abf5f32d8d86a35219225b3d4.

Our Take

Missing authorization on mutation endpoints is one of the most consistently underestimated vulnerability classes in modern web applications. Developers typically focus authentication hardening — token validation, session management, MFA — and treat authorization as a secondary concern. The result is a pattern we observe repeatedly in SAST engagements: the request is verified to come from someone, but never verified to come from someone with the right to do this specific thing.

Group DM membership in particular sits in a tricky authorization zone. It is not governed by guild-level role permissions, it is not a traditional resource-owner relationship, and it lacks the explicit ACL model that file systems or database rows might carry. This ambiguity creates exactly the kind of gap that slips through informal code review. Automated analysis tools that understand API route semantics and can correlate permission guard scope with the resource being mutated are essential for catching these cases at scale.

For enterprises deploying self-hosted collaboration infrastructure, this class of flaw is a reminder that Discord-API-compatible does not mean Discord-security-equivalent. Internal deployments often carry more sensitive conversations than their operators assume, and they lack the security engineering investment of the platforms they emulate.

Detection with SAST

This vulnerability maps to CWE-862: Missing Authorization. In SAST analysis, Offensive360’s engine targets this class through several complementary signals:

  • Unguarded mutation routes: Any PUT, POST, PATCH, or DELETE handler that writes to a resource identified by a URL parameter (:channel_id, :user_id) is flagged for authorization taint analysis. The engine traces whether the authenticated principal’s identity is compared against ownership or membership of that resource before the write occurs.
  • Scope mismatch in middleware: Permission checks that evaluate guild-scoped or global permission flags on endpoints operating over DM or group-DM resources are flagged as potentially insufficient — the guard exists but its scope does not cover the resource class being protected.
  • Missing membership assertion pattern: Absence of a query or assertion of the form recipients.includes(caller_id) or equivalent ORM lookup prior to a addRecipient / update call on a channel record triggers a CWE-862 finding at HIGH confidence.

In a DAST context, this is detectable by replaying PUT /channels/{id}/recipients/{uid} with a low-privilege token that is not a member of the target channel and asserting that a 403 Forbidden response is returned. Any 204 or 200 response from a non-member caller is a confirmed finding.

References

#missing-authorization #broken-access-control #api-security #privilege-escalation

Detect this vulnerability class in your codebase

Offensive360 SAST scans your source code for CVE-2026-70617-class vulnerabilities and thousands of other patterns — across 60+ languages.