TL;DR

While analyzing a JavaScript bundle on redacted.com, I found a reference to an internal staging/development site. The staging site shared the same invite ID namespace as production — meaning invite codes generated on staging resolved to real, unrelated servers on production. By automating invite generation on staging, an attacker could forcefully join random invite-only servers on production without authorization.

Target & Scope

  • Program: Roblox
  • Asset: *.redacted.com
  • Severity: Medium (CVSS 4.2)
  • Weakness: Improper Access Control (CWE-284)

Recon & Discovery

I started with some JavaScript recon on the main site. While digging through a publicly accessible admin bundle at a path like https://www.redacted.com/REDACTED/adminBundle.js, I noticed a hardcoded reference to what appeared to be a staging or development domain — something like staging.redacted.com.

Visiting the staging site, it was running the same application as production. I tried logging in with my existing production account, but got an error indicating the account didn’t exist — confirming this was a separate environment with its own user database.

So I created a fresh account on staging and set up a test server.

Vulnerability Details

The core issue was that staging and production shared the same invite ID namespace. Invite codes weren’t scoped to the environment that generated them.

Here’s how I discovered this:

  1. On staging, I created a server and used the “Invite People” feature to generate an invite link — something like https://staging.redacted.com/i/AbCd1234
  2. I took just the invite ID (AbCd1234) and plugged it into the production URL: https://www.redacted.com/i/AbCd1234
  3. Instead of returning an error, production resolved the invite ID to a completely different, real server — one I had no connection to I repeated this several times. Each invite ID generated on staging mapped to a different random server on production. The invite IDs were valid and accepted — clicking “Continue” would add me to that server.

This meant the invite ID pool was shared across environments. Staging was essentially a free invite ID oracle for production.

Automating the Attack

To demonstrate the scalability of this issue, I wrote a Python script that:

  1. Authenticates to the staging site
  2. Generates a new invite ID via the staging API (POST /api/teams/REDACTED/invites)
  3. Takes the returned invite ID and queries the production API (GET /api/invites/{inviteId})
  4. Extracts the target server’s ID, name, and visibility setting from the response
import requests
 
session = requests.session()
 
# Step 1: Generate an invite ID on staging
staging_url = "https://staging.redacted.com/api/teams/REDACTED/invites"
staging_cookies = {"hmac_signed_session": "REDACTED", "authenticated": "true"}
staging_headers = {"Content-Type": "application/json"}
staging_json = {"gameId": None, "teamId": "REDACTED"}
 
req = session.post(staging_url, headers=staging_headers,
                   cookies=staging_cookies, json=staging_json)
 
# Parse out the invite ID from the response
invite_id = parse_invite_id(req.text)
 
# Step 2: Use the invite ID on production
prod_url = f"https://www.redacted.com/api/invites/{invite_id}"
response = requests.get(prod_url)
 
# Step 3: Extract server info
server_data = response.json()
print(f"Server ID: {server_data['team']['id']}")
print(f"Server Name: {server_data['team']['name']}")
print(f"Visibility: {server_data['team']['visibility']}")

Each run returned a different server from production — including servers with "visibility": "default" (invite-only). Running this in a loop would let an attacker mass-join private servers without any invitation.

Impact

An attacker could forcefully join invite-only servers belonging to other users without authorization. Specifically:

  • Privacy violation — gain access to private community content, messages, and member lists
  • Scalable abuse — the script could be looped to join hundreds of servers, limited only by rate limiting (which was not enforced on the staging API)
  • No victim interaction required — the attacker didn’t need to know anything about the target servers; they were joined at random The attack complexity was assessed as High because the attacker could not target a specific server — the mapping between staging invite IDs and production servers was unpredictable. However, for an attacker whose goal is broad unauthorized access rather than targeted intrusion, this was trivially exploitable.

Remediation

The fix was to scope invite IDs to their originating environment. After the patch, using a staging-generated invite ID on production returned:

{"code": "NotFoundError", "message": ""}

This confirmed that the invite ID namespaces were properly isolated between environments.

Timeline

DateEvent
2022-07-21Vulnerability discovered and reported
2022-07-29HackerOne triage requested additional info
2022-07-29Provided video PoC demonstrating the issue
2022-08-02Triaged and handed off to Roblox security team
2022-08-02Roblox acknowledged the finding
2022-09-01Fix deployed, retest requested
2022-09-01Confirmed fix — invite IDs now return NotFoundError
2022-09-21Bounty awarded

Takeaways

JS bundles are recon goldmines. The entire attack chain started from reading a publicly accessible JavaScript file that leaked the staging domain. This is a reminder to always check for hardcoded URLs, API keys, and internal references in client-side code. Tools like LinkFinder, JSLuice, or even a simple grep through beautified bundles can surface things that aren’t meant to be public.

Staging environments are often softer targets. Staging and development environments frequently have weaker security controls, fewer rate limits, and — as in this case — shared resources with production that create unexpected attack paths. Always check if staging assets interact with production in any way.

Shared namespaces across environments are dangerous. If your staging and production systems share database state, ID pools, or any kind of namespace, a vulnerability in one environment can cascade into the other. Environment isolation should extend beyond just network segmentation — it includes data, identifiers, and authentication state.

Automation turns low-probability into certainty. The individual odds of hitting a specific private server were low, but the ability to generate unlimited invite IDs at no cost turned this into a reliable mass-exploitation vector. When evaluating severity, always consider what happens when a bug is scripted and run at scale.