Is JobOwl – AI Resume & ATS Cheat Code safe?
JobOwl sends the visible text from the active tab and its frames to api.jobowl.co when generating resumes or cover letters.
When a user asks JobOwl to generate an optimized resume or cover letter, the extension reads visible text from the active tab across its frames. It sends that page text, along with URL and frame details, to JobOwl's API endpoints even though the recorded claim says the intended job-board URL filter is not enforced.
Who publishes itMGolebiewski97 - no other listings under this identity
MGolebiewski97 - no other listings under this identity
What this publisher told the store about itself, and the other listings that told it the same thing.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
JobOwl sends active-tab text for resume generation
Using JobOwl's resume or cover-letter action reads visible text from the active tab, including frames, and sends it with the tab URL to JobOwl.
The source computes a job-board URL filter, but it goes unused before frames give up their text.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You ask the extension to generate a resume or cover letter.
This happens from the popup's optimized-resume or cover-letter action.
The extension reads visible text from the active tab and sends it to JobOwl with the tab URL.
The code computes a job-board URL filter but continues with the unfiltered frame list except for Google reCAPTCHA frames.
- Visible page textSenior Product Manager - Remote. Responsibilities include roadmap planning, stakeholder interviews, and enterprise customer research.
This can include the readable text on whatever active page you had open when you requested generated content.
- Active-tab URLhttps://example.com/careers/senior-product-manager
This identifies the page that supplied the text and can reveal the site or listing you were viewing.
- Frame locationorigin=https://example.com, isTopFrame=true, frameId=https://example.com/careers/senior-product-manager
This shows which page frame returned the text, including whether it was the top page or an embedded frame.
- Timezone for cover lettersAmerica/New_York
For cover-letter generation, the request also includes your browser's timezone.
- Content-Type
- application/json
- Content-Type
- application/json
The frame reader, text collector, and API callers
Readable equivalent of the frame enumeration
popup.100f6462.jsconst fetchInnerText = ({ jobBoardsUrls }) => new Promise((resolve) => { chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { const tabId = tabs[0]?.id; if (!tabId) { resolve(undefined); return; } chrome.webNavigation.getAllFrames({ tabId }, (frames) => { if (!frames || frames.length === 0) { resolve(undefined); return; } const responses = []; let completed = 0; const finish = () => { const text = responses.reduce((combined, response) => response.frameInfo?.isTopFrame ? response.innerText + combined : combined + "\n" + response.innerText, "" ); resolve(text || undefined); }; frames.filter((frame) => jobBoardsUrls.some((url) => frame.url.startsWith(url))); const queriedFrames = frames.filter((frame) => !frame.url.startsWith("https://www.google.com/recaptcha/")); queriedFrames.forEach((frame) => { chrome.tabs.sendMessage(tabId, { type: "GET_INNER_TEXT" }, { frameId: frame.frameId }, (response) => { completed++; if (response && response.innerText !== undefined) responses.push(response); if (completed === queriedFrames.length) finish(); }); }); }); });});Readable equivalent of the content-script text collector
content.06af5f40.jschrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message.type === "GET_INNER_TEXT") { try { const innerText = (() => { let node; const excludedTags = new Set(["script", "style", "noscript", "template", "svg", "canvas", "meta", "link", "head", "iframe"]); const isVisible = (element) => { let current = element; while (current) { const style = window.getComputedStyle(current); if (current.hidden || style.display === "none" || style.visibility === "hidden" || style.opacity === "0") return false; current = current.parentElement; } return true; }; const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, { acceptNode: (textNode) => { const parent = textNode.parentElement; if (!parent || excludedTags.has(parent.tagName.toLowerCase())) return NodeFilter.FILTER_REJECT; const value = textNode.nodeValue; return value && value.trim() && isVisible(parent) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; } }); const values = []; while ((node = walker.nextNode())) values.push((node.nodeValue ?? "").trim()); return values.join("\n").replace(/\n{3,}/g, "\n\n").trim(); })(); const frameInfo = getFrameInfo(); sendResponse({ innerText, frameInfo }); } catch (error) { sendResponse({ innerText: "", frameInfo: getFrameInfo(), error: error instanceof Error ? error.message : String(error) }); } return true; }});Readable equivalent of the optimized-resume POST
popup.100f6462.jsasync function postOptimizedResume(payload) { return authorizedFetch(`${JOBOWL_API_BASE_URL}/api/supabase/optimized-resume`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) });}const useGenerateOptimizedResumeMutation = () => { const userInfo = useQuery(userInfoQueryOptions).data; const activeTabUrl = useActiveTab(); return useMutation({ mutationFn: async () => { const description = await fetchInnerText({ jobBoardsUrls: userInfo?.jobBoardsUrls ?? [] }); if (!description || !activeTabUrl) throw Error("Unable to generate resume. Please refresh the current tab and try again."); const response = await postOptimizedResume({ description, url: activeTabUrl }); if (!response.ok) throw Error("Optimised resume generation failed"); } });};Readable equivalent of the cover-letter POST
popup.100f6462.jsasync function postCoverLetter(payload) { const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; return authorizedFetch(`${JOBOWL_API_BASE_URL}/api/supabase/cover-letter`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ...payload, timezone }) });}const useGenerateCoverLetterMutation = () => { const activeTabUrl = useActiveTab(); const userInfo = useQuery(userInfoQueryOptions).data; return useMutation({ mutationFn: async () => { const description = await fetchInnerText({ jobBoardsUrls: userInfo?.jobBoardsUrls ?? [] }); if (!description || !activeTabUrl) throw Error("Unable to generate cover letter. Please refresh the current tab and try again."); const response = await postCoverLetter({ description, url: activeTabUrl }); if (!response.ok) throw Error("Cover letter generation failed"); } });};- api.jobowl.co
Receives resume and cover-letter generation requests containing the collected page text and active-tab URL.
What it can do
Permissions this extension asks for, as declared in version 2.2.1. 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 every secure site you visit
https://*/*
Store data in your browser
storage
Act on the current tab, but only after you click the extension
activeTab
See every page you navigate to, as you navigate to it
webNavigation
Where it sends data
Destinations our analysis observed JobOwl – AI Resume & ATS Cheat Code 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.
- api.jobowl.co
JobOwl – AI Resume & ATS Cheat Code sends data to api.jobowl.co. No other extension we have analysed sends data here.