diff --git a/docs/features/domain-squatting-detection.md b/docs/features/domain-squatting-detection.md index 078fe20..fae350b 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 (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 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 @@ -108,8 +109,7 @@ Edit your `rules/detection-rules.json` file to customize: ```json { "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 +118,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 } } ``` diff --git a/options/options.js b/options/options.js index b2df83d..c1a169c 100644 --- a/options/options.js +++ b/options/options.js @@ -1371,9 +1371,19 @@ class CheckOptions { if (!escaped.startsWith('^')) { escaped = '^' + escaped; } - // Add end anchor if pattern doesn't end with wildcard + // 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 + '$'; + 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/rules/detection-rules.json b/rules/detection-rules.json index 7edd397..7fc753c 100644 --- a/rules/detection-rules.json +++ b/rules/detection-rules.json @@ -1,6 +1,6 @@ { - "version": "1.2.3", - "lastUpdated": "2026-06-08T00:00:00Z", + "version": "1.2.4", + "lastUpdated": "2026-07-02T00:00:00Z", "description": "Phishing detection logic for identifying phishing attempts targeting Microsoft 365 login pages", "trusted_login_patterns": [ "^https:\\/\\/login\\.microsoftonline\\.(com|us)$", @@ -144,14 +144,6 @@ "weight": 3, "category": "primary" }, - { - "id": "img_alt_microsoft", - "type": "source_content", - "pattern": "]+alt\\s*=\\s*[\"']Microsoft[\"'][^>]*>", - "description": "Image with alt=\"Microsoft\" - durable visual hook used by CSS-clone phishing kits that strip canonical MS DOM hooks", - "weight": 3, - "category": "primary" - }, { "id": "ms_visible_ux_combo", "type": "code_driven", diff --git a/scripts/content.js b/scripts/content.js index 453a178..bf82fab 100644 --- a/scripts/content.js +++ b/scripts/content.js @@ -3411,9 +3411,25 @@ if (window.checkExtensionLoaded) { escaped = "^" + escaped; } - // Add end anchor if pattern doesn't end with wildcard + // 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 + "$"; + 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; @@ -4247,19 +4263,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 +4308,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 +4327,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 +4356,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 =>