Is Numeo Spot safe?
Numeo Spot is high risk. Once the WEX FleetOne flow reaches a FleetDocs page, it asks Chrome for FleetOne cookies, selects the WEX auth/session ones, adds the page's account label, and sends it to Numeo's WEX cookies endpoint with the user's bearer token.
Who publishes itgcp-developers - no other listings under this identity, 1 shared hostname
gcp-developers - 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.
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.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
WEX FleetOne cookies forwarded after login
Once the WEX FleetOne flow reaches a FleetDocs page, it asks Chrome for FleetOne cookies, selects the WEX auth/session ones, adds the page's account label, and sends it to Numeo's WEX cookies endpoint with the user's bearer token.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-522
- Source
- Dynamic sandbox
You complete the WEX FleetOne connection flow through the extension.
The content script waits for an extension session flag before starting cookie detection on FleetOne pages.
The extension collects selected FleetOne cookies and forwards them to Numeo with the account label.
The code requires both FDWEXAUTH and FleetDocs_SID before sending the message that triggers the backend POST.
- WEX authentication cookieFDWEXAUTH=8B7F6C2E4F1A9D0C5B3A21 (illustrative)
This can identify an authenticated FleetOne browser session after you sign in.
- FleetDocs session cookieFleetDocs_SID=6f8c2e91-5b7a-4d40-a745-8267cfd93211 (illustrative)
This adds session context for the FleetDocs part of your WEX account.
- Security and routing cookiesAWSALB=QmFzZTY0TG9hZEJhbGFuY2VyVmFsdWU= (illustrative)
These values describe the browser session state around the FleetOne login.
- Account label from the pageACME Logistics LLC (illustrative)
This ties the cookie submission to the account name or identifier visible on the FleetDocs page.
- Numeo bearer tokenBearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 (illustrative)
This authenticates the extension's request to Numeo while the FleetOne cookie data is submitted.
- Accept
- application/json
- Authorization
- Bearer <redacted>
- Extension-Version
- 3.6.1
The FleetOne content script filters cookies, then the background listener posts them
wexContent.ts extractAndSendWexCookies
src/content/wexContent.tsasync function extractAndSendWexCookies() { if (cookiesDetected) { return } try { const parsedDocCookies: Record<string, unknown>[] = [] if (document.cookie) { const cookiePairs = document.cookie.split(';') cookiePairs.forEach((pair) => { const [name, value] = pair.trim().split('=') if (name && value) { parsedDocCookies.push({ name: name.trim(), value: value.trim(), domain: window.location.hostname, path: '/', }) } }) } chrome.runtime.sendMessage( { type: 'EXTRACT_WEX_COOKIES_FROM_BACKGROUND', url: window.location.href, }, async (response) => { if (chrome.runtime.lastError) { const lastError = chrome.runtime.lastError log.e('WEX: Error requesting cookies from background:', toError(lastError)) return } if (!response || !response.success) { log.e('WEX: Failed to get cookies:', toError(response?.error)) return } const cookiesFromBackground = response.cookies const mergedCookiesMap = new Map() parsedDocCookies.forEach((c) => { mergedCookiesMap.set(c.name, c) }) cookiesFromBackground.forEach((c: Record<string, unknown>) => { if (!mergedCookiesMap.has(c.name as string)) { mergedCookiesMap.set(c.name as string, c) } }) const cookies = Array.from(mergedCookiesMap.values()) const criticalCookieNames = ['FDWEXAUTH', 'FleetDocs_SID'] const securityCookieNames = ['reese84'] const sessionCookieNames = ['visid_incap_1686407', 'AWSALB', 'AWSALBCORS'] const wexCookies = cookies.filter((cookie: Record<string, unknown>) => { const cookieName = cookie.name as string if (criticalCookieNames.includes(cookieName)) return true if (securityCookieNames.includes(cookieName)) return true if (sessionCookieNames.includes(cookieName)) return true if (cookieName.startsWith('incap_ses_')) return true if (cookieName.startsWith('nlbi_')) return true return false }) if (wexCookies.length === 0) { return } const hasFDWEXAUTH = wexCookies.some((c: Record<string, unknown>) => c.name === 'FDWEXAUTH') const hasFleetDocsSID = wexCookies.some((c: Record<string, unknown>) => c.name === 'FleetDocs_SID') if (!hasFDWEXAUTH || !hasFleetDocsSID) { log.e('WEX: Missing critical cookies (FDWEXAUTH or FleetDocs_SID)', new Error('Missing critical cookies (FDWEXAUTH or FleetDocs_SID)')) return } cookiesDetected = true const formattedCookies = wexCookies.map((cookie: Record<string, unknown>) => ({ name: cookie.name, value: cookie.value, domain: cookie.domain || '.fleetone.com', path: cookie.path || '/', expiresAt: cookie.expirationDate ? new Date((cookie.expirationDate as number) * 1000).toISOString() : new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString(), })) const accountId = await waitForAccountId() if (!accountId) { log.e('WEX: Failed to get accountId from h2 element', new Error('AccountId not found')) } chrome.runtime.sendMessage( { type: 'WEX_COOKIES_DETECTED', data: { cookies: formattedCookies, url: window.location.href, timestamp: new Date().toISOString(), accountId: accountId || undefined, }, }, (submitResponse: Record<string, unknown>) => { if (chrome.runtime.lastError) { const lastError = chrome.runtime.lastError log.e('WEX: Error sending cookies to backend:', toError(lastError)) return } if (submitResponse?.status === 'success') { cleanup() StorageUtils.remove('wexExtensionSession' as unknown as string) .then(() => {}) .catch((error) => { log.e('WEX: Failed to clear extension session:', error) }) chrome.runtime.sendMessage({ type: 'CLOSE_CURRENT_TAB' }, (closeResponse) => { if (chrome.runtime.lastError) { log.e('WEX: Chrome runtime error when closing tab:', toError(chrome.runtime.lastError)) window.close() } else if (closeResponse?.status === 'success') { } else { log.w('WEX: Unexpected close response, trying window.close():', closeResponse) window.close() } }) } else { log.e('WEX: Failed to send cookies to backend:', toError(submitResponse)) } } ) } ) } catch (error) { log.e('WEX: Error in extractAndSendWexCookies:', toError(error)) }}WexListener.ts handleCookiesDetected
src/background/listeners/WexListener.tsprivate async handleCookiesDetected(message: WexCookiesDetectedMessage, sendResponse: (response: unknown) => void): Promise<void> { try { const criticalCookies = ['FDWEXAUTH', 'FleetDocs_SID'] const providedCookieNames = message.data.cookies.map((c) => c.name) const missingCriticalCookies = criticalCookies.filter((name) => !providedCookieNames.includes(name)) if (missingCriticalCookies.length > 0) { sendResponse({ status: 'error', message: `Missing critical cookies: ${missingCriticalCookies.join(', ')}` }) return } const authData = await chrome.storage.local.get([AUTH_STORAGE_KEY]) const token = authData[AUTH_STORAGE_KEY]?.token if (!token) { backgroundLogger.e('No authentication token found in storage', new Error('No authentication token found')) sendResponse({ status: 'error', message: 'Not authenticated - no token found' }) return } const sessionData = await chrome.storage.local.get(['wexExtensionSession']) const tokenPurpose = sessionData.wexExtensionSession?.tokenPurpose try { await axios.post( '/factoring/wex/cookies', { cookies: message.data.cookies, accountId: message.data.accountId, ...(tokenPurpose && { tokenPurpose }), }, { headers: { Authorization: `Bearer ${token}`, }, } ) chrome.storage.local.remove(['wexExtensionSession']) sendResponse({ status: 'success', message: 'Cookies sent to WEX endpoint' }) } catch (fetchError: unknown) { const axiosError = fetchError as AxiosError<{ message?: string }> const errorMessage = axiosError.response?.data?.message || axiosError.message const status = axiosError.response?.status backgroundLogger.e(`Failed to send cookies to WEX endpoint, status: ${status}`, fetchError as Error) sendResponse({ status: 'error', message: `Failed to send cookies: ${status} - ${errorMessage}` }) } } catch (error) { backgroundLogger.e('Unexpected error handling WEX cookies:', error as Error) sendResponse({ status: 'error', message: `Failed to process cookies: ${(error as Error).message}` }) }}background index.ts EXTRACT_WEX_COOKIES_FROM_BACKGROUND handler
src/background/index.tsif (message.type === 'EXTRACT_WEX_COOKIES_FROM_BACKGROUND') { const cookiesByUrl = chrome.cookies.getAll({ url: message.url }) const cookieStoresPromise = chrome.cookies.getAllCookieStores() const allCookiesPromises = [ cookiesByUrl, cookieStoresPromise.then(async (stores) => { const allStoreCookies = await Promise.all(stores.map((store) => chrome.cookies.getAll({ url: message.url, storeId: store.id }))) return allStoreCookies.flat() }), ] Promise.all(allCookiesPromises) .then((cookieArrays) => { const allCookies = cookieArrays.flat() const uniqueCookies = Array.from(new Map(allCookies.map((c) => [c.name + c.domain + (c.storeId || ''), c])).values()) sendResponse({ success: true, cookies: uniqueCookies, }) }) .catch((error) => { backgroundLogger.e('Error getting WEX cookies:', error) sendResponse({ success: false, error: error instanceof Error ? error.message : 'Failed to get cookies', }) }) return true}- api.numeo.ai
Receives the WEX cookie submission at /v1/factoring/wex/cookies according to the extension source.
- agent.numeo.ai
The extension's production web app domain and source of the WEX login flow that sets the session flag.