Is RoRegion – Reduce Lag, Ping & Boost FPS: Roblox Region Selector safe?
RoRegion is high risk. The Upgrade button starts a Roblox purchase from the background worker: it gets a CSRF token via your cookies, then POSTs purchase-with-robux with an EXP key and 75 or 500 price. DA saw the CSRF request and price lookups, not the purchase.…
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Roblox cookies can authorize a Robux subscription purchase
The Upgrade button starts a Roblox purchase from the background worker: it gets a CSRF token via your cookies, then POSTs purchase-with-robux with an EXP key and 75 or 500 price.
DA saw the CSRF request and price lookups, not the purchase.
You click the Upgrade button in the RoRegion membership panel.
The button is enabled when the page has a subscription target key for the selected tier.
The extension asks the background service worker to complete a Roblox subscription purchase.
The background flow uses your logged-in Roblox cookie context to request a CSRF token and prepare the purchase request.
| Field | Value | Why it matters | |
|---|---|---|---|
Your Roblox session cookies | ROBLOSECURITY=<redacted> (illustrative) | They let the request run as your logged-in Roblox account instead of as an anonymous visitor. | |
Roblox CSRF token | x-csrf-token response header from auth.roblox.com/v2/logout | This token is required before Roblox accepts account-changing POST requests from the browser session. | |
Subscription target key | EXP-7702603079316800003 | This chooses the Roblox subscription product that the purchase request points to. | |
Robux price | 75 or 500 | This is the amount of Robux the request says should be charged for the subscription tier. | |
Roblox user ID | 5743677898 | After a successful purchase, the extension records which Roblox account should receive the subscription grant. |
The shipped code obtains a Roblox CSRF token and builds the subscription purchase POST
async function rrFetchCsrfToken() {
const res = await fetch('https://auth.roblox.com/v2/logout', {
method: 'POST',
credentials: 'include'
});
const token = res.headers.get('x-csrf-token');
if (!token) throw new Error('Could not fetch CSRF token');
return token;
}
async function rrPurchaseSubscription(subscriptionTargetKey, priceInRobux) {
const xsrf = await rrFetchCsrfToken();
const res = await fetch(
`https://apis.roblox.com/v1/subscriptions/purchase-with-robux/${encodeURIComponent(subscriptionTargetKey)}`,
{
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': xsrf
},
body: JSON.stringify({ priceInRobux })
}
);
let data = null;
try { data = await res.json(); } catch (e) { data = null; }
return { httpStatus: res.status, data };
}purchaseBtn.addEventListener('click', async () => {
if (purchaseBtn.disabled) return;
if (!cfg.subscriptionTargetKey) return;
const chargeAmount = (typeof cfg.priceInRobux === 'number' && cfg.priceInRobux > 0)
? cfg.priceInRobux
: cfg.price;
let liveRobux = null;
try {
const balRes = await chrome.runtime.sendMessage({ action: 'fetchRobuxBalance' });
if (balRes && balRes.success) liveRobux = balRes.robux;
} catch (e) {}
if (liveRobux !== null && liveRobux < chargeAmount) {
errorDiv.textContent = notEnoughRobux_Translated;
purchaseLabel.textContent = upgrade_Translated;
purchaseBtn.disabled = false;
return;
}
let purchaseResult = null;
try {
purchaseResult = await chrome.runtime.sendMessage({
action: 'rrPurchaseSubscription',
subscriptionTargetKey: cfg.subscriptionTargetKey,
priceInRobux: chargeAmount
});
} catch (e) {
purchaseResult = { success: false, error: e.message };
}
if (!purchaseResult || !purchaseResult.success || !purchaseResult.data || !purchaseResult.data.isSuccess) {
let msg = purchaseFailed_Translated;
if (purchaseResult && purchaseResult.error === 'insufficient_robux') {
msg = notEnoughRobux_Translated;
}
errorDiv.textContent = msg;
purchaseLabel.textContent = upgrade_Translated;
purchaseBtn.disabled = false;
return;
}
try {
await chrome.runtime.sendMessage({
action: 'rrRecordSubscription',
subscriptionTargetKey: cfg.subscriptionTargetKey,
priceInRobux: chargeAmount,
tier: 'unlimited'
});
} catch (e) {}
try {
await chrome.storage.local.set({ rr_tool_tier: 'unlimited' });
} catch (e) {}
_rrMembershipCache = null;
memberContent.innerHTML = '';
const successCard = document.createElement('div');
successCard.id = 'rr-purchase-success';
successCard.style.background = isDarkMode ? 'rgba(45,138,78,0.10)' : 'rgba(45,138,78,0.07)';
successCard.style.border = `1px solid ${isDarkMode ? 'rgba(45,138,78,0.30)' : 'rgba(45,138,78,0.25)'}`;
const successEmoji = document.createElement('div');
successEmoji.className = 'rr-ps-emoji';
successEmoji.textContent = '🎉';
const successTitle = document.createElement('div');
successTitle.className = 'rr-ps-title';
successTitle.style.color = isDarkMode ? '#7adb9a' : '#2d8a4e';
successTitle.textContent = purchaseSuccess_Translated;
const successSub = document.createElement('div');
successSub.className = 'rr-ps-sub';
successSub.style.color = isDarkMode ? '#aaa' : '#666';
successSub.textContent = purchaseSuccessSub_Translated;
successCard.append(successEmoji, successTitle, successSub);
memberContent.appendChild(successCard);
setTimeout(() => {
renderMembershipUI(true);
}, 2200);
});The subscription keys and tier selection are hardcoded in the page script
const RR_PROVIDER_ID = '5743677898';
const RR_SUB_KEYS = {
75: 'EXP-7702603079316800003',
500: 'EXP-5049868274708382266'
};
function rrStartPricingPrefetch() {
if (_rrPricingPromise) return _rrPricingPromise;
_rrPricingPromise = (async () => {
const balancePromise = chrome.runtime.sendMessage({ action: 'fetchRobuxBalance' })
.then(r => r && r.success && typeof r.robux === 'number' ? r.robux : null)
.catch(() => null);
const productsPromise = fetch(
`https://apis.roblox.com/v1/subscriptions/active-subscription-products?subscriptionProductType=1&subscriptionProviderId=${RR_PROVIDER_ID}`,
{ credentials: 'include' }
)
.then(resp => resp.ok ? resp.json() : null)
.then(json => (json && json.subscriptionProductsInfo) || [])
.catch(() => null);
const [balance, products] = await Promise.all([balancePromise, productsPromise]);
const tier = balance !== null && balance >= 75 && balance < 500 ? 75 : 500;
const subscriptionTargetKey = RR_SUB_KEYS[tier];
const match = products && subscriptionTargetKey
? products.find(p => p.subscriptionTargetKey === subscriptionTargetKey)
: null;
const priceInRobux = match && typeof match.priceInRobux === 'number' ? match.priceInRobux : null;
return {
tier,
displayPrice: priceInRobux !== null ? priceInRobux : tier,
priceInRobux,
subscriptionTargetKey,
providerId: RR_PROVIDER_ID,
balance
};
})();
return _rrPricingPromise;
}- auth.roblox.com
Receives the authenticated CSRF-token request with the browser's Roblox cookie context.
- apis.roblox.com
Hosts the active subscription product lookup and the purchase-with-robux endpoint built by the code.
- users.roblox.com
Returns the authenticated Roblox user ID used for balance checks and backend subscription recording.
- economy.roblox.com
Returns the authenticated account's Robux balance before the purchase attempt.
- api.roregion.com
Returns RoRegion subscription tier details and receives the post-purchase subscription record.
RoRegion forwards Roblox account IDs to its API
Code analysis and a proof of concept show RoRegion reads your Roblox user ID and Robux balance from the active session, saves the ID, and sends it to api.roregion.com.
An unauthenticated DA run reached Roblox but not RoRegion (401).
You open RoRegion's membership panel or purchase flow while logged in to Roblox.
The behavior depends on a Roblox-authenticated browser session; without that session, Roblox returns 401.
The extension reads your Roblox account ID and balance, stores the account ID, and sends that ID to RoRegion's API.
The same account ID is used for subscription lookup, purchase intent, and subscription-recording requests.
| Field | Value | Why it matters | |
|---|---|---|---|
Roblox account ID | 123456789 (illustrative) | This uniquely identifies the Roblox account currently signed in on your browser. | |
Stored account ID | authenticatedUserId: 123456789 (illustrative) | The extension keeps the account ID locally so later RoRegion screens can reuse it. | |
Robux balance | robux: 500 (illustrative) | This describes your Roblox account state and is used by the extension before purchase-related actions. | |
Roblox login context | GET users.roblox.com/v1/users/authenticated with credentials included | The extension relies on your existing Roblox sign-in to make account-specific Roblox API calls. |
| Content-Type | application/json |
The shipped code reads a Roblox account ID, stores it, and posts it to RoRegion
async function rrFetchRobuxBalance() {
const userRes = await fetch('https://users.roblox.com/v1/users/authenticated', {
credentials: 'include'
});
if (!userRes.ok) throw new Error('Could not fetch authenticated user');
const { id } = await userRes.json();
await chrome.storage.local.set({ authenticatedUserId: String(id) });
const econRes = await fetch(`https://economy.roblox.com/v1/users/${id}/currency`, {
credentials: 'include'
});
if (!econRes.ok) throw new Error('Could not fetch Robux balance');
const { robux } = await econRes.json();
return robux;
}async function rrFetchSubscriptionFromBackend(userId) {
try {
const res = await fetch(`${RR_API_BASE}/rr-get-subscription`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId })
});
if (!res.ok) return null;
const data = await res.json();
if (!data.success || !data.data) return null;
return data.data;
} catch (e) {
return null;
}
}if (message.action === 'rrGetSubscription') {
(async () => {
try {
const sub = await rrFetchSubscriptionFromBackend(message.userId);
sendResponse({ success: true, data: { data: sub } });
} catch (err) {
sendResponse({ success: false, error: err.message });
}
})();
return true;
}async function renderMembershipUI(forceRefresh = false) {
memberContent.innerHTML = '';
const memberLoading = document.createElement('div');
memberLoading.style.cssText = `text-align: center; padding: 20px; color: ${isDarkMode ? '#555' : '#bbb'}; font-size: 13px; font-style: italic;`;
memberLoading.textContent = loadingMembership_Translated;
memberContent.appendChild(memberLoading);
if (!forceRefresh && _rrMembershipCache) {
const { currentTier, renewalCanceled, stored, userId, subConfig } = _rrMembershipCache;
memberContent.innerHTML = '';
_renderMembershipContent(currentTier, renewalCanceled, stored, userId, subConfig);
return;
}
let liveUserId = null;
try {
const userRes = await fetch('https://users.roblox.com/v1/users/authenticated', { credentials: 'include' });
if (userRes.ok) {
const userData = await userRes.json();
liveUserId = String(userData.id);
const stored2 = await new Promise(r => chrome.storage.local.get(['authenticatedUserId'], r));
if (stored2.authenticatedUserId !== liveUserId) {
await chrome.storage.local.remove([
'authenticatedUserId',
'rr_tool_tier', 'rr_tool_tier_start', 'rr_tool_renewal_canceled',
'rr_tool_private_server_id', 'rr_tool_universe_id', 'rr_tool_owner_id', 'rr_tool_price',
'rr_robux_snapshot', 'rr_price_tier', 'rr_robux_snapshot_at'
]);
await chrome.storage.local.set({ authenticatedUserId: liveUserId });
}
}
} catch (e) {}
let currentTier = 'free';
let renewalCanceled = false;
let freshSub = null;
if (liveUserId) {
try {
const subRes = await chrome.runtime.sendMessage({ action: 'rrGetSubscription', userId: liveUserId });
if (subRes && subRes.success && subRes.data && subRes.data.data) {
freshSub = subRes.data.data;
currentTier = freshSub.tier || 'free';
renewalCanceled = !!freshSub.renewalCanceled;
chrome.storage.local.set({ rr_tool_tier: currentTier });
}
} catch (e) {}
}
const userLockedPrice = (freshSub && typeof freshSub.price === 'number')
? freshSub.price
: _displayPrice;
const subConfig = null;
const stored = freshSub ? {
rr_tool_tier: freshSub.tier,
rr_tool_tier_start: freshSub.tierStart,
rr_tool_renewal_canceled: freshSub.renewalCanceled || null,
rr_price_tier: userLockedPrice
} : { rr_price_tier: userLockedPrice };
const userId = liveUserId;
_rrMembershipCache = { currentTier, renewalCanceled, stored, userId, subConfig };
memberContent.innerHTML = '';
_renderMembershipContent(currentTier, renewalCanceled, stored, userId, subConfig);
}- users.roblox.com
Roblox authenticated-account endpoint used to resolve the signed-in user's numeric account ID.
- economy.roblox.com
Roblox currency endpoint used to read the account's Robux balance.
- api.roregion.com
RoRegion backend that receives the Roblox user ID for subscription, purchase-intent, and subscription-recording requests.
RoRegion replaces Roblox native purchase buttons with competitor promo buttons
On every Roblox catalog or bundle page, RoRegion hides native purchase buttons with injected CSS and shows a 'Buy with RoEarn' button, linking to RoEarn's CWS listing.
Not disclosed; the description only advertises lag and server selection.
You open any Roblox catalog or bundle item page.
No interaction is required, the injection fires automatically on navigation.
RoRegion suppresses the native Roblox buy button via injected CSS and replaces it with a promotional button for a competing extension.
The native .shopping-cart-buy-button controls are set to display:none !important via an injected stylesheet; the 'Buy with RoEarn' button links to the RoEarn extension's CWS listing with an affiliate-style utm_campaign=catalog tag.
Target: Roblox catalog item purchase panel (e.g. /catalog/128777391/Pinstripe-Fedora)
The native purchase button is removed from view by injected CSS (display:none !important) and a third-party promotional button is prepended in its place.
<div class="shopping-cart-buy-button item-purchase-btns-container">
<div class="btn-container">
<!-- Roblox native purchase button -->
<button class="btn-primary-md">Buy Now</button>
</div>
</div><!-- Injected promo wrapper inserted above -->
<div class="promo-wrapper-div">
<button class="promo-action-btn">Buy with RoEarn (Earn <span>...robux icon...</span> 42)</button>
</div>
<div class="shopping-cart-buy-button item-purchase-btns-container">
<!-- btn-container suppressed by injected CSS: display: none !important -->
<div class="btn-container" style="display: none !important;">
<button class="btn-primary-md">Buy Now</button>
</div>
</div>CSS that hides native Roblox purchase buttons (catalogInject.js:13-56)
const StyleInjector = {
init() {
const stylesheet = document.createElement('style');
stylesheet.innerHTML = `
.shopping-cart-buy-button.item-purchase-btns-container .btn-container {
display: none !important;
}
.shopping-cart-buy-button.item-purchase-btns-container .btn-container[data-excluded="true"] {
display: block !important;
}
`;
document.head.appendChild(stylesheet);
}
};
// ... (lines 293-300) ...
async removeCart() {
try {
const cart = await DOMHelper.waitForElement('.shopping-cart-btn-container');
cart?.parentNode?.removeChild(cart);
} catch (e) {}
}Promotional button linking to RoEarn CWS listing (catalogInject.js:190-218)
createButton(reward) {
const container = document.createElement('div');
container.className = 'promo-wrapper-div';
const button = document.createElement('button');
button.className = 'promo-action-btn';
button.textContent = 'Buy with RoEarn (Earn ';
// ... icon + reward amount appended ...
button.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
window.open(
'https://chromewebstore.google.com/detail/roearn-cashback-on-roblox/fooenmopnfaejehogdbmegaleanpdcea?utm_campaign=catalog',
'_blank'
);
});
container.appendChild(button);
return { container, button };
}- chromewebstore.google.com
Chrome Web Store listing for RoEarn, a separate cashback extension. The button directs users to install RoEarn rather than completing their Roblox purchase.
+1 more finding not shown