<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Khaled Mahmud</title>
    <description>The latest articles on DEV Community by Khaled Mahmud (@khaled_kmp).</description>
    <link>https://dev.to/khaled_kmp</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3946440%2Ffff3be78-b9b1-44cf-86d0-bdca446d405e.jpg</url>
      <title>DEV Community: Khaled Mahmud</title>
      <link>https://dev.to/khaled_kmp</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/khaled_kmp"/>
    <language>en</language>
    <item>
      <title>The Privacy Bug in My First Chrome Extension (And How to Avoid It)</title>
      <dc:creator>Khaled Mahmud</dc:creator>
      <pubDate>Mon, 25 May 2026 19:23:24 +0000</pubDate>
      <link>https://dev.to/khaled_kmp/the-privacy-bug-in-my-first-chrome-extension-and-how-to-avoid-it-1ni</link>
      <guid>https://dev.to/khaled_kmp/the-privacy-bug-in-my-first-chrome-extension-and-how-to-avoid-it-1ni</guid>
      <description>&lt;p&gt;Day 4 of building ReFind, and I found a bug that I'm embarrassed took me&lt;br&gt;
this long to find.&lt;/p&gt;

&lt;p&gt;My clipboard listener Chrome extension was capturing everything I copied.&lt;br&gt;
Not just URLs — passwords, phone numbers, random text, partial sentences.&lt;br&gt;
All of it was being sent to my webhook endpoint without any validation.&lt;/p&gt;

&lt;p&gt;Here's how I fixed it and why this validation step should be first in&lt;br&gt;
any clipboard-touching Chrome extension.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Problem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The clipboard listener flow looked like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copy event fires&lt;/li&gt;
&lt;li&gt;Extension reads clipboard&lt;/li&gt;
&lt;li&gt;Extension sends to webhook&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 3 had no filter. Every copy event, every piece of content, went through.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix: URL Validation Regex&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I added a validation check as the absolute first step in the pipeline,&lt;br&gt;
before any processing, storage, or network calls:&lt;/p&gt;

&lt;p&gt;const URL_PATTERN = /^https?:\/\/[^\s$.?#].[^\s]*$/i;&lt;/p&gt;

&lt;p&gt;function isValidUrl(text) {&lt;br&gt;
  return URL_PATTERN.test(text?.trim());&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// In the event handler:&lt;br&gt;
const clipboardText = await readClipboard();&lt;br&gt;
if (!isValidUrl(clipboardText)) return; // Drop immediately&lt;/p&gt;

&lt;p&gt;This check happens before anything else. If the content isn't a URL,&lt;br&gt;
the handler returns immediately. Nothing is logged, nothing is sent,&lt;br&gt;
nothing is stored. The clipboard data is discarded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why This Is a Privacy Issue, Not Just a Bug&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An extension that captures clipboard content without validation is&lt;br&gt;
functionally capturing everything the user copies. In a world where&lt;br&gt;
users copy passwords, OTPs, sensitive messages, and personal information —&lt;br&gt;
this is a meaningful privacy problem.&lt;/p&gt;

&lt;p&gt;Chrome's extension permission model requires justifying clipboard access&lt;br&gt;
in your store listing. An extension that uses that access broadly (capturing&lt;br&gt;
everything) and narrowly describes itself (URL collector) is at risk of&lt;br&gt;
policy violations, user trust issues, and justified negative reviews.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Validation Should Be First, Not Last&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The instinct when building is to "add validation later." This is the wrong&lt;br&gt;
order for clipboard extensions. Validate at the earliest possible point&lt;br&gt;
in the pipeline — before you do anything with the data.&lt;/p&gt;

&lt;p&gt;If you're building something that touches the clipboard, the first line&lt;br&gt;
of your handler should be a validity check that drops everything you&lt;br&gt;
don't intend to process.&lt;/p&gt;

&lt;p&gt;This one addition changes the security profile of the extension entirely&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>javascript</category>
      <category>privacy</category>
      <category>security</category>
    </item>
    <item>
      <title>Building a Clipboard Listener Chrome Extension in Manifest V3: What I Learned the Hard Way</title>
      <dc:creator>Khaled Mahmud</dc:creator>
      <pubDate>Fri, 22 May 2026 16:26:51 +0000</pubDate>
      <link>https://dev.to/khaled_kmp/building-a-clipboard-listener-chrome-extension-in-manifest-v3what-i-learned-the-hard-way-126l</link>
      <guid>https://dev.to/khaled_kmp/building-a-clipboard-listener-chrome-extension-in-manifest-v3what-i-learned-the-hard-way-126l</guid>
      <description>&lt;p&gt;When I started building ReFind — a Chrome extension that captures URLs&lt;br&gt;
as you copy them — I assumed the clipboard listener would be the easy part.&lt;br&gt;
It wasn't. Here's what actually happened and how I solved it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Goal&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every time the user copies a URL (Cmd+C / Ctrl+C on a link), the extension&lt;br&gt;
should silently capture it, validate it's actually a URL, and process it.&lt;br&gt;
No popup interaction required. Fully automatic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Background Scripts Are Tricky in MV3&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Manifest V3 replaced persistent background pages with service workers.&lt;br&gt;
Unlike a background page that stays alive as long as the browser is open,&lt;br&gt;
a service worker can be terminated by Chrome after ~30 seconds of inactivity.&lt;/p&gt;

&lt;p&gt;This creates a problem: if you store anything in memory (variables, state),&lt;br&gt;
it can vanish between events. The fix: persist everything to&lt;br&gt;
chrome.storage.local immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Clipboard Access Problem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's the tricky part: you can't directly access navigator.clipboard&lt;br&gt;
from a service worker context. Chrome restricts clipboard access to require&lt;br&gt;
an active user gesture (a click, keypress, etc.) — and service workers&lt;br&gt;
don't have a DOM or event context for this.&lt;/p&gt;

&lt;p&gt;The solution I landed on:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Listen for the copy keyboard shortcut using chrome.commands API&lt;/li&gt;
&lt;li&gt;Inject a content script into the active tab (which has DOM access)&lt;/li&gt;
&lt;li&gt;Have the content script read the clipboard and message the
background script with the result&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Content script → background script communication via chrome.runtime.sendMessage().&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;URL Validation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once I had clipboard access working, I needed to filter for URLs only.&lt;br&gt;
A simple regex check before processing:&lt;/p&gt;

&lt;p&gt;const isUrl = /^https?:\/\/.+/.test(text);&lt;/p&gt;

&lt;p&gt;This prevents capturing phone numbers, random copied text, passwords, etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I'd Do Differently&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If I were starting over, I'd add the URL validation earlier in the pipeline —&lt;br&gt;
ideally at the content script level before sending to the background. Less&lt;br&gt;
round-tripping, and it prevents unnecessary message passing.&lt;/p&gt;

&lt;p&gt;The one thing I underestimated: the permission model in MV3 is strict and&lt;br&gt;
intentionally so. Read the permission docs carefully before you assume&lt;br&gt;
something will just work.&lt;/p&gt;

&lt;p&gt;Questions? I'm happy to go deeper on any part of this.&lt;/p&gt;

</description>
      <category>devjournal</category>
      <category>javascript</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
