From 0fc2b1e35a1ab9fa98e3cde8da6861fc501c8152 Mon Sep 17 00:00:00 2001 From: Brian Simpson Date: Thu, 18 Dec 2025 15:01:58 +0000 Subject: [PATCH 01/27] GITBOOK-65: Hiding Firefox Instructions as Pending Add-On Publishing --- docs/README.md | 18 +- .../README.md | 8 +- docs/deployment/firefox-deployment.md | 166 +++++++++-------- docs/firefox-support.md | 168 ++++++++++-------- docs/settings/about.md | 23 +-- docs/settings/branding.md | 74 ++++---- 6 files changed, 232 insertions(+), 225 deletions(-) diff --git a/docs/README.md b/docs/README.md index c587033..2c7ccba 100644 --- a/docs/README.md +++ b/docs/README.md @@ -20,21 +20,19 @@ layout: ## What is Check? -**Check** is a browser extension that provides real-time protection against Microsoft 365 phishing attacks. +**Check** is a browser extension that provides real-time protection against Microsoft 365 phishing attacks. Specifically designed for enterprises and managed service providers, Check uses sophisticated detection algorithms to identify and block malicious login pages before credentials can be stolen by bad actors. -Check is available for **Chrome**, **Microsoft Edge**, and **Firefox** (109+). +Check is available for **Chrome**, **Microsoft Edge**, and **Firefox** (109+ Coming Soon!). -The extension integrates seamlessly with existing security workflows, offering centralized management, comprehensive logging, and offers an optional CIPP integration for MSPs managing multiple Microsoft 365 tenants. +The extension integrates seamlessly with existing security workflows, offering centralized management, comprehensive logging, and offers an optional CIPP integration for MSPs managing multiple Microsoft 365 tenants. -Check is completely free, open source, and can be delivered to users completely white-label, it is an open source project licensed under AGPL-3. You can contribute to check at [https://github.com/cyberdrain/Check](https://github.com/cyberdrain/Check). +Check is completely free, open source, and can be delivered to users completely white-label, it is an open-source project licensed under AGPL-3. You can contribute to check at [https://github.com/cyberdrain/Check](https://github.com/cyberdrain/Check). -Installing the plugin immediately gives you protection against AITM attacks, and takes seconds. Click the install button and you're good to go. +Installing the plugin immediately gives you protection against AITM attacks and takes seconds. Click the install button and you're good to go. -Install for Edge **OR** Install for Chrome - -**Firefox users:** See the [Firefox Support](firefox-support.md) guide for installation instructions. +Install for Edge **OR** Install for Chrome OR Firefox (Coming Soon!) ## Why was Check created? @@ -44,14 +42,10 @@ Check was created out of a need to have better protection against AITM attacks. This led to a hackathon in which the team crafted a proof of concept. This proof of concept led to the creation of Check by CyberDrain. CyberDrain decided to offer Check as a free to use community resource, for everyone. - - ### What information does Check collect? Nothing. We're not even kidding, we don't collect any data at all. You can set up a CIPP reporting server if you'd like, but this reports directly to your own environment. CyberDrain doesn't believe in making their users a product. We don't sell or collect any information. - - ## How does it look? When a user gets the plugin added, a new icon will appear, this icon is [brandable](settings/branding.md) to customize it to your own logo and name. diff --git a/docs/deployment/chrome-edge-deployment-instructions/README.md b/docs/deployment/chrome-edge-deployment-instructions/README.md index 809db3a..68403c3 100644 --- a/docs/deployment/chrome-edge-deployment-instructions/README.md +++ b/docs/deployment/chrome-edge-deployment-instructions/README.md @@ -5,7 +5,7 @@ description: >- icon: bolt --- -# Deployment Instructions +# Chrome/Edge Deployment Instructions Check is available for **Chrome**, **Microsoft Edge**, and **Firefox** with deployment guides for each browser. @@ -18,9 +18,3 @@ Check is available for **Chrome**, **Microsoft Edge**, and **Firefox** with depl {% content-ref url="macos.md" %} [macos.md](macos.md) {% endcontent-ref %} - -## Firefox Deployment - -{% content-ref url="../firefox-deployment.md" %} -[firefox-deployment.md](../firefox-deployment.md) -{% endcontent-ref %} diff --git a/docs/deployment/firefox-deployment.md b/docs/deployment/firefox-deployment.md index 83fe570..6dbf435 100644 --- a/docs/deployment/firefox-deployment.md +++ b/docs/deployment/firefox-deployment.md @@ -1,3 +1,7 @@ +--- +noIndex: true +--- + # Firefox Deployment This guide covers deploying Check to Firefox across different platforms using enterprise policies. @@ -12,12 +16,12 @@ The Check extension for Firefox uses the ID: **`check@cyberdrain.com`** ## Quick Reference -| Platform | Policy File Location | -|----------|---------------------| -| Windows | `%ProgramFiles%\Mozilla Firefox\distribution\policies.json` | -| macOS | `/Applications/Firefox.app/Contents/Resources/distribution/policies.json` | -| Linux (system) | `/etc/firefox/policies/policies.json` | -| Linux (app) | `/usr/lib/firefox/distribution/policies.json` | +| Platform | Policy File Location | +| -------------- | ------------------------------------------------------------------------- | +| Windows | `%ProgramFiles%\Mozilla Firefox\distribution\policies.json` | +| macOS | `/Applications/Firefox.app/Contents/Resources/distribution/policies.json` | +| Linux (system) | `/etc/firefox/policies/policies.json` | +| Linux (app) | `/usr/lib/firefox/distribution/policies.json` | ## Prerequisites @@ -36,21 +40,21 @@ For production deployment, you need a signed .xpi file: #### Option A: Mozilla Add-ons Signing (Recommended) -1. Build the Firefox version: - ```bash - npm run build:firefox - ``` - -2. Package the extension: - ```bash - zip -r check-firefox.zip . \ - -x ".*" \ - -x "node_modules/*" \ - -x "tests/*" \ - -x "*.md" \ - -x "manifest.chrome.json" - ``` - +1. Build the Firefox version: + + ```bash + npm run build:firefox + ``` +2. Package the extension: + + ```bash + zip -r check-firefox.zip . \ + -x ".*" \ + -x "node_modules/*" \ + -x "tests/*" \ + -x "*.md" \ + -x "manifest.chrome.json" + ``` 3. Submit to [addons.mozilla.org](https://addons.mozilla.org) for signing 4. Download the signed .xpi file 5. Host on your internal server or use Mozilla's CDN @@ -58,9 +62,10 @@ For production deployment, you need a signed .xpi file: #### Option B: Development Installation For testing or development: -- Use temporary add-on installation (no signing required) -- Enable unsigned extensions in Firefox developer edition -- Not recommended for production deployments + +* Use temporary add-on installation (no signing required) +* Enable unsigned extensions in Firefox developer edition +* Not recommended for production deployments ### 2. Configure policies.json @@ -125,20 +130,20 @@ Create or modify `policies.json` based on the template in `enterprise/firefox/po {% tabs %} {% tab title="Windows" %} -#### Windows Deployment +**Windows Deployment** **Manual Deployment:** -1. Create the distribution folder if it doesn't exist: - ```powershell - New-Item -ItemType Directory -Force -Path "$env:ProgramFiles\Mozilla Firefox\distribution" - ``` +1. Create the distribution folder if it doesn't exist: -2. Copy your configured `policies.json`: - ```powershell - Copy-Item policies.json "$env:ProgramFiles\Mozilla Firefox\distribution\policies.json" - ``` + ```powershell + New-Item -ItemType Directory -Force -Path "$env:ProgramFiles\Mozilla Firefox\distribution" + ``` +2. Copy your configured `policies.json`: + ```powershell + Copy-Item policies.json "$env:ProgramFiles\Mozilla Firefox\distribution\policies.json" + ``` 3. Restart Firefox on all systems **Group Policy Deployment:** @@ -180,25 +185,26 @@ Write-Output "Firefox policies deployed successfully" {% endtab %} {% tab title="macOS" %} -#### macOS Deployment +**macOS Deployment** **Manual Deployment:** -1. Create the distribution folder: - ```bash - sudo mkdir -p "/Applications/Firefox.app/Contents/Resources/distribution" - ``` +1. Create the distribution folder: + + ```bash + sudo mkdir -p "/Applications/Firefox.app/Contents/Resources/distribution" + ``` +2. Copy your configured `policies.json`: -2. Copy your configured `policies.json`: - ```bash - sudo cp policies.json "/Applications/Firefox.app/Contents/Resources/distribution/policies.json" - ``` + ```bash + sudo cp policies.json "/Applications/Firefox.app/Contents/Resources/distribution/policies.json" + ``` +3. Set appropriate permissions: -3. Set appropriate permissions: - ```bash - sudo chmod 644 "/Applications/Firefox.app/Contents/Resources/distribution/policies.json" - sudo chown root:wheel "/Applications/Firefox.app/Contents/Resources/distribution/policies.json" - ``` + ```bash + sudo chmod 644 "/Applications/Firefox.app/Contents/Resources/distribution/policies.json" + sudo chown root:wheel "/Applications/Firefox.app/Contents/Resources/distribution/policies.json" + ``` **MDM Deployment (Jamf, Intune, etc.):** @@ -235,37 +241,39 @@ Some MDM systems support Firefox configuration profiles. Check your MDM document {% endtab %} {% tab title="Linux" %} -#### Linux Deployment +**Linux Deployment** **System-Wide Deployment:** -1. Create the policies directory: - ```bash - sudo mkdir -p /etc/firefox/policies - ``` +1. Create the policies directory: -2. Copy your configured `policies.json`: - ```bash - sudo cp policies.json /etc/firefox/policies/policies.json - ``` + ```bash + sudo mkdir -p /etc/firefox/policies + ``` +2. Copy your configured `policies.json`: -3. Set proper permissions: - ```bash - sudo chmod 644 /etc/firefox/policies/policies.json - ``` + ```bash + sudo cp policies.json /etc/firefox/policies/policies.json + ``` +3. Set proper permissions: + + ```bash + sudo chmod 644 /etc/firefox/policies/policies.json + ``` **Distribution-Specific Locations:** Different Linux distributions may use different paths: -- **Debian/Ubuntu**: `/etc/firefox/policies/policies.json` -- **RHEL/CentOS/Fedora**: `/usr/lib64/firefox/distribution/policies.json` -- **SUSE/openSUSE**: `/usr/lib/firefox/distribution/policies.json` -- **Snap package**: Policies not supported via traditional methods +* **Debian/Ubuntu**: `/etc/firefox/policies/policies.json` +* **RHEL/CentOS/Fedora**: `/usr/lib64/firefox/distribution/policies.json` +* **SUSE/openSUSE**: `/usr/lib/firefox/distribution/policies.json` +* **Snap package**: Policies not supported via traditional methods **Automated Deployment:** Using Ansible: + ```yaml - name: Deploy Firefox Check Extension Policy copy: @@ -278,6 +286,7 @@ Using Ansible: ``` Using Puppet: + ```puppet file { '/etc/firefox/policies': ensure => directory, @@ -365,12 +374,13 @@ Configure a webhook to receive detection events: ``` **Available Event Types:** -- `detection_alert` - General phishing detection events -- `false_positive_report` - User-submitted false positive reports -- `page_blocked` - Page blocking events -- `rogue_app_detected` - OAuth rogue application detection -- `threat_detected` - General threat detection events -- `validation_event` - Legitimate page validation events + +* `detection_alert` - General phishing detection events +* `false_positive_report` - User-submitted false positive reports +* `page_blocked` - Page blocking events +* `rogue_app_detected` - OAuth rogue application detection +* `threat_detected` - General threat detection events +* `validation_event` - Legitimate page validation events For webhook payload schema and implementation details, see the [Webhook Documentation](../webhooks.md). @@ -438,7 +448,7 @@ To force an immediate update: **Common causes:** 1. **Unsigned extension**: Production deployments require signed .xpi -2. **Unreachable URL**: Verify the install_url is accessible +2. **Unreachable URL**: Verify the install\_url is accessible 3. **Network restrictions**: Check firewall/proxy settings 4. **Firefox version**: Ensure Firefox 109+ @@ -493,13 +503,13 @@ Remove the entire policies file (will remove all managed extensions and policies ## Support Resources -- **Template**: `enterprise/firefox/policies.json` -- **Schema**: `config/managed_schema.json` -- **Firefox Policies**: [Mozilla Policy Documentation](https://github.com/mozilla/policy-templates) -- **General Support**: See [Firefox Support](../firefox-support.md) +* **Template**: `enterprise/firefox/policies.json` +* **Schema**: `config/managed_schema.json` +* **Firefox Policies**: [Mozilla Policy Documentation](https://github.com/mozilla/policy-templates) +* **General Support**: See [Firefox Support](../firefox-support.md) ## Additional Resources -- [Firefox Enterprise Support](https://support.mozilla.org/en-US/products/firefox-enterprise) -- [Firefox Policy Templates](https://github.com/mozilla/policy-templates) -- [Enterprise Information for IT](https://support.mozilla.org/en-US/kb/enterprise-information-it) +* [Firefox Enterprise Support](https://support.mozilla.org/en-US/products/firefox-enterprise) +* [Firefox Policy Templates](https://github.com/mozilla/policy-templates) +* [Enterprise Information for IT](https://support.mozilla.org/en-US/kb/enterprise-information-it) diff --git a/docs/firefox-support.md b/docs/firefox-support.md index dedbc3d..b74c482 100644 --- a/docs/firefox-support.md +++ b/docs/firefox-support.md @@ -1,3 +1,8 @@ +--- +hidden: true +noIndex: true +--- + # Firefox Support Check fully supports Firefox 109+ with all the same phishing protection features available in Chrome and Edge. This page covers installation, deployment, and Firefox-specific considerations. @@ -35,26 +40,28 @@ git checkout manifest.json The Firefox version of Check includes several technical differences from the Chrome/Edge version to ensure compatibility: ### Manifest Differences -- **Background Scripts**: Uses `background.scripts` instead of `service_worker` -- **Content Scripts**: Excludes `file:///` protocol (not supported in Firefox) -- **Options Page**: Uses `options_ui` instead of `options_page` -- **Browser Settings**: Includes `browser_specific_settings` with Gecko ID `check@cyberdrain.com` -- **Permissions**: Excludes `identity.email` permission (not needed in Firefox) + +* **Background Scripts**: Uses `background.scripts` instead of `service_worker` +* **Content Scripts**: Excludes `file:///` protocol (not supported in Firefox) +* **Options Page**: Uses `options_ui` instead of `options_page` +* **Browser Settings**: Includes `browser_specific_settings` with Gecko ID `check@cyberdrain.com` +* **Permissions**: Excludes `identity.email` permission (not needed in Firefox) ### Cross-Browser Compatibility Check uses a browser polyfill (`scripts/browser-polyfill.js`) to handle API differences between Chrome and Firefox automatically. This ensures that: -- Extension APIs work consistently across browsers -- Code can be written once and work everywhere -- Updates maintain compatibility with all supported browsers + +* Extension APIs work consistently across browsers +* Code can be written once and work everywhere +* Updates maintain compatibility with all supported browsers ## Enterprise Deployment ### Prerequisites -- Firefox 109 or later -- Administrator access for system-wide deployment -- Extension signed by Mozilla (for permanent installation) +* Firefox 109 or later +* Administrator access for system-wide deployment +* Extension signed by Mozilla (for permanent installation) ### Deployment Methods @@ -62,36 +69,35 @@ Firefox supports enterprise deployment through the `policies.json` file. This me #### Windows Deployment -1. Create or edit the policies file at: - ``` - %ProgramFiles%\Mozilla Firefox\distribution\policies.json - ``` +1. Create or edit the policies file at: + ``` + %ProgramFiles%\Mozilla Firefox\distribution\policies.json + ``` 2. Use the template from `enterprise/firefox/policies.json` in the repository +3. Update the `install_url` to point to your signed .xpi file: -3. Update the `install_url` to point to your signed .xpi file: - ```json - { - "policies": { - "Extensions": { - "Install": ["https://your-server.com/check-extension.xpi"] - } - } - } - ``` + ```json + { + "policies": { + "Extensions": { + "Install": ["https://your-server.com/check-extension.xpi"] + } + } + } + ``` #### macOS/Linux Deployment 1. Create the policies file at: - - **macOS**: `/Applications/Firefox.app/Contents/Resources/distribution/policies.json` - - **Linux**: `/etc/firefox/policies/policies.json` or `/usr/lib/firefox/distribution/policies.json` - + * **macOS**: `/Applications/Firefox.app/Contents/Resources/distribution/policies.json` + * **Linux**: `/etc/firefox/policies/policies.json` or `/usr/lib/firefox/distribution/policies.json` 2. Use the template from `enterprise/firefox/policies.json` +3. Set proper permissions: -3. Set proper permissions: - ```bash - sudo chmod 644 /path/to/policies.json - ``` + ```bash + sudo chmod 644 /path/to/policies.json + ``` ### Extension Configuration @@ -167,6 +173,7 @@ To force-install Check and prevent users from disabling it: ### Development Signing For testing purposes, you can use Firefox's developer mode: + 1. Navigate to `about:config` 2. Set `xpinstall.signatures.required` to `false` 3. Load the extension as a temporary add-on @@ -180,11 +187,12 @@ Disabling signature verification is only recommended for development and testing For production deployment, you need to sign the extension with Mozilla: 1. Create a Mozilla Add-ons account at [addons.mozilla.org](https://addons.mozilla.org) -2. Package your extension: - ```bash - npm run build:firefox - zip -r check-firefox.zip . -x ".*" "node_modules/*" "tests/*" "*.md" "manifest.chrome.json" - ``` +2. Package your extension: + + ```bash + npm run build:firefox + zip -r check-firefox.zip . -x ".*" "node_modules/*" "tests/*" "*.md" "manifest.chrome.json" + ``` 3. Submit to Mozilla for signing (unlisted distribution for enterprise) 4. Download the signed .xpi file 5. Host the .xpi file on your server or use Mozilla's CDN @@ -192,6 +200,7 @@ For production deployment, you need to sign the extension with Mozilla: ### Self-Distribution For enterprise environments, you can self-distribute the signed .xpi: + 1. Host the .xpi file on an internal web server 2. Configure `policies.json` with your internal URL 3. Deploy the policies file to managed devices @@ -203,9 +212,9 @@ For enterprise environments, you can self-distribute the signed .xpi: 1. Load the extension using the Quick Start instructions 2. Open the test page: `test-extension-loading.html` 3. Verify that all components load correctly: - - Background scripts initialize - - Content scripts inject on pages - - Popup and options pages display correctly + * Background scripts initialize + * Content scripts inject on pages + * Popup and options pages display correctly ### Testing Detection Rules @@ -218,18 +227,18 @@ For enterprise environments, you can self-distribute the signed .xpi: When contributing or making changes, always test in both Chrome/Edge and Firefox: -1. Test in Chrome/Edge: - ```bash - npm run build:chrome - # Load in Chrome - ``` +1. Test in Chrome/Edge: -2. Test in Firefox: - ```bash - npm run build:firefox - # Load in Firefox - ``` + ```bash + npm run build:chrome + # Load in Chrome + ``` +2. Test in Firefox: + ```bash + npm run build:firefox + # Load in Firefox + ``` 3. Verify consistent behavior across browsers 4. Check for Firefox-specific console errors or warnings @@ -240,64 +249,71 @@ When contributing or making changes, always test in both Chrome/Edge and Firefox **Problem**: Extension doesn't load or shows errors **Solutions**: -- Ensure you ran `npm run build:firefox` before loading -- Check that Firefox version is 109 or later -- Look for errors in Browser Console (Ctrl+Shift+J) -- Verify manifest.json has Firefox-specific structure + +* Ensure you ran `npm run build:firefox` before loading +* Check that Firefox version is 109 or later +* Look for errors in Browser Console (Ctrl+Shift+J) +* Verify manifest.json has Firefox-specific structure ### Background Scripts Not Working **Problem**: Background functionality fails in Firefox **Solutions**: -- Firefox uses `background.scripts` not `service_worker` -- Verify the build script ran successfully -- Check for module loading errors in the Browser Console + +* Firefox uses `background.scripts` not `service_worker` +* Verify the build script ran successfully +* Check for module loading errors in the Browser Console ### Policies Not Applied **Problem**: Enterprise policies not taking effect **Solutions**: -- Verify policies.json is in the correct location for your OS -- Check file permissions (must be readable by Firefox) -- Restart Firefox after adding/modifying policies -- Use `about:policies` to verify policy application -- Check JSON syntax in policies.json + +* Verify policies.json is in the correct location for your OS +* Check file permissions (must be readable by Firefox) +* Restart Firefox after adding/modifying policies +* Use `about:policies` to verify policy application +* Check JSON syntax in policies.json ### Extension Removed on Restart **Problem**: Extension disappears when Firefox restarts **Solutions**: -- Temporary add-ons are removed on restart - this is expected -- For permanent installation, use enterprise deployment with signed .xpi -- Alternatively, sign the extension through Mozilla's process + +* Temporary add-ons are removed on restart - this is expected +* For permanent installation, use enterprise deployment with signed .xpi +* Alternatively, sign the extension through Mozilla's process ### Content Scripts Not Injecting **Problem**: Content scripts don't run on web pages **Solutions**: -- Firefox doesn't support `file:///` protocol in content scripts -- Ensure you're testing on `http://` or `https://` URLs -- Check content script permissions in manifest + +* Firefox doesn't support `file:///` protocol in content scripts +* Ensure you're testing on `http://` or `https://` URLs +* Check content script permissions in manifest ## Firefox Extension ID The Firefox extension uses the ID: `check@cyberdrain.com` This ID is configured in the `browser_specific_settings` section of `manifest.firefox.json` and is required for: -- Enterprise policy management -- Extension configuration -- Add-on signing and distribution + +* Enterprise policy management +* Extension configuration +* Add-on signing and distribution ## Support For Firefox-specific issues: -- Check the [Common Issues](troubleshooting/common-issues.md) guide -- Review Firefox Browser Console for errors -- Verify you're using Firefox 109 or later -- Ensure the extension was built for Firefox using `npm run build:firefox` -For general extension support, see the main [README](../README.md) and [CONTRIBUTING](../CONTRIBUTING.md) guides. +* Check the [Common Issues](troubleshooting/common-issues.md) guide +* Review Firefox Browser Console for errors +* Verify you're using Firefox 109 or later +* Ensure the extension was built for Firefox using `npm run build:firefox` + +For general extension support, see the main [README](../) and [CONTRIBUTING](../CONTRIBUTING.md) guides. diff --git a/docs/settings/about.md b/docs/settings/about.md index c494799..1651eee 100644 --- a/docs/settings/about.md +++ b/docs/settings/about.md @@ -1,4 +1,4 @@ -# About Check +# About The About section provides information about your Check installation, version details, and links to important resources. @@ -8,15 +8,15 @@ The About section provides information about your Check installation, version de The About section displays key information about your Check installation: -- **Extension Version** - The current version of the Check extension installed in your browser -- **Detection Rules Version** - The version of the detection rules currently loaded (from either default or custom source) -- **Last Updated** - When the detection rules were last refreshed from their source +* **Extension Version** - The current version of the Check extension installed in your browser +* **Detection Rules Version** - The version of the detection rules currently loaded (from either default or custom source) +* **Last Updated** - When the detection rules were last refreshed from their source This information is useful when: -- Reporting issues to support -- Verifying you have the latest updates -- Troubleshooting detection problems +* Reporting issues to support +* Verifying you have the latest updates +* Troubleshooting detection problems ### Product Information @@ -36,10 +36,11 @@ The About section provides quick access to essential resources: ### Extension Stores -- **Chrome Web Store** - Download, rate, and review the extension for Chrome and Chromium-based browsers -- **Edge Web Store** - Download and rate the extension for Microsoft Edge +* [**Chrome Web Store**](https://chromewebstore.google.com/detail/benimdeioplgkhanklclahllklceahbe) - Download, rate, and review the extension for Chrome and Chromium-based browsers +* [**Edge Add Ons Store**](https://microsoftedge.microsoft.com/addons/detail/check-by-cyberdrain/knepjpocdagponkonnbggpcnhnaikajg) - Download and rate the extension for Microsoft Edge +* Firefox Add-Ons - Coming soon! ### Development and Support -- **[GitHub Repository](https://github.com/CyberDrain/Check)** - View source code, report issues, and contribute to the project -- **[CyberDrain Website](https://cyberdrain.com)** - Learn more about CyberDrain's solutions and services +* [**GitHub Repository**](https://github.com/CyberDrain/Check) - View source code, report issues, and contribute to the project +* [**CyberDrain Website**](https://cyberdrain.com) - Learn more about CyberDrain's solutions and services diff --git a/docs/settings/branding.md b/docs/settings/branding.md index f6860a3..a4dce7b 100644 --- a/docs/settings/branding.md +++ b/docs/settings/branding.md @@ -12,15 +12,15 @@ Most individual users can skip this section unless they want to personalize the All user-facing components (suspicious login banner, blocked page, extension popup, and options page) use the same branding configuration. Your custom branding will be displayed consistently across: -- **Suspicious Login Banner** - Warning banner shown on potentially malicious sites -- **Blocked Page** - Full-page block screen for confirmed threats -- **Extension Popup** - Extension icon popup -- **Options Page** - Extension settings page +* **Suspicious Login Banner** - Warning banner shown on potentially malicious sites +* **Blocked Page** - Full-page block screen for confirmed threats +* **Extension Popup** - Extension icon popup +* **Options Page** - Extension settings page ## Company Information {% hint style="warning" %} -#### What if Settings Are Not Visible? +**What if Settings Are Not Visible?** If some settings do not appear on your version, it means your organization's IT department has set these for you. This is normal in business environments - your IT team wants to make sure everyone has the same security settings. You will also see text indicating that the extension is being managed by policy. {% endhint %} @@ -29,8 +29,8 @@ If some settings do not appear on your version, it means your organization's IT You can customize the following properties: -1. **Company Name** - Enter your organization's name. This appears in the extension interface and blocked page messages (displayed as "Protected by [Company Name]"). -2. **Company URL** - Your company website URL (e.g., `https://yourcompany.com`). Used in extension branding and contact information. *(Firefox: required, Chrome/Edge: optional)* +1. **Company Name** - Enter your organization's name. This appears in the extension interface and blocked page messages (displayed as "Protected by \[Company Name]"). +2. **Company URL** - Your company website URL (e.g., `https://yourcompany.com`). Used in extension branding and contact information. _(Firefox: required, Chrome/Edge: optional)_ 3. **Product Name** - What you want to call the extension (like "Contoso Security" instead of "Check"). This replaces the default "Check" branding throughout the interface. 4. **Support Email** - Where users should go for help. This email address is used in the "Contact Admin" button when phishing sites are blocked. @@ -56,10 +56,10 @@ The branding preview shows you exactly how your customizations will appear to us 1. Open the extension's Options page 2. Navigate to the "Branding" section 3. Fill in your branding information: - - Company Name - - Logo (upload or provide URL) - - Primary Color - - Support Email + * Company Name + * Logo (upload or provide URL) + * Primary Color + * Support Email 4. Click "Save" Your branding will be immediately applied to all components. @@ -93,10 +93,9 @@ For enterprise deployments using Windows Group Policy: For Firefox deployments, configure branding through the `policies.json` file: 1. Locate or create the policies file: - - **Windows:** `%ProgramFiles%\Mozilla Firefox\distribution\policies.json` - - **macOS:** `/Applications/Firefox.app/Contents/Resources/distribution/policies.json` - - **Linux:** `/etc/firefox/policies/policies.json` - + * **Windows:** `%ProgramFiles%\Mozilla Firefox\distribution\policies.json` + * **macOS:** `/Applications/Firefox.app/Contents/Resources/distribution/policies.json` + * **Linux:** `/etc/firefox/policies/policies.json` 2. Add the branding configuration under `3rdparty.Extensions`: ```json @@ -199,15 +198,17 @@ Enterprise policies always take precedence over manual settings. ## Browser-Specific Notes ### Firefox -- Uses extension ID: `check@cyberdrain.com` -- Configuration is managed through `policies.json` file -- Supports additional `companyURL` property -- Policies file location varies by operating system + +* Uses extension ID: `check@cyberdrain.com` +* Configuration is managed through `policies.json` file +* Supports additional `companyURL` property +* Policies file location varies by operating system ### Chrome & Edge -- Configuration through GPO, Intune, or Chrome Enterprise Policy -- Uses Windows Registry for advanced configurations -- Supports standard Chrome extension policy format + +* Configuration through GPO, Intune, or Chrome Enterprise Policy +* Uses Windows Registry for advanced configurations +* Supports standard Chrome extension policy format ## Troubleshooting Branding Issues @@ -234,16 +235,18 @@ Enterprise policies always take precedence over manual settings. 3. Clear your browser cache if problems persist ### **Branding Not Appearing** -- Verify the configuration is saved correctly -- Check browser console for errors -- Ensure logo URLs are accessible -- Restart the browser after configuration changes + +* Verify the configuration is saved correctly +* Check browser console for errors +* Ensure logo URLs are accessible +* Restart the browser after configuration changes ### **Enterprise Policy Not Working** -- Verify the policy is applied to the correct organizational unit -- Check that the extension ID matches your deployment -- Allow 15-30 minutes for policy propagation -- Run `gpupdate /force` on Windows to force policy refresh + +* Verify the policy is applied to the correct organizational unit +* Check that the extension ID matches your deployment +* Allow 15-30 minutes for policy propagation +* Run `gpupdate /force` on Windows to force policy refresh ## Example Configurations @@ -314,14 +317,3 @@ Logo URL: https://assets.globalmfg.com/security/gmi-logo-48.png } } ``` - -## Additional Resources - -### Firefox-Specific Documentation -- [Firefox Support Guide](../firefox-support.md) -- [Firefox Deployment Guide](../deployment/firefox-deployment.md) -- Template: `enterprise/firefox/policies.json` - -### Chrome/Edge Documentation -- [Chrome/Edge Deployment](../deployment/chrome-edge-deployment-instructions/README.md) -- Schema: `config/managed_schema.json` \ No newline at end of file From e4b644d0f1fafe8da166e0ee7f57bd32c02a51f2 Mon Sep 17 00:00:00 2001 From: Brian Simpson Date: Thu, 18 Dec 2025 15:07:24 +0000 Subject: [PATCH 02/27] GITBOOK-66: Fix Firefox Button Display Issues --- docs/README.md | 2 +- docs/deployment/firefox-deployment.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 2c7ccba..6d7918a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -32,7 +32,7 @@ Check is completely free, open source, and can be delivered to users completely Installing the plugin immediately gives you protection against AITM attacks and takes seconds. Click the install button and you're good to go. -Install for Edge **OR** Install for Chrome OR Firefox (Coming Soon!) +Install for Edge **OR** Install for Chrome OR Firefox (Coming Soon!) ## Why was Check created? diff --git a/docs/deployment/firefox-deployment.md b/docs/deployment/firefox-deployment.md index 6dbf435..9b9d4d3 100644 --- a/docs/deployment/firefox-deployment.md +++ b/docs/deployment/firefox-deployment.md @@ -1,4 +1,5 @@ --- +hidden: true noIndex: true --- From c23a2baff5afb44c87d7fcae2d3be742df6c5e01 Mon Sep 17 00:00:00 2001 From: Brian Simpson Date: Thu, 18 Dec 2025 15:09:35 +0000 Subject: [PATCH 03/27] GITBOOK-67: No subject --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 6d7918a..eb94efd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -32,7 +32,7 @@ Check is completely free, open source, and can be delivered to users completely Installing the plugin immediately gives you protection against AITM attacks and takes seconds. Click the install button and you're good to go. -Install for Edge **OR** Install for Chrome OR Firefox (Coming Soon!) +Install for Edge **OR** Install for Chrome OR Firefox (Coming Soon!) ## Why was Check created? From 7866b892de952cc4cdcdebdac6bcc8c0ae36ec0d Mon Sep 17 00:00:00 2001 From: Brian Simpson Date: Thu, 18 Dec 2025 15:10:16 +0000 Subject: [PATCH 04/27] GITBOOK-68: No subject --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index eb94efd..fa496fb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -32,7 +32,7 @@ Check is completely free, open source, and can be delivered to users completely Installing the plugin immediately gives you protection against AITM attacks and takes seconds. Click the install button and you're good to go. -Install for Edge **OR** Install for Chrome OR Firefox (Coming Soon!) +Install for Edge **OR** Install for Chrome **OR** Firefox (Coming Soon!) ## Why was Check created? From 739b735b9afcd850747544fb31ce4f66dfc5cf33 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 19 Dec 2025 20:42:38 -0500 Subject: [PATCH 05/27] Allow multiple subdomain levels for Microsoft.com --- rules/detection-rules.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/detection-rules.json b/rules/detection-rules.json index 46f448a..5e8531e 100644 --- a/rules/detection-rules.json +++ b/rules/detection-rules.json @@ -11,7 +11,7 @@ "^https:\\/\\/login\\.live\\.com$" ], "microsoft_domain_patterns": [ - "^https:\\/\\/[^.]*\\.microsoft\\.com$", + "^https:\\/\\/*\\.microsoft\\.com$", "^https:\\/\\/[^.]*\\.microsoftonline\\.com$", "^https:\\/\\/[^.]*\\.office\\.com$", "^https:\\/\\/[^.]*\\.office365\\.com$", From 41234725075247627e8d3c68bda482a6327b9ea1 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Fri, 19 Dec 2025 21:30:59 +0800 Subject: [PATCH 06/27] Update options.js --- options/options.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/options/options.js b/options/options.js index 6974be0..1de5808 100644 --- a/options/options.js +++ b/options/options.js @@ -1383,6 +1383,18 @@ class CheckOptions { this.elements.configDisplay.innerHTML = '
Loading configuration...
'; + // Try to load from cache first (this reflects what's actually being used) + const cacheResult = await chrome.storage.local.get(["detection_rules_cache"]); + const cached = cacheResult?.detection_rules_cache; + + if (cached && cached.rules) { + // Use cached rules which reflect the actual loaded configuration + this.currentConfigData = cached.rules; + this.updateConfigDisplay(); + return; + } + + // Fallback to packaged rules if no cache exists const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 5000); From a9843aa0064fdbfd6fae8aeb1f07bea68362acaa Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Sat, 20 Dec 2025 10:08:01 +0800 Subject: [PATCH 07/27] Update detection-rules.json (#117) * Update detection-rules.json Signed-off-by: Zacgoose <107489668+Zacgoose@users.noreply.github.com> * Update detection-rules.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: John Duprey * Update detection-rules.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: John Duprey * Update rules/detection-rules.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Zacgoose <107489668+Zacgoose@users.noreply.github.com> * Update detection-rules.json Signed-off-by: Zacgoose <107489668+Zacgoose@users.noreply.github.com> --------- Signed-off-by: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Signed-off-by: John Duprey Co-authored-by: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Co-authored-by: John Duprey Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- rules/detection-rules.json | 51 +++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/rules/detection-rules.json b/rules/detection-rules.json index 5e8531e..fadedaf 100644 --- a/rules/detection-rules.json +++ b/rules/detection-rules.json @@ -1,6 +1,6 @@ { - "version": "1.0.8", - "lastUpdated": "2024-12-04T12:00:00Z", + "version": "1.1.0", + "lastUpdated": "2024-12-20T00:00:00Z", "description": "Phishing detection logic for identifying phishing attempts targeting Microsoft 365 login pages", "trusted_login_patterns": [ "^https:\\/\\/login\\.microsoftonline\\.(com|us)$", @@ -8,32 +8,33 @@ "^https:\\/\\/login\\.microsoft\\.net$", "^https:\\/\\/login\\.windows\\.net$", "^https:\\/\\/login\\.partner\\.microsoftonline\\.cn$", - "^https:\\/\\/login\\.live\\.com$" + "^https:\\/\\/login\\.live\\.com$", + "^https:\\/\\/([a-zA-Z0-9-]+\\.)?ciamlogin\\.com$" ], "microsoft_domain_patterns": [ - "^https:\\/\\/*\\.microsoft\\.com$", - "^https:\\/\\/[^.]*\\.microsoftonline\\.com$", - "^https:\\/\\/[^.]*\\.office\\.com$", - "^https:\\/\\/[^.]*\\.office365\\.com$", - "^https:\\/\\/[^.]*\\.sharepoint\\.com$", - "^https:\\/\\/[^.]*\\.onedrive\\.com$", - "^https:\\/\\/[^.]*\\.live\\.com$", - "^https:\\/\\/[^.]*\\.hotmail\\.com$", - "^https:\\/\\/[^.]*\\.outlook\\.com$", - "^https:\\/\\/.*\\.azure\\.(com|cn|net)$", - "^https:\\/\\/[^.]*\\.azurewebsites\\.net$", - "^https:\\/\\/[^.]*\\.msauth\\.net$", - "^https:\\/\\/[^.]*\\.msftauth\\.net$", - "^https:\\/\\/[^.]*\\.msftauthimages\\.net$", - "^https:\\/\\/[^.]*\\.msauthimages\\.net$", - "^https:\\/\\/[^.]*\\.msidentity\\.com$", - "^https:\\/\\/[^.]*\\.microsoftonline-p\\.com$", - "^https:\\/\\/[^.]*\\.microsoftazuread-sso\\.com$", - "^https:\\/\\/[^.]*\\.azureedge\\.net$", - "^https:\\/\\/[^.]*\\.bing\\.com$", + "^https:\\/\\/([a-zA-Z0-9-]+\\.)*microsoft\\.com$", + "^https:\\/\\/([a-zA-Z0-9-]+\\.)*microsoftonline\\.com$", + "^https:\\/\\/([a-zA-Z0-9-]+\\.)*office\\.com$", + "^https:\\/\\/([a-zA-Z0-9-]+\\.)*office365\\.com$", + "^https:\\/\\/([a-zA-Z0-9-]+\\.)*sharepoint\\.com$", + "^https:\\/\\/([a-zA-Z0-9-]+\\.)*onedrive\\.com$", + "^https:\\/\\/(?!login\\.live\\.com$)([a-zA-Z0-9-]+\\.)*live\\.com$", + "^https:\\/\\/([a-zA-Z0-9-]+\\.)*hotmail\\.com$", + "^https:\\/\\/([a-zA-Z0-9-]+\\.)*outlook\\.com$", + "^https:\\/\\/([a-zA-Z0-9-]+\\.)*azure\\.(com|cn|net)$", + "^https:\\/\\/([a-zA-Z0-9-]+\\.)*azurewebsites\\.net$", + "^https:\\/\\/([a-zA-Z0-9-]+\\.)*msauth\\.net$", + "^https:\\/\\/([a-zA-Z0-9-]+\\.)*msftauth\\.net$", + "^https:\\/\\/([a-zA-Z0-9-]+\\.)*msftauthimages\\.net$", + "^https:\\/\\/([a-zA-Z0-9-]+\\.)*msauthimages\\.net$", + "^https:\\/\\/([a-zA-Z0-9-]+\\.)*msidentity\\.com$", + "^https:\\/\\/([a-zA-Z0-9-]+\\.)*microsoftonline-p\\.com$", + "^https:\\/\\/([a-zA-Z0-9-]+\\.)*microsoftazuread-sso\\.com$", + "^https:\\/\\/([a-zA-Z0-9-]+\\.)*azureedge\\.net$", + "^https:\\/\\/(([a-zA-Z0-9-]+\\.)+)?bing\\.com$", "^https:\\/\\/github\\.com$", - "^https:\\/\\/.*\\.cloud\\.microsoft$", - "^https:\\/\\/([^.]+\\.)*live\\.com(/.*)?$" + "^https:\\/\\/([a-zA-Z0-9-]+\\.)*cloud\\.microsoft$", + "^https:\\/\\/([a-zA-Z0-9-]+\\.)*powerbi\\.com$" ], "exclusion_system": { "description": "Centralized exclusion system to prevent false positives on legitimate sites (Microsoft partners, SSO providers, major platforms)", From 7c6f4bbc5f8fdbbcd8441a5f5e0633c4a7bbd8c7 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Sat, 20 Dec 2025 10:11:25 +0800 Subject: [PATCH 08/27] Replace ProjectX --- AGENTS.md | 2 +- CHANGELOG.md | 20 +++++++++---------- LICENSE | 2 +- .../check-extension-config.mobileconfig | 2 +- .../macos-linux/deploy-extension-prefs.sh | 2 +- options/options.html | 2 +- options/options.js | 2 +- package-lock.json | 4 ++-- package.json | 2 +- scripts/modules/config-manager.js | 2 +- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 58f3089..c20c469 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,4 +1,4 @@ -# ProjectX Agent Guide +# Check Agent Guide ## Purpose and Scope - Manifest V3 browser extension that detects phishing sites impersonating Microsoft 365 sign-in pages. diff --git a/CHANGELOG.md b/CHANGELOG.md index d056cdf..f9acc60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -### d02e0d1 - Merge branch 'main' of https://github.com/CyberDrain/ProjectX +### d02e0d1 - Merge branch 'main' of https://github.com/CyberDrain/Check - Contributor: John Duprey - Date: 2025-08-29T14:27:08+02:00 - Files changed: @@ -81,7 +81,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - scripts/content.js - Fixed error in content script. -### 65b5476 - Merge branch 'main' of https://github.com/CyberDrain/ProjectX +### 65b5476 - Merge branch 'main' of https://github.com/CyberDrain/Check - Contributor: John Duprey - Date: 2025-08-29T13:56:03+02:00 - Files changed: @@ -110,7 +110,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - CHANGELOG.md - Added comprehensive branding asset history to the changelog. -### 35d2d3c - Merge branch 'main' of https://github.com/CyberDrain/ProjectX +### 35d2d3c - Merge branch 'main' of https://github.com/CyberDrain/Check - Contributor: John Duprey - Date: 2025-08-29T13:43:39+02:00 - Files changed: @@ -176,7 +176,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - test-extension-loading.html - Standardized quote style and corrected linting across multiple files (752 insertions, 623 deletions). -### 7efb6a5 - Merge branch 'main' of https://github.com/CyberDrain/ProjectX +### 7efb6a5 - Merge branch 'main' of https://github.com/CyberDrain/Check - Contributor: John Duprey - Date: 2025-08-29T13:36:45+02:00 - Files changed: @@ -214,7 +214,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - test-extension-loading.html - Renamed legacy references to Check across code and documentation. -### d94508e - Merge branch 'main' of https://github.com/CyberDrain/ProjectX +### d94508e - Merge branch 'main' of https://github.com/CyberDrain/Check - Contributor: rvdwegen - Date: 2025-08-29T13:33:21+02:00 - Files changed: @@ -243,7 +243,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - CHANGELOG.md - Expanded changelog with per-commit details. -### 4225354 - Merge branch 'main' of https://github.com/CyberDrain/ProjectX +### 4225354 - Merge branch 'main' of https://github.com/CyberDrain/Check - Contributor: rvdwegen - Date: 2025-08-29T13:30:11+02:00 - Files changed: @@ -413,7 +413,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - docs/USER_GUIDE.md - Updated guides to align with repository structure. -### 632a6df - Merge branch 'main' of https://github.com/CyberDrain/ProjectX +### 632a6df - Merge branch 'main' of https://github.com/CyberDrain/Check - Contributor: John Duprey - Date: 2025-08-29T12:47:15+02:00 - Files changed: @@ -433,7 +433,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - rules/detection-rules.json - Revised detection rules JSON definitions. -### 2502595 - Merge branch 'main' of https://github.com/CyberDrain/ProjectX +### 2502595 - Merge branch 'main' of https://github.com/CyberDrain/Check - Contributor: rvdwegen - Date: 2025-08-29T12:43:14+02:00 - Files changed: @@ -536,5 +536,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - styles/content.css - Initial project scaffold with documentation, configuration, and detection rules. -[Unreleased]: https://github.com/CyberDrain/ProjectX/compare/0.1.0...HEAD -[0.1.0]: https://github.com/CyberDrain/ProjectX/releases/tag/0.1.0 +[Unreleased]: https://github.com/CyberDrain/Check/compare/0.1.0...HEAD +[0.1.0]: https://github.com/CyberDrain/Check/releases/tag/0.1.0 diff --git a/LICENSE b/LICENSE index ffc54f0..4851f69 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (C) 2025 ProjectX +Copyright (C) 2025 Check GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 diff --git a/enterprise/macos-linux/check-extension-config.mobileconfig b/enterprise/macos-linux/check-extension-config.mobileconfig index d90966c..c3b77bb 100644 --- a/enterprise/macos-linux/check-extension-config.mobileconfig +++ b/enterprise/macos-linux/check-extension-config.mobileconfig @@ -49,7 +49,7 @@ customRulesUrl Value - https://raw.githubusercontent.com/CyberDrain/ProjectX/refs/heads/main/rules/detection-rules.json + https://raw.githubusercontent.com/CyberDrain/Check/refs/heads/main/rules/detection-rules.json updateInterval diff --git a/enterprise/macos-linux/deploy-extension-prefs.sh b/enterprise/macos-linux/deploy-extension-prefs.sh index 40a2464..65b2b45 100644 --- a/enterprise/macos-linux/deploy-extension-prefs.sh +++ b/enterprise/macos-linux/deploy-extension-prefs.sh @@ -112,7 +112,7 @@ install_preferences() { log_info "Installing extension preferences..." # Chrome preferences - create_extension_preferences "$CHROME_PLIST_PATH" "Chrome" "https://raw.githubusercontent.com/CyberDrain/ProjectX/refs/heads/main/rules/detection-rules.json" + create_extension_preferences "$CHROME_PLIST_PATH" "Chrome" "https://raw.githubusercontent.com/CyberDrain/Check/refs/heads/main/rules/detection-rules.json" # Edge preferences create_extension_preferences "$EDGE_PLIST_PATH" "Edge" "" diff --git a/options/options.html b/options/options.html index 7cf1a0d..7a69aea 100644 --- a/options/options.html +++ b/options/options.html @@ -225,7 +225,7 @@

Detection Configuration

URL to fetch detection configuration from (leave empty for default)

diff --git a/options/options.js b/options/options.js index 1de5808..a24dc1f 100644 --- a/options/options.js +++ b/options/options.js @@ -2449,7 +2449,7 @@ class CheckOptions { cippServerUrl: "", cippTenantId: "", customRulesUrl: - "https://raw.githubusercontent.com/CyberDrain/ProjectX/refs/heads/main/rules/detection-rules.json", + "https://raw.githubusercontent.com/CyberDrain/Check/refs/heads/main/rules/detection-rules.json", updateInterval: 24, enableDebugLogging: false, // Note: enableDeveloperConsoleLogging is not policy-managed - remains under user control diff --git a/package-lock.json b/package-lock.json index 32b7389..c038897 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "projectx", + "name": "Check", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "projectx", + "name": "Check", "version": "1.0.0", "license": "ISC", "devDependencies": { diff --git a/package.json b/package.json index 1fc3f01..f954f1c 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "projectx", + "name": "Check", "version": "1.0.0", "description": "An open-source, Manifest V3 browser extension for detecting phishing attacks that impersonate Microsoft 365 sign-in pages.", "main": "index.js", diff --git a/scripts/modules/config-manager.js b/scripts/modules/config-manager.js index 99f10cb..2627a06 100644 --- a/scripts/modules/config-manager.js +++ b/scripts/modules/config-manager.js @@ -108,7 +108,7 @@ export class ConfigManager { cippServerUrl: "", cippTenantId: "", customRulesUrl: - "https://raw.githubusercontent.com/CyberDrain/ProjectX/refs/heads/main/rules/detection-rules.json", + "https://raw.githubusercontent.com/CyberDrain/Check/refs/heads/main/rules/detection-rules.json", updateInterval: 24, enableDebugLogging: false, // Note: enableDeveloperConsoleLogging is not policy-managed - remains under user control From c4a87328fb74ae137a95d46542f97063fbd8ce35 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 19 Dec 2025 21:27:00 -0500 Subject: [PATCH 09/27] Update rules/detection-rules.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: John Duprey --- rules/detection-rules.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/detection-rules.json b/rules/detection-rules.json index fadedaf..96f664f 100644 --- a/rules/detection-rules.json +++ b/rules/detection-rules.json @@ -1,6 +1,6 @@ { "version": "1.1.0", - "lastUpdated": "2024-12-20T00:00:00Z", + "lastUpdated": "2025-12-20T00:00:00Z", "description": "Phishing detection logic for identifying phishing attempts targeting Microsoft 365 login pages", "trusted_login_patterns": [ "^https:\\/\\/login\\.microsoftonline\\.(com|us)$", From dfb669cc2a344526d55337bb6c44dfb0b23e1932 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 19 Dec 2025 21:27:59 -0500 Subject: [PATCH 10/27] Update rules/detection-rules.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: John Duprey --- rules/detection-rules.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rules/detection-rules.json b/rules/detection-rules.json index 96f664f..0b50960 100644 --- a/rules/detection-rules.json +++ b/rules/detection-rules.json @@ -31,7 +31,7 @@ "^https:\\/\\/([a-zA-Z0-9-]+\\.)*microsoftonline-p\\.com$", "^https:\\/\\/([a-zA-Z0-9-]+\\.)*microsoftazuread-sso\\.com$", "^https:\\/\\/([a-zA-Z0-9-]+\\.)*azureedge\\.net$", - "^https:\\/\\/(([a-zA-Z0-9-]+\\.)+)?bing\\.com$", + "^https:\\/\\/([a-zA-Z0-9-]+\\.)*bing\\.com$", "^https:\\/\\/github\\.com$", "^https:\\/\\/([a-zA-Z0-9-]+\\.)*cloud\\.microsoft$", "^https:\\/\\/([a-zA-Z0-9-]+\\.)*powerbi\\.com$" From a92bdcc9fc127d59b6c858739e36529bfecf077a Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 19 Dec 2025 21:28:12 -0500 Subject: [PATCH 11/27] Update rules/detection-rules.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: John Duprey --- rules/detection-rules.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rules/detection-rules.json b/rules/detection-rules.json index 0b50960..a8bdeac 100644 --- a/rules/detection-rules.json +++ b/rules/detection-rules.json @@ -18,7 +18,8 @@ "^https:\\/\\/([a-zA-Z0-9-]+\\.)*office365\\.com$", "^https:\\/\\/([a-zA-Z0-9-]+\\.)*sharepoint\\.com$", "^https:\\/\\/([a-zA-Z0-9-]+\\.)*onedrive\\.com$", - "^https:\\/\\/(?!login\\.live\\.com$)([a-zA-Z0-9-]+\\.)*live\\.com$", + "^https:\\/\\/live\\.com$", + "^https:\\/\\/(?!login\\.)[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*\\.live\\.com$", "^https:\\/\\/([a-zA-Z0-9-]+\\.)*hotmail\\.com$", "^https:\\/\\/([a-zA-Z0-9-]+\\.)*outlook\\.com$", "^https:\\/\\/([a-zA-Z0-9-]+\\.)*azure\\.(com|cn|net)$", From 34ca801040e09a0dac2a3bd53b45bab09ed5d5be Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Thu, 8 Jan 2026 08:35:24 +0800 Subject: [PATCH 12/27] Refactor config retrieval to use background messaging Updated content and detection rules manager scripts to retrieve configuration and branding data via chrome.runtime messaging to the background script, ensuring merged enterprise and local config is used. DetectionRulesManager now accepts a ConfigManager instance for improved config access. Fallbacks to local storage remain for robustness. --- scripts/background.js | 2 +- scripts/content.js | 108 +++++++++++++++++---- scripts/modules/detection-rules-manager.js | 14 ++- 3 files changed, 102 insertions(+), 22 deletions(-) diff --git a/scripts/background.js b/scripts/background.js index 8d50fe8..ada2099 100644 --- a/scripts/background.js +++ b/scripts/background.js @@ -288,7 +288,7 @@ class CheckBackground { constructor() { this.configManager = new ConfigManager(); this.policyManager = new PolicyManager(); - this.detectionRulesManager = new DetectionRulesManager(); + this.detectionRulesManager = new DetectionRulesManager(this.configManager); this.rogueAppsManager = new RogueAppsManager(); this.webhookManager = new WebhookManager(this.configManager); this.isInitialized = false; diff --git a/scripts/content.js b/scripts/content.js index 60a2522..7e21dd2 100644 --- a/scripts/content.js +++ b/scripts/content.js @@ -583,9 +583,25 @@ if (window.checkExtensionLoaded) { */ async function loadDeveloperConsoleLoggingSetting() { try { + // Request config from background to get merged enterprise + local config const config = await new Promise((resolve) => { - chrome.storage.local.get(["config"], (result) => { - resolve(result.config || {}); + chrome.runtime.sendMessage({ type: "GET_CONFIG" }, (response) => { + if (chrome.runtime.lastError) { + logger.log( + `[M365-Protection] Error getting config from background: ${chrome.runtime.lastError.message}` + ); + // Fallback to local storage if background not available + chrome.storage.local.get(["config"], (result) => { + resolve(result.config || {}); + }); + } else if (!response || !response.success) { + // Fallback to local storage if response invalid + chrome.storage.local.get(["config"], (result) => { + resolve(result.config || {}); + }); + } else { + resolve(response.config); + } }); }); @@ -5580,12 +5596,27 @@ if (window.checkExtensionLoaded) { // Set flag to prevent DOM monitoring loops showingBanner = true; - // Fetch branding configuration (uniform pattern: storage only, like applyBrandingColors) + // Fetch branding configuration from background to get merged config const fetchBranding = () => new Promise((resolve) => { try { - chrome.storage.local.get(["brandingConfig"], (result) => { - resolve(result?.brandingConfig || {}); + chrome.runtime.sendMessage({ type: "GET_BRANDING_CONFIG" }, (response) => { + if (chrome.runtime.lastError) { + logger.log( + `[M365-Protection] Error getting branding from background: ${chrome.runtime.lastError.message}` + ); + // Fallback to local storage if background not available + chrome.storage.local.get(["brandingConfig"], (result) => { + resolve(result?.brandingConfig || {}); + }); + } else if (!response || !response.success) { + // Fallback to local storage if response invalid + chrome.storage.local.get(["brandingConfig"], (result) => { + resolve(result?.brandingConfig || {}); + }); + } else { + resolve(response.branding || {}); + } }); } catch (_) { resolve({}); @@ -5902,10 +5933,25 @@ if (window.checkExtensionLoaded) { validBadgeTimeoutId = null; } - // Load timeout configuration + // Load timeout configuration from background to get merged config const config = await new Promise((resolve) => { - chrome.storage.local.get(["config"], (result) => { - resolve(result.config || {}); + chrome.runtime.sendMessage({ type: "GET_CONFIG" }, (response) => { + if (chrome.runtime.lastError) { + logger.log( + `[M365-Protection] Error getting config from background: ${chrome.runtime.lastError.message}` + ); + // Fallback to local storage if background not available + chrome.storage.local.get(["config"], (result) => { + resolve(result.config || {}); + }); + } else if (!response || !response.success) { + // Fallback to local storage if response invalid + chrome.storage.local.get(["config"], (result) => { + resolve(result.config || {}); + }); + } else { + resolve(response.config); + } }); }); @@ -6281,15 +6327,28 @@ if (window.checkExtensionLoaded) { return; } - // Get CIPP configuration from storage - const result = await new Promise((resolve) => { - chrome.storage.local.get(["config"], (result) => { - resolve(result.config || {}); + // Get CIPP configuration from background to get merged config + const config = await new Promise((resolve) => { + chrome.runtime.sendMessage({ type: "GET_CONFIG" }, (response) => { + if (chrome.runtime.lastError) { + logger.log( + `[M365-Protection] Error getting config from background: ${chrome.runtime.lastError.message}` + ); + // Fallback to local storage if background not available + chrome.storage.local.get(["config"], (result) => { + resolve(result.config || {}); + }); + } else if (!response || !response.success) { + // Fallback to local storage if response invalid + chrome.storage.local.get(["config"], (result) => { + resolve(result.config || {}); + }); + } else { + resolve(response.config); + } }); }); - const config = result; - // Check if CIPP reporting is enabled and URL is configured if (!config.enableCippReporting || !config.cippServerUrl) { logger.debug("CIPP reporting disabled or no server URL configured"); @@ -6348,10 +6407,25 @@ if (window.checkExtensionLoaded) { */ async function applyBrandingColors() { try { - // Get branding configuration from storage + // Get branding configuration from background to get merged config const result = await new Promise((resolve) => { - chrome.storage.local.get(["brandingConfig"], (result) => { - resolve(result.brandingConfig || {}); + chrome.runtime.sendMessage({ type: "GET_BRANDING_CONFIG" }, (response) => { + if (chrome.runtime.lastError) { + logger.log( + `[M365-Protection] Error getting branding from background: ${chrome.runtime.lastError.message}` + ); + // Fallback to local storage if background not available + chrome.storage.local.get(["brandingConfig"], (result) => { + resolve(result?.brandingConfig || {}); + }); + } else if (!response || !response.success) { + // Fallback to local storage if response invalid + chrome.storage.local.get(["brandingConfig"], (result) => { + resolve(result?.brandingConfig || {}); + }); + } else { + resolve(response.branding || {}); + } }); }); diff --git a/scripts/modules/detection-rules-manager.js b/scripts/modules/detection-rules-manager.js index 97ad58e..ad6d35d 100644 --- a/scripts/modules/detection-rules-manager.js +++ b/scripts/modules/detection-rules-manager.js @@ -7,7 +7,7 @@ import { chrome, storage } from "../browser-polyfill.js"; import logger from "../utils/logger.js"; export class DetectionRulesManager { - constructor() { + constructor(configManager = null) { this.cachedRules = null; this.lastUpdate = 0; this.updateInterval = 24 * 60 * 60 * 1000; // Default: 24 hours @@ -16,6 +16,7 @@ export class DetectionRulesManager { this.remoteUrl = "https://raw.githubusercontent.com/CyberDrain/Check/refs/heads/main/rules/detection-rules.json"; this.config = null; + this.configManager = configManager; this.initialized = false; } @@ -53,9 +54,14 @@ export class DetectionRulesManager { async loadConfiguration() { try { - // Load from chrome storage to get user configuration - const result = await storage.local.get(["config"]); - this.config = result?.config || {}; + // Use ConfigManager if available to get merged configuration (enterprise + local) + if (this.configManager) { + this.config = await this.configManager.getConfig(); + } else { + // Fallback to direct storage access if ConfigManager is not available + const result = await storage.local.get(["config"]); + this.config = result?.config || {}; + } // Set remote URL from configuration or use default if (this.config.customRulesUrl) { From 11461fef0180928d3316e1d4681fd9314a6fb799 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Sun, 8 Feb 2026 13:17:22 -0800 Subject: [PATCH 13/27] Domain Squatting, UX and Re-run fixes --- config/managed_schema.json | 70 ++ docs/SUMMARY.md | 4 + docs/features/domain-squatting-detection.md | 232 +++++++ docs/settings/detection-rules.md | 8 + options/options.css | 90 +++ options/options.html | 53 +- options/options.js | 686 +++++++++++-------- rules/detection-rules.json | 47 ++ scripts/background.js | 50 ++ scripts/blocked.js | 86 ++- scripts/content.js | 378 +++++++++- scripts/modules/domain-squatting-detector.js | 574 ++++++++++++++++ 12 files changed, 1936 insertions(+), 342 deletions(-) create mode 100644 docs/features/domain-squatting-detection.md create mode 100644 scripts/modules/domain-squatting-detector.js diff --git a/config/managed_schema.json b/config/managed_schema.json index fa279af..68c9ed8 100644 --- a/config/managed_schema.json +++ b/config/managed_schema.json @@ -160,6 +160,76 @@ "default": "" } } + }, + "domainSquatting": { + "title": "Domain Squatting Detection", + "description": "Configuration for domain squatting detection to protect against typosquatting, homoglyphs, and combosquatting attacks. Enable/disable is controlled by the detection rules JSON. Domains are automatically extracted from the URL allowlist.", + "type": "object", + "properties": { + "deviationThreshold": { + "title": "Deviation Threshold", + "description": "Maximum number of character differences (Levenshtein distance) to trigger detection. Lower values are stricter.", + "type": "integer", + "minimum": 1, + "maximum": 5, + "default": 2 + }, + "algorithms": { + "title": "Detection Algorithms", + "description": "Enable or disable specific detection algorithms", + "type": "object", + "properties": { + "levenshtein": { + "title": "Levenshtein Distance", + "description": "Detect domains with small character differences", + "type": "boolean", + "default": true + }, + "homoglyph": { + "title": "Homoglyph Detection", + "description": "Detect confusable characters (e.g., 'a' vs 'а')", + "type": "boolean", + "default": true + }, + "typosquat": { + "title": "Typosquatting Detection", + "description": "Detect common typing mistakes and character swaps", + "type": "boolean", + "default": true + }, + "combosquat": { + "title": "Combosquatting Detection", + "description": "Detect domains with added prefixes/suffixes", + "type": "boolean", + "default": true + } + } + }, + "protectedDomains": { + "title": "Additional Protected Domains", + "description": "OPTIONAL: Additional domains to protect beyond those automatically extracted from the URL allowlist. Normally you should just add domains to the URL allowlist instead.", + "type": "array", + "items": { + "type": "string", + "title": "Domain", + "description": "Domain name to protect (e.g., 'company.com')" + }, + "default": [] + }, + "Action": { + "title": "Action", + "description": "Action to take when domain squatting is detected", + "type": "string", + "enum": ["block", "warn", "log"], + "default": "block" + }, + "logDetections": { + "title": "Log Detections", + "description": "Log all domain squatting detections to activity log", + "type": "boolean", + "default": true + } + } } } } \ No newline at end of file diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 6023154..38ff855 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -3,6 +3,10 @@ - [About](README.md) - [Firefox Support](firefox-support.md) +## Features + +- [Domain Squatting Detection](features/domain-squatting-detection.md) + ## Deployment - [Chrome/Edge Deployment Instructions](deployment/chrome-edge-deployment-instructions/README.md) diff --git a/docs/features/domain-squatting-detection.md b/docs/features/domain-squatting-detection.md new file mode 100644 index 0000000..d518f0c --- /dev/null +++ b/docs/features/domain-squatting-detection.md @@ -0,0 +1,232 @@ +# Domain Squatting Detection + +Domain squatting protection helps keep you safe from fake websites that try to trick you by using look-alike domain names. Attackers create these fake domains to steal your login credentials. + +## What is Domain Squatting? + +Domain squatting (sometimes called "typosquatting") is when attackers register website addresses that are intentionally similar to legitimate sites. For example: + +- `micros0ft.com` (using a zero instead of the letter O) +- `microsоft.com` (using a Cyrillic "о" that looks like an English "o") +- `login-microsoft.com` (adding extra words to a real domain) + +These fake sites often look exactly like the real Microsoft login page, but they're designed to capture your username and password. + +## How Check Protects You + +Check automatically watches for these fake domains using four smart detection methods: + +### 1. **Character Difference Detection** +Spots domains where characters are changed, missing, or swapped around. + +**Examples Check catches:** +- `microsft.com` → missing the letter "o" +- `micorsoft.com` → letters swapped ("or" instead of "ro") +- `microosoft.com` → extra letter added + +### 2. **Look-Alike Character Detection** +Finds domains using special characters that look similar to normal letters. + +**Examples Check catches:** +- `micrоsoft.com` → uses a Cyrillic "о" that looks like an English "o" +- `microsоft.com` → mixes different alphabet characters +- `micro𝐬oft.com` → uses special Unicode characters + +### 3. **Typing Mistake Detection** +Identifies domains based on common typing errors and keyboard slip-ups. + +**Examples Check catches:** +- `micrisoft.com` → finger slipped to nearby key +- `microssoft.com` → double-typed a letter +- `microosft.com` → typo mixing up letters + +### 4. **Suspicious Word Combination Detection** +Spots domains that add words before or after legitimate domains to look more official. + +**Examples Check catches:** +- `secure-microsoft.com` +- `login-microsoft-verify.com` +- `microsoft-auth.com` +- `official-microsoft-support.com` + +Common suspicious words attackers use: `login`, `secure`, `verify`, `official`, `support`, `auth`, `signin`, `portal` + +## What Domains Are Protected? + +Check protects **30+ popular domains** by default, including: + +**Microsoft Services:** +- microsoft.com, microsoftonline.com, office.com, outlook.com, onedrive.com, and more + +**Other Popular Services:** +- google.com, github.com, facebook.com, amazon.com, apple.com, paypal.com, and more + +**Plus: Your URL Allowlist** + +{% hint style="info" %} +**Unified Protection:** Check uses your [URL Allowlist](../settings/detection-rules.md#url-allowlist-regex-or-url-with-wildcards) for double protection. Any domains you add there are automatically protected from squatting attempts too! + +For example, if you add `https://yourcompany.com/*` to your allowlist, Check will also protect against fake domains like `yourcompany.net` or `your-company.com`. +{% endhint %} + +## How It Works in Practice + +When you visit a website, Check automatically: + +1. **Checks** if the domain looks similar to any protected domain +2. **Analyzes** using all four detection methods +3. **Warns** you if it finds a suspicious match +4. **Blocks** the page if it's clearly a phishing attempt + +You don't need to do anything - the protection works automatically in the background! + +## Configuration + +{% hint style="warning" %} +**For most users**: Domain squatting detection works automatically with default settings. You don't need to change anything! +{% endhint %} + +### Page Blocking Control + +Check has an **"Enable Page Blocking"** setting in the extension options that controls how suspicious pages are handled: + +- **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) + +This gives you control over whether you want aggressive blocking or just warnings for suspicious domains. + +### For Advanced Users and IT Departments + +Domain squatting detection is configured in your detection rules file (not in the Settings UI). This follows the same pattern as other advanced security features like Rogue Apps Detection. + +#### How to Configure + +Edit your `rules/detection-rules.json` file to customize: + +**Enable/Disable Detection:** +```json +{ + "domain_squatting": { + "enabled": true, // Turn detection on/off + "action": "block" // Action when detected: "block" or "warn" + } +} +``` + +**Set Action Type:** +```json +{ + "domain_squatting": { + "action": "block" // "block" = full page block, "warn" = banner only + } +} +``` +Note: Page blocking also requires "Enable Page Blocking" to be turned ON in settings. + +**Adjust Sensitivity:** + "enabled": true + } +} +``` + +**Adjust Sensitivity** (how strict the checking is): +```json +{ + "domain_squatting": { + "deviation_threshold": 2 + } +} +``` +- Lower numbers (1) = Very strict, catches fewer variations +- Higher numbers (3-5) = More lenient, catches more variations +- Default is 2 (recommended for most organizations) + +**Choose Detection Methods:** +```json +{ + "domain_squatting": { + "algorithms": { + "levenshtein": true, + "homoglyph": true, + "typosquat": true, + "combosquat": true + } + } +} +``` + +You can turn individual detection methods on/off. We recommend keeping all four enabled for maximum protection. + +## For MSPs and Enterprise IT + +### Enterprise Policy Management + +Domain squatting detection can be managed through Group Policy (GPO) or Microsoft Intune, just like other Check settings. + +**What You Can Control via Policy:** +- Detection sensitivity (character difference threshold) +- Which detection methods are active +- Additional protected domains specific to your organization + +**What's in the Rules File:** +- Enable/disable domain squatting detection +- Default protected domains list +- Detection rules and patterns + +This separation gives you flexibility - you control the core security settings through your detection rules file, while still allowing policy-based customization for different clients or departments. + +### Adding Organization-Specific Domains + +{% hint style="info" %} +**Use the URL Allowlist!** + +The easiest way to protect your organization's domains is to add them to the URL Allowlist in Detection Rules settings. This automatically: +1. Prevents false positives on your internal sites +2. Protects those domains from squatting attempts +3. Works without modifying detection rules files +{% endhint %} + +**Example:** Adding `https://contoso.com/*` to your allowlist protects against fake domains like: +- `cont0so.com` (zero instead of o) +- `contos0.com` (zero at the end) +- `login-contoso.com` (suspicious prefix) + +### CIPP Reporting and Webhooks + +Domain squatting detections are automatically reported through your existing Check monitoring: + +- **Activity Logs**: View all domain squatting warnings and blocks +- **CIPP Integration**: Squatting detections appear in your CIPP logbook +- **Webhooks**: Configure webhooks to receive `domain_squatting_detected` events + +See [General Settings](../settings/general.md) for configuring reporting and webhooks. + +## Troubleshooting + +### "Check blocked a legitimate site" + +If Check blocks a site you trust: + +1. **Add it to your URL Allowlist** in Detection Rules settings +2. The site will be both allowed and protected from squatting +3. Report the false positive to help improve Check + +### "A phishing site wasn't detected" + +Domain squatting detection works alongside Check's other phishing protections. If a site gets through: + +1. Use "Report False Negative" if you encounter a phishing site +2. Check will update rules to catch it in the future +3. Your report helps protect the entire community + +### "Settings are grayed out" + +If you can't see or change domain squatting settings, your IT department has configured these centrally. This is normal for managed deployments - contact your IT team if you need adjustments. + +## Related Documentation + +- [Detection Rules](../settings/detection-rules.md) - Configure your URL allowlist +- [General Settings](../settings/general.md) - Set up reporting and webhooks +- [Enterprise Deployment](../deployment/) - Deploy Check across your organization +- [Creating Detection Rules](../advanced/creating-detection-rules.md) - Advanced rule customization diff --git a/docs/settings/detection-rules.md b/docs/settings/detection-rules.md index 4f3c466..1babb7b 100644 --- a/docs/settings/detection-rules.md +++ b/docs/settings/detection-rules.md @@ -41,6 +41,14 @@ MSPs and IT departments commonly need to exclude phishing training platforms (li Add URLs or patterns that should be excluded from phishing detection. This is useful for internal company sites or trusted third-party services that might trigger false positives. +**Dual Protection:** Your allowlist serves two purposes: +1. **Prevents false positives** - Sites you add won't be flagged as phishing +2. **Domain squatting protection** - Domains extracted from your allowlist are automatically protected against typosquatting and look-alike attacks + +For example, adding `https://yourcompany.com/*` will both allow that site AND protect against fake domains like `yourcompany.net`, `your-company.com`, or `y0urcompany.com`. + +Learn more about [Domain Squatting Detection](../features/domain-squatting-detection.md). + **How it works:** Your allowlist patterns are **added to** (not replacing) the default CyberDrain exclusions, providing additional protection without losing baseline coverage. You can use: diff --git a/options/options.css b/options/options.css index 5e4ab92..a81a7aa 100644 --- a/options/options.css +++ b/options/options.css @@ -722,6 +722,96 @@ body { letter-spacing: 0.3px; } +/* Collapsible config sections */ +.config-section-collapsible { + margin-bottom: 12px; + border: 1px solid var(--border-color); + border-radius: var(--radius); + overflow: hidden; + background: var(--surface-color); +} + +.config-section-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + cursor: pointer; + user-select: none; + background: var(--surface-color); + transition: background-color 0.2s ease; +} + +.config-section-header:hover { + background: var(--background-color); +} + +.config-section-header-title { + font-weight: 600; + color: var(--primary-color); + font-size: 13px; + text-transform: uppercase; + letter-spacing: 0.5px; + display: flex; + align-items: center; + gap: 8px; +} + +.config-section-toggle { + color: var(--text-muted); + font-size: 20px; + transition: transform 0.2s ease; + line-height: 1; + transform: rotate(180deg); +} + +.config-section-collapsible.expanded .config-section-toggle { + transform: rotate(90deg); +} + +.config-section-content { + max-height: 0; + overflow: hidden; + transition: max-height 0.6s ease-out; +} + +.config-section-collapsible.expanded .config-section-content { + max-height: 2000px; /* Large enough for content */ + transition: max-height 0.8s ease-in; +} + +.config-section-body { + padding: 12px 16px; + border-top: 1px solid var(--border-color); +} + +/* Nested collapsible for lists */ +.config-list-toggle { + color: var(--primary-color); + cursor: pointer; + text-decoration: underline; + font-size: 12px; + margin-top: 8px; + display: inline-block; + transition: color 0.2s ease; +} + +.config-list-toggle:hover { + color: var(--primary-hover); +} + +.config-list-expanded { + margin-top: 0; + margin-bottom: 8px; + padding-left: 0; + display: none; + transition: all 0.5s ease; +} + +.config-list-expanded.visible { + display: block; +} + .config-raw-json { white-space: pre-wrap; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; diff --git a/options/options.html b/options/options.html index 7a69aea..93ea755 100644 --- a/options/options.html +++ b/options/options.html @@ -169,6 +169,11 @@

Generic Webhook

Rogue App Detected + +

Add URLs or regex patterns to allowlist from detection. Use simple URLs with * wildcards (e.g., https://google.com/*) or advanced regex patterns. These will be added to the exclusion rules without replacing the entire ruleset. This allowlist also protects the extracted domains from typosquatting, homoglyphs, and other domain squatting attacks.

@@ -260,10 +265,16 @@

Detection Configuration

Configuration Overview

- +
+ + +
Loading configuration...
@@ -566,35 +577,5 @@ - diff --git a/options/options.js b/options/options.js index a24dc1f..3ca12aa 100644 --- a/options/options.js +++ b/options/options.js @@ -66,6 +66,8 @@ class CheckOptions { this.elements.configDisplay = document.getElementById("configDisplay"); this.elements.toggleConfigView = document.getElementById("toggleConfigView"); + this.elements.expandCollapseAll = + document.getElementById("expandCollapseAll"); // Rule Playground elements this.elements.playgroundRulesInput = document.getElementById("playgroundRulesInput"); @@ -164,6 +166,11 @@ class CheckOptions { this.toggleConfigView() ); + // Expand/collapse all sections + this.elements.expandCollapseAll?.addEventListener("click", () => + this.toggleExpandCollapseAll() + ); + // Simulate enterprise mode toggle (dev only) this.elements.simulateEnterpriseMode?.addEventListener("change", () => this.toggleSimulateEnterpriseMode() @@ -173,6 +180,9 @@ class CheckOptions { this.elements.refreshDetectionRules?.addEventListener("click", () => this.refreshDetectionRules() ); + + // Domain squatting management + // (View default domains now in Configuration Overview) // Playground actions this.elements.runRuleTestBtn?.addEventListener("click", () => this.runRulePlaygroundTest()); @@ -956,6 +966,7 @@ class CheckOptions { "false_positive_report", "page_blocked", "rogue_app_detected", + "domain_squatting_detected", "threat_detected", "validation_event" ]; @@ -1210,6 +1221,7 @@ class CheckOptions { "false_positive_report", "page_blocked", "rogue_app_detected", + "domain_squatting_detected", "threat_detected", "validation_event" ].filter(eventType => @@ -1426,332 +1438,280 @@ class CheckOptions { if (!this.elements.configDisplay) return; const sections = []; + let sectionId = 0; + + // Helper function to create collapsible section + const createCollapsibleSection = (title, content, expanded = false) => { + const id = `config-section-${sectionId++}`; + return ` +
+
+
${title}
+ ► +
+
+
+ ${content} +
+
+
+ `; + }; - // Basic info - sections.push(` -
-
Basic Information
-
Version: ${ - config.version || "Unknown" - }
-
Last Updated: ${ - config.lastUpdated || "Unknown" - }
-
Description: ${ - config.description || "No description" - }
-
- `); + // Helper function to create expandable list + const createExpandableList = (items, label, showCount = 5) => { + if (!items || items.length === 0) return ''; + + const listId = `list-${sectionId++}`; + const displayed = items.slice(0, showCount); + const remaining = items.slice(showCount); + + let html = displayed.map(item => `
• ${item}
`).join(''); + + if (remaining.length > 0) { + html += ` +
+ ${remaining.map(item => `
• ${item}
`).join('')} +
+
+ ▸ Show ${remaining.length} more ${label} +
+ `; + } + + return html; + }; + + // Basic Information - Open by default + const basicInfoContent = ` +
Version: ${config.version || "Unknown"}
+
Last Updated: ${config.lastUpdated || "Unknown"}
+
Description: ${config.description || "No description"}
+ `; + sections.push(createCollapsibleSection('Basic Information', basicInfoContent, true)); // Detection Thresholds if (config.thresholds) { - sections.push(` -
-
Detection Thresholds
-
Legitimate Site Threshold: ${config.thresholds.legitimate}%
-
Suspicious Site Threshold: ${config.thresholds.suspicious}%
-
Phishing Site Threshold: ${config.thresholds.phishing}%
-
- `); + const thresholdsContent = ` +
Legitimate Site Threshold: ${config.thresholds.legitimate}%
+
Suspicious Site Threshold: ${config.thresholds.suspicious}%
+
Phishing Site Threshold: ${config.thresholds.phishing}%
+ `; + sections.push(createCollapsibleSection('Detection Thresholds', thresholdsContent, false)); } // Trusted Login Patterns - if ( - config.trusted_login_patterns && - config.trusted_login_patterns.length > 0 - ) { - sections.push(` -
-
Trusted Login Patterns (${ - config.trusted_login_patterns.length - })
- ${config.trusted_login_patterns - .slice(0, 5) - .map((pattern) => `
• ${pattern}
`) - .join("")} - ${ - config.trusted_login_patterns.length > 5 - ? `
... and ${ - config.trusted_login_patterns.length - 5 - } more
` - : "" - } -
- `); - } - - // Microsoft 365 Detection Requirements - if (config.m365_detection_requirements) { - const req = config.m365_detection_requirements; - const primaryCount = req.primary_elements - ? req.primary_elements.length - : 0; - const secondaryCount = req.secondary_elements - ? req.secondary_elements.length - : 0; - - sections.push(` -
-
Microsoft 365 Detection Requirements
-
Primary Elements: ${primaryCount}
-
Secondary Elements: ${secondaryCount}
-
Description: ${ - req.description || "No description" - }
-
- `); + if (config.trusted_login_patterns && config.trusted_login_patterns.length > 0) { + const patternsContent = createExpandableList(config.trusted_login_patterns, 'patterns', 5); + sections.push(createCollapsibleSection( + `Trusted Login Patterns (${config.trusted_login_patterns.length})`, + patternsContent, + false + )); } // Microsoft Domain Patterns - if ( - config.microsoft_domain_patterns && - config.microsoft_domain_patterns.length > 0 - ) { - sections.push(` -
-
Microsoft Domain Patterns (${ - config.microsoft_domain_patterns.length - })
- ${config.microsoft_domain_patterns - .slice(0, 10) - .map((pattern) => `
• ${pattern}
`) - .join("")} - ${ - config.microsoft_domain_patterns.length > 10 - ? `
... and ${ - config.microsoft_domain_patterns.length - 10 - } more
` - : "" - } -
- `); + if (config.microsoft_domain_patterns && config.microsoft_domain_patterns.length > 0) { + const domainsContent = createExpandableList(config.microsoft_domain_patterns, 'domains', 10); + sections.push(createCollapsibleSection( + `Microsoft Domain Patterns (${config.microsoft_domain_patterns.length})`, + domainsContent, + false + )); + } + + // Domain Squatting Detection + if (config.domain_squatting) { + const ds = config.domain_squatting; + let squattingContent = ` +
Enabled: ${ds.enabled ? 'Yes' : 'No'}
+
Deviation Threshold: ${ds.deviation_threshold || 2}
+
Action: ${ds.action || 'block'}
+
Severity: ${ds.severity || 'high'}
+ `; + + if (ds.algorithms) { + squattingContent += `
Algorithms:
`; + if (ds.algorithms.levenshtein !== false) squattingContent += `
✓ Levenshtein Distance
`; + if (ds.algorithms.homoglyph !== false) squattingContent += `
✓ Homoglyph Detection
`; + if (ds.algorithms.typosquat !== false) squattingContent += `
✓ Typosquatting
`; + if (ds.algorithms.combosquat !== false) squattingContent += `
✓ Combosquatting
`; + } + + if (ds.protected_domains && ds.protected_domains.length > 0) { + squattingContent += `
Protected Domains (${ds.protected_domains.length}):
`; + squattingContent += createExpandableList(ds.protected_domains, 'domains', 10); + } + + sections.push(createCollapsibleSection('Domain Squatting Detection', squattingContent, false)); } - // Exclusion System - if (config.exclusion_system) { - const exclusions = config.exclusion_system; - const domainPatterns = exclusions.domain_patterns || []; - const legitimateContexts = - exclusions.context_indicators?.legitimate_contexts || []; - const legitimateSsoPatterns = - exclusions.context_indicators?.legitimate_sso_patterns || []; - const suspiciousContexts = - exclusions.context_indicators?.suspicious_contexts || []; - - sections.push(` -
-
Exclusion System
-
Domain Patterns: ${ - domainPatterns.length - }
-
Legitimate Context Indicators: ${ - legitimateContexts.length - }
-
Legitimate SSO Patterns: ${ - legitimateSsoPatterns.length - }
-
Suspicious Context Indicators: ${ - suspiciousContexts.length - }
-
Description: ${ - exclusions.description || "No description" - }
- ${ - domainPatterns.length > 0 - ? `
-
Sample Domain Patterns:
- ${domainPatterns - .slice(0, 5) - .map((pattern) => `
• ${pattern}
`) - .join("")} - ${ - domainPatterns.length > 5 - ? `
... and ${ - domainPatterns.length - 5 - } more
` - : "" - } -
` - : "" - } -
- `); + // Rogue Apps Detection + if (config.rogue_apps_detection) { + const rogue = config.rogue_apps_detection; + const rogueContent = ` +
Enabled: ${rogue.enabled ? 'Yes' : 'No'}
+
Source URL: ${rogue.source_url || 'None'}
+
Cache Duration: ${Math.round((rogue.cache_duration || 0) / 3600000)}h
+
Update Interval: ${Math.round((rogue.update_interval || 0) / 3600000)}h
+
Detection Action: ${rogue.detection_action || 'None'}
+
Auto Update: ${rogue.auto_update ? 'Yes' : 'No'}
+ `; + sections.push(createCollapsibleSection('Rogue Apps Detection', rogueContent, false)); } // Phishing Indicators Summary if (config.phishing_indicators && config.phishing_indicators.length > 0) { const indicatorTypes = {}; - const criticalCount = config.phishing_indicators.filter( - (indicator) => indicator.severity === "critical" - ).length; + const criticalCount = config.phishing_indicators.filter(i => i.severity === 'critical').length; + const codeDrivenCount = config.phishing_indicators.filter(i => i.code_driven).length; config.phishing_indicators.forEach((indicator) => { - const type = indicator.type || "unknown"; + const type = indicator.type || indicator.category || 'unknown'; indicatorTypes[type] = (indicatorTypes[type] || 0) + 1; }); - const indicatorSections = Object.entries(indicatorTypes) - .map( - ([type, count]) => - `
${type}: ${count}
` - ) - .join(""); - - // Code-driven indicators summary - const codeDrivenIndicators = config.phishing_indicators.filter(r => r.code_driven); - let codeDrivenHtml = ''; - if (codeDrivenIndicators.length > 0) { - codeDrivenHtml = `
Code-Driven Indicators: ${codeDrivenIndicators.length}
`; - } + let indicatorsContent = ` +
Total Indicators: ${config.phishing_indicators.length}
+
Critical Severity: ${criticalCount}
+
Code-Driven: ${codeDrivenCount}
+
By Type:
+ `; + + Object.entries(indicatorTypes).forEach(([type, count]) => { + indicatorsContent += `
${type}: ${count}
`; + }); - sections.push(` -
-
Phishing Indicators (${config.phishing_indicators.length} total)
-
Critical Severity Rules: ${criticalCount}
- ${indicatorSections} - ${codeDrivenHtml} -
- `); - } - - // Legacy format support - Trusted origins - if (config.trusted_origins && config.trusted_origins.length > 0) { - sections.push(` -
-
Trusted Origins (${ - config.trusted_origins.length - })
- ${config.trusted_origins - .map((origin) => `
• ${origin}
`) - .join("")} -
- `); + sections.push(createCollapsibleSection('Phishing Indicators', indicatorsContent, false)); } - // Legacy format support - Pattern categories - const patternSections = []; - if (config.phishing && config.phishing.length > 0) { - patternSections.push( - `
Phishing Patterns: ${config.phishing.length}
` - ); - } - if (config.malicious && config.malicious.length > 0) { - patternSections.push( - `
Malicious Patterns: ${config.malicious.length}
` - ); - } - if (config.suspicious && config.suspicious.length > 0) { - patternSections.push( - `
Suspicious Patterns: ${config.suspicious.length}
` - ); - } - if (config.legitimate_patterns && config.legitimate_patterns.length > 0) { - patternSections.push( - `
Legitimate Patterns: ${config.legitimate_patterns.length}
` - ); - } + // Microsoft 365 Detection Requirements + if (config.m365_detection_requirements) { + const req = config.m365_detection_requirements; + const primaryCount = req.primary_elements ? req.primary_elements.length : 0; + const secondaryCount = req.secondary_elements ? req.secondary_elements.length : 0; - if (patternSections.length > 0) { - sections.push(` -
-
Legacy Pattern Categories
- ${patternSections.join("")} -
- `); + const m365Content = ` +
Primary Elements: ${primaryCount}
+
Secondary Elements: ${secondaryCount}
+
Description: ${req.description || 'No description'}
+ `; + sections.push(createCollapsibleSection('Microsoft 365 Detection Requirements', m365Content, false)); } - // Rogue apps detection - if (config.rogue_apps_detection) { - const rogue = config.rogue_apps_detection; - sections.push(` -
-
Rogue Apps Detection
-
Enabled: ${ - rogue.enabled ? "Yes" : "No" - }
-
Source: ${ - rogue.source_url ? rogue.source_url : "None" - }
-
Cache Duration: ${Math.round( - (rogue.cache_duration || 0) / 3600000 - )}h
-
Update Interval: ${Math.round( - (rogue.update_interval || 0) / 3600000 - )}h
-
Detection Action: ${ - rogue.detection_action || "None" - }
-
Auto Update: ${ - rogue.auto_update ? "Yes" : "No" - }
-
- `); + // Exclusion System + if (config.exclusion_system) { + const exclusions = config.exclusion_system; + const domainPatterns = exclusions.domain_patterns || []; + const legitimateContexts = exclusions.context_indicators?.legitimate_contexts || []; + const legitimateSsoPatterns = exclusions.context_indicators?.legitimate_sso_patterns || []; + const suspiciousContexts = exclusions.context_indicators?.suspicious_contexts || []; + + let exclusionsContent = ` +
Domain Patterns: ${domainPatterns.length}
+
Legitimate Context Indicators: ${legitimateContexts.length}
+
Legitimate SSO Patterns: ${legitimateSsoPatterns.length}
+
Suspicious Context Indicators: ${suspiciousContexts.length}
+ `; + + if (domainPatterns.length > 0) { + exclusionsContent += `
Domain Patterns:
`; + exclusionsContent += createExpandableList(domainPatterns, 'patterns', 5); + } + + sections.push(createCollapsibleSection('Exclusion System', exclusionsContent, false)); } - // Configuration statistics + // Configuration Statistics let totalPatterns = 0; - if (config.phishing_indicators) - totalPatterns += config.phishing_indicators.length; + if (config.phishing_indicators) totalPatterns += config.phishing_indicators.length; if (config.phishing) totalPatterns += config.phishing.length; if (config.malicious) totalPatterns += config.malicious.length; if (config.suspicious) totalPatterns += config.suspicious.length; - if (config.legitimate_patterns) - totalPatterns += config.legitimate_patterns.length; + if (config.legitimate_patterns) totalPatterns += config.legitimate_patterns.length; - let totalDetectionElements = 0; - if (config.m365_detection_requirements) { - if (config.m365_detection_requirements.primary_elements) - totalDetectionElements += - config.m365_detection_requirements.primary_elements.length; - if (config.m365_detection_requirements.secondary_elements) - totalDetectionElements += - config.m365_detection_requirements.secondary_elements.length; + let criticalRules = 0; + if (config.phishing_indicators) { + criticalRules = config.phishing_indicators.filter(i => i.severity === 'critical').length; } - let totalExclusions = 0; - if (config.exclusion_system) { - if (config.exclusion_system.domain_patterns) - totalExclusions += config.exclusion_system.domain_patterns.length; - if (config.exclusion_system.context_indicators?.legitimate_contexts) - totalExclusions += - config.exclusion_system.context_indicators.legitimate_contexts.length; - if (config.exclusion_system.context_indicators?.legitimate_sso_patterns) - totalExclusions += - config.exclusion_system.context_indicators.legitimate_sso_patterns - .length; - if (config.exclusion_system.context_indicators?.suspicious_contexts) - totalExclusions += - config.exclusion_system.context_indicators.suspicious_contexts.length; + const statsContent = ` +
Total Detection Patterns: ${totalPatterns}
+
Trusted Login Patterns: ${config.trusted_login_patterns ? config.trusted_login_patterns.length : 0}
+
Microsoft Domain Patterns: ${config.microsoft_domain_patterns ? config.microsoft_domain_patterns.length : 0}
+
Critical Severity Rules: ${criticalRules}
+ `; + sections.push(createCollapsibleSection('Configuration Statistics', statsContent, false)); + + this.elements.configDisplay.innerHTML = sections.join(''); + + // Add event delegation for collapsible sections + this.elements.configDisplay.querySelectorAll('[data-toggle-section]').forEach(header => { + header.addEventListener('click', () => { + const sectionId = header.getAttribute('data-toggle-section'); + this.toggleConfigSection(sectionId); + }); + }); + + // Add event delegation for expandable lists + this.elements.configDisplay.querySelectorAll('[data-toggle-list]').forEach(toggle => { + toggle.addEventListener('click', () => { + const listId = toggle.getAttribute('data-toggle-list'); + this.toggleConfigList(listId); + }); + }); + } + + toggleConfigSection(sectionId) { + const section = document.querySelector(`[data-section-id="${sectionId}"]`); + if (section) { + section.classList.toggle('expanded'); } + } - let criticalRules = 0; - if (config.phishing_indicators) { - criticalRules = config.phishing_indicators.filter( - (indicator) => indicator.severity === "critical" - ).length; - } - - sections.push(` -
-
Configuration Statistics
-
Total Detection Patterns: ${totalPatterns}
-
Microsoft 365 Detection Elements: ${totalDetectionElements}
-
Trusted Login Patterns: ${ - config.trusted_login_patterns - ? config.trusted_login_patterns.length - : 0 - }
-
Microsoft Domain Patterns: ${ - config.microsoft_domain_patterns - ? config.microsoft_domain_patterns.length - : 0 - }
-
Critical Severity Rules: ${criticalRules}
-
Total Exclusions: ${totalExclusions}
-
- `); + toggleConfigList(listId) { + const list = document.getElementById(listId); + const toggle = list?.nextElementSibling; + if (list && toggle) { + const isVisible = list.classList.contains('visible'); + list.classList.toggle('visible'); + if (isVisible) { + const remaining = list.querySelectorAll('.config-item').length; + const label = toggle.textContent.match(/more (.+)$/)?.[1] || 'items'; + toggle.textContent = `▸ Show ${remaining} more ${label}`; + } else { + toggle.textContent = `▾ Hide details`; + } + } + } + + toggleExpandCollapseAll() { + const sections = document.querySelectorAll('.config-section-collapsible'); + if (!sections.length) return; - this.elements.configDisplay.innerHTML = sections.join(""); + // Check if any section is collapsed + const hasCollapsed = Array.from(sections).some(section => !section.classList.contains('expanded')); + + // If any are collapsed, expand all. Otherwise, collapse all. + sections.forEach(section => { + if (hasCollapsed) { + section.classList.add('expanded'); + } else { + section.classList.remove('expanded'); + } + }); + + // Update button text + if (this.elements.expandCollapseAll) { + const icon = this.elements.expandCollapseAll.querySelector(".material-icons"); + if (hasCollapsed) { + icon.textContent = "unfold_less"; + this.elements.expandCollapseAll.innerHTML = 'unfold_less Collapse All'; + } else { + icon.textContent = "unfold_more"; + this.elements.expandCollapseAll.innerHTML = 'unfold_more Expand All'; + } + } } toggleConfigView() { @@ -1806,6 +1766,11 @@ class CheckOptions { console.log("Simulate Enterprise Mode:", this.simulateEnterpriseMode); + // If disabling, reload the original branding configuration first + if (!this.simulateEnterpriseMode) { + await this.loadBrandingConfiguration(); + } + // Reload the policy information to apply/remove enterprise restrictions await this.loadPolicyInfo(); @@ -1815,7 +1780,7 @@ class CheckOptions { // Show notification to user const mode = this.simulateEnterpriseMode ? "enabled" : "disabled"; this.showToast( - `Enterprise simulation mode ${mode}. Page will reflect policy restrictions.`, + `Enterprise simulation mode ${mode}. Page will reflect ${this.simulateEnterpriseMode ? 'policy restrictions' : 'original settings'}.`, "info" ); } @@ -2512,10 +2477,20 @@ class CheckOptions { } else { console.log("👤 No managed policies found - user mode"); + // Clear managed policies + this.managedPolicies = null; + + // Restore original branding (not enterprise branding) + await this.loadBrandingConfiguration(); + this.applyBranding(); + // Hide policy badge if (this.elements.policyBadge) { this.elements.policyBadge.style.display = "none"; } + + // Re-enable all fields that might have been disabled by policies + this.enableAllPolicyManagedFields(); } } catch (error) { console.error("Failed to load policy info:", error); @@ -2655,6 +2630,76 @@ class CheckOptions { } } + enableAllPolicyManagedFields() { + // Re-enable all fields that might have been disabled by policies + const allFields = [ + this.elements.showNotifications, + this.elements.enableValidPageBadge, + this.elements.enablePageBlocking, + this.elements.enableCippReporting, + this.elements.cippServerUrl, + this.elements.cippTenantId, + this.elements.customRulesUrl, + this.elements.updateInterval, + this.elements.urlAllowlist, + this.elements.enableDebugLogging, + this.elements.companyName, + this.elements.companyURL, + this.elements.productName, + this.elements.supportEmail, + this.elements.primaryColor, + this.elements.logoUrl, + ]; + + allFields.forEach((element) => { + if (element) { + element.disabled = false; + element.title = ''; + element.classList.remove("policy-managed"); + + // Remove lock icons + const lockIcon = element.parentNode?.querySelector(".policy-lock"); + if (lockIcon) { + lockIcon.remove(); + } + } + }); + + // Re-enable the save button and restore original text + if (this.elements.saveSettings) { + this.elements.saveSettings.title = "Save all settings"; + this.elements.saveSettings.textContent = "Save Settings"; + this.elements.saveSettings.classList.remove("managed-mode"); + } + + // Re-show tabs that might have been hidden + const restrictedTabs = ["general", "detection", "branding"]; + restrictedTabs.forEach((tabName) => { + const menuItem = document.querySelector(`[data-section="${tabName}"]`); + if (menuItem) { + const listItem = menuItem.closest("li"); + if (listItem) { + listItem.style.display = ""; + } + } + }); + + // Remove enterprise notice + const enterpriseNotice = document.querySelector(".enterprise-notice"); + if (enterpriseNotice) { + enterpriseNotice.remove(); + } + + // Remove development notice + const devNotice = document.querySelector(".development-notice"); + if (devNotice) { + devNotice.remove(); + } + + // Clear enterprise managed flag + this.isEnterpriseManaged = false; + } + applyEnterpriseRestrictions(policies) { // Set enterprise managed flag this.isEnterpriseManaged = true; @@ -3566,8 +3611,75 @@ class CheckOptions { toggleIcon.textContent = "dark_mode"; } } + + async viewDefaultDomains() { + try { + // Load detection rules to get default protected domains + const result = await chrome.storage.local.get(['detection_rules_cache']); + const cachedRules = result?.detection_rules_cache?.rules; + + let defaultDomains = []; + if (cachedRules && cachedRules.domain_squatting && cachedRules.domain_squatting.protected_domains) { + defaultDomains = cachedRules.domain_squatting.protected_domains; + } else { + // Fallback to loading from local file + const response = await fetch(chrome.runtime.getURL('rules/detection-rules.json')); + const rules = await response.json(); + if (rules.domain_squatting && rules.domain_squatting.protected_domains) { + defaultDomains = rules.domain_squatting.protected_domains; + } + } + + if (defaultDomains.length === 0) { + this.showToast('No default protected domains found', 'warning'); + return; + } + + // Show confirm dialog with default domains + const domainList = defaultDomains.map(d => `• ${d}`).join('\n'); + const message = `Default Protected Domains (${defaultDomains.length}):\n\n${domainList}\n\nThese domains are protected by default. You can add additional domains in the "Protected Domains" field above.`; + + await this.showConfirmDialog( + 'Default Protected Domains', + message + ); + } catch (error) { + console.error('Failed to load default domains:', error); + this.showToast('Failed to load default protected domains', 'error'); + } + } } +// Minimal config summary: just show code-driven indicator count and key settings +function renderConfigSummary(config) { + const codeDrivenDiv = document.getElementById('codeDrivenIndicators'); + const keySettingsUl = document.getElementById('configKeySettings'); + if (!config || !config.phishing_indicators) return; + // Show only code-driven indicator count + const codeDrivenCount = config.phishing_indicators.filter(r => r.code_driven).length; + codeDrivenDiv.innerHTML = `
Code-Driven Indicators: ${codeDrivenCount}
`; + // Show some key settings + keySettingsUl.innerHTML = ''; + if (config.version) keySettingsUl.innerHTML += `
  • Rules Version: ${config.version}
  • `; + if (config.lastUpdated) keySettingsUl.innerHTML += `
  • Last Updated: ${config.lastUpdated}
  • `; + if (config.detection_settings && config.detection_settings.block_threshold !== undefined) { + keySettingsUl.innerHTML += `
  • Block Threshold: ${config.detection_settings.block_threshold}
  • `; + } + if (config.detection_settings && config.detection_settings.warn_threshold !== undefined) { + keySettingsUl.innerHTML += `
  • Warn Threshold: ${config.detection_settings.warn_threshold}
  • `; + } +} + +// Patch into config loading logic +(function() { + const origShowConfig = window.showConfigDisplay; + window.showConfigDisplay = function(config) { + if (typeof origShowConfig === 'function') origShowConfig(config); + renderConfigSummary(config); + document.getElementById('configSummary').style.display = ''; + }; +})(); + // Initialize options page when DOM is loaded document.addEventListener("DOMContentLoaded", () => { window.checkOptions = new CheckOptions(); diff --git a/rules/detection-rules.json b/rules/detection-rules.json index a8bdeac..747f60c 100644 --- a/rules/detection-rules.json +++ b/rules/detection-rules.json @@ -1928,5 +1928,52 @@ "log_matches": true, "auto_update": true, "fallback_on_error": true + }, + "domain_squatting": { + "description": "Domain squatting detection configuration to protect against typosquatting, homoglyphs, and combosquatting attacks", + "enabled": true, + "action": "block", + "deviation_threshold": 2, + "algorithms": { + "levenshtein": true, + "homoglyph": true, + "typosquat": true, + "combosquat": true + }, + "protected_domains": [ + "microsoft.com", + "microsoftonline.com", + "office.com", + "office365.com", + "outlook.com", + "hotmail.com", + "live.com", + "onedrive.com", + "sharepoint.com", + "azure.com", + "windows.com", + "xbox.com", + "skype.com", + "linkedin.com", + "github.com", + "google.com", + "gmail.com", + "facebook.com", + "twitter.com", + "instagram.com", + "amazon.com", + "apple.com", + "paypal.com", + "netflix.com", + "dropbox.com", + "salesforce.com", + "adobe.com", + "zoom.us", + "slack.com", + "atlassian.com", + "shopify.com" + ], + "severity": "high", + "log_detections": true } } diff --git a/scripts/background.js b/scripts/background.js index 8d50fe8..33e5ba8 100644 --- a/scripts/background.js +++ b/scripts/background.js @@ -11,6 +11,7 @@ import { ConfigManager } from "./modules/config-manager.js"; import { PolicyManager } from "./modules/policy-manager.js"; import { DetectionRulesManager } from "./modules/detection-rules-manager.js"; import { WebhookManager } from "./modules/webhook-manager.js"; +import { DomainSquattingDetector } from "./modules/domain-squatting-detector.js"; import logger from "./utils/logger.js"; import { store as storeLog } from "./utils/background-logger.js"; @@ -290,6 +291,7 @@ class CheckBackground { this.policyManager = new PolicyManager(); this.detectionRulesManager = new DetectionRulesManager(); this.rogueAppsManager = new RogueAppsManager(); + this.domainSquattingDetector = new DomainSquattingDetector(); this.webhookManager = new WebhookManager(this.configManager); this.isInitialized = false; this.initializationPromise = null; @@ -398,6 +400,15 @@ class CheckBackground { // Initialize detection rules manager await this.detectionRulesManager.initialize(); + + // Initialize domain squatting detector with rules and URL allowlist + const detectionRules = this.detectionRulesManager.cachedRules; + if (detectionRules) { + const config = await this.configManager.getConfig(); + const urlAllowlist = config?.urlAllowlist || []; + await this.domainSquattingDetector.initialize(detectionRules, urlAllowlist); + logger.log("Domain squatting detector initialized"); + } await this.refreshPolicy(); @@ -1411,6 +1422,15 @@ class CheckBackground { rules, message: "Detection rules updated", }); + + // Also update domain squatting detector with new rules and URL allowlist + const updatedRules = await this.detectionRulesManager.getDetectionRules(); + if (updatedRules && this.domainSquattingDetector) { + const config = await this.configManager.getConfig(); + const urlAllowlist = config?.urlAllowlist || []; + await this.domainSquattingDetector.initialize(updatedRules, urlAllowlist); + logger.log("Domain squatting detector updated with new rules"); + } } catch (error) { logger.error( "Check: Failed to force update detection rules:", @@ -1419,6 +1439,27 @@ class CheckBackground { sendResponse({ success: false, error: error.message }); } break; + + case "check_domain_squatting": + try { + const { domain } = message; + if (!domain) { + sendResponse({ success: false, error: "Domain parameter required" }); + break; + } + + const result = this.domainSquattingDetector.checkDomain(domain); + // Include the action from rules configuration + if (result && result.detected) { + const rules = await this.detectionRulesManager.getDetectionRules(); + result.action = rules?.domain_squatting?.action || 'block'; + } + sendResponse({ success: true, result }); + } catch (error) { + logger.error("Check: Failed to check domain squatting:", error); + sendResponse({ success: false, error: error.message }); + } + break; case "UPDATE_CONFIG": try { @@ -1440,6 +1481,15 @@ class CheckBackground { updatedConfig?.enableValidPageBadge || this.policy?.EnableValidPageBadge; + // Update domain squatting detector with new configuration + // If URL allowlist changed, reinitialize detector to extract new domains + if (this.domainSquattingDetector) { + const detectionRules = this.detectionRulesManager.cachedRules; + const urlAllowlist = updatedConfig?.urlAllowlist || []; + await this.domainSquattingDetector.initialize(detectionRules, urlAllowlist); + logger.log("Domain squatting detector configuration updated"); + } + // If badge was disabled, remove badges from all tabs if (previousBadgeEnabled && !newBadgeEnabled) { logger.log( diff --git a/scripts/blocked.js b/scripts/blocked.js index a998c31..6bda05d 100644 --- a/scripts/blocked.js +++ b/scripts/blocked.js @@ -25,7 +25,8 @@ function parseUrlParams() { if (detailsParam) { try { - const details = JSON.parse(decodeURIComponent(detailsParam)); + // URLSearchParams.get() already decodes the URI component, so don't decode again + const details = JSON.parse(detailsParam); console.log("Parsed details:", details); // Store details globally for false positive reporting @@ -50,8 +51,15 @@ function parseUrlParams() { document.getElementById("blockReason").textContent = details.reason; } - // Update threat category based on rule description or score - if (details.ruleDescription) { + // Update threat category based on type or rule description + if (details.type === "domain_squatting") { + document.getElementById("threatCategory").textContent = "Domain Squatting"; + // Custom messaging for domain squatting + if (details.protectedDomain) { + document.getElementById("blockReason").textContent = + `This website's domain closely resembles "${details.protectedDomain}" but is NOT the legitimate site. Entering your credentials here could compromise your account.`; + } + } else if (details.ruleDescription) { document.getElementById("threatCategory").textContent = details.ruleDescription; } else if (details.rule) { @@ -756,8 +764,53 @@ function populateTechnicalDetails(details) { console.log("Details.threats:", details.threats); console.log("Details.phishingIndicators:", details.phishingIndicators); console.log("Details.foundIndicators:", details.foundIndicators); + console.log("Details.type:", details.type); + + // Handle domain squatting specific details + if (details.type === "domain_squatting") { + console.log("Populating domain squatting details"); + + // Detection Scores - use confidence for domain squatting + if (details.confidence !== undefined) { + document.getElementById("techScore").textContent = `${Math.round(details.confidence * 100)}%`; + } + document.getElementById("techThreshold").textContent = "Domain Similarity"; + + // Threat Analysis - use techniques + let indicatorCount = 0; + if (details.techniques && Array.isArray(details.techniques)) { + indicatorCount = details.techniques.length; + document.getElementById("techIndicatorCount").textContent = indicatorCount; + + // Set severity + const severityElement = document.getElementById("techSeverity"); + const severityMap = { critical: "CRITICAL", high: "HIGH", medium: "MEDIUM", low: "LOW" }; + const severityText = severityMap[details.severity] || details.severity.toUpperCase(); + severityElement.innerHTML = `${severityText}`; + + // Populate techniques as indicators + populatePhishingIndicatorsList(details.techniques, details); + } + + // Detection method + document.getElementById("techDetectionMethod").textContent = "Domain Squatting Detection"; + + // Page Information + if (details.testDomain) { + document.getElementById("techPageTitle").textContent = `Suspicious Domain: ${details.testDomain}`; + } + if (details.protectedDomain) { + const userAgent = document.getElementById("techUserAgent"); + userAgent.textContent = `Impersonating: ${details.protectedDomain}`; + } + document.getElementById("techTimestamp").textContent = details.detectionTime + ? new Date(details.detectionTime).toLocaleString() + : new Date().toLocaleString(); + + return; // Exit early for domain squatting + } - // Detection Scores + // Detection Scores for phishing detection if (details.score !== undefined) { document.getElementById("techScore").textContent = details.score; } @@ -765,7 +818,7 @@ function populateTechnicalDetails(details) { document.getElementById("techThreshold").textContent = details.threshold; } - // Threat Analysis - Use multiple data sources + // Threat Analysis - Use multiple data sources for phishing let phishingIndicators = []; let indicatorCount = 0; @@ -889,6 +942,7 @@ function populateTechnicalDetails(details) { function populatePhishingIndicatorsList(indicators, details) { console.log("=== POPULATING PHISHING INDICATORS LIST ==="); console.log("Indicators to display:", indicators); + console.log("Details type:", details.type); const container = document.getElementById("techPhishingIndicators"); @@ -919,7 +973,27 @@ function populatePhishingIndicatorsList(indicators, details) { return; } - // Create formatted list of indicators + // Handle domain squatting techniques differently + if (details.type === "domain_squatting") { + console.log("Displaying domain squatting techniques"); + const techniquesHTML = indicators + .map((technique) => { + const techniqueName = technique.technique || technique.id || "Unknown Technique"; + const description = technique.description || "Domain similarity detected"; + + return `
    + ${techniqueName}
    + ${description} +
    `; + }) + .join(""); + + container.innerHTML = techniquesHTML; + console.log("Populated domain squatting techniques with", indicators.length, "techniques"); + return; + } + + // Create formatted list of indicators for phishing const indicatorHTML = indicators .map((indicator) => { const id = indicator.id || indicator.type || "Unknown"; diff --git a/scripts/content.js b/scripts/content.js index 60a2522..17007d8 100644 --- a/scripts/content.js +++ b/scripts/content.js @@ -30,8 +30,8 @@ if (window.checkExtensionLoaded) { let developerConsoleLoggingEnabled = false; // Cache for developer console logging setting let showingBanner = false; // Flag to prevent DOM monitoring loops when showing banners let escalatedToBlock = false; // Flag to indicate page has been escalated to block - stop all monitoring - const MAX_SCANS = 5; // Prevent infinite scanning - reduced for performance - const SCAN_COOLDOWN = 1200; // 1200ms between scans - increased for performance + const MAX_SCANS = 8; // Allow more rescans for dynamically loaded content + const SCAN_COOLDOWN = 800; // 800ms between scans - allow faster rescans for dynamic content const THREAT_TRIGGERED_COOLDOWN = 500; // Shorter cooldown for threat-triggered re-scans const WARNING_THRESHOLD = 3; // Block if 4+ warning threats found (escalation threshold) const PHISHING_PROCESSING_TIMEOUT = 10000; // 10 second timeout for phishing indicator processing @@ -3944,7 +3944,138 @@ if (window.checkExtensionLoaded) { }` ); - // Step 4: Pre-check domain for obvious non-threats only + // Step 4: Check for domain squatting (typosquatting, homoglyphs, etc.) + // This runs BEFORE phishing detection to catch domain-based threats early + try { + const domainSquattingResult = await chrome.runtime.sendMessage({ + type: "check_domain_squatting", + domain: window.location.hostname + }); + + if (domainSquattingResult?.success && domainSquattingResult.result?.detected) { + const squattingData = domainSquattingResult.result; + logger.warn(`⚠️ DOMAIN SQUATTING DETECTED:`); + logger.warn(` Test Domain: ${squattingData.testDomain}`); + logger.warn(` Protected Domain: ${squattingData.protectedDomain}`); + logger.warn(` Techniques: ${squattingData.techniques.map(t => t.technique).join(', ')}`); + logger.warn(` Severity: ${squattingData.severity}`); + logger.warn(` Confidence: ${(squattingData.confidence * 100).toFixed(1)}%`); + logger.warn(` Action: ${squattingData.action || 'warn'}`); + + // 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" + logger.debug(` enablePageBlocking: ${config.enablePageBlocking}`); + logger.debug(` squattingData.action: ${squattingData.action}`); + const shouldBlock = squattingData.action === 'block' && + config.enablePageBlocking !== false; + logger.debug(` shouldBlock: ${shouldBlock}`); + + // Log domain squatting detection + logProtectionEvent({ + type: "threat_detected", + action: shouldBlock ? "blocked" : "warned", + url: location.href, + origin: currentOrigin, + reason: `Domain squatting detected: ${squattingData.techniques.map(t => t.description).join('; ')}`, + severity: squattingData.severity, + redirectTo: null, + clientId: null, + ruleType: "domain_squatting", + squattingDetails: squattingData + }); + + // Send CIPP report for domain squatting detection + sendCippReport({ + type: "domain_squatting_detected", + url: defangUrl(location.href), + origin: currentOrigin, + testDomain: squattingData.testDomain, + protectedDomain: squattingData.protectedDomain, + techniques: squattingData.techniques.map(t => ({ + technique: t.technique, + description: t.description, + details: t.details + })), + severity: squattingData.severity, + confidence: squattingData.confidence, + action: shouldBlock ? "blocked" : "warned", + reason: `Domain squatting detected: ${squattingData.techniques.map(t => t.description).join('; ')}` + }); + + // Send domain_squatting_detected webhook + chrome.runtime + .sendMessage({ + type: "send_webhook", + webhookType: "domain_squatting_detected", + data: { + url: defangUrl(location.href), + testDomain: squattingData.testDomain, + protectedDomain: squattingData.protectedDomain, + techniques: squattingData.techniques.map(t => ({ + technique: t.technique, + description: t.description + })), + severity: squattingData.severity, + confidence: squattingData.confidence, + action: shouldBlock ? "blocked" : "warned", + reason: `Domain squatting detected: ${squattingData.techniques.map(t => t.description).join('; ')}` + }, + }) + .catch((err) => { + logger.debug("Failed to send domain squatting webhook:", err); + }); + + if (shouldBlock) { + // Block the page - redirect to blocked page with domain squatting context + const techniquesDesc = squattingData.techniques.map(t => + `${t.technique}: ${t.description}` + ).join('; '); + + await showBlockingOverlay( + `Domain Squatting: This site closely resembles "${squattingData.protectedDomain}" but is not the legitimate site`, + { + type: "domain_squatting", + severity: squattingData.severity, + testDomain: squattingData.testDomain, + protectedDomain: squattingData.protectedDomain, + techniques: squattingData.techniques, + confidence: squattingData.confidence, + reason: `Domain squatting detected: ${techniquesDesc}`, + detectionMethod: "domain-squatting", + detectionTime: Date.now() + } + ); + return; // Stop processing, page is blocked + } else if (showNotifications) { + // Show warning banner for domain squatting (only if notifications enabled) + const techniquesDesc = squattingData.techniques.map(t => + `${t.technique}: ${t.description}` + ).join('\n'); + + showWarningBanner( + `⚠️ POTENTIAL DOMAIN SQUATTING: This domain closely resembles "${squattingData.protectedDomain}"`, + { + type: "domain_squatting", + severity: squattingData.severity, + reason: `Domain squatting techniques detected:\n${techniquesDesc}`, + protectedDomain: squattingData.protectedDomain, + confidence: squattingData.confidence, + techniques: squattingData.techniques + } + ); + } + + // If we showed a warning but not blocking, continue with phishing detection + // If we blocked, we already returned above + } + } catch (squattingError) { + logger.debug("Domain squatting check failed or disabled:", squattingError.message); + } + + // Step 5: Pre-check domain for obvious non-threats only // NOTE: We removed the restrictive domain check that was blocking training platforms // like KnowBe4. Phishing simulations use legitimate domains but copy Microsoft UI. // Let content-based detection handle all cases. @@ -3956,7 +4087,7 @@ if (window.checkExtensionLoaded) { `Analyzing domain "${currentDomain}" - proceeding with content-based detection` ); - // Step 5: Check if page is an MS logon page (using rule file requirements) + // Step 6: Check if page is an MS logon page (using rule file requirements) const msDetection = detectMicrosoftElements(); if (!msDetection.isLogonPage) { // Check if page has ANY Microsoft-related elements before running expensive phishing indicators @@ -5342,6 +5473,8 @@ if (window.checkExtensionLoaded) { }); // Fallback: Check periodically for content that might have loaded without triggering observer + let fallbackCheckCount = 0; + const MAX_FALLBACK_CHECKS = 5; // Allow up to 5 fallback checks const checkInterval = setInterval(() => { // Stop if page has been escalated to block if (escalatedToBlock) { @@ -5360,17 +5493,27 @@ if (window.checkExtensionLoaded) { return; } + fallbackCheckCount++; const currentElementCount = document.querySelectorAll("*").length; const hasSignificantContent = document.body?.textContent?.length > 1000; if (hasSignificantContent && currentElementCount > 50) { logger.log( - "⏰ Fallback timer detected significant content - re-running analysis" + `⏰ Fallback timer detected significant content - re-running analysis (check ${fallbackCheckCount}/${MAX_FALLBACK_CHECKS})` ); - clearInterval(checkInterval); runProtection(true); + + // Stop after MAX_FALLBACK_CHECKS successful rescans + if (fallbackCheckCount >= MAX_FALLBACK_CHECKS) { + logger.log("⏰ Maximum fallback checks reached - stopping"); + clearInterval(checkInterval); + } + } else if (fallbackCheckCount >= MAX_FALLBACK_CHECKS) { + // Stop after MAX_FALLBACK_CHECKS attempts even if no significant content + logger.debug("⏰ Maximum fallback check attempts reached - stopping"); + clearInterval(checkInterval); } - }, 2000); + }, 1500); // Check every 1.5 seconds // Stop monitoring after 30 seconds to prevent resource drain setTimeout(() => { @@ -5789,6 +5932,20 @@ if (window.checkExtensionLoaded) { bannerIcon = "🔍"; bannerColor = "linear-gradient(135deg, #2196f3, #1976d2)"; // Blue for scanning } + // Check for domain squatting detection - tailored messaging + else if ( + analysisData?.type === "domain_squatting" || + reason.toLowerCase().includes("domain squatting") || + reason.toLowerCase().includes("typosquat") + ) { + bannerTitle = "⚠️ Suspicious Domain Detected"; + bannerIcon = "🔗"; + bannerColor = "linear-gradient(135deg, #ff5722, #d84315)"; // Same orange-red as high risk warnings + // Override reason text for domain squatting to be more user-friendly + if (analysisData?.protectedDomain) { + reason = `This website's domain looks similar to "${analysisData.protectedDomain}" but is NOT the legitimate site. Be careful entering any credentials.`; + } + } // Check for rogue app detection else if ( analysisData?.type === "rogue_app_on_legitimate_domain" || @@ -5810,7 +5967,7 @@ if (window.checkExtensionLoaded) { // Layout: left branding slot, absolutely centered message block, dismiss button on right. const bannerContent = ` -
    +
    ${bannerIcon} @@ -5818,10 +5975,10 @@ if (window.checkExtensionLoaded) { ${reason}${detailsText}
    + font-size:14px;font-weight:bold;line-height:1;box-sizing:border-box;font-family:monospace;z-index:3;">×
    `; // Check if banner already exists @@ -6382,12 +6539,101 @@ if (window.checkExtensionLoaded) { } } + /** + * Track network activity for better timing detection + */ + let pendingRequests = 0; + let networkIdleTimer = null; + let lastNetworkActivity = Date.now(); + + /** + * Monitor fetch/XHR to detect when network is idle + */ + function setupNetworkMonitoring() { + // Intercept fetch + const originalFetch = window.fetch; + window.fetch = function(...args) { + pendingRequests++; + lastNetworkActivity = Date.now(); + logger.debug(`🌐 Fetch request started (pending: ${pendingRequests})`); + + return originalFetch.apply(this, arguments).finally(() => { + pendingRequests--; + lastNetworkActivity = Date.now(); + logger.debug(`🌐 Fetch request completed (pending: ${pendingRequests})`); + checkNetworkIdle(); + }); + }; + + // Intercept XMLHttpRequest + const originalOpen = XMLHttpRequest.prototype.open; + const originalSend = XMLHttpRequest.prototype.send; + + XMLHttpRequest.prototype.open = function(...args) { + this._check_tracked = true; + return originalOpen.apply(this, args); + }; + + XMLHttpRequest.prototype.send = function(...args) { + if (this._check_tracked) { + pendingRequests++; + lastNetworkActivity = Date.now(); + logger.debug(`🌐 XHR request started (pending: ${pendingRequests})`); + + this.addEventListener('loadend', () => { + pendingRequests--; + lastNetworkActivity = Date.now(); + logger.debug(`🌐 XHR request completed (pending: ${pendingRequests})`); + checkNetworkIdle(); + }); + } + return originalSend.apply(this, args); + }; + } + + /** + * Check if network has been idle for a period + */ + function checkNetworkIdle() { + if (networkIdleTimer) { + clearTimeout(networkIdleTimer); + } + + // Wait 300ms after last network activity before considering network "idle" + networkIdleTimer = setTimeout(() => { + if (pendingRequests === 0) { + const timeSinceActivity = Date.now() - lastNetworkActivity; + if (timeSinceActivity >= 300) { + logger.log("🌐 Network idle detected - content likely loaded"); + // Trigger a scan if we haven't scanned recently + if (scanCount < MAX_SCANS && !showingBanner && !escalatedToBlock) { + logger.log("🔄 Triggering scan after network idle"); + runProtection(true); + } + } + } + }, 300); + } + + /** + * Check if critical elements exist (forms, inputs, etc.) + */ + function hasCriticalElements() { + const hasForm = document.querySelector('form') !== null; + const hasPasswordInput = document.querySelector('input[type="password"]') !== null; + const hasEmailInput = document.querySelector('input[type="email"]') !== null; + const hasTextInput = document.querySelectorAll('input[type="text"]').length > 0; + + return hasForm || hasPasswordInput || hasEmailInput || hasTextInput; + } + /** * Initialize protection when DOM is ready */ function initializeProtection() { try { logger.log("Initializing Check"); + logger.log(`Initial document.readyState: ${document.readyState}`); // Console capture is now setup only when developer mode is enabled (see loadDeveloperConsoleLoggingSetting) // This eliminates performance overhead for normal users @@ -6397,15 +6643,121 @@ if (window.checkExtensionLoaded) { // Setup dynamic script monitoring early to catch any immediate script execution setupDynamicScriptMonitoring(); + + // Setup network monitoring for better timing detection + setupNetworkMonitoring(); + + // Track when we've completed different loading stages + let domContentLoadedFired = false; + let windowLoadFired = false; + let initialScanDone = false; + + /** + * Perform initial scan with smart timing + */ + function performInitialScan() { + if (initialScanDone) { + logger.debug("Initial scan already completed, skipping"); + return; + } + + initialScanDone = true; + logger.log("📊 Performing initial scan"); + logger.log(` - DOMContentLoaded: ${domContentLoadedFired}`); + logger.log(` - window.load: ${windowLoadFired}`); + logger.log(` - Pending requests: ${pendingRequests}`); + logger.log(` - Critical elements: ${hasCriticalElements()}`); + + runProtection(); + } + // Strategy 1: Wait for DOMContentLoaded if still loading if (document.readyState === "loading") { + logger.log("⏳ Document still loading, waiting for DOMContentLoaded"); + document.addEventListener("DOMContentLoaded", () => { - setTimeout(runProtection, 100); // Small delay to ensure DOM is stable + domContentLoadedFired = true; + logger.log("✅ DOMContentLoaded fired"); + + // Check if critical elements already exist + if (hasCriticalElements()) { + logger.log("🎯 Critical elements detected immediately after DOMContentLoaded"); + // Scan quickly if we have forms/inputs + setTimeout(performInitialScan, 200); + } else { + // Wait a bit longer for dynamic content + setTimeout(performInitialScan, 600); + } }); } else { - // DOM already ready - setTimeout(runProtection, 100); + // DOM already loaded + domContentLoadedFired = true; + logger.log("✅ Document already loaded (readyState: " + document.readyState + ")"); + + if (hasCriticalElements()) { + logger.log("🎯 Critical elements already present"); + // Scan quickly if we have forms/inputs + setTimeout(performInitialScan, 200); + } else { + // Wait longer for dynamic content to load + setTimeout(performInitialScan, 800); + } } + + // Strategy 2: Also wait for window.load event (all resources loaded) + if (document.readyState !== "complete") { + window.addEventListener("load", () => { + windowLoadFired = true; + logger.log("✅ Window load event fired (all resources loaded)"); + + // Trigger another scan if initial scan was too early + if (initialScanDone && !hasCriticalElements() && scanCount < MAX_SCANS) { + logger.log("🔄 Re-scanning after window.load as critical elements may have loaded"); + setTimeout(() => runProtection(true), 300); + } + }); + } else { + windowLoadFired = true; + logger.log("✅ Window already fully loaded"); + } + + // Strategy 3: Monitor for critical elements appearing + // If we scan early and find nothing, watch for forms/inputs to appear + const criticalElementObserver = new MutationObserver((mutations) => { + for (const mutation of mutations) { + if (mutation.type === "childList") { + for (const node of mutation.addedNodes) { + if (node.nodeType === Node.ELEMENT_NODE) { + const tagName = node.tagName?.toLowerCase(); + // If a form or input appears, trigger immediate scan + if (tagName === "form" || tagName === "input") { + logger.log("🎯 Critical element detected via observer: " + tagName); + criticalElementObserver.disconnect(); + + if (scanCount < MAX_SCANS && !showingBanner && !escalatedToBlock) { + logger.log("🔄 Triggering immediate scan"); + runProtection(true); + } + return; + } + } + } + } + } + }); + + // Start observing for critical elements + criticalElementObserver.observe(document.documentElement, { + childList: true, + subtree: true, + }); + + // Stop observing after 5 seconds + setTimeout(() => { + criticalElementObserver.disconnect(); + logger.debug("Critical element observer stopped"); + }, 5000); + } catch (error) { logger.error("Failed to initialize protection:", error.message); } diff --git a/scripts/modules/domain-squatting-detector.js b/scripts/modules/domain-squatting-detector.js new file mode 100644 index 0000000..0c0e0c8 --- /dev/null +++ b/scripts/modules/domain-squatting-detector.js @@ -0,0 +1,574 @@ +/** + * Domain Squatting Detector Module + * Detects typosquatting, homoglyphs, combosquatting, and other domain impersonation techniques + */ + +import logger from '../utils/logger.js'; + +export class DomainSquattingDetector { + constructor() { + this.protectedDomains = []; + this.enabled = true; + this.deviationThreshold = 2; // Maximum Levenshtein distance + this.algorithms = { + levenshtein: true, + homoglyph: true, + typosquat: true, + combosquat: true + }; + + // Common homoglyphs (confusable characters) + this.homoglyphs = { + 'a': ['à', 'á', 'â', 'ã', 'ä', 'å', 'ā', 'ă', 'ą', 'α', 'а'], + 'b': ['ḃ', 'ḅ', 'ḇ', 'ь', 'в'], + 'c': ['ć', 'ĉ', 'ċ', 'ç', 'č', 'ϲ', 'с'], + 'd': ['ď', 'ḋ', 'ḍ', 'ḏ', 'ḑ', 'ḓ', 'ԁ', 'ժ'], + 'e': ['è', 'é', 'ê', 'ë', 'ē', 'ĕ', 'ė', 'ę', 'ě', 'е', 'ε'], + 'g': ['ĝ', 'ğ', 'ġ', 'ģ', 'ց', 'ǥ'], + 'h': ['ĥ', 'ḣ', 'ḥ', 'ḧ', 'ḩ', 'ḫ', 'һ', 'հ'], + 'i': ['ì', 'í', 'î', 'ï', 'ĩ', 'ī', 'ĭ', 'į', 'ı', 'і', 'ι'], + 'j': ['ĵ', 'ј'], + 'k': ['ķ', 'ḱ', 'ḳ', 'ḵ', 'κ', 'к'], + 'l': ['ĺ', 'ļ', 'ľ', 'ḷ', 'ḹ', 'ḻ', 'ḽ', 'ӏ', 'ℓ'], + 'm': ['ḿ', 'ṁ', 'ṃ', 'м', 'ṁ'], + 'n': ['ñ', 'ń', 'ņ', 'ň', 'ṅ', 'ṇ', 'ṉ', 'ṋ', 'п'], + 'o': ['ò', 'ó', 'ô', 'õ', 'ö', 'ø', 'ō', 'ŏ', 'ő', 'ο', 'о', 'օ'], + 'p': ['ṕ', 'ṗ', 'р', 'ρ'], + 'q': ['ԛ'], + 'r': ['ŕ', 'ŗ', 'ř', 'ṙ', 'ṛ', 'ṝ', 'ṟ', 'г'], + 's': ['ś', 'ŝ', 'ş', 'š', 'ṡ', 'ṣ', 'ṥ', 'ṧ', 'ṩ', 'ѕ'], + 't': ['ţ', 'ť', 'ṫ', 'ṭ', 'ṯ', 'ṱ', 'т', 'τ'], + 'u': ['ù', 'ú', 'û', 'ü', 'ũ', 'ū', 'ŭ', 'ů', 'ű', 'ų', 'υ', 'и'], + 'v': ['ṽ', 'ṿ', 'ν', 'ѵ'], + 'w': ['ŵ', 'ẁ', 'ẃ', 'ẅ', 'ẇ', 'ẉ', 'ẘ', 'ԝ'], + 'x': ['ẋ', 'ẍ', 'х', 'χ'], + 'y': ['ý', 'ÿ', 'ŷ', 'ẏ', 'ẙ', 'ỳ', 'ỵ', 'у', 'ү'], + 'z': ['ź', 'ż', 'ž', 'ẑ', 'ẓ', 'ẕ', 'ᴢ'], + '0': ['о', 'ο', 'о', '᧐'], + '1': ['l', 'і', 'Ӏ', 'ǀ'], + '3': ['з', 'ʒ', 'ȝ'], + '5': ['ƽ'], + '6': ['б'], + '8': ['ց'], + '9': ['ԛ', 'ց'] + }; + + // Reverse lookup for homoglyphs + this.homoglyphReverse = {}; + this.buildReverseHomoglyphMap(); + } + + /** + * Build reverse lookup map for homoglyphs + */ + buildReverseHomoglyphMap() { + for (const [base, variants] of Object.entries(this.homoglyphs)) { + for (const variant of variants) { + this.homoglyphReverse[variant] = base; + } + } + } + + /** + * Extract domains from URL allowlist patterns + * Handles regex patterns, wildcards, and plain URLs/domains + */ + extractDomainsFromAllowlist(allowlist) { + if (!Array.isArray(allowlist) || allowlist.length === 0) { + return []; + } + + const domains = []; + + for (const pattern of allowlist) { + if (!pattern || typeof pattern !== 'string') continue; + + try { + let domain = null; + + // Remove regex anchors and escaping + let cleaned = pattern.trim() + .replace(/^\^/, '') // Remove leading ^ + .replace(/\$$/, '') // Remove trailing $ + .replace(/\\/g, ''); // Remove escape characters + + // Try to extract domain from URL pattern + // Pattern formats: + // - https://example.com/... + // - ^https://example\.com$ + // - *.example.com + // - example.com + + // Extract hostname from URL-like patterns + const urlMatch = cleaned.match(/^(?:https?:\/\/)?([a-zA-Z0-9][\w\-\.]*[a-zA-Z0-9])/); + if (urlMatch) { + domain = urlMatch[1]; + + // Remove wildcards + domain = domain.replace(/^\*\./, ''); + + // Remove path and query string indicators + domain = domain.split('/')[0].split('?')[0].split('#')[0]; + + // Remove regex patterns like (.*)? + domain = domain.replace(/\(.*?\)/g, ''); + + // Remove trailing dots or special chars + domain = domain.replace(/[^\w\-\.]/g, '').replace(/\.$/, ''); + + // Validate it looks like a domain + if (domain && domain.includes('.') && domain.length > 3) { + domains.push(domain.toLowerCase()); + } + } + } catch (error) { + logger.debug(`Could not extract domain from pattern: ${pattern}`, error); + } + } + + // Remove duplicates + return [...new Set(domains)]; + } + + /** + * Initialize with configuration + */ + async initialize(config, urlAllowlist = []) { + try { + if (config.domain_squatting) { + this.enabled = config.domain_squatting.enabled !== false; + this.protectedDomains = config.domain_squatting.protected_domains || []; + this.deviationThreshold = config.domain_squatting.deviation_threshold || 2; + + if (config.domain_squatting.algorithms) { + this.algorithms = { ...this.algorithms, ...config.domain_squatting.algorithms }; + } + } + + // Extract domains from URL allowlist patterns + const allowlistDomains = this.extractDomainsFromAllowlist(urlAllowlist); + if (allowlistDomains.length > 0) { + // Merge with protected domains from rules (avoid duplicates) + const allDomains = [...new Set([...this.protectedDomains, ...allowlistDomains])]; + this.protectedDomains = allDomains; + logger.log(`Added ${allowlistDomains.length} domains from URL allowlist`); + } + + logger.log('DomainSquattingDetector initialized:', { + enabled: this.enabled, + protectedDomains: this.protectedDomains.length, + fromRules: config.domain_squatting?.protected_domains?.length || 0, + fromAllowlist: allowlistDomains.length, + deviationThreshold: this.deviationThreshold + }); + } catch (error) { + logger.error('Failed to initialize DomainSquattingDetector:', error); + } + } + + /** + * Update configuration + */ + updateConfig(config) { + if (config.enabled !== undefined) { + this.enabled = config.enabled; + } + if (config.protected_domains) { + this.protectedDomains = config.protected_domains; + } + if (config.deviation_threshold !== undefined) { + this.deviationThreshold = config.deviation_threshold; + } + if (config.algorithms) { + this.algorithms = { ...this.algorithms, ...config.algorithms }; + } + } + + /** + * Check if a domain is attempting to squat on protected domains + * @param {string} testDomain - Domain to test + * @returns {Object|null} Detection result or null if no squatting detected + */ + checkDomain(testDomain) { + if (!this.enabled || !testDomain) { + return null; + } + + // Extract domain without subdomain and TLD for comparison + const testBase = this.extractBaseDomain(testDomain); + + for (const protectedDomain of this.protectedDomains) { + const protectedBase = this.extractBaseDomain(protectedDomain); + + // Skip if domains are identical + if (testBase === protectedBase) { + continue; + } + + // Run detection algorithms + const detections = []; + + if (this.algorithms.levenshtein) { + const levenshteinResult = this.detectLevenshtein(testBase, protectedBase); + if (levenshteinResult) { + detections.push(levenshteinResult); + } + } + + if (this.algorithms.homoglyph) { + const homoglyphResult = this.detectHomoglyph(testBase, protectedBase); + if (homoglyphResult) { + detections.push(homoglyphResult); + } + } + + if (this.algorithms.typosquat) { + const typosquatResult = this.detectTyposquat(testBase, protectedBase); + if (typosquatResult) { + detections.push(typosquatResult); + } + } + + if (this.algorithms.combosquat) { + const combosquatResult = this.detectCombosquat(testBase, protectedBase); + if (combosquatResult) { + detections.push(combosquatResult); + } + } + + // If any detection triggered, return result + if (detections.length > 0) { + return { + detected: true, + testDomain: testDomain, + protectedDomain: protectedDomain, + techniques: detections, + severity: this.calculateSeverity(detections), + confidence: this.calculateConfidence(detections) + }; + } + } + + return null; + } + + /** + * Extract base domain without subdomain and TLD + */ + extractBaseDomain(domain) { + if (!domain) return ''; + + // Remove protocol + domain = domain.replace(/^https?:\/\//, ''); + + // Remove path + domain = domain.split('/')[0]; + + // Remove port + domain = domain.split(':')[0]; + + // Split by dots + const parts = domain.split('.'); + + // Get the main domain part (second-to-last typically) + if (parts.length >= 2) { + // Handle common two-part TLDs like .co.uk, .com.au + if (parts.length >= 3 && ['co', 'com', 'net', 'org', 'gov', 'edu'].includes(parts[parts.length - 2])) { + return parts[parts.length - 3]; + } + return parts[parts.length - 2]; + } + + return parts[0] || ''; + } + + /** + * Calculate Levenshtein distance between two strings + */ + levenshteinDistance(str1, str2) { + const matrix = []; + + for (let i = 0; i <= str2.length; i++) { + matrix[i] = [i]; + } + + for (let j = 0; j <= str1.length; j++) { + matrix[0][j] = j; + } + + for (let i = 1; i <= str2.length; i++) { + for (let j = 1; j <= str1.length; j++) { + if (str2.charAt(i - 1) === str1.charAt(j - 1)) { + matrix[i][j] = matrix[i - 1][j - 1]; + } else { + matrix[i][j] = Math.min( + matrix[i - 1][j - 1] + 1, // substitution + matrix[i][j - 1] + 1, // insertion + matrix[i - 1][j] + 1 // deletion + ); + } + } + } + + return matrix[str2.length][str1.length]; + } + + /** + * Detect domain squatting using Levenshtein distance + */ + detectLevenshtein(testDomain, protectedDomain) { + const distance = this.levenshteinDistance(testDomain, protectedDomain); + + if (distance > 0 && distance <= this.deviationThreshold) { + return { + technique: 'levenshtein', + description: `Domain differs by ${distance} character(s) from protected domain`, + distance: distance, + confidence: 1 - (distance / this.deviationThreshold) + }; + } + + return null; + } + + /** + * Normalize domain by replacing homoglyphs with standard characters + */ + normalizeHomoglyphs(domain) { + let normalized = ''; + for (const char of domain) { + normalized += this.homoglyphReverse[char] || char; + } + return normalized; + } + + /** + * Detect homoglyph substitution + */ + detectHomoglyph(testDomain, protectedDomain) { + const normalized = this.normalizeHomoglyphs(testDomain); + + if (normalized !== testDomain && normalized === protectedDomain) { + return { + technique: 'homoglyph', + description: 'Domain uses confusable characters (homoglyphs) to mimic protected domain', + original: testDomain, + normalized: normalized, + confidence: 0.95 + }; + } + + // Also check if normalized version is close to protected domain + const distance = this.levenshteinDistance(normalized, protectedDomain); + if (normalized !== testDomain && distance > 0 && distance <= this.deviationThreshold) { + return { + technique: 'homoglyph', + description: 'Domain uses confusable characters and differs slightly from protected domain', + original: testDomain, + normalized: normalized, + distance: distance, + confidence: 0.85 - (distance * 0.1) + }; + } + + return null; + } + + /** + * Detect common typosquatting patterns + */ + detectTyposquat(testDomain, protectedDomain) { + // Check for character swaps (transposition) + for (let i = 0; i < protectedDomain.length - 1; i++) { + const swapped = protectedDomain.substring(0, i) + + protectedDomain.charAt(i + 1) + + protectedDomain.charAt(i) + + protectedDomain.substring(i + 2); + + if (swapped === testDomain) { + return { + technique: 'typosquat', + description: 'Domain has swapped adjacent characters', + pattern: 'character_swap', + position: i, + confidence: 0.9 + }; + } + } + + // Check for missing character + for (let i = 0; i < protectedDomain.length; i++) { + const missing = protectedDomain.substring(0, i) + protectedDomain.substring(i + 1); + + if (missing === testDomain) { + return { + technique: 'typosquat', + description: 'Domain is missing a character', + pattern: 'character_omission', + position: i, + confidence: 0.85 + }; + } + } + + // Check for duplicate character + for (let i = 0; i < protectedDomain.length; i++) { + const duplicated = protectedDomain.substring(0, i + 1) + + protectedDomain.charAt(i) + + protectedDomain.substring(i + 1); + + if (duplicated === testDomain) { + return { + technique: 'typosquat', + description: 'Domain has a duplicated character', + pattern: 'character_duplication', + position: i, + confidence: 0.85 + }; + } + } + + // Check for adjacent key substitution (common keyboard mistakes) + const keyboardAdjacent = { + 'q': ['w', 'a'], + 'w': ['q', 'e', 's', 'a'], + 'e': ['w', 'r', 'd', 's'], + 'r': ['e', 't', 'f', 'd'], + 't': ['r', 'y', 'g', 'f'], + 'y': ['t', 'u', 'h', 'g'], + 'u': ['y', 'i', 'j', 'h'], + 'i': ['u', 'o', 'k', 'j'], + 'o': ['i', 'p', 'l', 'k'], + 'p': ['o', 'l'], + 'a': ['q', 'w', 's', 'z'], + 's': ['a', 'w', 'd', 'x', 'z'], + 'd': ['s', 'e', 'f', 'c', 'x'], + 'f': ['d', 'r', 'g', 'v', 'c'], + 'g': ['f', 't', 'h', 'b', 'v'], + 'h': ['g', 'y', 'j', 'n', 'b'], + 'j': ['h', 'u', 'k', 'm', 'n'], + 'k': ['j', 'i', 'l', 'm'], + 'l': ['k', 'o', 'p'], + 'z': ['a', 's', 'x'], + 'x': ['z', 's', 'd', 'c'], + 'c': ['x', 'd', 'f', 'v'], + 'v': ['c', 'f', 'g', 'b'], + 'b': ['v', 'g', 'h', 'n'], + 'n': ['b', 'h', 'j', 'm'], + 'm': ['n', 'j', 'k'] + }; + + for (let i = 0; i < protectedDomain.length; i++) { + const char = protectedDomain.charAt(i); + const adjacentKeys = keyboardAdjacent[char] || []; + + for (const adjacentKey of adjacentKeys) { + const substituted = protectedDomain.substring(0, i) + + adjacentKey + + protectedDomain.substring(i + 1); + + if (substituted === testDomain) { + return { + technique: 'typosquat', + description: 'Domain has keyboard-adjacent character substitution', + pattern: 'adjacent_key_substitution', + position: i, + original: char, + substituted: adjacentKey, + confidence: 0.8 + }; + } + } + } + + return null; + } + + /** + * Detect combosquatting (adding prefixes/suffixes) + */ + detectCombosquat(testDomain, protectedDomain) { + // Check if protected domain is contained in test domain + if (testDomain.includes(protectedDomain) && testDomain !== protectedDomain) { + const prefix = testDomain.substring(0, testDomain.indexOf(protectedDomain)); + const suffix = testDomain.substring(testDomain.indexOf(protectedDomain) + protectedDomain.length); + + // Common combosquatting prefixes and suffixes + const commonCombos = [ + 'secure', 'login', 'account', 'verify', 'support', 'help', 'my', + 'auth', 'sso', 'signin', 'app', 'portal', 'online', 'web', + 'mobile', 'service', 'official', 'verified', 'safe' + ]; + + const hasCommonCombo = commonCombos.some(combo => + prefix.includes(combo) || suffix.includes(combo) + ); + + if (hasCommonCombo) { + return { + technique: 'combosquat', + description: 'Domain adds suspicious prefix/suffix to protected domain', + pattern: 'common_combo', + prefix: prefix, + suffix: suffix, + confidence: 0.9 + }; + } + + // Any prefix/suffix is suspicious but lower confidence + if (prefix || suffix) { + return { + technique: 'combosquat', + description: 'Domain adds prefix/suffix to protected domain', + pattern: 'generic_combo', + prefix: prefix, + suffix: suffix, + confidence: 0.7 + }; + } + } + + // Check if test domain contains protected domain with separator + const separators = ['-', '_', '']; + for (const sep of separators) { + if (testDomain.startsWith(protectedDomain + sep) || testDomain.endsWith(sep + protectedDomain)) { + return { + technique: 'combosquat', + description: `Domain adds text with separator '${sep || '(none)'}' to protected domain`, + pattern: 'separator_combo', + separator: sep, + confidence: 0.75 + }; + } + } + + return null; + } + + /** + * Calculate overall severity based on detections + */ + calculateSeverity(detections) { + const maxConfidence = Math.max(...detections.map(d => d.confidence)); + + if (maxConfidence >= 0.9) return 'critical'; + if (maxConfidence >= 0.8) return 'high'; + if (maxConfidence >= 0.6) return 'medium'; + return 'low'; + } + + /** + * Calculate overall confidence based on detections + */ + calculateConfidence(detections) { + if (detections.length === 0) return 0; + + // If multiple techniques detected, increase confidence + const avgConfidence = detections.reduce((sum, d) => sum + d.confidence, 0) / detections.length; + const multiTechniqueBonus = detections.length > 1 ? 0.1 : 0; + + return Math.min(0.99, avgConfidence + multiTechniqueBonus); + } +} + +export default DomainSquattingDetector; From 1fcf7dc821db85395d609e874e31cae6ef964647 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Sun, 8 Feb 2026 13:33:52 -0800 Subject: [PATCH 14/27] Update scripts/background.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Zacgoose <107489668+Zacgoose@users.noreply.github.com> --- scripts/background.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/background.js b/scripts/background.js index 32906cd..e27cf77 100644 --- a/scripts/background.js +++ b/scripts/background.js @@ -1484,7 +1484,7 @@ class CheckBackground { // Update domain squatting detector with new configuration // If URL allowlist changed, reinitialize detector to extract new domains if (this.domainSquattingDetector) { - const detectionRules = this.detectionRulesManager.cachedRules; + const detectionRules = this.detectionRulesManager.cachedRules || {}; const urlAllowlist = updatedConfig?.urlAllowlist || []; await this.domainSquattingDetector.initialize(detectionRules, urlAllowlist); logger.log("Domain squatting detector configuration updated"); From dea4457f70e043d300bbf0e2df348d50a7049ca5 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Sun, 8 Feb 2026 13:34:35 -0800 Subject: [PATCH 15/27] Update scripts/content.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Zacgoose <107489668+Zacgoose@users.noreply.github.com> --- scripts/content.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/content.js b/scripts/content.js index 048179f..b78278f 100644 --- a/scripts/content.js +++ b/scripts/content.js @@ -4011,9 +4011,7 @@ if (window.checkExtensionLoaded) { testDomain: squattingData.testDomain, protectedDomain: squattingData.protectedDomain, techniques: squattingData.techniques.map(t => ({ - technique: t.technique, - description: t.description, - details: t.details + ...t })), severity: squattingData.severity, confidence: squattingData.confidence, From 1ceb9ab3f42445cd4edfa2eb11ac5eec1a8dfae5 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Feb 2026 20:54:12 +0800 Subject: [PATCH 16/27] Add enterprise webhook policy parity across ADMX, deployment templates, and docs --- .../windows/manual-deployment.md | 1 + enterprise/Check-Extension-Policy.reg | 58 ++++--- enterprise/README.md | 5 +- enterprise/Remove-Windows-Chrome-and-Edge.ps1 | 35 ++++ enterprise/Test-Extension-Policy.ps1 | 28 +++ enterprise/admx/Check-Extension.admx | 92 ++++++++-- enterprise/admx/en-US/Check-Extension.adml | 162 +++++++++++++----- enterprise/macos-linux/README.md | 32 ++-- .../check-extension-config.mobileconfig | 34 ++-- .../macos-linux/deploy-extension-prefs.sh | 11 +- 10 files changed, 352 insertions(+), 106 deletions(-) diff --git a/docs/deployment/chrome-edge-deployment-instructions/windows/manual-deployment.md b/docs/deployment/chrome-edge-deployment-instructions/windows/manual-deployment.md index b4ce740..f26d209 100644 --- a/docs/deployment/chrome-edge-deployment-instructions/windows/manual-deployment.md +++ b/docs/deployment/chrome-edge-deployment-instructions/windows/manual-deployment.md @@ -10,6 +10,7 @@ This script is designed to deploy the extension to both Chrome and Edge. It is r 1. Review the Extension Configuration Settings and Custom Branding Settings variables and update those to your desired values. The current values in the script are the default values. Leaving any unchanged will set the defaults. 2. If you are leveraging a RMM that has the ability to define the variables in the deployment section of scripting, then you may be able to remove this section and enter the variable definitions into the RMM scripting pages. +3. For webhook deployment, configure `$enableGenericWebhook`, `$webhookUrl`, and `$webhookEvents` in the script. Supported events are documented in [Webhook Documentation](../../../webhooks.md). Download the Script from GitHub {% endtab %} diff --git a/enterprise/Check-Extension-Policy.reg b/enterprise/Check-Extension-Policy.reg index fce2f70..afef703 100644 --- a/enterprise/Check-Extension-Policy.reg +++ b/enterprise/Check-Extension-Policy.reg @@ -13,16 +13,25 @@ Windows Registry Editor Version 5.00 "showNotifications"=dword:00000001 "enableValidPageBadge"=dword:00000000 "enablePageBlocking"=dword:00000001 -"enableCippReporting"=dword:00000001 -"cippServerUrl"="" -"cippTenantId"="" -"customRulesUrl"="" -"updateInterval"=dword:00000018 -"enableDebugLogging"=dword:00000000 - -; Custom branding configuration -[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge\3rdparty\extensions\knepjpocdagponkonnbggpcnhnaikajg\policy\customBranding] -"companyName"="CyberDrain" +"enableCippReporting"=dword:00000001 +"cippServerUrl"="" +"cippTenantId"="" +"customRulesUrl"="" +"updateInterval"=dword:00000018 +"enableDebugLogging"=dword:00000000 + +; Generic webhook configuration (optional) +[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge\3rdparty\extensions\knepjpocdagponkonnbggpcnhnaikajg\policy\genericWebhook] +"enabled"=dword:00000000 +"url"="" + +[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge\3rdparty\extensions\knepjpocdagponkonnbggpcnhnaikajg\policy\genericWebhook\events] +"1"="detection_alert" +"2"="page_blocked" + +; Custom branding configuration +[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge\3rdparty\extensions\knepjpocdagponkonnbggpcnhnaikajg\policy\customBranding] +"companyName"="CyberDrain" "productName"="Check" "supportEmail"="" "primaryColor"="#F77F00" @@ -45,16 +54,25 @@ Windows Registry Editor Version 5.00 "showNotifications"=dword:00000001 "enableValidPageBadge"=dword:00000000 "enablePageBlocking"=dword:00000001 -"enableCippReporting"=dword:00000001 -"cippServerUrl"="" -"cippTenantId"="" -"customRulesUrl"="" -"updateInterval"=dword:00000018 -"enableDebugLogging"=dword:00000000 - -; Custom branding configuration for Chrome -[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\3rdparty\extensions\benimdeioplgkhanklclahllklceahbe\policy\customBranding] -"companyName"="CyberDrain" +"enableCippReporting"=dword:00000001 +"cippServerUrl"="" +"cippTenantId"="" +"customRulesUrl"="" +"updateInterval"=dword:00000018 +"enableDebugLogging"=dword:00000000 + +; Generic webhook configuration for Chrome (optional) +[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\3rdparty\extensions\benimdeioplgkhanklclahllklceahbe\policy\genericWebhook] +"enabled"=dword:00000000 +"url"="" + +[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\3rdparty\extensions\benimdeioplgkhanklclahllklceahbe\policy\genericWebhook\events] +"1"="detection_alert" +"2"="page_blocked" + +; Custom branding configuration for Chrome +[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\3rdparty\extensions\benimdeioplgkhanklclahllklceahbe\policy\customBranding] +"companyName"="CyberDrain" "productName"="Check" "supportEmail"="" "primaryColor"="#F77F00" diff --git a/enterprise/README.md b/enterprise/README.md index 5ec8974..db3e4f1 100644 --- a/enterprise/README.md +++ b/enterprise/README.md @@ -26,8 +26,9 @@ This folder contains enterprise deployment resources for the Check Microsoft 365 ## Quick Links - **Chrome/Edge Deployment**: See `Deploy-Windows-Chrome-and-Edge.ps1` for Windows, `macos-linux/` for macOS/Linux -- **Firefox Deployment**: See `firefox/policies.json` template and [Firefox Deployment Guide](../docs/deployment/firefox-deployment.md) -- **Configuration Schema**: See `../config/managed_schema.json` for all available settings +- **Firefox Deployment**: See `firefox/policies.json` template and [Firefox Deployment Guide](../docs/deployment/firefox-deployment.md) +- **Configuration Schema**: See `../config/managed_schema.json` for all available settings +- **Webhook Configuration**: See `../docs/webhooks.md` for webhook payloads and supported event types ## Security Considerations diff --git a/enterprise/Remove-Windows-Chrome-and-Edge.ps1 b/enterprise/Remove-Windows-Chrome-and-Edge.ps1 index fed3c9d..9e33788 100644 --- a/enterprise/Remove-Windows-Chrome-and-Edge.ps1 +++ b/enterprise/Remove-Windows-Chrome-and-Edge.ps1 @@ -58,6 +58,41 @@ function Remove-ExtensionSettings { } } + # Remove generic webhook subkey and event properties + $genericWebhookKey = "$ManagedStorageKey\genericWebhook" + if (Test-Path $genericWebhookKey) { + $webhookEventsKey = "$genericWebhookKey\events" + if (Test-Path $webhookEventsKey) { + $eventProperties = Get-ItemProperty -Path $webhookEventsKey -ErrorAction SilentlyContinue + if ($eventProperties) { + $eventProperties.PSObject.Properties | Where-Object { $_.Name -match '^\d+$' } | ForEach-Object { + Remove-ItemProperty -Path $webhookEventsKey -Name $_.Name -Force -ErrorAction SilentlyContinue + Write-Host "Removed webhook event property: $($_.Name) from $webhookEventsKey" + } + } + try { + Remove-Item -Path $webhookEventsKey -Force -ErrorAction SilentlyContinue + Write-Host "Removed webhook events subkey: $webhookEventsKey" + } catch { + # Key may not be empty or may have been removed already + } + } + + foreach ($property in @("enabled", "url")) { + if (Get-ItemProperty -Path $genericWebhookKey -Name $property -ErrorAction SilentlyContinue) { + Remove-ItemProperty -Path $genericWebhookKey -Name $property -Force -ErrorAction SilentlyContinue + Write-Host "Removed generic webhook property: $property from $genericWebhookKey" + } + } + + try { + Remove-Item -Path $genericWebhookKey -Force -ErrorAction SilentlyContinue + Write-Host "Removed generic webhook subkey: $genericWebhookKey" + } catch { + # Key may not be empty or may have been removed already + } + } + # Remove custom branding subkey and all its properties $customBrandingKey = "$ManagedStorageKey\customBranding" if (Test-Path $customBrandingKey) { diff --git a/enterprise/Test-Extension-Policy.ps1 b/enterprise/Test-Extension-Policy.ps1 index 6a07d23..9fae324 100644 --- a/enterprise/Test-Extension-Policy.ps1 +++ b/enterprise/Test-Extension-Policy.ps1 @@ -33,6 +33,12 @@ $testBranding = @{ logoUrl = "" } +$testGenericWebhook = @{ + enabled = 0 + url = "" + events = @("detection_alert", "page_blocked") +} + function Set-TestPolicies { param([string]$PolicyKey) @@ -55,6 +61,22 @@ function Set-TestPolicies { foreach ($key in $testBranding.Keys) { New-ItemProperty -Path $brandingKey -Name $key -PropertyType String -Value $testBranding[$key] -Force | Out-Null } + + $genericWebhookKey = "$PolicyKey\genericWebhook" + if (!(Test-Path $genericWebhookKey)) { + New-Item -Path $genericWebhookKey -Force | Out-Null + } + New-ItemProperty -Path $genericWebhookKey -Name "enabled" -PropertyType DWord -Value $testGenericWebhook.enabled -Force | Out-Null + New-ItemProperty -Path $genericWebhookKey -Name "url" -PropertyType String -Value $testGenericWebhook.url -Force | Out-Null + + $webhookEventsKey = "$genericWebhookKey\events" + if (!(Test-Path $webhookEventsKey)) { + New-Item -Path $webhookEventsKey -Force | Out-Null + } + Remove-ItemProperty -Path $webhookEventsKey -Name * -Force -ErrorAction SilentlyContinue | Out-Null + for ($i = 0; $i -lt $testGenericWebhook.events.Count; $i++) { + New-ItemProperty -Path $webhookEventsKey -Name ($i + 1) -PropertyType String -Value $testGenericWebhook.events[$i] -Force | Out-Null + } Write-Output "Applied test policies to: $PolicyKey" } @@ -71,6 +93,12 @@ function Show-CurrentPolicies { Write-Output "`nCustom Branding:" Get-ItemProperty -Path $brandingKey | Format-List } + + $genericWebhookKey = "$PolicyKey\genericWebhook" + if (Test-Path $genericWebhookKey) { + Write-Output "`nGeneric Webhook:" + Get-ItemProperty -Path $genericWebhookKey | Format-List + } } else { Write-Output "No policies set at: $PolicyKey" } diff --git a/enterprise/admx/Check-Extension.admx b/enterprise/admx/Check-Extension.admx index d69c262..55eaef0 100644 --- a/enterprise/admx/Check-Extension.admx +++ b/enterprise/admx/Check-Extension.admx @@ -113,14 +113,44 @@ - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -297,14 +327,44 @@ - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/enterprise/admx/en-US/Check-Extension.adml b/enterprise/admx/en-US/Check-Extension.adml index 078a8c1..1b0e485 100644 --- a/enterprise/admx/en-US/Check-Extension.adml +++ b/enterprise/admx/en-US/Check-Extension.adml @@ -108,17 +108,47 @@ CIPP tenant identifier - - This policy specifies the tenant identifier to include with CIPP alerts for multi-tenant environments. - - Example: contoso.onmicrosoft.com - - This helps identify which tenant/organization the security event originated from when using a shared CIPP instance. - - - Custom detection rules URL - - This policy specifies a custom URL from which the extension should fetch detection rules. + + This policy specifies the tenant identifier to include with CIPP alerts for multi-tenant environments. + + Example: contoso.onmicrosoft.com + + This helps identify which tenant/organization the security event originated from when using a shared CIPP instance. + + + Enable generic webhook + + This policy controls whether the Check extension sends selected security events to a custom webhook endpoint. + + When enabled: Events listed in "Generic webhook event types" are sent to the configured Generic Webhook URL. + When disabled (default): No generic webhook events are sent. + + Note: Requires Generic Webhook URL and at least one event type. + + Generic webhook URL + + This policy specifies the URL for a custom webhook endpoint that receives Check event payloads. + + Example: https://webhook.yourcompany.com/check-events + + This setting is only used when generic webhook is enabled. + + Generic webhook event types + + This policy specifies which event types are sent to the generic webhook endpoint. + + Supported values: + - detection_alert + - false_positive_report + - page_blocked + - rogue_app_detected + - threat_detected + - validation_event + + + Custom detection rules URL + + This policy specifies a custom URL from which the extension should fetch detection rules. Example: https://yourcompany.com/detection-rules.json @@ -293,17 +323,47 @@ CIPP tenant identifier (Chrome) - - This policy specifies the tenant identifier to include with CIPP alerts for multi-tenant environments from Google Chrome. - - Example: contoso.onmicrosoft.com - - This helps identify which tenant/organization the security event originated from when using a shared CIPP instance. - - - Custom detection rules URL (Chrome) - - This policy specifies a custom URL from which the extension should fetch detection rules in Google Chrome. + + This policy specifies the tenant identifier to include with CIPP alerts for multi-tenant environments from Google Chrome. + + Example: contoso.onmicrosoft.com + + This helps identify which tenant/organization the security event originated from when using a shared CIPP instance. + + + Enable generic webhook (Chrome) + + This policy controls whether the Check extension sends selected security events to a custom webhook endpoint in Google Chrome. + + When enabled: Events listed in "Generic webhook event types (Chrome)" are sent to the configured Generic Webhook URL. + When disabled (default): No generic webhook events are sent. + + Note: Requires Generic Webhook URL and at least one event type. + + Generic webhook URL (Chrome) + + This policy specifies the URL for a custom webhook endpoint that receives Check event payloads from Google Chrome. + + Example: https://webhook.yourcompany.com/check-events + + This setting is only used when generic webhook is enabled. + + Generic webhook event types (Chrome) + + This policy specifies which event types are sent to the generic webhook endpoint from Google Chrome. + + Supported values: + - detection_alert + - false_positive_report + - page_blocked + - rogue_app_detected + - threat_detected + - validation_event + + + Custom detection rules URL (Chrome) + + This policy specifies a custom URL from which the extension should fetch detection rules in Google Chrome. Example: https://yourcompany.com/detection-rules.json @@ -382,16 +442,24 @@ - - - - - - - - - - + + + + + + + + + + + + Generic Webhook Event Types: + + + + + + Update Interval (hours): @@ -439,16 +507,24 @@ - - - - - - - - - - + + + + + + + + + + + + Generic Webhook Event Types: + + + + + + Update Interval (hours): @@ -491,4 +567,4 @@ - \ No newline at end of file + diff --git a/enterprise/macos-linux/README.md b/enterprise/macos-linux/README.md index 8e1d55a..835a75f 100644 --- a/enterprise/macos-linux/README.md +++ b/enterprise/macos-linux/README.md @@ -154,13 +154,18 @@ All settings are based on the managed schema and include: - **`enableCippReporting`** - Enable CIPP server reporting (default: false) - **`enableDebugLogging`** - Enable debug logging (default: false) -### CIPP Integration -- **`cippServerUrl`** - CIPP server URL for reporting -- **`cippTenantId`** - Tenant identifier for multi-tenant environments - -### Rule Management -- **`customRulesUrl`** - URL for custom detection rules -- **`updateInterval`** - Rule update interval in hours (default: 24) +### CIPP Integration +- **`cippServerUrl`** - CIPP server URL for reporting +- **`cippTenantId`** - Tenant identifier for multi-tenant environments + +### Generic Webhook Integration +- **`genericWebhook.enabled`** - Enable sending events to custom webhook endpoint +- **`genericWebhook.url`** - Webhook endpoint URL +- **`genericWebhook.events`** - Event types to send (`detection_alert`, `false_positive_report`, `page_blocked`, `rogue_app_detected`, `threat_detected`, `validation_event`) + +### Rule Management +- **`customRulesUrl`** - URL for custom detection rules +- **`updateInterval`** - Rule update interval in hours (default: 24) ### Custom Branding - **`companyName`** - Company name for white labeling @@ -258,12 +263,13 @@ ls -la /etc/microsoft-edge/policies/managed/ ## Customization -Before deployment, edit the JSON files to customize: -1. **CIPP Integration** - Set `cippServerUrl` and `cippTenantId` -2. **Custom Rules** - Set `customRulesUrl` to your rules endpoint -3. **Branding** - Configure company name, colors, and logo URL -4. **Security Settings** - Adjust notification and blocking preferences -5. **Debug Mode** - Enable `enableDebugLogging` for troubleshooting +Before deployment, edit the JSON files to customize: +1. **CIPP Integration** - Set `cippServerUrl` and `cippTenantId` +2. **Webhook Integration** - Configure `genericWebhook.enabled`, `genericWebhook.url`, and `genericWebhook.events` +3. **Custom Rules** - Set `customRulesUrl` to your rules endpoint +4. **Branding** - Configure company name, colors, and logo URL +5. **Security Settings** - Adjust notification and blocking preferences +6. **Debug Mode** - Enable `enableDebugLogging` for troubleshooting ## Security Considerations diff --git a/enterprise/macos-linux/check-extension-config.mobileconfig b/enterprise/macos-linux/check-extension-config.mobileconfig index c3b77bb..37c479d 100644 --- a/enterprise/macos-linux/check-extension-config.mobileconfig +++ b/enterprise/macos-linux/check-extension-config.mobileconfig @@ -41,16 +41,28 @@ Value - cippTenantId - - Value - - - customRulesUrl - - Value - https://raw.githubusercontent.com/CyberDrain/Check/refs/heads/main/rules/detection-rules.json - + cippTenantId + + Value + + + genericWebhook + + Value + + enabled + + url + + events + + + + customRulesUrl + + Value + https://raw.githubusercontent.com/CyberDrain/Check/refs/heads/main/rules/detection-rules.json + updateInterval Value @@ -102,4 +114,4 @@ TargetDeviceType 5 - \ No newline at end of file + diff --git a/enterprise/macos-linux/deploy-extension-prefs.sh b/enterprise/macos-linux/deploy-extension-prefs.sh index 65b2b45..2f76344 100644 --- a/enterprise/macos-linux/deploy-extension-prefs.sh +++ b/enterprise/macos-linux/deploy-extension-prefs.sh @@ -71,6 +71,15 @@ create_extension_preferences() { cippTenantId + genericWebhook + + enabled + + url + + events + + customRulesUrl $custom_rules_url updateInterval @@ -211,4 +220,4 @@ main() { esac } -main "$@" \ No newline at end of file +main "$@" From f384771f33214330c751e3f36e09344e2086abb7 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 06:19:17 +0800 Subject: [PATCH 17/27] Add `msn.com` to Microsoft domain allow-list in detection rules --- rules/detection-rules.json | 1 + 1 file changed, 1 insertion(+) diff --git a/rules/detection-rules.json b/rules/detection-rules.json index a8bdeac..13a4056 100644 --- a/rules/detection-rules.json +++ b/rules/detection-rules.json @@ -33,6 +33,7 @@ "^https:\\/\\/([a-zA-Z0-9-]+\\.)*microsoftazuread-sso\\.com$", "^https:\\/\\/([a-zA-Z0-9-]+\\.)*azureedge\\.net$", "^https:\\/\\/([a-zA-Z0-9-]+\\.)*bing\\.com$", + "^https:\\/\\/([a-zA-Z0-9-]+\\.)*msn\\.com$", "^https:\\/\\/github\\.com$", "^https:\\/\\/([a-zA-Z0-9-]+\\.)*cloud\\.microsoft$", "^https:\\/\\/([a-zA-Z0-9-]+\\.)*powerbi\\.com$" From 6539939d0488142d889396bc8048e3ed62fb1600 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Sat, 28 Feb 2026 14:03:23 +0800 Subject: [PATCH 18/27] Add support/privacy/about branding URLs Introduce supportUrl, privacyPolicyUrl and aboutUrl branding properties across the project. Updates include managed schema, default configs, enterprise policy templates (REG/PS/ADMX/ADML/JSON), options and popup UI/JS, config manager merging logic (including deriving supportUrl from supportEmail when missing), and tests covering branding link behavior. Also removes the legacy companyURL field from many places and updates docs to show the new properties and examples. --- config/branding.json | 4 +- config/managed_schema.json | 29 ++- docs/settings/branding.md | 37 +++- enterprise/Check-Extension-Policy.reg | 30 +-- enterprise/Deploy-Windows-Chrome-and-Edge.ps1 | 10 +- enterprise/Remove-Windows-Chrome-and-Edge.ps1 | 4 +- enterprise/Test-Extension-Policy.ps1 | 1 - enterprise/admx/Check-Extension.admx | 148 +++++++++----- enterprise/admx/en-US/Check-Extension.adml | 186 +++++++++++------- enterprise/firefox/policies.json | 6 +- .../macos-linux/chrome-managed-policy.json | 5 +- .../macos-linux/edge-managed-policy.json | 5 +- options/options.html | 32 ++- options/options.js | 37 ++-- popup/popup.html | 2 +- popup/popup.js | 18 +- scripts/modules/config-manager.js | 17 +- tests/config-persistence.test.js | 47 +++++ 18 files changed, 428 insertions(+), 190 deletions(-) diff --git a/config/branding.json b/config/branding.json index c09a76b..1e54978 100644 --- a/config/branding.json +++ b/config/branding.json @@ -1,7 +1,9 @@ { "companyName": "CyberDrain", - "companyURL": "https://cyberdrain.com/", "productName": "Check", + "supportUrl": "https://support.cyberdrain.com", + "privacyPolicyUrl": "https://cyberdrain.com/privacy", + "aboutUrl": "", "version": "1.1.0", "description": "Protect against phishing attacks targeting Microsoft 365 login pages with enterprise-grade detection", "branding": { diff --git a/config/managed_schema.json b/config/managed_schema.json index fa279af..5cc63bc 100644 --- a/config/managed_schema.json +++ b/config/managed_schema.json @@ -125,12 +125,6 @@ "description": "Company name to display in the extension", "type": "string", "default": "" - }, - "companyURL": { - "title": "Company URL", - "description": "Company URL used in the extension", - "type": "string", - "default": "https://cyberdrain.com/" }, "productName": { "title": "Product Name", @@ -145,6 +139,27 @@ "format": "email", "default": "" }, + "supportUrl": { + "title": "Support URL", + "description": "URL opened by the popup Support link", + "type": "string", + "format": "uri", + "default": "" + }, + "privacyPolicyUrl": { + "title": "Privacy URL", + "description": "URL opened by the popup Privacy link", + "type": "string", + "format": "uri", + "default": "" + }, + "aboutUrl": { + "title": "About URL", + "description": "URL opened by the popup About link", + "type": "string", + "format": "uri", + "default": "" + }, "primaryColor": { "title": "Primary Color", "description": "Primary theme color (hex code)", @@ -162,4 +177,4 @@ } } } -} \ No newline at end of file +} diff --git a/docs/settings/branding.md b/docs/settings/branding.md index a4dce7b..9ba3fed 100644 --- a/docs/settings/branding.md +++ b/docs/settings/branding.md @@ -30,9 +30,11 @@ If some settings do not appear on your version, it means your organization's IT You can customize the following properties: 1. **Company Name** - Enter your organization's name. This appears in the extension interface and blocked page messages (displayed as "Protected by \[Company Name]"). -2. **Company URL** - Your company website URL (e.g., `https://yourcompany.com`). Used in extension branding and contact information. _(Firefox: required, Chrome/Edge: optional)_ -3. **Product Name** - What you want to call the extension (like "Contoso Security" instead of "Check"). This replaces the default "Check" branding throughout the interface. -4. **Support Email** - Where users should go for help. This email address is used in the "Contact Admin" button when phishing sites are blocked. +2. **Product Name** - What you want to call the extension (like "Contoso Security" instead of "Check"). This replaces the default "Check" branding throughout the interface. +3. **Support Email** - Where users should go for help. This email address is used in the "Contact Admin" button when phishing sites are blocked. +4. **Support URL** - URL opened by the popup **Support** link (for example, `https://support.yourcompany.com`). +5. **Privacy Policy URL** (`privacyPolicyUrl`) - URL opened by the popup **Privacy** link (for example, `https://yourcompany.com/privacy`). +6. **About URL** (`aboutUrl`) - URL opened by the popup **About** link. Leave empty to use the built-in extension About page. ## Visual Customization @@ -60,6 +62,9 @@ The branding preview shows you exactly how your customizations will appear to us * Logo (upload or provide URL) * Primary Color * Support Email + * Support URL + * Privacy Policy URL + * About URL 4. Click "Save" Your branding will be immediately applied to all components. @@ -78,7 +83,10 @@ For enterprise deployments using Windows Group Policy: "companyName": "Your Company", "logoUrl": "https://example.com/logo.png", "primaryColor": "#FF5733", - "supportEmail": "security@example.com" + "supportEmail": "security@example.com", + "supportUrl": "https://support.example.com", + "privacyPolicyUrl": "https://example.com/privacy", + "aboutUrl": "https://example.com/about" } } ``` @@ -106,9 +114,11 @@ For Firefox deployments, configure branding through the `policies.json` file: "check@cyberdrain.com": { "customBranding": { "companyName": "Your Company", - "companyURL": "https://yourcompany.com", "productName": "Security Extension", "supportEmail": "security@example.com", + "supportUrl": "https://support.example.com", + "privacyPolicyUrl": "https://example.com/privacy", + "aboutUrl": "https://example.com/about", "primaryColor": "#FF5733", "logoUrl": "https://example.com/logo.png" } @@ -137,7 +147,10 @@ For organizations using Microsoft Intune with Chrome/Edge: "companyName": "Your Company", "logoUrl": "https://example.com/logo.png", "primaryColor": "#FF5733", - "supportEmail": "security@example.com" + "supportEmail": "security@example.com", + "supportUrl": "https://support.example.com", + "privacyPolicyUrl": "https://example.com/privacy", + "aboutUrl": "https://example.com/about" } } ``` @@ -201,7 +214,6 @@ Enterprise policies always take precedence over manual settings. * Uses extension ID: `check@cyberdrain.com` * Configuration is managed through `policies.json` file -* Supports additional `companyURL` property * Policies file location varies by operating system ### Chrome & Edge @@ -290,7 +302,10 @@ Logo URL: https://assets.globalmfg.com/security/gmi-logo-48.png "productName": "Contoso Defender", "logoUrl": "https://contoso.com/assets/logo.png", "primaryColor": "#0078D4", - "supportEmail": "security@contoso.com" + "supportEmail": "security@contoso.com", + "supportUrl": "https://support.contoso.com", + "privacyPolicyUrl": "https://contoso.com/privacy", + "aboutUrl": "https://contoso.com/about" } } ``` @@ -305,11 +320,13 @@ Logo URL: https://assets.globalmfg.com/security/gmi-logo-48.png "check@cyberdrain.com": { "customBranding": { "companyName": "Contoso Corporation", - "companyURL": "https://contoso.com", "productName": "Contoso Defender", "logoUrl": "https://contoso.com/assets/logo.png", "primaryColor": "#0078D4", - "supportEmail": "security@contoso.com" + "supportEmail": "security@contoso.com", + "supportUrl": "https://support.contoso.com", + "privacyPolicyUrl": "https://contoso.com/privacy", + "aboutUrl": "https://contoso.com/about" } } } diff --git a/enterprise/Check-Extension-Policy.reg b/enterprise/Check-Extension-Policy.reg index fce2f70..9f2f004 100644 --- a/enterprise/Check-Extension-Policy.reg +++ b/enterprise/Check-Extension-Policy.reg @@ -21,12 +21,15 @@ Windows Registry Editor Version 5.00 "enableDebugLogging"=dword:00000000 ; Custom branding configuration -[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge\3rdparty\extensions\knepjpocdagponkonnbggpcnhnaikajg\policy\customBranding] -"companyName"="CyberDrain" -"productName"="Check" -"supportEmail"="" -"primaryColor"="#F77F00" -"logoUrl"="" +[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge\3rdparty\extensions\knepjpocdagponkonnbggpcnhnaikajg\policy\customBranding] +"companyName"="CyberDrain" +"productName"="Check" +"supportEmail"="" +"supportUrl"="" +"privacyPolicyUrl"="" +"aboutUrl"="" +"primaryColor"="#F77F00" +"logoUrl"="" ; Optional: Prevent users from disabling the extension [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge\ExtensionSettings] @@ -53,12 +56,15 @@ Windows Registry Editor Version 5.00 "enableDebugLogging"=dword:00000000 ; Custom branding configuration for Chrome -[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\3rdparty\extensions\benimdeioplgkhanklclahllklceahbe\policy\customBranding] -"companyName"="CyberDrain" -"productName"="Check" -"supportEmail"="" -"primaryColor"="#F77F00" -"logoUrl"="" +[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\3rdparty\extensions\benimdeioplgkhanklclahllklceahbe\policy\customBranding] +"companyName"="CyberDrain" +"productName"="Check" +"supportEmail"="" +"supportUrl"="" +"privacyPolicyUrl"="" +"aboutUrl"="" +"primaryColor"="#F77F00" +"logoUrl"="" ; Optional: Prevent users from disabling the extension in Chrome [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\ExtensionSettings] diff --git a/enterprise/Deploy-Windows-Chrome-and-Edge.ps1 b/enterprise/Deploy-Windows-Chrome-and-Edge.ps1 index b886eb8..1e991b7 100644 --- a/enterprise/Deploy-Windows-Chrome-and-Edge.ps1 +++ b/enterprise/Deploy-Windows-Chrome-and-Edge.ps1 @@ -31,9 +31,11 @@ $webhookEvents = @() # This will set the "Event Types" to send to the webhook; d # Custom Branding Settings $companyName = "CyberDrain" # This will set the "Company Name" option in the Custom Branding settings; default is "CyberDrain". -$companyURL = "https://cyberdrain.com" # This will set the Company URL option in the Custom Branding settings; default is "https://cyberdrain.com"; Must include the protocol (e.g., https://). $productName = "Check - Phishing Protection" # This will set the "Product Name" option in the Custom Branding settings; default is "Check - Phishing Protection". $supportEmail = "" # This will set the "Support Email" option in the Custom Branding settings; default is blank. +$supportUrl = "" # This will set the "Support URL" option in the Custom Branding settings; default is blank. +$privacyPolicyUrl = "" # This will set the "Privacy URL" option in the Custom Branding settings; default is blank. +$aboutUrl = "" # This will set the "About URL" option in the Custom Branding settings; default is blank. $primaryColor = "#F77F00" # This will set the "Primary Color" option in the Custom Branding settings; default is "#F77F00"; must be a valid hex color code (e.g., #FFFFFF). $logoUrl = "" # This will set the "Logo URL" option in the Custom Branding settings; default is blank. Must be a valid URL including the protocol (e.g., https://example.com/logo.png); protocol must be https; recommended size is 48x48 pixels with a maximum of 128x128. @@ -90,9 +92,11 @@ function Configure-ExtensionSettings { # Set custom branding settings New-ItemProperty -Path $customBrandingKey -Name "companyName" -PropertyType String -Value $companyName -Force | Out-Null - New-ItemProperty -Path $customBrandingKey -Name "companyURL" -PropertyType String -Value $companyURL -Force | Out-Null New-ItemProperty -Path $customBrandingKey -Name "productName" -PropertyType String -Value $productName -Force | Out-Null New-ItemProperty -Path $customBrandingKey -Name "supportEmail" -PropertyType String -Value $supportEmail -Force | Out-Null + New-ItemProperty -Path $customBrandingKey -Name "supportUrl" -PropertyType String -Value $supportUrl -Force | Out-Null + New-ItemProperty -Path $customBrandingKey -Name "privacyPolicyUrl" -PropertyType String -Value $privacyPolicyUrl -Force | Out-Null + New-ItemProperty -Path $customBrandingKey -Name "aboutUrl" -PropertyType String -Value $aboutUrl -Force | Out-Null New-ItemProperty -Path $customBrandingKey -Name "primaryColor" -PropertyType String -Value $primaryColor -Force | Out-Null New-ItemProperty -Path $customBrandingKey -Name "logoUrl" -PropertyType String -Value $logoUrl -Force | Out-Null @@ -145,4 +149,4 @@ function Configure-ExtensionSettings { # Configure settings for Chrome and Edge Configure-ExtensionSettings -ExtensionId $chromeExtensionId -UpdateUrl $chromeUpdateUrl -ManagedStorageKey $chromeManagedStorageKey -ExtensionSettingsKey $chromeExtensionSettingsKey -Configure-ExtensionSettings -ExtensionId $edgeExtensionId -UpdateUrl $edgeUpdateUrl -ManagedStorageKey $edgeManagedStorageKey -ExtensionSettingsKey $edgeExtensionSettingsKey \ No newline at end of file +Configure-ExtensionSettings -ExtensionId $edgeExtensionId -UpdateUrl $edgeUpdateUrl -ManagedStorageKey $edgeManagedStorageKey -ExtensionSettingsKey $edgeExtensionSettingsKey diff --git a/enterprise/Remove-Windows-Chrome-and-Edge.ps1 b/enterprise/Remove-Windows-Chrome-and-Edge.ps1 index fed3c9d..b221b23 100644 --- a/enterprise/Remove-Windows-Chrome-and-Edge.ps1 +++ b/enterprise/Remove-Windows-Chrome-and-Edge.ps1 @@ -63,9 +63,11 @@ function Remove-ExtensionSettings { if (Test-Path $customBrandingKey) { $brandingPropertiesToRemove = @( "companyName", - "companyURL", "productName", "supportEmail", + "supportUrl", + "privacyPolicyUrl", + "aboutUrl", "primaryColor", "logoUrl" ) diff --git a/enterprise/Test-Extension-Policy.ps1 b/enterprise/Test-Extension-Policy.ps1 index 6a07d23..eaa3218 100644 --- a/enterprise/Test-Extension-Policy.ps1 +++ b/enterprise/Test-Extension-Policy.ps1 @@ -26,7 +26,6 @@ $testConfig = @{ # Custom branding test values $testBranding = @{ companyName = "Test Company" - companyURL = "https://example.com" productName = "Test Product" supportEmail = "test@example.com" primaryColor = "#FF6B00" diff --git a/enterprise/admx/Check-Extension.admx b/enterprise/admx/Check-Extension.admx index d69c262..2c93722 100644 --- a/enterprise/admx/Check-Extension.admx +++ b/enterprise/admx/Check-Extension.admx @@ -149,35 +149,62 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -333,34 +360,61 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/enterprise/admx/en-US/Check-Extension.adml b/enterprise/admx/en-US/Check-Extension.adml index 078a8c1..3a217d8 100644 --- a/enterprise/admx/en-US/Check-Extension.adml +++ b/enterprise/admx/en-US/Check-Extension.adml @@ -181,16 +181,7 @@ The company name appears in the extension popup and settings pages. - - Company URL - - This policy specifies the company URL used in the extension for branding and navigation purposes. - - Example: https://contoso.com - - The company URL is used for linking back to the company website from the extension interface. - - + Product name This policy specifies a custom product name for the extension. @@ -201,15 +192,36 @@ Support email address - - This policy specifies the email address users should contact for support with the extension. - - Example: security@contoso.com - - This email address is displayed in the extension interface and help documentation. - - - Primary theme color + + This policy specifies the email address users should contact for support with the extension. + + Example: security@contoso.com + + This email address is displayed in the extension interface and help documentation. + + + Support URL + + This policy specifies the support URL opened by the extension Support link. + + Example: https://support.contoso.com + + + Privacy URL + + This policy specifies the privacy policy URL opened by the extension Privacy link. + + Example: https://contoso.com/privacy + + + About URL + + This policy specifies the about URL opened by the extension About link. + + Example: https://contoso.com/about + + + Primary theme color This policy specifies the primary theme color for the extension interface using a hex color code. @@ -330,16 +342,7 @@ The company name appears in the extension popup and settings pages. - - Company URL (Chrome) - - This policy specifies the company URL used in the extension for branding and navigation purposes in Google Chrome. - - Example: https://contoso.com - - The company URL is used for linking back to the company website from the extension interface. - - + Product name (Chrome) This policy specifies a custom product name for the extension in Google Chrome. @@ -350,15 +353,36 @@ Support email address (Chrome) - - This policy specifies the email address users should contact for support with the extension in Google Chrome. - - Example: security@contoso.com - - This email address is displayed in the extension interface and help documentation. - - - Primary theme color (Chrome) + + This policy specifies the email address users should contact for support with the extension in Google Chrome. + + Example: security@contoso.com + + This email address is displayed in the extension interface and help documentation. + + + Support URL (Chrome) + + This policy specifies the support URL opened by the extension Support link in Google Chrome. + + Example: https://support.contoso.com + + + Privacy URL (Chrome) + + This policy specifies the privacy policy URL opened by the extension Privacy link in Google Chrome. + + Example: https://contoso.com/privacy + + + About URL (Chrome) + + This policy specifies the about URL opened by the extension About link in Google Chrome. + + Example: https://contoso.com/about + + + Primary theme color (Chrome) This policy specifies the primary theme color for the extension interface using a hex color code in Google Chrome. @@ -409,26 +433,36 @@ - - - - - - + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + @@ -458,26 +492,36 @@ - - - - - - + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + @@ -491,4 +535,4 @@ - \ No newline at end of file + diff --git a/enterprise/firefox/policies.json b/enterprise/firefox/policies.json index be564a9..16f7e49 100644 --- a/enterprise/firefox/policies.json +++ b/enterprise/firefox/policies.json @@ -31,9 +31,11 @@ "enableDebugLogging": false, "customBranding": { "companyName": "", - "companyURL": "https://cyberdrain.com/", "productName": "", "supportEmail": "", + "supportUrl": "", + "privacyPolicyUrl": "", + "aboutUrl": "", "primaryColor": "#F77F00", "logoUrl": "" }, @@ -50,4 +52,4 @@ } } } -} \ No newline at end of file +} diff --git a/enterprise/macos-linux/chrome-managed-policy.json b/enterprise/macos-linux/chrome-managed-policy.json index 58ac0eb..190df72 100644 --- a/enterprise/macos-linux/chrome-managed-policy.json +++ b/enterprise/macos-linux/chrome-managed-policy.json @@ -22,6 +22,9 @@ "companyName": "", "productName": "", "supportEmail": "", + "supportUrl": "", + "privacyPolicyUrl": "", + "aboutUrl": "", "primaryColor": "#F77F00", "logoUrl": "" }, @@ -37,4 +40,4 @@ } } } -} \ No newline at end of file +} diff --git a/enterprise/macos-linux/edge-managed-policy.json b/enterprise/macos-linux/edge-managed-policy.json index fc44bd0..5939408 100644 --- a/enterprise/macos-linux/edge-managed-policy.json +++ b/enterprise/macos-linux/edge-managed-policy.json @@ -22,6 +22,9 @@ "companyName": "", "productName": "", "supportEmail": "", + "supportUrl": "", + "privacyPolicyUrl": "", + "aboutUrl": "", "primaryColor": "#F77F00", "logoUrl": "" }, @@ -37,4 +40,4 @@ } } } -} \ No newline at end of file +} diff --git a/options/options.html b/options/options.html index 7a69aea..0162651 100644 --- a/options/options.html +++ b/options/options.html @@ -382,14 +382,6 @@

    Company Information

    Company name displayed in the extension

    -
    - -

    Your company website URL

    -
    -

    Email address for user support

    + +
    + +

    URL opened by the popup Support button

    +
    + +
    + +

    URL opened by the popup Privacy button

    +
    + +
    + +

    URL opened by the popup About button (leave empty to use extension About page)

    +
    diff --git a/options/options.js b/options/options.js index a24dc1f..94f45d6 100644 --- a/options/options.js +++ b/options/options.js @@ -95,9 +95,11 @@ class CheckOptions { // Branding this.elements.companyName = document.getElementById("companyName"); - this.elements.companyURL = document.getElementById("companyURL"); this.elements.productName = document.getElementById("productName"); this.elements.supportEmail = document.getElementById("supportEmail"); + this.elements.supportUrl = document.getElementById("supportUrl"); + this.elements.privacyPolicyUrl = document.getElementById("privacyPolicyUrl"); + this.elements.aboutUrl = document.getElementById("aboutUrl"); this.elements.primaryColor = document.getElementById("primaryColor"); this.elements.logoUrl = document.getElementById("logoUrl"); this.elements.brandingPreview = document.getElementById("brandingPreview"); @@ -185,8 +187,10 @@ class CheckOptions { // Branding preview updates const brandingInputs = [ this.elements.companyName, - this.elements.companyURL, this.elements.productName, + this.elements.supportUrl, + this.elements.privacyPolicyUrl, + this.elements.aboutUrl, this.elements.primaryColor, this.elements.logoUrl, ]; @@ -476,8 +480,10 @@ class CheckOptions { console.warn("Options: Using fallback branding configuration"); this.brandingConfig = { companyName: "CyberDrain", - companyURL: "https://cyberdrain.com/", productName: "Check", + supportUrl: "https://support.cyberdrain.com", + privacyPolicyUrl: "https://cyberdrain.com/privacy", + aboutUrl: "", primaryColor: "#F77F00", logoUrl: "images/icon48.png", }; @@ -485,8 +491,10 @@ class CheckOptions { console.error("Error loading branding configuration:", error); this.brandingConfig = { companyName: "CyberDrain", - companyURL: "https://cyberdrain.com/", productName: "Check", + supportUrl: "https://support.cyberdrain.com", + privacyPolicyUrl: "https://cyberdrain.com/privacy", + aboutUrl: "", primaryColor: "#F77F00", logoUrl: "images/icon48.png", }; @@ -1011,9 +1019,12 @@ class CheckOptions { // Branding settings this.elements.companyName.value = this.brandingConfig?.companyName || ""; - this.elements.companyURL.value = this.brandingConfig?.companyURL || ""; this.elements.productName.value = this.brandingConfig?.productName || ""; this.elements.supportEmail.value = this.brandingConfig?.supportEmail || ""; + this.elements.supportUrl.value = this.brandingConfig?.supportUrl || ""; + this.elements.privacyPolicyUrl.value = + this.brandingConfig?.privacyPolicyUrl || ""; + this.elements.aboutUrl.value = this.brandingConfig?.aboutUrl || ""; this.elements.primaryColor.value = this.brandingConfig?.primaryColor || "#F77F00"; this.elements.logoUrl.value = this.brandingConfig?.logoUrl || ""; @@ -1339,9 +1350,11 @@ class CheckOptions { gatherBrandingData() { return { companyName: this.elements.companyName.value, - companyURL: this.elements.companyURL.value, productName: this.elements.productName.value, supportEmail: this.elements.supportEmail.value, + supportUrl: this.elements.supportUrl.value, + privacyPolicyUrl: this.elements.privacyPolicyUrl.value, + aboutUrl: this.elements.aboutUrl.value, primaryColor: this.elements.primaryColor.value, logoUrl: this.elements.logoUrl.value, }; @@ -2457,8 +2470,10 @@ class CheckOptions { // Custom branding (matches managed_schema.json structure) customBranding: { companyName: "CyberDrain", - companyURL: "https://cyberdrain.com/", productName: "Check Enterprise", + supportUrl: "https://support.cyberdrain.com", + privacyPolicyUrl: "https://cyberdrain.com/privacy", + aboutUrl: "", primaryColor: "#F77F00", logoUrl: "https://cyberdrain.com/images/favicon_hu_20e77b0e20e363e.png", @@ -2598,9 +2613,11 @@ class CheckOptions { // Note: enableDeveloperConsoleLogging is excluded - should remain available for debugging // Branding fields (if customBranding policy is present) companyName: this.elements.companyName, - companyURL: this.elements.companyURL, productName: this.elements.productName, supportEmail: this.elements.supportEmail, + supportUrl: this.elements.supportUrl, + privacyPolicyUrl: this.elements.privacyPolicyUrl, + aboutUrl: this.elements.aboutUrl, primaryColor: this.elements.primaryColor, logoUrl: this.elements.logoUrl, }; @@ -3249,10 +3266,6 @@ class CheckOptions { } updateBrandingPreview() { - const companyName = - this.elements.companyName.value || this.brandingConfig.companyName; - const companyURL = - this.elements.companyURL.value || this.brandingConfig.companyURL; const productName = this.elements.productName.value || this.brandingConfig.productName; const primaryColor = diff --git a/popup/popup.html b/popup/popup.html index 5fb452d..97ed1d9 100644 --- a/popup/popup.html +++ b/popup/popup.html @@ -220,7 +220,7 @@

    Enterprise

    About
    - CyberDrain + CyberDrain
    diff --git a/popup/popup.js b/popup/popup.js index 7d841e6..a8d8881 100644 --- a/popup/popup.js +++ b/popup/popup.js @@ -67,7 +67,6 @@ class CheckPopup { this.elements.aboutLink = document.getElementById("aboutLink"); this.elements.companyBranding = document.getElementById("companyBranding"); this.elements.companyName = document.getElementById("companyName"); - this.elements.companyLink = document.getElementById("companyLink"); // Debug section this.elements.debugSection = document.getElementById("debugSection"); @@ -144,9 +143,6 @@ class CheckPopup { this.elements.aboutLink.addEventListener("click", (e) => this.handleFooterLink(e, "about") ); - this.elements.companyLink.addEventListener("click", (e) => - this.handleFooterLink(e, "company") - ); // Notification close listener this.elements.notificationClose.addEventListener("click", () => @@ -384,22 +380,22 @@ class CheckPopup { console.warn("Popup: Using fallback branding configuration"); this.brandingConfig = { companyName: "CyberDrain", - companyURL: "https://cyberdrain.com/", productName: "Check", logoUrl: "images/icon32.png", supportUrl: "https://support.cyberdrain.com", privacyPolicyUrl: "https://cyberdrain.com/privacy", + aboutUrl: "", primaryColor: "#F77F00", }; } catch (error) { console.error("Error loading branding configuration:", error); this.brandingConfig = { companyName: "CyberDrain", - companyURL: "https://cyberdrain.com/", productName: "Check", logoUrl: "images/icon32.png", supportUrl: "https://support.cyberdrain.com", privacyPolicyUrl: "https://cyberdrain.com/privacy", + aboutUrl: "", primaryColor: "#F77F00", }; } @@ -450,6 +446,9 @@ class CheckPopup { if (this.brandingConfig.privacyPolicyUrl) { this.elements.privacyLink.href = this.brandingConfig.privacyPolicyUrl; } + if (this.brandingConfig.aboutUrl) { + this.elements.aboutLink.href = this.brandingConfig.aboutUrl; + } // Apply primary color if available if (this.brandingConfig.primaryColor) { @@ -1093,10 +1092,9 @@ class CheckPopup { url = this.brandingConfig.privacyPolicyUrl; break; case "about": - url = chrome.runtime.getURL("options/options.html#about"); - break; - case "company": - url = this.brandingConfig.companyURL; + url = + this.brandingConfig.aboutUrl || + chrome.runtime.getURL("options/options.html#about"); break; } diff --git a/scripts/modules/config-manager.js b/scripts/modules/config-manager.js index 2627a06..5d29715 100644 --- a/scripts/modules/config-manager.js +++ b/scripts/modules/config-manager.js @@ -117,6 +117,9 @@ export class ConfigManager { customBranding: { companyName: "CyberDrain", productName: "Check Enterprise", + supportUrl: "https://support.cyberdrain.com", + privacyPolicyUrl: "https://cyberdrain.com/privacy", + aboutUrl: "", primaryColor: "#F77F00", logoUrl: "https://cyberdrain.com/images/favicon_hu_20e77b0e20e363e.png", @@ -333,6 +336,7 @@ export class ConfigManager { supportEmail: "support@check.com", supportUrl: "https://support.check.com", privacyPolicyUrl: "https://check.com/privacy", + aboutUrl: "", termsOfServiceUrl: "https://check.com/terms", // Customizable text @@ -462,8 +466,12 @@ export class ConfigManager { await this.loadConfig(); } - // Start with the base branding config - let finalBranding = await this.getBrandingConfig(); + // Start with defaults to ensure required branding links are always available + const defaultBranding = this.getDefaultBrandingConfig(); + let finalBranding = { + ...defaultBranding, + ...(await this.getBrandingConfig()), + }; // If enterprise has custom branding, merge it in (takes precedence) if (this.enterpriseConfig && this.enterpriseConfig.customBranding) { @@ -480,6 +488,11 @@ export class ConfigManager { finalBranding.genericWebhook = currentConfig.genericWebhook; } + // Derive support URL when only partial branding is configured + if (!finalBranding.supportUrl && finalBranding.supportEmail) { + finalBranding.supportUrl = `mailto:${finalBranding.supportEmail}`; + } + return finalBranding; } diff --git a/tests/config-persistence.test.js b/tests/config-persistence.test.js index b991d5f..e0630e0 100644 --- a/tests/config-persistence.test.js +++ b/tests/config-persistence.test.js @@ -235,3 +235,50 @@ test('ConfigManager - merge precedence', async (t) => { delete global.fetch; teardownGlobalChrome(); }); + +test('ConfigManager - branding links for manual and enterprise config', async (t) => { + const chromeMock = setupGlobalChrome(); + + global.fetch = async () => ({ + ok: false, + status: 404 + }); + + const { ConfigManager } = await import('../scripts/modules/config-manager.js'); + + await t.test('should honor explicit support/privacy/about URLs from enterprise custom branding', async () => { + chromeMock.storage.managed.set({ + customBranding: { + supportUrl: 'https://enterprise.example/support', + privacyPolicyUrl: 'https://enterprise.example/privacy', + aboutUrl: 'https://enterprise.example/about' + } + }); + + const configManager = new ConfigManager(); + const branding = await configManager.getFinalBrandingConfig(); + + assert.strictEqual(branding.supportUrl, 'https://enterprise.example/support'); + assert.strictEqual(branding.privacyPolicyUrl, 'https://enterprise.example/privacy'); + assert.strictEqual(branding.aboutUrl, 'https://enterprise.example/about'); + }); + + await t.test('should derive support link from supportEmail when URLs are not set', async () => { + await chromeMock.storage.local.set({ + brandingConfig: { + supportEmail: 'help@manual.example' + } + }); + + const configManager = new ConfigManager(); + const branding = await configManager.getFinalBrandingConfig(); + const defaultBranding = configManager.getDefaultBrandingConfig(); + + assert.strictEqual(branding.supportUrl, 'mailto:help@manual.example'); + assert.strictEqual(branding.privacyPolicyUrl, defaultBranding.privacyPolicyUrl); + assert.strictEqual(branding.aboutUrl, defaultBranding.aboutUrl); + }); + + delete global.fetch; + teardownGlobalChrome(); +}); From be0369a1525f463de8ec83fd02c7e8edbf2b9d29 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Mon, 16 Mar 2026 23:27:47 +0800 Subject: [PATCH 19/27] Update detection-rules.json Signed-off-by: Zacgoose <107489668+Zacgoose@users.noreply.github.com> --- rules/detection-rules.json | 1 + 1 file changed, 1 insertion(+) diff --git a/rules/detection-rules.json b/rules/detection-rules.json index 13a4056..dc99de0 100644 --- a/rules/detection-rules.json +++ b/rules/detection-rules.json @@ -34,6 +34,7 @@ "^https:\\/\\/([a-zA-Z0-9-]+\\.)*azureedge\\.net$", "^https:\\/\\/([a-zA-Z0-9-]+\\.)*bing\\.com$", "^https:\\/\\/([a-zA-Z0-9-]+\\.)*msn\\.com$", + "^https:\\/\\/([a-zA-Z0-9-]+\\.)*xbox\\.com$", "^https:\\/\\/github\\.com$", "^https:\\/\\/([a-zA-Z0-9-]+\\.)*cloud\\.microsoft$", "^https:\\/\\/([a-zA-Z0-9-]+\\.)*powerbi\\.com$" From f5ea671f95cee093463ef7d546649ebe86055879 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Tue, 24 Mar 2026 07:57:13 +0800 Subject: [PATCH 20/27] Update detection-rules.json --- rules/detection-rules.json | 1 + 1 file changed, 1 insertion(+) diff --git a/rules/detection-rules.json b/rules/detection-rules.json index dc99de0..de315a9 100644 --- a/rules/detection-rules.json +++ b/rules/detection-rules.json @@ -35,6 +35,7 @@ "^https:\\/\\/([a-zA-Z0-9-]+\\.)*bing\\.com$", "^https:\\/\\/([a-zA-Z0-9-]+\\.)*msn\\.com$", "^https:\\/\\/([a-zA-Z0-9-]+\\.)*xbox\\.com$", + "^https:\\/\\/([a-zA-Z0-9-]+\\.)*mcas\\.ms$", "^https:\\/\\/github\\.com$", "^https:\\/\\/([a-zA-Z0-9-]+\\.)*cloud\\.microsoft$", "^https:\\/\\/([a-zA-Z0-9-]+\\.)*powerbi\\.com$" From 2b6f95e04884bb6038923ef8a2233b3337dd687b Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:25:32 +0800 Subject: [PATCH 21/27] Fix firefox json and apply code changes as requested --- config/branding.json | 4 +- config/managed_schema.json | 8 +- enterprise/Check-Extension-Policy.reg | 6 + enterprise/Deploy-Windows-Chrome-and-Edge.ps1 | 8 + enterprise/Test-Extension-Policy.ps1 | 16 + enterprise/admx/Check-Extension.admx | 378 +++++---- enterprise/admx/en-US/Check-Extension.adml | 480 +++++------ enterprise/firefox/policies.json | 3 + enterprise/macos-linux/README.md | 39 +- .../check-extension-config.mobileconfig | 54 +- .../macos-linux/chrome-managed-policy.json | 3 + .../macos-linux/edge-managed-policy.json | 3 + manifest.firefox.json | 5 +- options/options.html | 9 + options/options.js | 265 ++++-- popup/popup.js | 315 ++++--- rules/detection-rules.json | 44 +- scripts/background.js | 52 +- scripts/blocked.js | 137 ++- scripts/content.js | 784 ++++++++++++------ scripts/modules/config-manager.js | 42 +- scripts/modules/domain-squatting-detector.js | 135 ++- 22 files changed, 1793 insertions(+), 997 deletions(-) diff --git a/config/branding.json b/config/branding.json index 1e54978..4a0cfdb 100644 --- a/config/branding.json +++ b/config/branding.json @@ -1,8 +1,8 @@ { "companyName": "CyberDrain", "productName": "Check", - "supportUrl": "https://support.cyberdrain.com", - "privacyPolicyUrl": "https://cyberdrain.com/privacy", + "supportUrl": "", + "privacyPolicyUrl": "", "aboutUrl": "", "version": "1.1.0", "description": "Protect against phishing attacks targeting Microsoft 365 login pages with enterprise-grade detection", diff --git a/config/managed_schema.json b/config/managed_schema.json index d874c98..057b641 100644 --- a/config/managed_schema.json +++ b/config/managed_schema.json @@ -178,9 +178,15 @@ }, "domainSquatting": { "title": "Domain Squatting Detection", - "description": "Configuration for domain squatting detection to protect against typosquatting, homoglyphs, and combosquatting attacks. Enable/disable is controlled by the detection rules JSON. Domains are automatically extracted from the URL allowlist.", + "description": "Configuration for domain squatting detection to protect against typosquatting, homoglyphs, and combosquatting attacks. Enable/disable is controlled here (config/policy), and domains are automatically extracted from the URL allowlist.", "type": "object", "properties": { + "enabled": { + "title": "Enabled", + "description": "Enable or disable domain squatting detection", + "type": "boolean", + "default": true + }, "deviationThreshold": { "title": "Deviation Threshold", "description": "Maximum number of character differences (Levenshtein distance) to trigger detection. Lower values are stricter.", diff --git a/enterprise/Check-Extension-Policy.reg b/enterprise/Check-Extension-Policy.reg index bb5b31b..3ac0464 100644 --- a/enterprise/Check-Extension-Policy.reg +++ b/enterprise/Check-Extension-Policy.reg @@ -20,6 +20,9 @@ Windows Registry Editor Version 5.00 "updateInterval"=dword:00000018 "enableDebugLogging"=dword:00000000 +[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge\3rdparty\extensions\knepjpocdagponkonnbggpcnhnaikajg\policy\domainSquatting] +"enabled"=dword:00000001 + ; Generic webhook configuration (optional) [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Edge\3rdparty\extensions\knepjpocdagponkonnbggpcnhnaikajg\policy\genericWebhook] "enabled"=dword:00000000 @@ -64,6 +67,9 @@ Windows Registry Editor Version 5.00 "updateInterval"=dword:00000018 "enableDebugLogging"=dword:00000000 +[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\3rdparty\extensions\benimdeioplgkhanklclahllklceahbe\policy\domainSquatting] +"enabled"=dword:00000001 + ; Generic webhook configuration for Chrome (optional) [HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome\3rdparty\extensions\benimdeioplgkhanklclahllklceahbe\policy\genericWebhook] "enabled"=dword:00000000 diff --git a/enterprise/Deploy-Windows-Chrome-and-Edge.ps1 b/enterprise/Deploy-Windows-Chrome-and-Edge.ps1 index 1e991b7..af90153 100644 --- a/enterprise/Deploy-Windows-Chrome-and-Edge.ps1 +++ b/enterprise/Deploy-Windows-Chrome-and-Edge.ps1 @@ -22,6 +22,7 @@ $cippTenantId = "" # This will set the "Tenant ID/Domain" option in the extensio $customRulesUrl = "" # This will set the "Config URL" option in the Detection Configuration settings; default is blank. $updateInterval = 24 # This will set the "Update Interval" option in the Detection Configuration settings; default is 24 (hours). Range: 1-168 hours (1 hour to 1 week). $urlAllowlist = @() # This will set the "URL Allowlist" option in the Detection Configuration settings; default is blank; if you want to add multiple URLs, add them as a comma-separated list within the brackets (e.g., @("https://example1.com", "https://example2.com")). Supports simple URLs with * wildcard (e.g., https://*.example.com) or advanced regex patterns (e.g., ^https:\/\/(www\.)?example\.com\/.*$). +$domainSquattingEnabled = 1 # 0 = Disabled, 1 = Enabled; default is 1; controls domain squatting detection from managed policy/config. $enableDebugLogging = 0 # 0 = Unchecked, 1 = Checked (Enabled); default is 0; This will set the "Enable Debug Logging" option in the Activity Log settings. # Generic Webhook Settings @@ -68,6 +69,13 @@ function Configure-ExtensionSettings { New-ItemProperty -Path $ManagedStorageKey -Name "updateInterval" -PropertyType DWord -Value $updateInterval -Force | Out-Null New-ItemProperty -Path $ManagedStorageKey -Name "enableDebugLogging" -PropertyType DWord -Value $enableDebugLogging -Force | Out-Null + # Create and configure domain squatting policy settings + $domainSquattingKey = "$ManagedStorageKey\domainSquatting" + if (!(Test-Path $domainSquattingKey)) { + New-Item -Path $domainSquattingKey -Force | Out-Null + } + New-ItemProperty -Path $domainSquattingKey -Name "enabled" -PropertyType DWord -Value $domainSquattingEnabled -Force | Out-Null + # Create and configure URL allow list $urlAllowlistKey = "$ManagedStorageKey\urlAllowlist" if (!(Test-Path $urlAllowlistKey)) { diff --git a/enterprise/Test-Extension-Policy.ps1 b/enterprise/Test-Extension-Policy.ps1 index 5b58b5e..79c60ec 100644 --- a/enterprise/Test-Extension-Policy.ps1 +++ b/enterprise/Test-Extension-Policy.ps1 @@ -23,6 +23,10 @@ $testConfig = @{ enableDebugLogging = 1 } +$testDomainSquatting = @{ + enabled = 1 +} + # Custom branding test values $testBranding = @{ companyName = "Test Company" @@ -51,6 +55,12 @@ function Set-TestPolicies { $type = if ($value -is [int]) { "DWord" } else { "String" } New-ItemProperty -Path $PolicyKey -Name $key -PropertyType $type -Value $value -Force | Out-Null } + + $domainSquattingKey = "$PolicyKey\domainSquatting" + if (!(Test-Path $domainSquattingKey)) { + New-Item -Path $domainSquattingKey -Force | Out-Null + } + New-ItemProperty -Path $domainSquattingKey -Name "enabled" -PropertyType DWord -Value $testDomainSquatting.enabled -Force | Out-Null $brandingKey = "$PolicyKey\customBranding" if (!(Test-Path $brandingKey)) { @@ -98,6 +108,12 @@ function Show-CurrentPolicies { Write-Output "`nGeneric Webhook:" Get-ItemProperty -Path $genericWebhookKey | Format-List } + + $domainSquattingKey = "$PolicyKey\domainSquatting" + if (Test-Path $domainSquattingKey) { + Write-Output "`nDomain Squatting:" + Get-ItemProperty -Path $domainSquattingKey | Format-List + } } else { Write-Output "No policies set at: $PolicyKey" } diff --git a/enterprise/admx/Check-Extension.admx b/enterprise/admx/Check-Extension.admx index 959c36e..2918bd2 100644 --- a/enterprise/admx/Check-Extension.admx +++ b/enterprise/admx/Check-Extension.admx @@ -113,44 +113,44 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -179,62 +179,62 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -263,6 +263,18 @@ + + + + + + + + + + + + @@ -354,44 +366,44 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -420,61 +432,61 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -503,6 +515,18 @@ + + + + + + + + + + + + diff --git a/enterprise/admx/en-US/Check-Extension.adml b/enterprise/admx/en-US/Check-Extension.adml index 2fa7712..af97141 100644 --- a/enterprise/admx/en-US/Check-Extension.adml +++ b/enterprise/admx/en-US/Check-Extension.adml @@ -108,47 +108,47 @@ CIPP tenant identifier - - This policy specifies the tenant identifier to include with CIPP alerts for multi-tenant environments. - - Example: contoso.onmicrosoft.com - - This helps identify which tenant/organization the security event originated from when using a shared CIPP instance. - - - Enable generic webhook - - This policy controls whether the Check extension sends selected security events to a custom webhook endpoint. - - When enabled: Events listed in "Generic webhook event types" are sent to the configured Generic Webhook URL. - When disabled (default): No generic webhook events are sent. - - Note: Requires Generic Webhook URL and at least one event type. - - Generic webhook URL - - This policy specifies the URL for a custom webhook endpoint that receives Check event payloads. - - Example: https://webhook.yourcompany.com/check-events - - This setting is only used when generic webhook is enabled. - - Generic webhook event types - - This policy specifies which event types are sent to the generic webhook endpoint. - - Supported values: - - detection_alert - - false_positive_report - - page_blocked - - rogue_app_detected - - threat_detected - - validation_event - - - Custom detection rules URL - - This policy specifies a custom URL from which the extension should fetch detection rules. + + This policy specifies the tenant identifier to include with CIPP alerts for multi-tenant environments. + + Example: contoso.onmicrosoft.com + + This helps identify which tenant/organization the security event originated from when using a shared CIPP instance. + + + Enable generic webhook + + This policy controls whether the Check extension sends selected security events to a custom webhook endpoint. + + When enabled: Events listed in "Generic webhook event types" are sent to the configured Generic Webhook URL. + When disabled (default): No generic webhook events are sent. + + Note: Requires Generic Webhook URL and at least one event type. + + Generic webhook URL + + This policy specifies the URL for a custom webhook endpoint that receives Check event payloads. + + Example: https://webhook.yourcompany.com/check-events + + This setting is only used when generic webhook is enabled. + + Generic webhook event types + + This policy specifies which event types are sent to the generic webhook endpoint. + + Supported values: + - detection_alert + - false_positive_report + - page_blocked + - rogue_app_detected + - threat_detected + - validation_event + + + Custom detection rules URL + + This policy specifies a custom URL from which the extension should fetch detection rules. Example: https://yourcompany.com/detection-rules.json @@ -211,7 +211,7 @@ The company name appears in the extension popup and settings pages. - + Product name This policy specifies a custom product name for the extension. @@ -222,36 +222,36 @@ Support email address - - This policy specifies the email address users should contact for support with the extension. - - Example: security@contoso.com - - This email address is displayed in the extension interface and help documentation. - - - Support URL - - This policy specifies the support URL opened by the extension Support link. - - Example: https://support.contoso.com - - - Privacy URL - - This policy specifies the privacy policy URL opened by the extension Privacy link. - - Example: https://contoso.com/privacy - - - About URL - - This policy specifies the about URL opened by the extension About link. - - Example: https://contoso.com/about - - - Primary theme color + + This policy specifies the email address users should contact for support with the extension. + + Example: security@contoso.com + + This email address is displayed in the extension interface and help documentation. + + + Support URL + + This policy specifies the support URL opened by the extension Support link. + + Example: https://support.contoso.com + + + Privacy URL + + This policy specifies the privacy policy URL opened by the extension Privacy link. + + Example: https://contoso.com/privacy + + + About URL + + This policy specifies the about URL opened by the extension About link. + + Example: https://contoso.com/about + + + Primary theme color This policy specifies the primary theme color for the extension interface using a hex color code. @@ -278,6 +278,13 @@ Debug logging should only be enabled for troubleshooting as it may impact performance and generate large log files. + Enable domain squatting detection + + This policy controls domain squatting detection in the Check extension. + + When enabled (default): Typosquatting, homoglyph, and combosquatting protections are active. + When disabled: Domain squatting detections are skipped. + Enable debug logging (Chrome) @@ -288,6 +295,13 @@ Debug logging should only be enabled for troubleshooting as it may impact performance and generate large log files. + Enable domain squatting detection (Chrome) + + This policy controls domain squatting detection in the Check extension for Google Chrome. + + When enabled (default): Typosquatting, homoglyph, and combosquatting protections are active. + When disabled: Domain squatting detections are skipped. + Show valid page badge (Chrome) @@ -335,47 +349,47 @@ CIPP tenant identifier (Chrome) - - This policy specifies the tenant identifier to include with CIPP alerts for multi-tenant environments from Google Chrome. - - Example: contoso.onmicrosoft.com - - This helps identify which tenant/organization the security event originated from when using a shared CIPP instance. - - - Enable generic webhook (Chrome) - - This policy controls whether the Check extension sends selected security events to a custom webhook endpoint in Google Chrome. - - When enabled: Events listed in "Generic webhook event types (Chrome)" are sent to the configured Generic Webhook URL. - When disabled (default): No generic webhook events are sent. - - Note: Requires Generic Webhook URL and at least one event type. - - Generic webhook URL (Chrome) - - This policy specifies the URL for a custom webhook endpoint that receives Check event payloads from Google Chrome. - - Example: https://webhook.yourcompany.com/check-events - - This setting is only used when generic webhook is enabled. - - Generic webhook event types (Chrome) - - This policy specifies which event types are sent to the generic webhook endpoint from Google Chrome. - - Supported values: - - detection_alert - - false_positive_report - - page_blocked - - rogue_app_detected - - threat_detected - - validation_event - - - Custom detection rules URL (Chrome) - - This policy specifies a custom URL from which the extension should fetch detection rules in Google Chrome. + + This policy specifies the tenant identifier to include with CIPP alerts for multi-tenant environments from Google Chrome. + + Example: contoso.onmicrosoft.com + + This helps identify which tenant/organization the security event originated from when using a shared CIPP instance. + + + Enable generic webhook (Chrome) + + This policy controls whether the Check extension sends selected security events to a custom webhook endpoint in Google Chrome. + + When enabled: Events listed in "Generic webhook event types (Chrome)" are sent to the configured Generic Webhook URL. + When disabled (default): No generic webhook events are sent. + + Note: Requires Generic Webhook URL and at least one event type. + + Generic webhook URL (Chrome) + + This policy specifies the URL for a custom webhook endpoint that receives Check event payloads from Google Chrome. + + Example: https://webhook.yourcompany.com/check-events + + This setting is only used when generic webhook is enabled. + + Generic webhook event types (Chrome) + + This policy specifies which event types are sent to the generic webhook endpoint from Google Chrome. + + Supported values: + - detection_alert + - false_positive_report + - page_blocked + - rogue_app_detected + - threat_detected + - validation_event + + + Custom detection rules URL (Chrome) + + This policy specifies a custom URL from which the extension should fetch detection rules in Google Chrome. Example: https://yourcompany.com/detection-rules.json @@ -402,7 +416,7 @@ The company name appears in the extension popup and settings pages. - + Product name (Chrome) This policy specifies a custom product name for the extension in Google Chrome. @@ -413,36 +427,36 @@ Support email address (Chrome) - - This policy specifies the email address users should contact for support with the extension in Google Chrome. - - Example: security@contoso.com - - This email address is displayed in the extension interface and help documentation. - - - Support URL (Chrome) - - This policy specifies the support URL opened by the extension Support link in Google Chrome. - - Example: https://support.contoso.com - - - Privacy URL (Chrome) - - This policy specifies the privacy policy URL opened by the extension Privacy link in Google Chrome. - - Example: https://contoso.com/privacy - - - About URL (Chrome) - - This policy specifies the about URL opened by the extension About link in Google Chrome. - - Example: https://contoso.com/about - - - Primary theme color (Chrome) + + This policy specifies the email address users should contact for support with the extension in Google Chrome. + + Example: security@contoso.com + + This email address is displayed in the extension interface and help documentation. + + + Support URL (Chrome) + + This policy specifies the support URL opened by the extension Support link in Google Chrome. + + Example: https://support.contoso.com + + + Privacy URL (Chrome) + + This policy specifies the privacy policy URL opened by the extension Privacy link in Google Chrome. + + Example: https://contoso.com/privacy + + + About URL (Chrome) + + This policy specifies the about URL opened by the extension About link in Google Chrome. + + Example: https://contoso.com/about + + + Primary theme color (Chrome) This policy specifies the primary theme color for the extension interface using a hex color code in Google Chrome. @@ -466,24 +480,24 @@ - - - - - - - - - - - - Generic Webhook Event Types: - - - - - - + + + + + + + + + + + + Generic Webhook Event Types: + + + + + + Update Interval (hours): @@ -501,36 +515,36 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + @@ -541,24 +555,24 @@ - - - - - - - - - - - - Generic Webhook Event Types: - - - - - - + + + + + + + + + + + + Generic Webhook Event Types: + + + + + + Update Interval (hours): @@ -568,36 +582,36 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + @@ -611,4 +625,4 @@ - + diff --git a/enterprise/firefox/policies.json b/enterprise/firefox/policies.json index 16f7e49..bb6a250 100644 --- a/enterprise/firefox/policies.json +++ b/enterprise/firefox/policies.json @@ -28,6 +28,9 @@ "customRulesUrl": "https://raw.githubusercontent.com/CyberDrain/Check/refs/heads/main/rules/detection-rules.json", "updateInterval": 24, "urlAllowlist": [], + "domainSquatting": { + "enabled": true + }, "enableDebugLogging": false, "customBranding": { "companyName": "", diff --git a/enterprise/macos-linux/README.md b/enterprise/macos-linux/README.md index 835a75f..fc9509b 100644 --- a/enterprise/macos-linux/README.md +++ b/enterprise/macos-linux/README.md @@ -154,18 +154,19 @@ All settings are based on the managed schema and include: - **`enableCippReporting`** - Enable CIPP server reporting (default: false) - **`enableDebugLogging`** - Enable debug logging (default: false) -### CIPP Integration -- **`cippServerUrl`** - CIPP server URL for reporting -- **`cippTenantId`** - Tenant identifier for multi-tenant environments - -### Generic Webhook Integration -- **`genericWebhook.enabled`** - Enable sending events to custom webhook endpoint -- **`genericWebhook.url`** - Webhook endpoint URL -- **`genericWebhook.events`** - Event types to send (`detection_alert`, `false_positive_report`, `page_blocked`, `rogue_app_detected`, `threat_detected`, `validation_event`) - -### Rule Management -- **`customRulesUrl`** - URL for custom detection rules -- **`updateInterval`** - Rule update interval in hours (default: 24) +### CIPP Integration +- **`cippServerUrl`** - CIPP server URL for reporting +- **`cippTenantId`** - Tenant identifier for multi-tenant environments + +### Generic Webhook Integration +- **`genericWebhook.enabled`** - Enable sending events to custom webhook endpoint +- **`genericWebhook.url`** - Webhook endpoint URL +- **`genericWebhook.events`** - Event types to send (`detection_alert`, `false_positive_report`, `page_blocked`, `rogue_app_detected`, `threat_detected`, `validation_event`) + +### Rule Management +- **`customRulesUrl`** - URL for custom detection rules +- **`updateInterval`** - Rule update interval in hours (default: 24) +- **`domainSquatting.enabled`** - Enable/disable domain squatting detection (default: true) ### Custom Branding - **`companyName`** - Company name for white labeling @@ -263,13 +264,13 @@ ls -la /etc/microsoft-edge/policies/managed/ ## Customization -Before deployment, edit the JSON files to customize: -1. **CIPP Integration** - Set `cippServerUrl` and `cippTenantId` -2. **Webhook Integration** - Configure `genericWebhook.enabled`, `genericWebhook.url`, and `genericWebhook.events` -3. **Custom Rules** - Set `customRulesUrl` to your rules endpoint -4. **Branding** - Configure company name, colors, and logo URL -5. **Security Settings** - Adjust notification and blocking preferences -6. **Debug Mode** - Enable `enableDebugLogging` for troubleshooting +Before deployment, edit the JSON files to customize: +1. **CIPP Integration** - Set `cippServerUrl` and `cippTenantId` +2. **Webhook Integration** - Configure `genericWebhook.enabled`, `genericWebhook.url`, and `genericWebhook.events` +3. **Custom Rules** - Set `customRulesUrl` to your rules endpoint +4. **Branding** - Configure company name, colors, and logo URL +5. **Security Settings** - Adjust notification and blocking preferences +6. **Debug Mode** - Enable `enableDebugLogging` for troubleshooting ## Security Considerations diff --git a/enterprise/macos-linux/check-extension-config.mobileconfig b/enterprise/macos-linux/check-extension-config.mobileconfig index 37c479d..e87ff3d 100644 --- a/enterprise/macos-linux/check-extension-config.mobileconfig +++ b/enterprise/macos-linux/check-extension-config.mobileconfig @@ -41,33 +41,41 @@ Value - cippTenantId - - Value - - - genericWebhook - - Value - - enabled - - url - - events - - - - customRulesUrl - - Value - https://raw.githubusercontent.com/CyberDrain/Check/refs/heads/main/rules/detection-rules.json - + cippTenantId + + Value + + + genericWebhook + + Value + + enabled + + url + + events + + + + customRulesUrl + + Value + https://raw.githubusercontent.com/CyberDrain/Check/refs/heads/main/rules/detection-rules.json + updateInterval Value 24 + domainSquatting + + Value + + enabled + + + enableDebugLogging @@ -114,4 +122,4 @@ TargetDeviceType 5 - + diff --git a/enterprise/macos-linux/chrome-managed-policy.json b/enterprise/macos-linux/chrome-managed-policy.json index 190df72..9890c57 100644 --- a/enterprise/macos-linux/chrome-managed-policy.json +++ b/enterprise/macos-linux/chrome-managed-policy.json @@ -18,6 +18,9 @@ "customRulesUrl": "", "updateInterval": 24, "enableDebugLogging": false, + "domainSquatting": { + "enabled": true + }, "customBranding": { "companyName": "", "productName": "", diff --git a/enterprise/macos-linux/edge-managed-policy.json b/enterprise/macos-linux/edge-managed-policy.json index 5939408..6fe986b 100644 --- a/enterprise/macos-linux/edge-managed-policy.json +++ b/enterprise/macos-linux/edge-managed-policy.json @@ -18,6 +18,9 @@ "customRulesUrl": "", "updateInterval": 24, "enableDebugLogging": false, + "domainSquatting": { + "enabled": true + }, "customBranding": { "companyName": "", "productName": "", diff --git a/manifest.firefox.json b/manifest.firefox.json index e8eb957..499a9e0 100644 --- a/manifest.firefox.json +++ b/manifest.firefox.json @@ -67,7 +67,10 @@ "browser_specific_settings": { "gecko": { "id": "check@cyberdrain.com", - "strict_min_version": "109.0" + "strict_min_version": "142.0", + "data_collection_permissions": { + "required": ["none"] + } } } } diff --git a/options/options.html b/options/options.html index 9aeba61..72f1534 100644 --- a/options/options.html +++ b/options/options.html @@ -251,6 +251,15 @@

    Detection Configuration

    Add URLs or regex patterns to allowlist from detection. Use simple URLs with * wildcards (e.g., https://google.com/*) or advanced regex patterns. These will be added to the exclusion rules without replacing the entire ruleset. This allowlist also protects the extracted domains from typosquatting, homoglyphs, and other domain squatting attacks.

    +
    + +

    Enable or disable typosquatting, homoglyph, and combosquatting detection. Protected domains continue to come from detection rules and your URL allowlist.

    +
    +
    -
    `; + const renderBannerContent = (bannerElement) => { + if (!bannerElement) return; + + const root = document.createElement("div"); + root.style.position = "relative"; + root.style.display = "flex"; + root.style.alignItems = "center"; + root.style.gap = "16px"; + root.style.minHeight = "56px"; + root.style.paddingRight = "40px"; + + const left = document.createElement("div"); + left.id = "check-banner-left"; + left.style.display = "flex"; + left.style.alignItems = "center"; + left.style.gap = "12px"; + left.style.zIndex = "2"; + + const center = document.createElement("div"); + center.style.position = "absolute"; + center.style.left = "50%"; + center.style.top = "50%"; + center.style.transform = "translate(-50%,-50%)"; + center.style.textAlign = "center"; + center.style.maxWidth = "60%"; + center.style.zIndex = "1"; + center.style.pointerEvents = "none"; + + const icon = document.createElement("span"); + icon.style.display = "block"; + icon.style.fontSize = "24px"; + icon.style.marginBottom = "4px"; + icon.textContent = bannerIcon; + + const title = document.createElement("strong"); + title.style.display = "block"; + title.textContent = bannerTitle; + + const subtitle = document.createElement("small"); + subtitle.style.opacity = "0.95"; + subtitle.style.display = "block"; + subtitle.style.marginTop = "2px"; + subtitle.textContent = `${reason}${detailsText}`; + + center.appendChild(icon); + center.appendChild(title); + center.appendChild(subtitle); + + const dismissButton = document.createElement("button"); + dismissButton.title = "Dismiss"; + dismissButton.style.position = "absolute"; + dismissButton.style.right = "16px"; + dismissButton.style.top = "50%"; + dismissButton.style.transform = "translateY(-50%)"; + dismissButton.style.background = "rgba(255,255,255,0.2)"; + dismissButton.style.border = "1px solid rgba(255,255,255,0.3)"; + dismissButton.style.color = "#fff"; + dismissButton.style.padding = "0"; + dismissButton.style.borderRadius = "4px"; + dismissButton.style.cursor = "pointer"; + dismissButton.style.width = "24px"; + dismissButton.style.height = "24px"; + dismissButton.style.minWidth = "24px"; + dismissButton.style.minHeight = "24px"; + dismissButton.style.display = "flex"; + dismissButton.style.alignItems = "center"; + dismissButton.style.justifyContent = "center"; + dismissButton.style.fontSize = "14px"; + dismissButton.style.fontWeight = "bold"; + dismissButton.style.lineHeight = "1"; + dismissButton.style.boxSizing = "border-box"; + dismissButton.style.fontFamily = "monospace"; + dismissButton.style.zIndex = "3"; + dismissButton.textContent = "×"; + dismissButton.addEventListener("click", () => { + bannerElement.remove(); + document.body.style.marginTop = "0"; + showingBanner = false; + }); + + root.appendChild(left); + root.appendChild(center); + root.appendChild(dismissButton); + bannerElement.replaceChildren(root); + }; // Check if banner already exists let banner = document.getElementById("ms365-warning-banner"); if (banner) { // Update existing banner content and color - banner.innerHTML = bannerContent; + renderBannerContent(banner); banner.style.background = bannerColor; fetchBranding().then((branding) => applyBranding(banner, branding)); @@ -6047,7 +6325,7 @@ if (window.checkExtensionLoaded) { // CRITICAL: Register the banner BEFORE adding to DOM registerInjectedElement(banner); - banner.innerHTML = bannerContent; + renderBannerContent(banner); document.body.insertBefore(banner, document.body.firstChild); // Register all child elements created via innerHTML @@ -6473,11 +6751,19 @@ if (window.checkExtensionLoaded) { const isCriticalThreat = severity === "critical" || severity === "high"; const isRogueApp = reportData.type === "critical_rogue_app_detected"; const isPhishingBlocked = reportData.type === "phishing_blocked"; + const isBlockedDomainSquatting = + reportData.type === "domain_squatting_detected" && + reportData.action === "blocked"; // Allow critical/high threats and rogue apps, skip informational reports - if (!isCriticalThreat && !isRogueApp && !isPhishingBlocked) { + if ( + !isCriticalThreat && + !isRogueApp && + !isPhishingBlocked && + !isBlockedDomainSquatting + ) { logger.debug( - `CIPP reporting skipped for ${reportData.type} - only high/critical threats are reported` + `CIPP reporting skipped for ${reportData.type} - only high/critical threats or blocked domain squatting events are reported` ); return; } diff --git a/scripts/modules/config-manager.js b/scripts/modules/config-manager.js index 5d29715..7a87673 100644 --- a/scripts/modules/config-manager.js +++ b/scripts/modules/config-manager.js @@ -111,14 +111,27 @@ export class ConfigManager { "https://raw.githubusercontent.com/CyberDrain/Check/refs/heads/main/rules/detection-rules.json", updateInterval: 24, enableDebugLogging: false, + domainSquatting: { + enabled: true, + deviationThreshold: 2, + algorithms: { + levenshtein: true, + homoglyph: true, + typosquat: true, + combosquat: true, + }, + protectedDomains: [], + Action: "block", + logDetections: true, + }, // Note: enableDeveloperConsoleLogging is not policy-managed - remains under user control // Custom branding (matches managed_schema.json structure) customBranding: { companyName: "CyberDrain", productName: "Check Enterprise", - supportUrl: "https://support.cyberdrain.com", - privacyPolicyUrl: "https://cyberdrain.com/privacy", + supportUrl: "", + privacyPolicyUrl: "", aboutUrl: "", primaryColor: "#F77F00", logoUrl: @@ -291,6 +304,21 @@ export class ConfigManager { customRulesUrl: "https://raw.githubusercontent.com/CyberDrain/Check/refs/heads/main/rules/detection-rules.json", updateInterval: 24, // hours + // Domain squatting runtime settings + domainSquatting: { + enabled: true, + deviationThreshold: 2, + algorithms: { + levenshtein: true, + homoglyph: true, + typosquat: true, + combosquat: true, + }, + protectedDomains: [], + Action: "block", + logDetections: true, + }, + // Performance settings scanDelay: 100, maxScanDepth: 10, @@ -327,17 +355,17 @@ export class ConfigManager { version: "1.0.0", // Visual branding - primaryColor: "#2563eb", + primaryColor: "#F77F00", secondaryColor: "#64748b", logoUrl: "images/logo.png", faviconUrl: "images/favicon.ico", // Contact information - supportEmail: "support@check.com", - supportUrl: "https://support.check.com", - privacyPolicyUrl: "https://check.com/privacy", + supportEmail: "", + supportUrl: "", + privacyPolicyUrl: "", aboutUrl: "", - termsOfServiceUrl: "https://check.com/terms", + termsOfServiceUrl: "", // Customizable text welcomeMessage: diff --git a/scripts/modules/domain-squatting-detector.js b/scripts/modules/domain-squatting-detector.js index 0c0e0c8..1edde81 100644 --- a/scripts/modules/domain-squatting-detector.js +++ b/scripts/modules/domain-squatting-detector.js @@ -9,6 +9,9 @@ export class DomainSquattingDetector { constructor() { this.protectedDomains = []; this.enabled = true; + this.action = 'block'; + this.minimumSeverity = 'high'; + this.logDetections = true; this.deviationThreshold = 2; // Maximum Levenshtein distance this.algorithms = { levenshtein: true, @@ -133,17 +136,39 @@ export class DomainSquattingDetector { /** * Initialize with configuration */ - async initialize(config, urlAllowlist = []) { + async initialize(rulesConfig = {}, runtimeConfig = {}) { try { - if (config.domain_squatting) { - this.enabled = config.domain_squatting.enabled !== false; - this.protectedDomains = config.domain_squatting.protected_domains || []; - this.deviationThreshold = config.domain_squatting.deviation_threshold || 2; - - if (config.domain_squatting.algorithms) { - this.algorithms = { ...this.algorithms, ...config.domain_squatting.algorithms }; - } - } + const rulesDomainSquatting = rulesConfig?.domain_squatting || {}; + const runtimeDomainSquatting = runtimeConfig?.domainSquatting || {}; + + this.enabled = runtimeDomainSquatting.enabled !== false; + this.protectedDomains = rulesDomainSquatting.protected_domains || []; + this.deviationThreshold = + runtimeDomainSquatting.deviationThreshold || + rulesDomainSquatting.deviation_threshold || + 2; + this.action = + runtimeDomainSquatting.Action || + runtimeDomainSquatting.action || + rulesDomainSquatting.action || + 'block'; + this.minimumSeverity = + runtimeDomainSquatting.severity || + rulesDomainSquatting.severity || + 'high'; + this.logDetections = + runtimeDomainSquatting.logDetections !== undefined + ? runtimeDomainSquatting.logDetections + : rulesDomainSquatting.log_detections !== false; + + this.algorithms = { + ...this.algorithms, + ...(rulesDomainSquatting.algorithms || {}), + ...(runtimeDomainSquatting.algorithms || {}), + }; + + // Extract domains from URL allowlist patterns from runtime config + const urlAllowlist = runtimeConfig?.urlAllowlist || []; // Extract domains from URL allowlist patterns const allowlistDomains = this.extractDomainsFromAllowlist(urlAllowlist); @@ -156,8 +181,11 @@ export class DomainSquattingDetector { logger.log('DomainSquattingDetector initialized:', { enabled: this.enabled, + action: this.action, + minimumSeverity: this.minimumSeverity, + logDetections: this.logDetections, protectedDomains: this.protectedDomains.length, - fromRules: config.domain_squatting?.protected_domains?.length || 0, + fromRules: rulesDomainSquatting?.protected_domains?.length || 0, fromAllowlist: allowlistDomains.length, deviationThreshold: this.deviationThreshold }); @@ -173,82 +201,115 @@ export class DomainSquattingDetector { if (config.enabled !== undefined) { this.enabled = config.enabled; } - if (config.protected_domains) { - this.protectedDomains = config.protected_domains; + if (config.protected_domains || config.protectedDomains) { + this.protectedDomains = config.protected_domains || config.protectedDomains; + } + if (config.action || config.Action) { + this.action = config.action || config.Action; + } + if (config.severity) { + this.minimumSeverity = config.severity; } - if (config.deviation_threshold !== undefined) { - this.deviationThreshold = config.deviation_threshold; + if (config.logDetections !== undefined) { + this.logDetections = config.logDetections; + } + if (config.deviation_threshold !== undefined || config.deviationThreshold !== undefined) { + this.deviationThreshold = config.deviation_threshold !== undefined + ? config.deviation_threshold + : config.deviationThreshold; } if (config.algorithms) { this.algorithms = { ...this.algorithms, ...config.algorithms }; } } - - /** - * Check if a domain is attempting to squat on protected domains - * @param {string} testDomain - Domain to test - * @returns {Object|null} Detection result or null if no squatting detected - */ + + getAction() { + return this.action || 'block'; + } + + getSeverityRank(severity) { + const rank = { + low: 1, + medium: 2, + high: 3, + critical: 4 + }; + return rank[String(severity || '').toLowerCase()] || 1; + } + + getActionForSeverity(severity) { + const configuredAction = this.getAction(); + if (configuredAction !== 'block') { + return configuredAction; + } + + const minimumRank = this.getSeverityRank(this.minimumSeverity || 'high'); + const detectedRank = this.getSeverityRank(severity); + return detectedRank >= minimumRank ? 'block' : 'warn'; + } + + shouldLogDetections() { + return this.logDetections !== false; + } + checkDomain(testDomain) { if (!this.enabled || !testDomain) { return null; } - - // Extract domain without subdomain and TLD for comparison + const testBase = this.extractBaseDomain(testDomain); - + for (const protectedDomain of this.protectedDomains) { const protectedBase = this.extractBaseDomain(protectedDomain); - - // Skip if domains are identical + if (testBase === protectedBase) { continue; } - - // Run detection algorithms + const detections = []; - + if (this.algorithms.levenshtein) { const levenshteinResult = this.detectLevenshtein(testBase, protectedBase); if (levenshteinResult) { detections.push(levenshteinResult); } } - + if (this.algorithms.homoglyph) { const homoglyphResult = this.detectHomoglyph(testBase, protectedBase); if (homoglyphResult) { detections.push(homoglyphResult); } } - + if (this.algorithms.typosquat) { const typosquatResult = this.detectTyposquat(testBase, protectedBase); if (typosquatResult) { detections.push(typosquatResult); } } - + if (this.algorithms.combosquat) { const combosquatResult = this.detectCombosquat(testBase, protectedBase); if (combosquatResult) { detections.push(combosquatResult); } } - - // If any detection triggered, return result + if (detections.length > 0) { + const severity = this.calculateSeverity(detections); return { detected: true, testDomain: testDomain, protectedDomain: protectedDomain, techniques: detections, - severity: this.calculateSeverity(detections), - confidence: this.calculateConfidence(detections) + severity, + confidence: this.calculateConfidence(detections), + action: this.getActionForSeverity(severity), }; } } - + return null; } From 59f90188a90aef9cd756aabb4d3f295abd414905 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:41:36 +0800 Subject: [PATCH 22/27] fixes --- rules/detection-rules.json | 62 +++++++++++++++++++++++++++++++++----- scripts/content.js | 7 ++++- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/rules/detection-rules.json b/rules/detection-rules.json index 7a46eb8..99a5e62 100644 --- a/rules/detection-rules.json +++ b/rules/detection-rules.json @@ -225,19 +225,65 @@ }, { "id": "segoe_ui_font", - "type": "source_content", - "pattern": "Segoe\\s+UI(?:\\s+(?:Webfont|Symbol|Historic|Emoji))?", + "type": "code_driven", + "code_logic": { + "type": "all_of", + "operations": [ + { + "type": "substring_present", + "values": [ + "segoe ui" + ] + }, + { + "type": "substring_present", + "values": [ + "loginfmt", + "idsibutton9", + "idpartnerpl", + "urlmsasignup", + "aadcdn.msauth.net", + "aadcdn.msftauthimages.net" + ] + } + ] + }, "description": "Microsoft's Segoe UI font family variants (supporting evidence only)", "weight": 0.5, "category": "secondary" }, { "id": "ms_container_layout", - "type": "css_pattern", - "patterns": [ - "display:\\s*grid.*place-items:\\s*center", - "height:\\s*100vh.*width:\\s*100vw" - ], + "type": "code_driven", + "code_logic": { + "type": "all_of", + "operations": [ + { + "type": "pattern_count", + "patterns": [ + "(?:^|[^-\\w])height\\s*:\\s*100vh\\s*;" + ], + "flags": "i", + "min_count": 1 + }, + { + "type": "pattern_count", + "patterns": [ + "(?:^|[^-\\w])width\\s*:\\s*100vw\\s*;" + ], + "flags": "i", + "min_count": 1 + }, + { + "type": "substring_present", + "values": [ + "loginfmt", + "idsibutton9", + "#i0116" + ] + } + ] + }, "description": "Microsoft login container layout (supporting evidence only)", "weight": 0.5, "category": "secondary" @@ -245,7 +291,7 @@ { "id": "ms_external_css", "type": "source_content", - "pattern": "(?:href=[\"'].*(?:aadcdn\\.msauth|aadcdn\\.msftauth|login\\.microsoftonline).*\\.css[\"']|src=[\"'].*(?:aadcdn\\.msauth|login\\.microsoft).*\\.css[\"'])", + "pattern": "(?:<(?:link|script)[^>]+(?:href|src)=[\"']https?:\\/\\/[^\"']*(?:aadcdn\\.msauth\\.net|aadcdn\\.msftauth\\.net|aadcdn\\.msftauthimages\\.net|login\\.microsoftonline\\.com)[^\"']*\\.css(?:\\?[^\"']*)?[\"'][^>]*>)", "description": "Microsoft login-specific CSS files (strong evidence)", "weight": 3, "category": "secondary" diff --git a/scripts/content.js b/scripts/content.js index f562d62..a02b530 100644 --- a/scripts/content.js +++ b/scripts/content.js @@ -1342,7 +1342,12 @@ if (window.checkExtensionLoaded) { try { let found = false; - if (element.type === "source_content") { + if (element.type === "code_driven" && element.code_logic) { + found = evaluatePrimitivePortable(pageSource, element.code_logic, { + cache: new Map(), + currentUrl: window.location.href, + }); + } else if (element.type === "source_content") { const regex = new RegExp(element.pattern, "i"); found = regex.test(pageSource); } else if (element.type === "page_title") { From 059d8700d6fa851b65f33f1fd9577e85966e288d Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:49:01 +0800 Subject: [PATCH 23/27] Update detection-rules.json --- rules/detection-rules.json | 65 ++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/rules/detection-rules.json b/rules/detection-rules.json index 99a5e62..785dec6 100644 --- a/rules/detection-rules.json +++ b/rules/detection-rules.json @@ -236,14 +236,28 @@ ] }, { - "type": "substring_present", - "values": [ - "loginfmt", - "idsibutton9", - "idpartnerpl", - "urlmsasignup", - "aadcdn.msauth.net", - "aadcdn.msftauthimages.net" + "type": "any_of", + "operations": [ + { + "type": "substring_present", + "values": [ + "loginfmt", + "idsibutton9", + "idpartnerpl", + "urlmsasignup", + "aadcdn.msauth.net", + "aadcdn.msftauthimages.net" + ] + }, + { + "type": "substring_present", + "values": [ + "type=\"password\"", + "signin", + "sign in", + "login" + ] + } ] } ] @@ -256,30 +270,35 @@ "id": "ms_container_layout", "type": "code_driven", "code_logic": { - "type": "all_of", + "type": "any_of", "operations": [ { "type": "pattern_count", "patterns": [ - "(?:^|[^-\\w])height\\s*:\\s*100vh\\s*;" - ], - "flags": "i", - "min_count": 1 - }, - { - "type": "pattern_count", - "patterns": [ - "(?:^|[^-\\w])width\\s*:\\s*100vw\\s*;" + "display:\\s*grid[^;{}]*;[^{}]*place-items:\\s*center" ], "flags": "i", "min_count": 1 }, { - "type": "substring_present", - "values": [ - "loginfmt", - "idsibutton9", - "#i0116" + "type": "all_of", + "operations": [ + { + "type": "pattern_count", + "patterns": [ + "(?:^|[^-\\w])height\\s*:\\s*100vh\\s*;" + ], + "flags": "i", + "min_count": 1 + }, + { + "type": "pattern_count", + "patterns": [ + "(?:^|[^-\\w])width\\s*:\\s*100vw\\s*;" + ], + "flags": "i", + "min_count": 1 + } ] } ] From c11fa4f479d46197755f57289e8f1585c5d94ec0 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Tue, 7 Apr 2026 19:39:16 +0800 Subject: [PATCH 24/27] Update scripts/content.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Zacgoose <107489668+Zacgoose@users.noreply.github.com> --- scripts/content.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/content.js b/scripts/content.js index a02b530..4a717a7 100644 --- a/scripts/content.js +++ b/scripts/content.js @@ -2364,9 +2364,10 @@ if (window.checkExtensionLoaded) { }; } + const portableDetectionPrimitives = getPortableDetectionPrimitives(); + function evaluatePrimitivePortable(source, operation, context = {}) { - const primitives = getPortableDetectionPrimitives(); - const primitive = primitives[operation?.type]; + const primitive = portableDetectionPrimitives[operation?.type]; if (!primitive) { return false; } From 2a53a54512cdbe4febfe685c9fff79b9a8f8bc86 Mon Sep 17 00:00:00 2001 From: Zacgoose <107489668+Zacgoose@users.noreply.github.com> Date: Tue, 7 Apr 2026 19:42:06 +0800 Subject: [PATCH 25/27] Update scripts/content.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Zacgoose <107489668+Zacgoose@users.noreply.github.com> --- scripts/content.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/content.js b/scripts/content.js index 4a717a7..0a6bf57 100644 --- a/scripts/content.js +++ b/scripts/content.js @@ -5305,11 +5305,13 @@ if (window.checkExtensionLoaded) { const clientInfo = await extractClientInfo(location.href); const threatAction = severity === "high" && protectionEnabled ? "blocked" : "warned"; + const threatEventType = + threatAction === "blocked" || threatAction === "warned" + ? "threat_detected" + : "threat_detected_no_action"; logProtectionEvent({ - type: protectionEnabled - ? "threat_detected" - : "threat_detected_no_action", + type: threatEventType, action: threatAction, url: location.href, threatLevel: severity, From 865210ebbdc5467042def931bfb5415a7854f8b4 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Tue, 7 Apr 2026 13:46:57 +0200 Subject: [PATCH 26/27] Check update --- manifest.firefox.json | 2 +- manifest.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/manifest.firefox.json b/manifest.firefox.json index 499a9e0..d163c20 100644 --- a/manifest.firefox.json +++ b/manifest.firefox.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Check by CyberDrain", - "version": "1.1.0", + "version": "1.2.0", "description": "Protect against phishing attacks targeting Microsoft 365 login pages with enterprise-grade detection", "permissions": [ "storage", diff --git a/manifest.json b/manifest.json index ca78360..04fe37c 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Check by CyberDrain", - "version": "1.1.0", + "version": "1.2.0", "description": "Protect against phishing attacks targeting Microsoft 365 login pages with enterprise-grade detection", "permissions": [ "storage", diff --git a/package.json b/package.json index f954f1c..f639c87 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Check", - "version": "1.0.0", + "version": "1.2.0", "description": "An open-source, Manifest V3 browser extension for detecting phishing attacks that impersonate Microsoft 365 sign-in pages.", "main": "index.js", "directories": { From 45c1a36b38c784479d14ecf87428772a0a47e2d3 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Tue, 7 Apr 2026 13:49:23 +0200 Subject: [PATCH 27/27] update rules --- rules/detection-rules.json | 289 ++++++++----------------------------- 1 file changed, 59 insertions(+), 230 deletions(-) diff --git a/rules/detection-rules.json b/rules/detection-rules.json index 785dec6..13d0558 100644 --- a/rules/detection-rules.json +++ b/rules/detection-rules.json @@ -1,5 +1,5 @@ { - "version": "1.1.0", + "version": "1.2.0", "lastUpdated": "2025-12-20T00:00:00Z", "description": "Phishing detection logic for identifying phishing attempts targeting Microsoft 365 login pages", "trusted_login_patterns": [ @@ -182,11 +182,7 @@ "id": "meta_og_title_microsoft", "type": "meta_tag", "attribute": "og:title", - "patterns": [ - "microsoft", - "office\\s*365", - "azure" - ], + "patterns": ["microsoft", "office\\s*365", "azure"], "description": "Open Graph title contains Microsoft branding", "weight": 0.5, "category": "secondary" @@ -231,9 +227,7 @@ "operations": [ { "type": "substring_present", - "values": [ - "segoe ui" - ] + "values": ["segoe ui"] }, { "type": "any_of", @@ -251,12 +245,7 @@ }, { "type": "substring_present", - "values": [ - "type=\"password\"", - "signin", - "sign in", - "login" - ] + "values": ["type=\"password\"", "signin", "sign in", "login"] } ] } @@ -285,17 +274,13 @@ "operations": [ { "type": "pattern_count", - "patterns": [ - "(?:^|[^-\\w])height\\s*:\\s*100vh\\s*;" - ], + "patterns": ["(?:^|[^-\\w])height\\s*:\\s*100vh\\s*;"], "flags": "i", "min_count": 1 }, { "type": "pattern_count", - "patterns": [ - "(?:^|[^-\\w])width\\s*:\\s*100vw\\s*;" - ], + "patterns": ["(?:^|[^-\\w])width\\s*:\\s*100vw\\s*;"], "flags": "i", "min_count": 1 } @@ -412,26 +397,19 @@ "aad_detection_elements": [ { "id": "loginfmt_field", - "selectors": [ - "input[name='loginfmt']", - "#i0116" - ], + "selectors": ["input[name='loginfmt']", "#i0116"], "description": "Azure AD username/email input field", "weight": 30 }, { "id": "next_button", - "selectors": [ - "#idSIButton9" - ], + "selectors": ["#idSIButton9"], "description": "Azure AD Next/Sign in button", "weight": 25 }, { "id": "password_field", - "selectors": [ - "input[type='password']" - ], + "selectors": ["input[type='password']"], "description": "Password input field", "weight": 20 }, @@ -461,25 +439,19 @@ }, { "id": "urlMsaSignUp", - "text_patterns": [ - "urlMsaSignUp" - ], + "text_patterns": ["urlMsaSignUp"], "description": "Microsoft signup URL reference", "weight": 15 }, { "id": "flowToken", - "text_patterns": [ - "flowToken" - ], + "text_patterns": ["flowToken"], "description": "Microsoft authentication flow token", "weight": 15 }, { "id": "aadcdn_msauth", - "text_patterns": [ - "https:\\/\\/aadcdn\\.msauth\\.net/" - ], + "text_patterns": ["https:\\/\\/aadcdn\\.msauth\\.net/"], "description": "Microsoft authentication CDN reference", "weight": 15 } @@ -490,9 +462,7 @@ "type": "url", "weight": 25, "condition": { - "domains": [ - "login.microsoftonline.com" - ] + "domains": ["login.microsoftonline.com"] }, "description": "Verify legitimate Microsoft domain (must be login.microsoftonline.com)" }, @@ -544,10 +514,7 @@ "type": "dom", "weight": 20, "condition": { - "selectors": [ - "input[name='loginfmt']", - "#i0116" - ] + "selectors": ["input[name='loginfmt']", "#i0116"] }, "description": "Check for loginfmt input field availability" }, @@ -679,11 +646,7 @@ }, { "type": "substring_present", - "values": [ - "microsoft", - "office", - "365" - ] + "values": ["microsoft", "office", "365"] } ] }, @@ -719,9 +682,7 @@ "code_logic": { "type": "resource_from_domain", "resource_type": "customcss", - "allowed_domains": [ - "aadcdn.msftauthimages.net" - ], + "allowed_domains": ["aadcdn.msftauthimages.net"], "invert": true }, "severity": "high", @@ -741,33 +702,21 @@ "operations": [ { "type": "substring_present", - "values": [ - "microsoft", - "office", - "365" - ] + "values": ["microsoft", "office", "365"] }, { "type": "substring_present", - "values": [ - "login", - "password", - "signin" - ] + "values": ["login", "password", "signin"] }, { "type": "pattern_count", - "patterns": [ - "]*action" - ], + "patterns": ["]*action"], "flags": "i", "min_count": 1 }, { "type": "has_but_not", - "required": [ - "action" - ], + "required": ["action"], "prohibited": [ "login.microsoftonline.com", ".auth/login/", @@ -817,21 +766,13 @@ "operations": [ { "type": "substring_count", - "substrings": [ - "loginfmt", - "i0116", - "idSIButton9" - ], + "substrings": ["loginfmt", "i0116", "idSIButton9"], "min_count": 2 }, { "type": "has_but_not", - "required": [ - "password" - ], - "prohibited": [ - "login.microsoftonline.com" - ] + "required": ["password"], + "prohibited": ["login.microsoftonline.com"] } ] }, @@ -862,24 +803,15 @@ "operations": [ { "type": "substring_present", - "values": [ - "microsoft", - "office", - "365" - ] + "values": ["microsoft", "office", "365"] }, { "type": "substring_present", - "values": [ - "password", - "passwd" - ] + "values": ["password", "passwd"] }, { "type": "form_action_check", - "required_domains": [ - "login.microsoftonline.com" - ] + "required_domains": ["login.microsoftonline.com"] } ] }, @@ -1129,12 +1061,7 @@ }, { "type": "substring_present", - "values": [ - "eval(", - "atob(", - "unescape(", - "String.fromCharCode(" - ] + "values": ["eval(", "atob(", "unescape(", "String.fromCharCode("] } ] }, @@ -1213,13 +1140,7 @@ "operations": [ { "type": "substring_present", - "values": [ - "microsoft", - "office", - "365", - "outlook", - "azure" - ] + "values": ["microsoft", "office", "365", "outlook", "azure"] }, { "type": "any_of", @@ -1270,11 +1191,7 @@ }, { "type": "has_but_not", - "required": [ - "team", - "department", - "support" - ], + "required": ["team", "department", "support"], "prohibited": [ "sign in with microsoft", "continue with microsoft", @@ -1332,164 +1249,95 @@ "type": "multi_proximity", "pairs": [ { - "words": [ - "verify", - "account" - ], + "words": ["verify", "account"], "max_distance": 50 }, { - "words": [ - "verify", - "information" - ], + "words": ["verify", "information"], "max_distance": 50 }, { - "words": [ - "verify", - "identity" - ], + "words": ["verify", "identity"], "max_distance": 50 }, { - "words": [ - "suspended", - "365" - ], + "words": ["suspended", "365"], "max_distance": 50 }, { - "words": [ - "suspended", - "account" - ], + "words": ["suspended", "account"], "max_distance": 50 }, { - "words": [ - "suspended", - "office" - ], + "words": ["suspended", "office"], "max_distance": 50 }, { - "words": [ - "update", - "office" - ], + "words": ["update", "office"], "max_distance": 50 }, { - "words": [ - "update", - "microsoft" - ], + "words": ["update", "microsoft"], "max_distance": 50 }, { - "words": [ - "update", - "365" - ], + "words": ["update", "365"], "max_distance": 50 }, { - "words": [ - "secure", - "microsoft" - ], + "words": ["secure", "microsoft"], "max_distance": 50 }, { - "words": [ - "secure", - "account" - ], + "words": ["secure", "account"], "max_distance": 50 }, { - "words": [ - "account", - "security" - ], + "words": ["account", "security"], "max_distance": 50 }, { - "words": [ - "security", - "verification" - ], + "words": ["security", "verification"], "max_distance": 50 }, { - "words": [ - "security", - "alert" - ], + "words": ["security", "alert"], "max_distance": 50 }, { - "words": [ - "login", - "microsoft" - ], + "words": ["login", "microsoft"], "max_distance": 50 }, { - "words": [ - "microsoft", - "login" - ], + "words": ["microsoft", "login"], "max_distance": 50 }, { - "words": [ - "microsoft", - "authentication" - ], + "words": ["microsoft", "authentication"], "max_distance": 50 }, { - "words": [ - "authentication", - "microsoft" - ], + "words": ["authentication", "microsoft"], "max_distance": 50 }, { - "words": [ - "office", - "365" - ], + "words": ["office", "365"], "max_distance": 50 }, { - "words": [ - "365", - "login" - ], + "words": ["365", "login"], "max_distance": 50 }, { - "words": [ - "office", - "login" - ], + "words": ["office", "login"], "max_distance": 50 }, { - "words": [ - "365", - "suspended" - ], + "words": ["365", "suspended"], "max_distance": 50 }, { - "words": [ - "office", - "suspended" - ], + "words": ["office", "suspended"], "max_distance": 50 } ] @@ -1587,34 +1435,22 @@ "type": "multi_proximity", "pairs": [ { - "words": [ - "microsoft", - "login" - ], + "words": ["microsoft", "login"], "max_distance": 750 }, { - "words": [ - "office", - "sign in" - ], + "words": ["office", "sign in"], "max_distance": 750 }, { - "words": [ - "365", - "authentication" - ], + "words": ["365", "authentication"], "max_distance": 750 } ] }, { "type": "has_but_not", - "required": [ - "login", - "sign" - ], + "required": ["login", "sign"], "prohibited": [ "sign in with microsoft", "continue with microsoft", @@ -1722,10 +1558,7 @@ "code_driven": true, "code_logic": { "type": "pattern_count", - "patterns": [ - "(?:){5,}", - "(?:){5,}" - ], + "patterns": ["(?:){5,}", "(?:){5,}"], "flags": "i", "min_count": 1 }, @@ -1954,9 +1787,7 @@ "action": "warn", "category": "code_obfuscation", "confidence": 0.7, - "context_required": [ - "(?:atob|eval|innerHTML|document\\.write)" - ] + "context_required": ["(?:atob|eval|innerHTML|document\\.write)"] }, { "id": "phi_022_cross_origin_fullscreen_iframe", @@ -1991,9 +1822,7 @@ { "id": "validate_css_origin", "pattern": "customcss", - "required_origins": [ - "aadcdn.msftauthimages.net" - ], + "required_origins": ["aadcdn.msftauthimages.net"], "action": "block", "description": "Custom CSS must come from Microsoft CDN" }