From 9e9b562a8648d1a375206a5786e0ad359e4efda3 Mon Sep 17 00:00:00 2001 From: MWG-Logan <2997336+MWG-Logan@users.noreply.github.com> Date: Wed, 1 Jul 2026 19:08:18 -0400 Subject: [PATCH 1/5] fix(content): treat domain squatting 'log' action as a distinct state The domain squatting result handler in scripts/content.js collapsed the block | warn | log action enum into block vs. everything-else. Because only the 'block' case was checked, the 'log' action fell through to the warning branch: it showed the orange warning banner and recorded every telemetry line (protection event, CIPP report, webhook) as 'warned'. The 'log' action was therefore functionally identical to 'warn'. Resolve the effective action as a real three-state value (block | warn | log) and derive a matching telemetry outcome (blocked | warned | logged). Preserve the semantics that warn logs telemetry and shows a banner, while log logs telemetry only and shows nothing to the user. Warn logs, log does not warn. The detector side (getActionForSeverity in domain-squatting-detector.js) already passed 'log' through intact and is unchanged. Fixes: #161 --- scripts/content.js | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/scripts/content.js b/scripts/content.js index 453a178..cf9fe0c 100644 --- a/scripts/content.js +++ b/scripts/content.js @@ -4247,19 +4247,29 @@ if (window.checkExtensionLoaded) { // Check if notifications should be shown const showNotifications = config.showNotifications !== false; - - // Determine if we should block the page - // Requires: 1) enablePageBlocking is ON, 2) domain_squatting action is "block" + + // Resolve the effective action as a real three-state value: + // 'block' | 'warn' | 'log'. Previously this only checked for + // 'block', which collapsed 'log' into 'warn' (banner + "warned"). + // Semantics: warn logs telemetry AND shows a banner; log logs + // telemetry only and shows nothing to the user. + const squattingAction = squattingData.action || 'warn'; logger.debug(` enablePageBlocking: ${config.enablePageBlocking}`); logger.debug(` squattingData.action: ${squattingData.action}`); - const shouldBlock = squattingData.action === 'block' && + const shouldBlock = squattingAction === 'block' && config.enablePageBlocking !== false; + const outcome = shouldBlock + ? "blocked" + : squattingAction === 'log' + ? "logged" + : "warned"; logger.debug(` shouldBlock: ${shouldBlock}`); - + logger.debug(` outcome: ${outcome}`); + // Log domain squatting detection logProtectionEvent({ type: "threat_detected", - action: shouldBlock ? "blocked" : "warned", + action: outcome, url: location.href, origin: currentOrigin, reason: `Domain squatting detected: ${squattingData.techniques.map(t => t.description).join('; ')}`, @@ -4282,7 +4292,7 @@ if (window.checkExtensionLoaded) { })), severity: squattingData.severity, confidence: squattingData.confidence, - action: shouldBlock ? "blocked" : "warned", + action: outcome, reason: `Domain squatting detected: ${squattingData.techniques.map(t => t.description).join('; ')}` }); @@ -4301,7 +4311,7 @@ if (window.checkExtensionLoaded) { })), severity: squattingData.severity, confidence: squattingData.confidence, - action: shouldBlock ? "blocked" : "warned", + action: outcome, reason: `Domain squatting detected: ${squattingData.techniques.map(t => t.description).join('; ')}` }, }) @@ -4330,6 +4340,9 @@ if (window.checkExtensionLoaded) { } ); return; // Stop processing, page is blocked + } else if (squattingAction === 'log') { + // Log-only action: telemetry has already been emitted above. + // Intentionally show nothing to the user. Log must not warn. } else if (showNotifications) { // Show warning banner for domain squatting (only if notifications enabled) const techniquesDesc = squattingData.techniques.map(t => From a0cd545b8c90790244b033a37a441667c11e1e64 Mon Sep 17 00:00:00 2001 From: MWG-Logan <2997336+MWG-Logan@users.noreply.github.com> Date: Wed, 1 Jul 2026 19:55:14 -0400 Subject: [PATCH 2/5] fix(allowlist): match deep-link URLs in urlPatternToRegex urlPatternToRegex appended a hard end-anchor to any non-wildcard pattern, so an allowlisted host or root URL only matched the bare root URL and never a real navigated deep link. An admin who allowlisted the exact host of a page (including the protocol-qualified form) still saw that page scanned or blocked, because the actual URL carries a path and the trailing anchor rejected it. Relax only the trailing anchoring: normalize a single trailing slash and tolerate an optional trailing path, query, or fragment. The tolerated remainder must begin with /, ?, or # so suffix tricks such as https://host.evil.com/ still do not match an entry for https://host/. Leading protocol and subdomain tolerance is intentionally out of scope, so bare-domain entries still require the documented https://.../ * form. The change is applied to both copies of urlPatternToRegex (scripts/content.js and options/options.js), which must stay in sync. Fixes: #162 --- options/options.js | 7 +++++-- scripts/content.js | 10 ++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/options/options.js b/options/options.js index b2df83d..da45355 100644 --- a/options/options.js +++ b/options/options.js @@ -1371,9 +1371,12 @@ class CheckOptions { if (!escaped.startsWith('^')) { escaped = '^' + escaped; } - // Add end anchor if pattern doesn't end with wildcard + // Add end anchor if pattern doesn't end with wildcard. Tolerate an optional + // trailing path, query, or fragment so a host or root URL pattern (with or + // without a trailing slash) also matches deep links such as https://host/path. + // Kept in sync with the copy in scripts/content.js. if (!pattern.endsWith('*') && !escaped.endsWith('.*')) { - escaped = escaped + '$'; + escaped = escaped.replace(/\/$/, '') + '(?:[/?#].*)?$'; } return escaped; } diff --git a/scripts/content.js b/scripts/content.js index cf9fe0c..7a4427c 100644 --- a/scripts/content.js +++ b/scripts/content.js @@ -3411,9 +3411,15 @@ if (window.checkExtensionLoaded) { escaped = "^" + escaped; } - // Add end anchor if pattern doesn't end with wildcard + // Add end anchor if pattern doesn't end with wildcard. Tolerate an + // optional trailing path, query, or fragment so that allowlisting a host + // or a root URL (with or without a trailing slash) also matches deep + // links such as https://host/path. A single trailing slash in the pattern + // is normalized so it does not force an exact match on the bare root URL. + // Suffix tricks (for example https://host.evil.com/) still do not match, + // because the tolerated remainder must begin with /, ?, or #. if (!pattern.endsWith("*") && !escaped.endsWith(".*")) { - escaped = escaped + "$"; + escaped = escaped.replace(/\/$/, "") + "(?:[/?#].*)?$"; } return escaped; From 27221875cae5e059edd2dc55e66a34abce0fc6ad Mon Sep 17 00:00:00 2001 From: MWG-Logan <2997336+MWG-Logan@users.noreply.github.com> Date: Wed, 1 Jul 2026 21:18:47 -0400 Subject: [PATCH 3/5] fix(allowlist): restrict deep-link tolerance to host and root URL patterns The deep-link matching added for the URL allowlist applied its tolerant trailing matcher to every non-wildcard pattern, which turned path-specific entries into prefix matches. For example an entry for https://host/safe also matched https://host/safe/anything, over-broadening a bypass allowlist entry beyond what the admin intended. Gate the tolerant trailing matcher on host or root URL patterns only (no path segment beyond an optional single trailing slash). A pattern that includes an explicit path now stays an exact match. Host and root URL patterns still match deep links, and suffix or prefix tricks such as https://host.evil.com/ still do not match. Applied to both copies of urlPatternToRegex in scripts/content.js and options/options.js, which must stay in sync. Addresses PR review feedback on BezaluLLC/Check#2 (review comments on the urlPatternToRegex suffix handling). --- options/options.js | 17 ++++++++++++----- scripts/content.js | 24 +++++++++++++++++------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/options/options.js b/options/options.js index da45355..c1a169c 100644 --- a/options/options.js +++ b/options/options.js @@ -1371,12 +1371,19 @@ class CheckOptions { if (!escaped.startsWith('^')) { escaped = '^' + escaped; } - // Add end anchor if pattern doesn't end with wildcard. Tolerate an optional - // trailing path, query, or fragment so a host or root URL pattern (with or - // without a trailing slash) also matches deep links such as https://host/path. - // Kept in sync with the copy in scripts/content.js. + // Add the end anchor if the pattern does not already end with a wildcard. + // Only a host or root URL pattern (no path beyond an optional single + // trailing slash) gets the tolerant trailing matcher, so patterns that + // include an explicit path stay exact matches and allowlist entries are + // not silently broadened into prefix matches. Kept in sync with the copy + // in scripts/content.js. if (!pattern.endsWith('*') && !escaped.endsWith('.*')) { - escaped = escaped.replace(/\/$/, '') + '(?:[/?#].*)?$'; + const afterScheme = pattern.replace(/^https?:\/\//i, ''); + const firstSlash = afterScheme.indexOf('/'); + const isHostOrRoot = firstSlash === -1 || firstSlash === afterScheme.length - 1; + escaped = isHostOrRoot + ? escaped.replace(/\/$/, '') + '(?:[/?#].*)?$' + : escaped + '$'; } return escaped; } diff --git a/scripts/content.js b/scripts/content.js index 7a4427c..bf82fab 100644 --- a/scripts/content.js +++ b/scripts/content.js @@ -3411,15 +3411,25 @@ if (window.checkExtensionLoaded) { escaped = "^" + escaped; } - // Add end anchor if pattern doesn't end with wildcard. Tolerate an - // optional trailing path, query, or fragment so that allowlisting a host - // or a root URL (with or without a trailing slash) also matches deep - // links such as https://host/path. A single trailing slash in the pattern - // is normalized so it does not force an exact match on the bare root URL. - // Suffix tricks (for example https://host.evil.com/) still do not match, + // Add the end anchor if the pattern does not already end with a wildcard. + // + // Only a host or root URL pattern (no path segment beyond an optional + // single trailing slash) is given the tolerant trailing matcher, so that + // allowlisting a host or root URL also matches deep links such as + // https://host/path. A pattern that includes an explicit path stays an + // exact match, so allowlist entries are not silently broadened into prefix + // matches (for example "https://host/safe" must not also allow + // "https://host/safe/anything"). Suffix tricks such as + // "https://host.evil.com/" still do not match a "https://host/" entry, // because the tolerated remainder must begin with /, ?, or #. if (!pattern.endsWith("*") && !escaped.endsWith(".*")) { - escaped = escaped.replace(/\/$/, "") + "(?:[/?#].*)?$"; + const afterScheme = pattern.replace(/^https?:\/\//i, ""); + const firstSlash = afterScheme.indexOf("/"); + const isHostOrRoot = + firstSlash === -1 || firstSlash === afterScheme.length - 1; + escaped = isHostOrRoot + ? escaped.replace(/\/$/, "") + "(?:[/?#].*)?$" + : escaped + "$"; } return escaped; From a70ae59bd0552432d298808dad5eebec90d36577 Mon Sep 17 00:00:00 2001 From: MWG-Logan <2997336+MWG-Logan@users.noreply.github.com> Date: Wed, 1 Jul 2026 22:45:46 -0400 Subject: [PATCH 4/5] docs(domain-squatting): document the log detection action Reflect the three-state detection action shipped in this branch. The log action is now documented as a distinct outcome: the detection is recorded in Activity Logs and sent to reporting and webhooks, but no warning banner and no block are shown to the user. This also corrects the earlier statement that a warning banner is shown regardless of the action setting when page blocking is disabled, which is no longer true for the log action, and updates the inline action comments from 'block or warn' to include 'log'. Refs: #161 --- docs/features/domain-squatting-detection.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/features/domain-squatting-detection.md b/docs/features/domain-squatting-detection.md index 078fe20..3528412 100644 --- a/docs/features/domain-squatting-detection.md +++ b/docs/features/domain-squatting-detection.md @@ -88,13 +88,14 @@ You don't need to do anything - the protection works automatically in the backgr ### Page Blocking Control -Check has an **"Enable Page Blocking"** setting in the extension options that controls how suspicious pages are handled: +Check has an **"Enable Page Blocking"** setting in the extension options that controls how suspicious pages are handled. The detection **Action** can be one of three values: `block`, `warn`, or `log`. - **Page Blocking Enabled** + **Action: "block"** = Page is completely blocked with full-page warning - **Page Blocking Enabled** + **Action: "warn"** = Warning banner shown, page remains accessible -- **Page Blocking Disabled** = Warning banner shown regardless of action setting (never blocks) +- **Action: "log"** = Detection is recorded in Activity Logs and sent to reporting and webhooks only. No banner and no block are shown to the user, regardless of the Page Blocking setting. +- **Page Blocking Disabled** = Never blocks. A `block` or `warn` action shows a warning banner instead; a `log` action stays silent. -This gives you control over whether you want aggressive blocking or just warnings for suspicious domains. +This gives you control over whether you want aggressive blocking, visible warnings, or silent monitoring for suspicious domains. ### For Advanced Users and IT Departments @@ -109,7 +110,7 @@ Edit your `rules/detection-rules.json` file to customize: { "domain_squatting": { "enabled": false, // Turn detection on/off (default: false) - "action": "block" // Action when detected: "block" or "warn" + "action": "block" // Action when detected: "block", "warn", or "log" } } ``` @@ -118,7 +119,7 @@ Edit your `rules/detection-rules.json` file to customize: ```json { "domain_squatting": { - "action": "block" // "block" = full page block, "warn" = banner only + "action": "block" // "block" = full page block, "warn" = banner only, "log" = silent, telemetry only } } ``` From 2a56d171a4cf0b6b70921cda3a71f009c766c2dc Mon Sep 17 00:00:00 2001 From: Logan Cook <2997336+MWG-Logan@users.noreply.github.com> Date: Wed, 1 Jul 2026 22:57:13 -0400 Subject: [PATCH 5/5] fix(docs): phrasing fix Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Logan Cook <2997336+MWG-Logan@users.noreply.github.com> --- docs/features/domain-squatting-detection.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/features/domain-squatting-detection.md b/docs/features/domain-squatting-detection.md index 3528412..ee404d2 100644 --- a/docs/features/domain-squatting-detection.md +++ b/docs/features/domain-squatting-detection.md @@ -92,8 +92,8 @@ Check has an **"Enable Page Blocking"** setting in the extension options that co - **Page Blocking Enabled** + **Action: "block"** = Page is completely blocked with full-page warning - **Page Blocking Enabled** + **Action: "warn"** = Warning banner shown, page remains accessible -- **Action: "log"** = Detection is recorded in Activity Logs and sent to reporting and webhooks only. No banner and no block are shown to the user, regardless of the Page Blocking setting. -- **Page Blocking Disabled** = Never blocks. A `block` or `warn` action shows a warning banner instead; a `log` action stays silent. +- **Action: "log"** = Detection is recorded in Activity Logs and (if configured) sent to reporting and webhooks. No banner and no block are shown to the user, regardless of the Page Blocking setting. +- **Page Blocking Disabled** = Never blocks. If **Show Notifications** is enabled, a `block` or `warn` action shows a warning banner instead; a `log` action stays silent. This gives you control over whether you want aggressive blocking, visible warnings, or silent monitoring for suspicious domains.