Is Careerflow AI Job Application Tracker, ATS Resume Checker, Autofill & more safe?

High risk

Careerflow is high risk. The extension requests Careerflow config endpoints for selectors, profile selectors, form selectors, data paths, and settings after visiting LinkedIn jobs. Shipped code stores these rules to pick which elements are read on matching pages.…

careerflow.aiv2.20.8Chrome Web Store
75Risk
Who publishes it

careerflow.ai - 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
careerflow.ai
Registered address
950 Linden Ave, Apt 230, Sunnyvale, CA 94086-8817, US
Registered contact
Puneet Kohli

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

Publishers can request a review.

Findings

SeverityHIGH
ClassUNWANTED
TypeUnexpected
CWECWE-829
SourceAI SANDBOX

Remote selector rules control page data extraction

The extension requests Careerflow config endpoints for selectors, profile selectors, form selectors, data paths, and settings after visiting LinkedIn jobs.

Shipped code stores these rules to pick which elements are read on matching pages.

01EvidenceCAUSE EFFECT
What actually happens
You did this

You open a job or profile page with the extension enabled.

The extension did this

The extension loads server-provided selector rules and uses matching rules to read page content.

02EvidenceNETWORK CAPTURE
Captured request
GEThttps://us-central1-faang-path.cloudfunctions.net/faangpathV2/misc/selectors
HTTP 200 observed during dynamic analysis. Related GET requests to /misc/formSelectors, /misc/userDataPath, /misc/extensionConfig, and /misc/profileSelectors also returned HTTP 200.
03EvidenceFIELD TABLE
Remote configuration fields used by the page reader
FieldValueWhy it matters
Selector endpoint
https://us-central1-faang-path.cloudfunctions.net/faangpathV2/misc/selectorsTells the extension where to load rules that decide which page text can be read.
Site matcher
linkedin_com_jobs_search_collectionsMatches the current page to a rule set, so only selected sites and layouts are processed.
DOM selector set
websiteSelectors.selectors.descriptionLists the page elements the extension can read once a site rule matches.
Version controls
versionControl.selectorsLets the backend change which selector version the extension should use.
04EvidenceSTORAGE DUMP
What's stored on your device

The selector and configuration responses are kept in local browser storage so later page processing can use them without another prompt.

Locationchrome.storage.local keys 'selectors', 'profileSelectors', 'formSelectors', 'extensionConfig'
Contents (JSON)
{
  "selectors": "res.data.content from /misc/selectors or selectors CDN",
  "formSelectors": "res.data.content from /misc/formSelectors when form-data features are enabled",
  "extensionConfig": "res.data.content from /misc/extensionConfig",
  "profileSelectors": "res.data.content from /misc/profileSelectors or bundled fallback"
}
05EvidenceCODE COMPARE
The code that does this

Shipped code fetches remote selectors and applies them to the DOM

What it actually does
Remote selector/config fetchersdeobfuscated/contentScript.bundle.js
var fetchSelectorsViaCdnOrApi = function (filename, apiPath, expectedVersion) { return MiscService_awaiter(void 0, void 0, void 0, function () {
    var body, _a;
    return MiscService_generator(this, function (_b) {
        switch (_b.label) {
            case 0: return [4 /*yield*/, isSelectorsCdnEnabled()];
            case 1:
                if (!_b.sent()) return [3 /*break*/, 5];
                _b.label = 2;
            case 2:
                _b.trys.push([2, 4, , 5]);
                return [4 /*yield*/, fetchSelectorsJson(filename, expectedVersion)];
            case 3:
                body = _b.sent();
                return [2 /*return*/, wrapAsAxiosResponse(body)];
            case 4:
                _a = _b.sent();
                return [3 /*break*/, 5];
            case 5: return [2 /*return*/, utils_axios.get(apiPath)];
        }
    });
}); };
var getExtensionConfig = function () {
    return utils_axios.get("".concat(miscApiPath, "/extensionConfig"));
};
var getSelectors = function (expectedVersion) {
    return fetchSelectorsViaCdnOrApi(SELECTOR_FILES.selectors, "".concat(miscApiPath, "/selectors"), expectedVersion);
};
var getProfileSelectors = function (expectedVersion) {
    return fetchSelectorsViaCdnOrApi(SELECTOR_FILES.profileSelectors, "".concat(miscApiPath, "/profileSelectors"), expectedVersion);
};
var getFormSelectors = function (expectedVersion) {
    return fetchSelectorsViaCdnOrApi(SELECTOR_FILES.formSelectors, "".concat(miscApiPath, "/formSelectors"), expectedVersion);
};
var getUserDataPath = function () {
    return utils_axios.get("".concat(miscApiPath, "/userDataPath"));
};
var MiscService = {
    getExtensionConfig: getExtensionConfig,
    getSelectors: getSelectors,
    getProfileSelectors: getProfileSelectors,
    getFormSelectors: getFormSelectors,
    getUserDataPath: getUserDataPath,
};
Selector-driven job page extractiondeobfuscated/contentScript.bundle.js
var extractAndTransformJobDetails = function extractAndTransformJobDetails(tabUrl, selectors) {
  var document = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : document;
  try {
    var baseDocument = document;

    // Find website selectors
    var websiteSelectors = selectors === null || selectors === void 0 ? void 0 : selectors.find(function (data) {
      return (data === null || data === void 0 ? void 0 : data.id) && safeUrlMatch(tabUrl, data.id) && (data !== null && data !== void 0 && data.is_ai_generated ? data === null || data === void 0 ? void 0 : data.is_active : true);
    });
    if (!websiteSelectors) {
      return 'Site Not Supported';
    }
    var documentToExtract = baseDocument;
    if (websiteSelectors !== null && websiteSelectors !== void 0 && websiteSelectors.iframeSelector) {
      var iframeDoc = getIframeDocument(websiteSelectors.iframeSelector);
      if (iframeDoc) {
        documentToExtract = iframeDoc;
      }
    }

    // EXTRACT
    var extractor = getExtractor(websiteSelectors.name);
    var extractedDataFromWebsite = extractor.extract(websiteSelectors.selectors, documentToExtract);

    // Treat empty or literal "undefined" as missing for fallback
    var descriptionMissingStandalone = !(extractedDataFromWebsite !== null && extractedDataFromWebsite !== void 0 && extractedDataFromWebsite.description) || String(extractedDataFromWebsite.description).trim().toLowerCase() === 'undefined';
    // Fallback for LinkedIn search/collections pages:
    // sometimes the job details are rendered outside the interop iframe.
    if (websiteSelectors !== null && websiteSelectors !== void 0 && websiteSelectors.iframeSelector && websiteSelectors.name === 'linkedin_com_jobs_search_collections' && (descriptionMissingStandalone || !(extractedDataFromWebsite !== null && extractedDataFromWebsite !== void 0 && extractedDataFromWebsite.company) || !(extractedDataFromWebsite !== null && extractedDataFromWebsite !== void 0 && extractedDataFromWebsite.role) || !(extractedDataFromWebsite !== null && extractedDataFromWebsite !== void 0 && extractedDataFromWebsite.location))) {
      try {
        var fallbackExtracted = extractor.extract(websiteSelectors.selectors, baseDocument);
        var fallbackDesc = fallbackExtracted !== null && fallbackExtracted !== void 0 && fallbackExtracted.description && String(fallbackExtracted.description).trim().toLowerCase() !== 'undefined' ? fallbackExtracted.description : null;
        if (descriptionMissingStandalone && fallbackDesc) {
          extractedDataFromWebsite.description = fallbackDesc;
        }
        if (!extractedDataFromWebsite.company && fallbackExtracted !== null && fallbackExtracted !== void 0 && fallbackExtracted.company) {
          extractedDataFromWebsite.company = fallbackExtracted.company;
        }
        if (!extractedDataFromWebsite.role && fallbackExtracted !== null && fallbackExtracted !== void 0 && fallbackExtracted.role) {
          extractedDataFromWebsite.role = fallbackExtracted.role;
        }
        if (!extractedDataFromWebsite.location && fallbackExtracted !== null && fallbackExtracted !== void 0 && fallbackExtracted.location) {
          extractedDataFromWebsite.location = fallbackExtracted.location;
        }
      } catch (err) {
        // LinkedIn search/collections fallback is best-effort.
      }
    }

    // TRANSFORM
    var transformer = getTransformer(websiteSelectors.name);
    if (!transformer) {
      return {};
    }
    var transformedData = transformer.transform(extractedDataFromWebsite);
    if (!transformedData) {
      return {};
    }
    var finalData = new JobDetailToModelTransformer().transform(transformedData);

    // Add company name if available
    var companyName = websiteSelectors === null || websiteSelectors === void 0 ? void 0 : websiteSelectors.company_name;
    if (companyName) {
      try {
        finalData.company = companyName;
        finalData.companyPayload = extractAndTransformJobDetails_objectSpread(extractAndTransformJobDetails_objectSpread({}, finalData.companyPayload), {}, {
          id: companyName,
          name: companyName
        });
      } catch (err) {
        // Setting company name is best-effort.
      }
    }
    return finalData;
  } catch (err) {
    return {};
  }
};
06EvidenceTHIRD PARTY LIST
Hosts involved in selector delivery
  • us-central1-faang-path.cloudfunctions.net

    Careerflow Cloud Functions host serving misc selector and configuration endpoints.

  • app.careerflow.ai

    Careerflow web app host used as the CDN base for static selector JSON files.

SeverityMEDIUM
ClassUNWANTED
TypeUnexpected
CWECWE-359
SourceAI SANDBOX

Referral cookie read during upgrade checkout

The Careerflow upgrade flow asks the extension for the `rewardful.referral` cookie from careerflow.ai.

The background reads it via the cookies API so checkout includes it as `client_reference_id`.

Prior analysis missed it; never triggered.

01EvidenceCAUSE EFFECT
What actually happens
You did this

When you start an upgrade checkout flow, the page asks the extension for a Careerflow referral identifier.

The extension did this

The background process reads the `rewardful.referral` cookie for Careerflow and returns its value to the requesting page code.

02EvidenceFIELD TABLE
Referral fields moved through the checkout flow
FieldValueWhy it matters
Careerflow referral cookie
rewardful.referral=%7B%22id%22%3A%22ref_7J8K2M%22%7D (illustrative)This can connect the checkout flow to the referral source stored for your Careerflow browser session.
Referral ID
ref_7J8K2M (illustrative)This is the decoded identifier that the checkout flow uses as a customer reference.
Checkout reference field
client_reference_id=ref_7J8K2M (illustrative)This places the referral identifier into the payment checkout request.
Success page
https://careerflow.ai/thank-you-premiumThis is where the checkout flow sends you after the payment provider flow completes.
03EvidenceCORRESPONDENCE
How the referral cookie moves through the flow
WhenYou didExtension did
same action
user
You click an upgrade plan.
content_script
The page code sends `getRewardfulClientId` to the extension.
message response
content_script
The content script asks for the referral identifier.
service_worker
The background process reads the Careerflow referral cookie.
after response
service_worker
The background process returns the cookie value.
content_script
The page code decodes the ID and adds it to the checkout request.
04EvidenceCODE COMPARE
The code that does this

Cookie read and checkout reference code

What it actually does
Readable background cookie readerdeobfuscated/background.bundle.js
if (msg.getRewardfulClientId) {
  chrome.cookies.get({
    url: 'https://www.careerflow.ai',
    name: 'rewardful.referral'
  }).then((res) => {
    sendResponse(res?.value);
  }).catch(() => {
    sendResponse(undefined);
  });
  return true;
}
Readable upgrade checkout callerdeobfuscated/contentScript.bundle.js
async function handleSubscribe(priceId) {
  chrome.runtime.sendMessage({
    analytics: {
      eventAction: 'Go to stripe payments page for upgrade'
    }
  });
  setPaymentLoading(true);
  chrome.runtime.sendMessage({ getRewardfulClientId: true }, (res) => {
    const referenceId = res ? JSON.parse(decodeURIComponent(res)).id : undefined;
    utils_axios.post(stripeCheckoutApiPath, {
      price: priceId,
      success_url: 'https://careerflow.ai/thank-you-premium',
      client_reference_id: referenceId
    }).then((res) => {
      window.open(res.data.checkout_session.url);
    }).finally(() => {
      setPaymentLoading(false);
      closeModal();
    });
  });
}
Updated 30 September 2026iadokddofjgcgjpjlfhngclhpmaelnli