Skip to content

feat: Chrome extension for one-click resume tailoring#821

Open
gingeekrishna wants to merge 2 commits into
srbhr:mainfrom
gingeekrishna:feature/chrome-extension
Open

feat: Chrome extension for one-click resume tailoring#821
gingeekrishna wants to merge 2 commits into
srbhr:mainfrom
gingeekrishna:feature/chrome-extension

Conversation

@gingeekrishna

@gingeekrishna gingeekrishna commented May 31, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #772

Adds a Manifest V3 Chrome extension that lets users tailor their resume directly from any job posting page — no copy-pasting required.

What's included

apps/chrome-extension/

  • manifest.json — MV3 extension; permissions: activeTab, storage, scripting; no broad host permissions beyond localhost
  • popup/popup.html — clean popup UI (360 px wide, Swiss International Style palette)
  • popup/popup.css — flat design: #F0F0E8 canvas, 1 px black borders, no border radius, Signal Green / Alert Red / Alert Orange for score colours
  • popup/popup.js — full tailor flow:
    1. Injects extractJobFromPage() into the active tab via chrome.scripting.executeScript to pull job title, company, and description from LinkedIn, Indeed, Greenhouse, Lever, Glassdoor, and Workday
    2. One-time setup: upload master resume PDF/DOCX (calls POST /api/v1/resumes/upload) or paste an existing Resume ID; persisted to chrome.storage.local
    3. On "Tailor Resume": POST /api/v1/jobs/uploadPOST /api/v1/resumes/improve
    4. Displays ATS score (colour-coded), missing-keywords chips, and recommendations from the response
    5. "Open in Resume Matcher" button opens the frontend on :3000 for PDF download

apps/backend/app/base.py

  • Adds allow_origin_regex=r"chrome-extension://.*" to CORSMiddleware so the extension popup can reach the local backend without CORS errors (extension IDs are dynamic, making an exact-match list impractical)

How to load the extension locally

  1. Open Chrome → chrome://extensions
  2. Enable Developer mode
  3. Click Load unpacked → select apps/chrome-extension/
  4. Navigate to any supported job posting and click the extension icon

Test plan

  • Load extension in developer mode
  • Navigate to a LinkedIn / Indeed / Greenhouse / Lever job posting — confirm title, company, and description are detected
  • First-run: upload a PDF resume from the popup — confirm resume ID is saved and displayed
  • Click Tailor Resume — confirm loading states appear and result panel shows score + keywords
  • Verify Open in Resume Matcher opens localhost:3000
  • Verify Tailor Another returns to the main panel
  • Verify existing backend routes are unaffected by the CORS change

Out of scope (follow-up)

  • Auto-filling job application forms (Workday/Greenhouse submit forms) — each ATS has a different structure; deferred
  • Exportable ATS report PDF — can be built on top once the extension is merged

Summary by cubic

Adds a Manifest V3 Chrome extension for one‑click resume tailoring and makes backend CORS opt‑in via EXTENSION_CORS_ORIGIN (disabled by default). Implements #772.

  • New Features

    • Extension at apps/chrome-extension/ with popup UI; permissions: activeTab, storage, scripting; host_permissions limited to http://localhost/* and http://127.0.0.1/*.
    • Injected extractor pulls title/company/description from LinkedIn, Indeed, Greenhouse, Lever, Glassdoor, and Workday.
    • First-time setup: upload master resume (POST /api/v1/resumes/upload) or paste Resume ID; backend URL and Resume ID saved in chrome.storage.local.
    • Tailor flow: POST /api/v1/jobs/uploadPOST /api/v1/resumes/improve; shows ATS score, missing keywords, and recommendations; “Open in Resume Matcher” opens :3000.
    • Backend CORS and safety: only adds allow_origin_regex when EXTENSION_CORS_ORIGIN is set; default is None. Popup validates backend URL (must be http/https) and restricts to localhost/127.0.0.1 to match manifest scope.
  • Migration

    • Chrome → chrome://extensions → enable Developer mode → Load unpacked → select apps/chrome-extension/.
    • Run the backend on localhost and the web app on :3000, open a supported job page, then click the extension.
    • Optional: set EXTENSION_CORS_ORIGIN to your exact extension ID for shared/production; for local dev you can use chrome-extension://.* (default is None).

Written for commit ee9b8ec. Summary will update on new commits.

Review in cubic

Copilot AI review requested due to automatic review settings May 31, 2026 08:15

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds a Chrome extension popup UI to detect job descriptions from common job boards and call the backend to upload jobs/tailor resumes, plus backend CORS tweaks to allow calls from the extension.

Changes:

  • Introduces MV3 Chrome extension popup (HTML/CSS/JS) with settings, resume upload, job extraction, and results UI.
  • Adds extension manifest with required permissions/host permissions.
  • Updates backend CORS middleware to accept chrome-extension:// origins.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
apps/chrome-extension/popup/popup.js Implements popup state, job extraction via chrome.scripting, backend calls, and UI rendering
apps/chrome-extension/popup/popup.html Adds popup layout for settings, main flow, and results
apps/chrome-extension/popup/popup.css Styles popup UI and loading overlay
apps/chrome-extension/manifest.json Defines MV3 extension permissions and popup entrypoint
apps/backend/app/base.py Allows CORS requests originating from Chrome extensions

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/backend/app/base.py Outdated
Comment on lines 46 to 52
app.add_middleware(
CORSMiddleware,
allow_origins=settings.ALLOWED_ORIGINS,
allow_origin_regex=r"chrome-extension://.*",
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
Comment on lines +6 to +10
"permissions": ["activeTab", "storage", "scripting"],
"host_permissions": [
"http://localhost/*",
"http://127.0.0.1/*"
],
Comment thread apps/chrome-extension/popup/popup.js Outdated
Comment on lines +229 to +235
ats.missing_keywords.forEach((kw, i) => {
const chip = document.createElement('span');
chip.className = 'chip';
chip.textContent = kw;
chip.setAttribute('key', `kw-${i}`);
keywordsChips.appendChild(chip);
});
Comment thread apps/chrome-extension/popup/popup.html Outdated
Comment on lines +24 to +27
<label>Resume</label>
<p class="hint">Upload your master resume once, or paste an existing Resume ID.</p>
<input id="input-resume-file" type="file" accept=".pdf,.docx" />
<button id="btn-upload-resume">Upload Resume</button>
Comment thread apps/chrome-extension/popup/popup.html Outdated
<body>
<header>
<span class="logo">Resume Matcher</span>
<button id="btn-settings-toggle" aria-label="Settings">&#9881;</button>
Comment thread apps/chrome-extension/popup/popup.html Outdated
<label>Resume</label>
<p class="hint">Upload your master resume once, or paste an existing Resume ID.</p>
<input id="input-resume-file" type="file" accept=".pdf,.docx" />
<button id="btn-upload-resume">Upload Resume</button>
Comment thread apps/chrome-extension/popup/popup.html Outdated

<label for="input-resume-id">Resume ID</label>
<input id="input-resume-id" type="text" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" />
<button id="btn-save-settings">Save</button>
Comment thread apps/chrome-extension/popup/popup.html Outdated
<span id="badge-resume-id" class="mono"></span>
</div>

<button id="btn-tailor" disabled>Tailor Resume</button>
Comment thread apps/chrome-extension/popup/popup.html Outdated
Comment on lines +76 to +77
<button id="btn-open-app">Open in Resume Matcher</button>
<button id="btn-tailor-again" class="secondary">Tailor Another</button>
Comment thread apps/backend/app/base.py Outdated
app.add_middleware(
CORSMiddleware,
allow_origins=settings.ALLOWED_ORIGINS,
allow_origin_regex=r"chrome-extension://.*",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: Overly permissive CORS regex allows ANY Chrome extension to access the backend

allow_origin_regex=r"chrome-extension://.*" matches ALL Chrome extension origins. Any malicious extension installed by a user could make authenticated requests to this backend with allow_credentials=True.

Recommend using a specific extension ID pattern: r"chrome-extension://<your-extension-id>" or moving the extension ID to settings/config.

? job.description.slice(0, 280) + (job.description.length > 280 ? '…' : '')
: '';
msgNoJob.hidden = Boolean(hasContent);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: No timeout on fetch requests - backend hangs will freeze the UI indefinitely

All fetch() calls in apiPost and apiUploadFile lack a timeout mechanism. If the backend becomes unresponsive, the extension popup will hang forever.

Consider wrapping fetch with AbortController and a timeout (e.g., 30 seconds).

resultKeywords.hidden = true;
}

if (ats?.recommendations?.length) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: No file type or size validation before upload

apiUploadFile accepts any file from the file input. Despite accept=".pdf,.docx" in the HTML (which is only a UI hint, not enforced), users could select any file type.

Validate file.type or check the file extension before sending to the backend.

const jobId = jobRes.job_id;

setLoading(true, 'Tailoring resume…');
const improveRes = await apiPost('/api/v1/resumes/improve', {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Partial failure state - job created but improve fails leaves dangling data

If apiPost('/api/v1/jobs/upload') succeeds but the subsequent apiPost('/api/v1/resumes/improve') fails, a job is created on the backend but the user never sees results. Consider adding cleanup logic or at least a clearer error message indicating the job was created.

} catch {
chrome.tabs.create({ url: 'http://localhost:3000' });
}
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: btnTailorAgain doesn't clear state.jobData - stale job info may display

Clicking "Tailor Another" returns to the main panel but state.jobData retains the previous job. The UI will show the old job title/company/description until a new job is extracted.

Consider adding state.jobData = null; renderJobData(null); here.

if (tab?.id) {
const [result] = await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: extractJobFromPage,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Silent error catch in init() - extraction failures are invisible

The catch {} block silently swallows all errors during job extraction. If chrome.scripting.executeScript fails for a non-internal-page reason (e.g., content blocked, tab closed), the user sees no feedback.

Consider logging to console or showing a subtle hint when extraction fails for unexpected reasons.

const el = document.querySelector(sel);
if (el && el.innerText && el.innerText.trim()) return el.innerText.trim();
} catch (_) {
// ignore invalid selectors

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: No content size limit - very long job descriptions could exceed backend/API limits

getText() extracts ALL innerText from description containers. Job descriptions on some sites can be 10,000+ characters. Consider truncating description before storing in state.jobData or before sending to the backend.

@kilo-code-bot

kilo-code-bot Bot commented May 31, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: 7 Issues Found (1 CRITICAL, 4 WARNING, 2 SUGGESTION) | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 4
SUGGESTION 2
Issue Details (click to expand)

CRITICAL

File Line Issue
apps/backend/app/base.py 49 Overly permissive CORS regex chrome-extension://.* allows ANY Chrome extension to access the backend with credentials

WARNING

File Line Issue
apps/chrome-extension/popup/popup.js 213 No timeout on fetch requests - backend hangs will freeze UI indefinitely
apps/chrome-extension/popup/popup.js 241 No file type or size validation before upload - accept attribute is UI-only
apps/chrome-extension/popup/popup.js 317 Partial failure state - job created but improve fails leaves dangling data
apps/chrome-extension/popup/popup.js 86 No content size limit - very long job descriptions could exceed backend limits

SUGGESTION

File Line Issue
apps/chrome-extension/popup/popup.js 341 btnTailorAgain doesn't clear state.jobData - stale job info may display
apps/chrome-extension/popup/popup.js 368 Silent error catch in init() - extraction failures are invisible
Other Observations (not in diff)

Issues found in unchanged code or structural concerns that cannot receive inline comments:

File Line Issue
apps/chrome-extension/popup/popup.js 350 btnOpenApp hardcodes port :3000 for frontend URL - will break if frontend runs on different port or in production
apps/chrome-extension/manifest.json 7-9 Host permissions limited to localhost only - no production host permissions configured
apps/chrome-extension/popup/popup.html 52 No loading overlay element in HTML - it's created dynamically in JS, which could cause flicker
apps/chrome-extension/popup/popup.js 100-160 Job extraction selectors are brittle and will break with any platform UI redesign - no fallback retry mechanism
apps/chrome-extension/popup/popup.js 100-160 getText() uses innerText which triggers reflow and may miss content in shadow DOMs or lazy-loaded sections
Files Reviewed (5 files)
  • apps/backend/app/base.py - 1 CRITICAL issue (CORS regex)
  • apps/chrome-extension/manifest.json - 1 observation (localhost-only hosts)
  • apps/chrome-extension/popup/popup.html - 1 observation (loading overlay)
  • apps/chrome-extension/popup/popup.css - No issues
  • apps/chrome-extension/popup/popup.js - 6 issues (timeouts, validation, partial failure, stale state, silent errors, content size)

Reviewed by qwen3.6-plus · 158,201 tokens

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7 issues found across 5 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/backend/app/base.py">

<violation number="1" location="apps/backend/app/base.py:49">
P1: Custom agent: **Flag Security Vulnerabilities**

CORS configuration trusts every chrome-extension:// origin, creating an overly broad cross-origin access surface with credentials enabled</violation>
</file>

<file name="apps/chrome-extension/popup/popup.js">

<violation number="1" location="apps/chrome-extension/popup/popup.js:164">
P2: The empty `catch {}` block silently swallows all errors during job extraction, including unexpected failures (content blocked, tab closed, permissions issues). At minimum, log to console for debuggability; ideally show a subtle hint when extraction fails for non-internal-page reasons.</violation>

<violation number="2" location="apps/chrome-extension/popup/popup.js:233">
P3: Setting a `key` attribute on a DOM element is a React concept with no semantic meaning in vanilla JS. This is likely carried over from React patterns and will confuse maintainers. Remove it or use `chip.dataset.key = ...` if you need an identifier for testing/automation.</violation>
</file>

<file name="apps/chrome-extension/manifest.json">

<violation number="1" location="apps/chrome-extension/manifest.json:8">
P1: The extension allows users to configure a custom `backendUrl` in settings, but `host_permissions` only grants access to `localhost` and `127.0.0.1`. In Manifest V3, `fetch()` from extension pages to origins not listed in `host_permissions` will be blocked by the browser. Either restrict the settings UI to only accept localhost URLs, or document that users must modify the manifest for non-local backends.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread apps/backend/app/base.py Outdated
app.add_middleware(
CORSMiddleware,
allow_origins=settings.ALLOWED_ORIGINS,
allow_origin_regex=r"chrome-extension://.*",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Custom agent: Flag Security Vulnerabilities

CORS configuration trusts every chrome-extension:// origin, creating an overly broad cross-origin access surface with credentials enabled

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/backend/app/base.py, line 49:

<comment>CORS configuration trusts every chrome-extension:// origin, creating an overly broad cross-origin access surface with credentials enabled</comment>

<file context>
@@ -46,6 +46,7 @@ def create_app() -> FastAPI:
     app.add_middleware(
         CORSMiddleware,
         allow_origins=settings.ALLOWED_ORIGINS,
+        allow_origin_regex=r"chrome-extension://.*",
         allow_credentials=True,
         allow_methods=["*"],
</file context>

@@ -0,0 +1,15 @@
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: The extension allows users to configure a custom backendUrl in settings, but host_permissions only grants access to localhost and 127.0.0.1. In Manifest V3, fetch() from extension pages to origins not listed in host_permissions will be blocked by the browser. Either restrict the settings UI to only accept localhost URLs, or document that users must modify the manifest for non-local backends.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/chrome-extension/manifest.json, line 8:

<comment>The extension allows users to configure a custom `backendUrl` in settings, but `host_permissions` only grants access to `localhost` and `127.0.0.1`. In Manifest V3, `fetch()` from extension pages to origins not listed in `host_permissions` will be blocked by the browser. Either restrict the settings UI to only accept localhost URLs, or document that users must modify the manifest for non-local backends.</comment>

<file context>
@@ -0,0 +1,15 @@
+  "description": "Detect job descriptions on job boards and tailor your resume in one click.",
+  "permissions": ["activeTab", "storage", "scripting"],
+  "host_permissions": [
+    "http://localhost/*",
+    "http://127.0.0.1/*"
+  ],
</file context>

Comment thread apps/chrome-extension/popup/popup.js Outdated
Comment thread apps/chrome-extension/popup/popup.js Outdated
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
} catch {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The empty catch {} block silently swallows all errors during job extraction, including unexpected failures (content blocked, tab closed, permissions issues). At minimum, log to console for debuggability; ideally show a subtle hint when extraction fails for non-internal-page reasons.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/chrome-extension/popup/popup.js, line 164:

<comment>The empty `catch {}` block silently swallows all errors during job extraction, including unexpected failures (content blocked, tab closed, permissions issues). At minimum, log to console for debuggability; ideally show a subtle hint when extraction fails for non-internal-page reasons.</comment>

<file context>
@@ -0,0 +1,380 @@
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify(body),
+    });
+  } catch {
+    throw new Error(
+      `Cannot reach backend at ${state.backendUrl}. Make sure it is running (uv run uvicorn app.main:app --port 8000).`
</file context>

Comment thread apps/chrome-extension/popup/popup.js
Comment thread apps/chrome-extension/popup/popup.js Outdated
Comment thread apps/chrome-extension/popup/popup.js Outdated
const chip = document.createElement('span');
chip.className = 'chip';
chip.textContent = kw;
chip.setAttribute('key', `kw-${i}`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Setting a key attribute on a DOM element is a React concept with no semantic meaning in vanilla JS. This is likely carried over from React patterns and will confuse maintainers. Remove it or use chip.dataset.key = ... if you need an identifier for testing/automation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/chrome-extension/popup/popup.js, line 233:

<comment>Setting a `key` attribute on a DOM element is a React concept with no semantic meaning in vanilla JS. This is likely carried over from React patterns and will confuse maintainers. Remove it or use `chip.dataset.key = ...` if you need an identifier for testing/automation.</comment>

<file context>
@@ -0,0 +1,380 @@
+      const chip = document.createElement('span');
+      chip.className = 'chip';
+      chip.textContent = kw;
+      chip.setAttribute('key', `kw-${i}`);
+      keywordsChips.appendChild(chip);
+    });
</file context>

@gingeekrishna gingeekrishna force-pushed the feature/chrome-extension branch 2 times, most recently from f0ad45b to 55f56f8 Compare May 31, 2026 08:24
Comment thread apps/backend/app/config.py Outdated
# Restrict to a specific extension ID (e.g. "chrome-extension://abcdef...") via
# EXTENSION_CORS_ORIGIN env var. Defaults to allowing any local extension for
# developer convenience; set an exact ID in shared / production environments.
extension_cors_origin: str = r"chrome-extension://.*"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: Overly permissive CORS regex allows ANY Chrome extension to access the backend with credentials

The default r"chrome-extension://.*" matches ANY Chrome extension origin, not just this specific extension. Combined with allow_credentials=True, this means any malicious Chrome extension installed by the user can make authenticated requests to your backend (including resume uploads, improvements, etc.).

The comment says to set EXTENSION_CORS_ORIGIN for production, but the default is insecure for ANY shared environment. The default should be empty/disabled ("" or None) and developers should explicitly enable it with their extension ID.


const resultData = improveRes.data ?? improveRes;
panelMain.hidden = true;
panelResult.hidden = false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Partial failure state - job created but improve fails leaves dangling data

If apiPost('/api/v1/resumes/improve', ...) fails, the job (created via apiPost('/api/v1/jobs/upload', ...)) is never cleaned up. This leaves orphaned job records in the database. The jobId is captured but there's no cleanup attempt on failure.

Consider adding a delete/cleanup call in the catch block or documenting this as expected behavior.


btnOpenApp.addEventListener('click', () => {
try {
const parsed = new URL(state.backendUrl);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: btnOpenApp hardcodes port :3000 for frontend URL - will break in production

If state.backendUrl is http://api.example.com (no port), this creates http://api.example.com:3000 which is likely incorrect. If the frontend runs on a different port (e.g., localhost:3001 for dev), this opens the wrong URL.

Consider making the frontend URL configurable in settings or inferring it from the backend URL more intelligently.

btnTailorAgain.addEventListener('click', () => {
panelResult.hidden = true;
panelMain.hidden = false;
// Re-extract job from the active tab so stale job data doesn't linger

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Silent error catch in btnTailorAgain - extraction failures are invisible

The .catch(() => {}) completely swallows any errors during job re-extraction. If executeScript fails (e.g., tab navigated away, permissions issue), the user sees no feedback and state.jobData may be left in an inconsistent state.

At minimum, log the error or show a message to the user.

- Manifest V3 extension with activeTab, storage, and scripting permissions
- Popup extracts job title, company, and description from LinkedIn, Indeed,
  Greenhouse, Lever, Glassdoor, and Workday using injected content function
- One-time setup: upload master resume PDF/DOCX or paste existing resume ID;
  settings persisted to chrome.storage.local
- Tailor flow: POST job description to /api/v1/jobs/upload, then POST to
  /api/v1/resumes/improve; shows ATS score, missing keywords, and
  recommendations from the response
- "Open in Resume Matcher" button opens the frontend (localhost:3000) for
  PDF download
- Backend: add allow_origin_regex for chrome-extension://* to CORSMiddleware
  so the extension popup can reach the local backend without CORS errors
@gingeekrishna gingeekrishna force-pushed the feature/chrome-extension branch from 55f56f8 to 23a3df3 Compare May 31, 2026 08:27
- config.py: change extension_cors_origin default to None so shared/server
  deployments are unaffected; opt-in by setting EXTENSION_CORS_ORIGIN env var
- main.py: only add allow_origin_regex to CORSMiddleware when the setting
  is non-empty, keeping the default config secure
- popup.js: validate backend URL format (must be http/https) and restrict
  to localhost/127.0.0.1 with a clear error when a non-local URL is entered,
  matching the manifest host_permissions scope
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Chrome extension for tailoring and uploading resume

2 participants