Is RoPro - Enhance Your Roblox Experience safe?

Medium risk

RoPro is medium risk. Submitting a Roblox trade offer makes RoPro POST to api.ropro.io/postWishlist.php with your Roblox ID and up to four wanted/offered item IDs. Analysis couldn't finish the post since Roblox needed Premium; the script/mapping are present.

RoPro Software Corporationv1.7.2Chrome Web Store
45Risk
Who publishes it

RoPro Software Corporation - no other listings under this identity, 1 shared hostname

What this publisher told the store about itself, and the other listings that told it the same thing.

Publisher
RoPro Software Corporation
Declared legal entity
RoPro Software Corporation
Registered address
999 Peachtree Street Northwest, Suite 400, Atlanta, GA 30309, US
Registered contact
Charles Padgett

Shared hosts - 1 hostname

Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.

api.ropro.io
Also called by 2 other listings, including RoPlus - Roblox, Supercharged

AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.

Publishers can request a review.

Findings

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI SANDBOX

Trade offer posts send wishlist item IDs to RoPro

Submitting a Roblox trade offer makes RoPro POST to api.ropro.io/postWishlist.php with your Roblox ID and up to four wanted/offered item IDs.

Analysis couldn't finish the post since Roblox needed Premium; the script/mapping are present.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You submit a Roblox trade-board offer with selected wanted and offered items.

The extension did this

RoPro prepares a request to its API containing your Roblox user ID and the selected item IDs.

02EvidenceFIELD TABLE
Fields prepared for the wishlist POST
FieldValueWhy it matters
Your Roblox user ID
userid=12345678 (illustrative)Connects the posted trade-board offer to your Roblox account.
Wanted item slots
want_item=1365767&want_item2=144630&want_item3=-1&want_item4=-1 (illustrative)Shows which Roblox items or trade terms you selected as items you want.
Offered item slots
item1=1029025&item2=17954048&item3=-1&item4=-1 (illustrative)Shows which Roblox items you selected as items you are offering.
Offer note and value
want_value=0&note=Adds trade-board context alongside the selected items.
03EvidenceNETWORK CAPTURE
Captured request
POSThttps://api.ropro.io/postWishlist.php
No live response was captured because Roblox required an authenticated Premium account before the trade-board post could be submitted during dynamic analysis.
Headers
ropro-idRoblox user ID from the authenticated extension state
04EvidenceCODE COMPARE
The code that does this

The content script builds the trade-offer payload, and the background script maps it to RoPro's API.

What it actually does
Content script wrapper for the RoPro API operationjs/page/tradeOffers.js
function postWishlist(payload) {
  return new Promise((resolve) => {
    roproApiAdapter.request("ropro_post_wishlist", payload, function (data) {
      resolve(data);
    });
  });
}
Trade-board submission builds user and item fieldsjs/page/tradeOffers.js
async function submitComposerOffer() {
  if (getComposerOfferedSelectedCount() === 0) {
    updateComposerStatus("Select at least one offered item.");
    return;
  }
  if (offersState.composer.wantedSelected.length === 0) {
    updateComposerStatus("Select at least one wanted item.");
    return;
  }
  var submitButton = offersState.composer.overlay
    ? offersState.composer.overlay.querySelector(".ropro-trade-offers-submit")
    : null;
  if (submitButton != null) {
    submitButton.disabled = true;
  }
  updateComposerStatus("Posting offer...");
  var offered = [];
  for (var offeredIndex = 0; offeredIndex < offersState.composer.offeredSelected.length; offeredIndex++) {
    var offeredEntry = offersState.composer.offeredSelected[offeredIndex];
    var quantity = parseInt(offeredEntry.quantity, 10);
    if (!Number.isFinite(quantity) || quantity <= 0) {
      quantity = 1;
    }
    for (var quantityIndex = 0; quantityIndex < quantity && offered.length < 4; quantityIndex++) {
      offered.push(offeredEntry);
    }
    if (offered.length >= 4) {
      break;
    }
  }
  var wanted = offersState.composer.wantedSelected.slice(0, 4);
  function getPostedItemId(entry, side) {
    if (entry == null || typeof entry !== "object") {
      return -1;
    }
    if (entry.isTerm === true) {
      return Number.isFinite(parseInt(entry.id, 10)) ? parseInt(entry.id, 10) : -1;
    }
    if (side === "offered") {
      return Number.isFinite(parseInt(entry.assetId, 10)) ? parseInt(entry.assetId, 10) : -1;
    }
    return Number.isFinite(parseInt(entry.id, 10)) ? parseInt(entry.id, 10) : -1;
  }
  var payload = {
    userId: offersState.myUserId,
    want_item: getPostedItemId(wanted[0], "wanted"),
    want_item2: getPostedItemId(wanted[1], "wanted"),
    want_item3: getPostedItemId(wanted[2], "wanted"),
    want_item4: getPostedItemId(wanted[3], "wanted"),
    want_value: 0,
    item1: getPostedItemId(offered[0], "offered"),
    item2: getPostedItemId(offered[1], "offered"),
    item3: getPostedItemId(offered[2], "offered"),
    item4: getPostedItemId(offered[3], "offered"),
    note: "",
  };
  var parsed = null;
  try {
    var response = await postWishlist(payload);
    parsed = parsePayload(response);
  } catch (_) {
    parsed = null;
  }
  if (submitButton != null) {
    submitButton.disabled = false;
  }
  if (parsed == null || parsed === "ERROR") {
    updateComposerStatus("Unable to post offer right now. Please try again.");
    return;
  }
  if (typeof parsed !== "object") {
    updateComposerStatus("Unable to confirm the post response. Please try again.");
    return;
  }
  if (typeof parsed.error === "string" && parsed.error.length > 0) {
    syncTradeOfferPostCooldownFromPayload(parsed);
    updateComposerStatus(parsed.error);
    return;
  }
  var postedWishId = parseInt(parsed.wishid, 10);
  if (parsed.success !== true && !Number.isFinite(postedWishId)) {
    syncTradeOfferPostCooldownFromPayload(parsed);
    updateComposerStatus("Unable to confirm that your offer posted. Please refresh and try again.");
    return;
  }
  syncTradeOfferPostCooldownFromPayload(parsed);
  if (getTradeOfferPostCooldownRemainingSeconds() <= 0) {
    setTradeOfferPostCooldown(
      offersState.postCooldown.cooldownSeconds,
      offersState.postCooldown.cooldownSeconds
    );
  }
  updateComposerStatus("Offer posted.");
  closeComposerModal();
  await loadTradeOffers(true);
}
Background operation mapping for postWishlist.phpbackground.js
ropro_post_wishlist: {
  path: "postWishlist.php",
  method: "POST",
  capability: "trade_offers_post",
  buildRequest: function (payload) {
    var rawUserId = payload.userId ?? payload.userid;
    var userId = null;
    if (rawUserId != null && String(rawUserId).trim().length > 0) {
      userId = normalizeRoProInteger(rawUserId, {
        min: 1,
      });
      if (userId == null) {
        return null;
      }
    }
    var wantItem = normalizeRoProInteger(payload.want_item, {
      min: -9,
      max: Number.MAX_SAFE_INTEGER,
    });
    var wantItem2 = normalizeRoProInteger(payload.want_item2, {
      min: -9,
      max: Number.MAX_SAFE_INTEGER,
    });
    var wantItem3 = normalizeRoProInteger(payload.want_item3, {
      min: -9,
      max: Number.MAX_SAFE_INTEGER,
    });
    var wantItem4 = normalizeRoProInteger(payload.want_item4, {
      min: -9,
      max: Number.MAX_SAFE_INTEGER,
    });
    var wantValue = normalizeRoProInteger(payload.want_value, {
      min: -10000000,
      max: 10000000,
    });
    var item1 = normalizeRoProInteger(payload.item1, {
      min: -9,
      max: Number.MAX_SAFE_INTEGER,
    });
    var item2 = normalizeRoProInteger(payload.item2, {
      min: -9,
      max: Number.MAX_SAFE_INTEGER,
    });
    var item3 = normalizeRoProInteger(payload.item3, {
      min: -9,
      max: Number.MAX_SAFE_INTEGER,
    });
    var item4 = normalizeRoProInteger(payload.item4, {
      min: -9,
      max: Number.MAX_SAFE_INTEGER,
    });
    var note = normalizeRoProString(payload.note, 40, { allowEmpty: true });
    if (
      wantItem == null ||
      wantItem2 == null ||
      wantItem3 == null ||
      wantItem4 == null ||
      wantValue == null ||
      item1 == null ||
      item2 == null ||
      item3 == null ||
      item4 == null ||
      note == null
    ) {
      return null;
    }
    var requestBody = {
      want_item: wantItem,
      want_item2: wantItem2,
      want_item3: wantItem3,
      want_item4: wantItem4,
      want_value: wantValue,
      item1: item1,
      item2: item2,
      item3: item3,
      item4: item4,
      note: note,
    };
    if (userId != null) {
      requestBody.userid = userId;
    }
    return {
      bodyType: "form",
      body: requestBody,
    };
  },
}
05EvidenceTHIRD PARTY LIST
Destination for the trade-board request
  • api.ropro.io

    Receives the trade-board wishlist POST addressed to /postWishlist.php.

Updated 30 September 2026adbacgifemdbhdkfppmeilbgppmhaobf