Is HaramBlur - Blur Haram & NSFW Images & Videos safe?
HaramBlur is medium risk. Dynamic analysis captured HaramBlur rebuilding a diagnostics payload on every settings change and startup, encrypting it with a plaintext-passphrase key, embedded in a Google Form set as the uninstall URL; it reaches Google on page load.…
Who publishes itMohamed Alganzory - 1 other listing from the same operator, none carrying a finding
Mohamed Alganzory - 1 other listing from the same operator, none carrying a finding
What this publisher told the store about itself, and the other listings that told it the same thing.
Same store account
1 other listing published from this account, 30k+ users between them, none of them carrying a finding.
Shared hosts - 1 hostname
Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Uninstall URL encodes browser fingerprint and settings to Google Forms
Dynamic analysis captured HaramBlur rebuilding a diagnostics payload on every settings change and startup, encrypting it with a plaintext-passphrase key, embedded in a Google Form set as the uninstall URL; it reaches Google on page load.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You change any HaramBlur setting, or the extension starts up in your browser.
This covers toggling blur options, updating your whitelist, or the extension loading after a browser restart, update, or fresh install.
The extension rebuilds an encrypted diagnostics payload and registers it as the URL Chrome will open when you uninstall the extension.
The registration happens well before any uninstall -- it re-arms on every qualifying event so the pending URL always reflects your current profile.
The diagnostics payload is AES-GCM encrypted with a key derived from a passphrase string that ships in plaintext inside the extension's own background.js bundle, so it can be decrypted by anyone who has the extension's code. This keeps the payload unreadable at a glance in the URL bar, but it is not a real confidentiality control.
ev=0.6.10;bn=chrome;bp=linux;bl=en-US;bg=en-US;hc=10;dm=16;mo=0;fi=1784663474235;ii=0;se=1;si=1;sv=1;sm=1;sf=1;ui=0;uv=0;sb=1;sg=0;ss=0;sh=0;sp=0;sc=0;su=0;pg=0;cm=0;wc=0;ba=30;st=0.4;th=0.4;md=vZNq2WHrFm7b;pm=free;au=0;ap=0;at=0;rc=0;ta=0;de=0;mm=vZNq2WHrFm7b;bo=0;be=0- Device hardware profile10 CPU cores, 16 GB memory
Combines your CPU core count and installed memory into a fingerprint that narrows down which device you're using.
- Browser, platform, and languagechrome, linux, en-US
Identifies your browser vendor, operating system, and language/locale, adding further detail to your device fingerprint.
- Extension install timestamp1784663474235 (2026-07-21T19:51:14Z)
Records the exact millisecond you first installed the extension.
- Full settings profileblur_male=true, blur_female=true, blur_amount=30, strictness=0.4, whitelist_count=0, password_protection_enabled=false
Every blur, whitelist, and protection setting configured, including password protection status and whitelisted site count.
- Account and subscription stateis_authenticated=false, is_premium=false, is_trial_active=false, protection_mode=free
Whether you're signed in, on a paid plan, or in a trial period.
- Crash and detection-model telemetrycrash_count=0, detection_errors=0, benchmark_model=vZNq2WHrFm7b
Local crash counts and which on-device image-detection model your install last benchmarked.
Diagnostics assembly, AES-GCM encryption, and the trigger re-arming the uninstall URL
Diagnostics payload assembly, encryption, and uninstall URL builder
dist/background.js_e="hb1",Te="hb-uninstall-v1-s6G7X9r2Q1m8Lp4Na0TzK5wV3cY7Df2H",R=null;function Q(){if(!globalThis.crypto?.subtle)throw new Error("Web Crypto API is unavailable");return globalThis.crypto}async function ke(){if(!R){let t=Q(),e=new TextEncoder().encode(Te),r=await t.subtle.digest("SHA-256",e);R=t.subtle.importKey("raw",r,{name:"AES-GCM"},!1,["encrypt","decrypt"])}return R}function j(t){let e=Array.from(t,s=>String.fromCharCode(s)).join("");return btoa(e).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/u,"")}function Ce(t){return t==null||t===""?"":typeof t=="boolean"?t:Array.isArray(t)?t.filter(e=>e!=null&&e!=="").join(","):String(t).trim()}function Me(t,e){let r=Ce(e);return r===""?"":Ie.has(t)?r===!0||r===1||r==="1"||r==="true"?"1":"0":encodeURIComponent(String(r))}function Y(t){return q.map(([e,r])=>{let s=Me(e,t[r]);return s===""?null:`${e}=${s}`}).filter(Boolean).join(";")}async function X(t){let e=String(t||"").trim();if(!e)return"";let r=Q(),s=r.getRandomValues(new Uint8Array(12)),n=await ke(),o=new TextEncoder().encode(e),a=new Uint8Array(await r.subtle.encrypt({name:"AES-GCM",iv:s},n,o));return`${_e}.${j(s)}.${j(a)}`}var De=new Set(["0.6.10"]);function J(t,e){return t===e||t==="0.6.8"?{continueInitialization:!1,showUpdateTour:!1}:{continueInitialization:!0,showUpdateTour:!De.has(e)}}var E=G,K=0,O=0,C=!1,M=!1,ee="authStateValidation",te="customerStateSync",re="trialExpirationCheck",x="installMetadata",P="uninstallDiagnosticsRuntime",Ue=1023,B="https://docs.google.com/forms/d/e/1FAIpQLSeKaTN6sOZkMUFLgfQJLHZadm3_mL_QYeq9dFKaF6frUkFICQ/viewform",Re="1257235115",L=!1,N=!1;function Oe(){let t=navigator.userAgentData?.brands||[];for(let r of t){if(/edge/i.test(r.brand))return"edge";if(/opera/i.test(r.brand))return"opera";if(/chrome/i.test(r.brand))return"chrome"}let e=navigator.userAgent||"";return/edg/i.test(e)?"edge":/opr\//i.test(e)?"opera":/firefox/i.test(e)?"firefox":/safari/i.test(e)&&!/chrome|chromium|edg/i.test(e)?"safari":/chrome|chromium/i.test(e)?"chrome":"unknown"}function Le(){let e=(navigator.userAgentData?.platform||navigator.platform||"").toLowerCase();return e.includes("mac")?"macos":e.includes("win")?"windows":e.includes("linux")?"linux":e.includes("android")?"android":e.includes("iphone")||e.includes("ipad")?"ios":e||"unknown"}function Ne(){return new Promise(t=>{if(!chrome.extension?.isAllowedIncognitoAccess){t(null);return}chrome.extension.isAllowedIncognitoAccess(e=>{t(!!e)})})}async function Pe(){return{browserName:Oe(),platformName:Le(),language:navigator.language||"",languages:Array.isArray(navigator.languages)?navigator.languages:[],hardwareConcurrency:navigator.hardwareConcurrency??"",deviceMemory:navigator.deviceMemory??"",mobile:navigator.userAgentData?.mobile??!1,incognitoAccessAllowed:await Ne()}}async function H(){let{[x]:t}=await chrome.storage.local.get(x),e={firstInstalledAt:t?.firstInstalledAt||Date.now()};return e.firstInstalledAt!==t?.firstInstalledAt&&await chrome.storage.local.set({[x]:e}),e}function Be(t){let e=new URLSearchParams({usp:"pp_url"});t&&e.set(`entry.${Re}`,t);let r=`${B}?${e.toString()}`;return r.length>Ue?(console.warn("Uninstall URL exceeded browser limit; falling back to Google Form without diagnostics",r.length),B):r}async function He(){let t=chrome.runtime.getManifest(),e=await chrome.storage.sync.get(["hb-settings","hb-auth-user","hb-allowed-sites"]),r=await chrome.storage.local.get(["crashInfo","turboModeStatus",P,x]),s=await Pe(),n=r[x]||await H(),o={...E,...e["hb-settings"]||{}},a=e["hb-auth-user"]||{},i=e["hb-allowed-sites"]||[],u=r.crashInfo||{},d=r.turboModeStatus||{},c=r[P]||{},l=c.benchmark||{},m=new Set([...Array.isArray(o.whitelist)?o.whitelist:[],...Array.isArray(i)?i:[]]).size,y={"extension.version":t.version,"browser.name":s.browserName,"browser.platform":s.platformName,"browser.language":s.language,"browser.languages":s.languages.join(","),"browser.hardware_concurrency":s.hardwareConcurrency,"browser.device_memory_gb":s.deviceMemory,"browser.mobile":s.mobile,"install.first_installed_at":n.firstInstalledAt||"","install.incognito_access_allowed":s.incognitoAccessAllowed,"settings.extension_enabled":o.status,"settings.blur_images":o.blurImages,"settings.blur_videos":o.blurVideos,"settings.blur_male":o.blurMale,"settings.blur_female":o.blurFemale,"settings.unblur_images":o.unblurImages,"settings.unblur_videos":o.unblurVideos,"settings.specific_blur":o.specificBlur,"settings.gray_mode":o.gray,"settings.use_solid_color":o.useSolidColor,"settings.hide_video_toggle":o.hideVideoToggle,"settings.password_protection_enabled":o.passwordProtectionEnabled,"settings.companion_enabled":o.companionMode?.enabled,"settings.uninstall_prevention_enabled":o.uninstallPreventionEnabled,"settings.prevent_incognito_guest_enabled":o.preventIncognitoGuestEnabled,"settings.compare_mode_enabled":o.compareMode?.enabled,"settings.whitelist_count":m,"settings.blur_amount":o.blurAmount,"settings.strictness":o.strictness,"settings.score_threshold":o.scoreThreshold,"settings.detection_model":o.detectionModel,"settings.protection_mode":o.protectionMode,"account.is_authenticated":!!a?.uid,"account.is_premium":!!a?.isPremium,"account.is_trial_active":!!a?.isTrialActive,"runtime.crash_count":u.crashesCount||0,"runtime.crash_timestamp":u.crashTimestamp||"","runtime.turbo_active":!!d.active,"runtime.turbo_error":d.error||"","runtime.active_backend":c.activeBackend||"","runtime.saved_texture_limit":c.savedTextureLimit||"","runtime.detection_errors":c.detectionErrors||0,"runtime.last_detection_error_at":c.lastDetectionErrorAt||"","runtime.model_error_at":c.modelErrorAt||"","runtime.last_recreation_attempt_at":c.lastRecreationAttemptAt||"","benchmark.selected_model":l.selectedModel??o.detectionModel,"benchmark.average_time_ms":l.averageTimeMs??"","benchmark.sample_count":l.sampleCount??"","benchmark.raw_sample_count":l.rawSampleCount??"","benchmark.timeout":!!l.timeout,"benchmark.early_exit":!!l.earlyExit,"benchmark.recorded_at":l.recordedAt||"","benchmark.error":l.error||""};return{diagnosticsPayload:Y(y)}}async function We(){try{let t=await He(),e=await X(t.diagnosticsPayload);await chrome.runtime.setUninstallURL(Be(e))}catch(t){console.error("Failed to update uninstall URL:",t);try{await chrome.runtime.setUninstallURL(B)}catch(e){console.error("Failed to set fallback uninstall URL:",e)}}}async function W(){if(N=!0,!L){L=!0;try{for(;N;)N=!1,await We()}finally{L=!1}}}Storage-change listener that re-fires the uninstall URL update
dist/background.jschrome.storage.onChanged.addListener(async(t,e)=>{if(e==="sync"&&t["hb-settings"]){let s=t["hb-settings"].newValue,n=t["hb-settings"].oldValue,o=s?.companionMode,a=n?.companionMode;if(o?.enabled&&o?.email){let i=Date.now(),u=o.lastNotificationTime||0,d=30*1e3;if(i-u>d){if(!await h.isPremiumUser()){console.error("Companion notification attempted without premium access");let f={...s};f.companionMode={...o,enabled:!1},chrome.storage.sync.set({"hb-settings":f});return}let l={},m=["detectionModel","version","trialWelcomeShown"];for(let f in s)!m.includes(f)&&JSON.stringify(s[f])!==JSON.stringify(n?.[f])&&(l[f]={old:n?.[f],new:s[f]});let y=JSON.stringify(o)!==JSON.stringify(a);if(Object.keys(l).length>0&&!y)try{await h.sendCompanionNotification({email:o.email,modifications:l,url:"Extension Settings"});let f={...s};f.companionMode={...o,lastNotificationTime:i},chrome.storage.sync.set({"hb-settings":f})}catch(f){console.error("Error sending companion notification:",f)}}}}(e==="sync"&&(t["hb-settings"]||t["hb-auth-user"]||t["hb-allowed-sites"])||e==="local"&&(t.crashInfo||t.turboModeStatus||t[P]||t[x]))&&await W()})Decrypts a captured HaramBlur uninstall-diagnostics token using the same AES-256-GCM key the extension derives from its own hardcoded passphrase, recovering the plaintext diagnostics string.
- Node.js 18+ (uses the built-in node:crypto module)
#!/usr/bin/env node// Decrypts a HaramBlur uninstall-diagnostics ciphertext ("hb1.<iv>.<ciphertext>")// using the AES-256-GCM key derived from the passphrase hardcoded in the// extension's shipped background.js bundle.//// Usage: node hb-uninstall-decode.js "hb1.<iv_b64url>.<ciphertext_b64url>"const crypto = require("node:crypto");// Hardcoded passphrase shipped in dist/background.js (variable `Te`).const HARDCODED_PASSPHRASE = "hb-uninstall-v1-s6G7X9r2Q1m8Lp4Na0TzK5wV3cY7Df2H";function b64urlToBuffer(s) { const padded = s.replace(/-/g, "+").replace(/_/g, "/"); const pad = padded.length % 4 === 0 ? "" : "=".repeat(4 - (padded.length % 4)); return Buffer.from(padded + pad, "base64");}function decode(token) { const [prefix, ivB64, ctB64] = token.split("."); if (prefix !== "hb1") throw new Error(`Unexpected prefix: ${prefix}`); const key = crypto.createHash("sha256").update(HARDCODED_PASSPHRASE).digest(); const iv = b64urlToBuffer(ivB64); const ctWithTag = b64urlToBuffer(ctB64); // Web Crypto's AES-GCM appends the 16-byte auth tag to the ciphertext. const tag = ctWithTag.subarray(ctWithTag.length - 16); const ciphertext = ctWithTag.subarray(0, ctWithTag.length - 16); const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv); decipher.setAuthTag(tag); const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]); return plaintext.toString("utf8");}const token = process.argv[2];if (!token) { console.error("Usage: node hb-uninstall-decode.js <entry.1257235115 value>"); process.exit(1);}console.log(decode(token));- 1Capture entry.1257235115 from the pending uninstall URL (chrome.management, or observe setUninstallURL in a test profile).
- 2Run: node hb-uninstall-decode.js "hb1.<iv>.<ciphertext>".
- 3It prints the decrypted diagnostics string.
- docs.google.com
Receives the diagnostics ciphertext as a URL query parameter the moment the pre-armed Google Forms link loads, which happens automatically when the extension is uninstalled.
Image report sends settings JSON to Google Forms
Submitting an image report posts the report choice, page URL, displayed/original image URLs, and the settings JSON to a Google Forms endpoint.
Settings can include blur preferences, whitelist entries, password state, and companion email.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-200
- Source
- Dynamic sandbox
You submit an image report from the extension modal.
The modal offers false-positive and false-negative choices before enabling Send Report.
The extension sends the report choice, page URL, image URLs, and saved settings JSON to Google Forms.
- Report choicefalse-positive
Shows whether you marked the image as wrongly blurred or missed by the filter.
- Displayed image URLhttps://cdn.example.org/gallery/photo-4812.jpg (illustrative)
Identifies the image shown in the report modal.
- Original image URLhttps://images.example.net/originals/photo-4812.jpg (illustrative)
Can identify the image source before the extension replaced or processed it.
- Current page URLhttps://example.com/gallery/article-17 (illustrative)
Identifies the page where you submitted the image report.
- Saved extension settings{"blurAmount":30,"strictness":0.4,"whitelist":["example.com"],"passwordProtectionEnabled":false,"companionMode":{"enabled":true,"email":"parent@example.com"}} (illustrative)
Can reveal your extension configuration, including whitelist entries, blur choices, and companion-mode email settings.
Report handler builds URLSearchParams and posts them to Google Forms
Deobfuscated report handler
dist/background.jsfunction fe(s,e,t){chrome.scripting.executeScript({target:{tabId:s.id},func:(r,o,a,n)=>{let i=document.getElementById("hb-report-modal");i&&i.remove();let c=` <div class="hb-report-modal" dir="ltr"> <div class="hb-modal-content"> <div class="hb-modal-header"> <div class="hb-header-title"> <img src="${chrome.runtime.getURL("src/assets/hb-icon-48.png")}" width="24" height="24" alt="HaramBlur"> <h3>Report Image</h3> </div> <button class="hb-close-button">×</button> </div> <div class="hb-modal-body"> <p class="hb-description">Help us improve our detection by reporting any issues.</p> <div class="hb-image-section"> <button class="hb-toggle-image hb-button hb-button-secondary"> <span class="hb-toggle-text">Show Image</span> <span class="hb-toggle-arrow">\u25B6</span> </button> <div class="hb-image-preview" style="display: none;"> <img src="${r}" alt="Content preview"> </div> </div> <div class="hb-report-options"> <p class="hb-label">What's wrong with this detection?</p> <label class="hb-radio-label"> <input type="radio" name="report-type" value="false-positive"> <span>False Positive - Content was wrongly blurred</span> </label> <label class="hb-radio-label"> <input type="radio" name="report-type" value="false-negative"> <span>False Negative - Inappropriate content wasn't blurred</span> </label> </div> </div> <div class="hb-modal-footer"> <button class="hb-button hb-button-secondary hb-cancel-button">Cancel</button> <button class="hb-button hb-button-primary hb-submit-button" disabled>Send Report</button> </div> </div> </div> `,h=document.createElement("div");h.id="hb-report-modal";let l=h.attachShadow({mode:"closed"}),u=document.createElement("style");u.textContent=` /* CSS Reset for Shadow DOM */ :host { all: initial; display: block; } * { margin: 0; padding: 0; box-sizing: border-box; font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.5; } .hb-report-modal { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; z-index: 999999; font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: #333; font-size: 14px; font-weight: normal; text-align: left; letter-spacing: normal; word-spacing: normal; text-transform: none; text-indent: 0; text-shadow: none; text-decoration: none; } .hb-modal-content { background: white; background-color: white; border-radius: 8px; width: 90%; max-width: 500px; max-height: 90vh; overflow: auto; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); color: #333; font-size: 14px; } .hb-modal-header { padding: 16px; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; } .hb-header-title { display: flex; align-items: center; gap: 8px; } .hb-header-title h3 { margin: 0; font-size: 18px; font-weight: 600; color: #333; line-height: 1.2; text-transform: none; letter-spacing: normal; } .hb-close-button { border: none; background: none; font-size: 24px; cursor: pointer; padding: 4px; opacity: 0.6; } .hb-close-button:hover { opacity: 1; } .hb-modal-body { padding: 16px; } .hb-description { margin: 0 0 16px; opacity: 0.7; font-size: 14px; color: #666; font-weight: normal; line-height: 1.5; } .hb-radio-label { display: flex; align-items: flex-start; padding: 8px 0; cursor: pointer; gap: 12px; position: relative; } .hb-radio-label input[type="radio"] { -webkit-appearance: none; appearance: none; margin: 0; width: 20px; height: 20px; border: 2px solid #ccc; border-radius: 50%; flex-shrink: 0; margin-top: 2px; position: relative; transition: all 0.2s ease; } .hb-radio-label:hover input[type="radio"] { border-color: #2196f3; } .hb-radio-label input[type="radio"]:checked { border-color: #2196f3; border-width: 6px; background: white; } .hb-radio-label span { font-size: 14px; color: #444; line-height: 1.4; padding-top: 2px; } .hb-report-options { margin-bottom: 20px; display: flex; flex-direction: column; gap: 8px; } .hb-label { font-weight: 500; margin-bottom: 8px; color: #333; font-size: 14px; display: block; line-height: 1.5; } .hb-image-section { margin-bottom: 16px; } .hb-toggle-image { width: 100%; display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; font-size: 14px; margin-bottom: 8px; } .hb-toggle-arrow { font-size: 12px; transition: transform 0.2s ease; } .hb-toggle-image.expanded .hb-toggle-arrow { transform: rotate(90deg); } .hb-image-preview { background: #f5f5f5; border-radius: 4px; text-align: center; padding: 8px; transition: all 0.3s ease; } .hb-image-preview img { max-width: 100%; max-height: 300px; object-fit: contain; } .hb-modal-footer { padding: 16px; border-top: 1px solid #eee; display: flex; justify-content: flex-end; gap: 8px; } .hb-button { padding: 8px 16px; border-radius: 4px; border: none; font-weight: 500; cursor: pointer; font-size: 14px; line-height: 1.5; font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; text-decoration: none; text-transform: none; display: inline-flex; align-items: center; justify-content: center; transition: all 0.2s ease; } .hb-button:disabled { opacity: 0.5; cursor: not-allowed; } .hb-button-primary { background: #2196f3; color: white; } .hb-button-primary:not(:disabled):hover { background: #1976d2; } .hb-button-secondary { background: #f5f5f5; background-color: #f5f5f5; color: #333; } .hb-button-secondary:hover { background: #e5e5e5; } .hb-success-message { text-align: center; padding: 32px 16px; } .hb-success-message h3 { color: #4caf50; margin: 0 0 8px; } `,l.innerHTML=c,l.prepend(u),document.body.appendChild(h);let f=l.querySelector(".hb-report-modal"),g=l.querySelector(".hb-close-button"),q=l.querySelector(".hb-cancel-button"),y=l.querySelector(".hb-submit-button"),Q=l.querySelectorAll('input[name="report-type"]'),Y=l.querySelector(".hb-modal-content"),M=l.querySelector(".hb-toggle-image"),J=l.querySelector(".hb-image-preview"),X=l.querySelector(".hb-toggle-text"),x=()=>h.remove();M.addEventListener("click",()=>{let b=M.classList.contains("expanded");M.classList.toggle("expanded"),J.style.display=b?"none":"block",X.textContent=b?"Show Image":"Hide Image"}),g.addEventListener("click",x),q.addEventListener("click",x),f.addEventListener("click",b=>{b.target===f&&x()}),Q.forEach(b=>{b.addEventListener("change",()=>{y.disabled=!1})}),y.addEventListener("click",async()=>{let b=l.querySelector('input[name="report-type"]:checked').value,D=l.querySelector(".hb-image-preview img").src;y.disabled=!0,y.textContent="Sending...";let K="625186413",Z="1528363305",ee="2049916266",te="2050137681",re=await chrome.storage.sync.get("hb-settings"),se="928202137",oe=a?.originalSrc,ae="https://docs.google.com/forms/u/0/d/e/1FAIpQLSfHbTsJaydX__sCbv7aGf0RiVBxTcbBSzv5LU-VALJm1TFnlg/formResponse",ne=new URLSearchParams({[`entry.${K}`]:b,[`entry.${Z}`]:D,[`entry.${se}`]:oe,[`entry.${ee}`]:o,[`entry.${te}`]:JSON.stringify(re["hb-settings"])});try{b==="false-negative"?chrome.runtime.sendMessage({type:"relay-false-negative",tabId:n,imageUrl:D}):b==="false-positive"&&chrome.runtime.sendMessage({type:"relay-false-positive",tabId:n,imageUrl:D}),await fetch(ae,{method:"POST",mode:"no-cors",body:ne}),Y.innerHTML=` <div class="hb-success-message"> <h3>Thank You!</h3> <p>Your report has been submitted successfully.<br>We appreciate your help in improving HaramBlur.</p> </div> `,setTimeout(x,2e3)}catch(ie){console.error("Failed to send report:",ie),y.textContent="Send Report",y.disabled=!1}})},args:[e.srcUrl,e.pageUrl,t,s.id]})}- docs.google.com
Receives the Google Forms formResponse POST containing report fields and the saved settings JSON.
Startup fetch applies GitHub blocklist rules
At init, the background code fetches a blocklist JSON from raw.githubusercontent.com with caching off, then converts blocked/allowed arrays into declarativeNetRequest rules, including redirects to the blocked page, with no signature check.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-829
- Source
- Dynamic sandbox
The extension initializes its background rule set.
The install and update listener calls the rule-update function after other startup work completes.
The service worker fetches a GitHub JSON blocklist and applies it as browser redirect and allow rules.
- Blocked URL patterns{"blocked":["exampleadult","sampleblocked"]} (illustrative)
Controls which page addresses are redirected to the extension blocked page.
- Allowed domains{"allowed":["example","trustedsite"]} (illustrative)
Controls which page addresses bypass the redirect rules.
- Redirect destinationchrome-extension://pbcoegikffnadpahojjhgdladmmddeji/src/assets/blocked/blocked.html?url=https://example.com/page (illustrative)
Shows where matching pages are sent instead of loading normally.
- User allowed sites["work.example.com"] (illustrative)
Your locally allowed sites are added as higher-priority allow rules.
Remote blocklist fetch and dynamic rule replacement
Rule fetch and update functions
dist/background.jsvar pe=100,H=1e4,be=async()=>{try{let e=await(await fetch("https://raw.githubusercontent.com/alganzoryP/hb-related/refs/heads/main/blocklist.json",{cache:"no-cache"})).json(),{blocked:t,allowed:r}=e,o=[],a=t.reduce((u,f,g)=>g%5===0?(o.push(u),f):`${u}|${f}`);o.push(a);let n=o.map((u,f)=>({id:pe+f,priority:1,action:{type:"redirect",redirect:{regexSubstitution:`${chrome.runtime.getURL("src/assets/blocked/blocked.html")}?url=\\0`}},condition:{regexFilter:`^[^:]*:\\/\\/(?:[^/]*(?:${u}))[^/]*\\/`,resourceTypes:["main_frame"]}})),i=r.map((u,f)=>({id:H+f,priority:2,action:{type:"allow"},condition:{regexFilter:`^[^:]*:\\/\\/(?:(?:www\\.)?(?:[^\\/]*\\.)?${u}\\.[^\\/]*)\\/`,resourceTypes:["main_frame"]}})),l=((await chrome.storage.sync.get("hb-allowed-sites"))["hb-allowed-sites"]||[]).map((u,f)=>({id:H+r.length+f,priority:3,action:{type:"allow"},condition:{urlFilter:`||${u}^`,resourceTypes:["main_frame"]}}));return[...n,...i,...l]}catch(s){return console.error("Error creating rules:",s),[]}},P=async()=>{try{let e=(await chrome.declarativeNetRequest.getDynamicRules()).map(r=>r.id),t=await be();await chrome.declarativeNetRequest.updateDynamicRules({removeRuleIds:e,addRules:t})}catch(s){console.error("Error initializing rules:",s)}}Install/update path that invokes rule update
dist/background.jschrome.runtime.onInstalled.addListener(async s=>{try{await R(),await S(),await he();let e=chrome.runtime.getManifest();if(s?.reason==="install")console.log("New installation detected - showing onboarding wizard"),chrome.tabs.create({url:chrome.runtime.getURL("dist/options/options.html?tour=full")});else if(s?.reason==="update"){if(s.previousVersion==e.version)return;console.log(`Extension updated from ${s.previousVersion} to ${e.version}`),chrome.tabs.create({url:chrome.runtime.getURL("dist/options/options.html?tour=update")})}P()}catch(e){console.error("Error during extension initialization:",e)}});- raw.githubusercontent.com
Serves the blocklist JSON whose blocked and allowed arrays are applied as main-frame request rules.