Skip to main content

Enterprise Browser Extension Deployment

This guide covers deploying the Surface Security browser extension across your organization using enterprise management tools (Intune, GPO, Jamf, CBCM) and configuring the optional native companion app for zero-touch enrollment.

Overview

Four deployment paths are available:

  1. Magic link (recommended) -- one tenant-scoped SSO link, no MDM needed. For organizations with SSO already configured.
  2. MDM rollout -- Intune, GPO, Jamf, or Linux managed-policy files push the extension and enrollment configuration.
  3. Self-install -- users install manually and type their tenant ID.
  4. Native companion app -- optional OS-level helper for zero-touch enrollment via native messaging (Windows/macOS).

For the magic-link path, see the Magic Link Quickstart below.

The fastest way to get started with enterprise extension deployment:

  1. Configure SSO at Settings > SSO in the dashboard
  2. Generate a magic link at Onboarding > Deploy method > Magic Link and click Generate
  3. Share the link with your team via Slack, email, or wiki

That's it. Users click the link, authenticate via SSO, and their extension enrolls automatically.

Details

  • The magic-link endpoint is tenant-scoped and single-use-proof (works multiple times from multiple devices)
  • Users who don't have the extension installed see a browser-detection page that deep-links to the Chrome Web Store or Edge Add-ons
  • After install, the page redirects through your IdP for SSO
  • On successful SSO, the page tells the extension to enroll and shows the user's identity
  • Optional: Configure email-domain allowlist at Settings > General > SSO Extension Enrollment to restrict enrollment to specific domains
  • Revoke active links from Onboarding > Magic Link (in-flight sessions complete gracefully)

For larger orgs that prefer to push the extension via MDM, see the MDM rollout section below.

Enterprise extension deployment components

For MDM and native companion paths, enterprise extension deployment involves:

  1. Browser Extension -- Force-installed via MDM/GPO with managed storage configuration
  2. Native Companion App -- Optional OS-level helper for automatic enrollment via native messaging
  3. Managed Storage -- Policy-driven configuration injected into the extension at install time

Build the Extension

cd extension
npm install
npm run build

Deploy to Chrome Web Store (Private)

  1. Go to the Chrome Web Store Developer Dashboard
  2. Create a new item
  3. Upload extension/dist as a ZIP
  4. Set visibility to Private and add your organization domain
  5. Submit for review

Extension Deployment via MDM

Chrome and Edge expose two separate policy surfaces for extensions:

  • Browser-wide policiesExtensionInstallForcelist, ExtensionSettings, etc. Live in the browser's policy registry (HKLM\Software\Policies\Google\Chrome), preference domain (com.google.Chrome), or Linux managed-policy JSON top-level.
  • Per-extension managed storage — the tenantId / apiEndpoint / enrollmentMode values the extension reads via chrome.storage.managed. Lives under 3rdparty\extensions\<id>\policy on Windows, the com.google.Chrome.extensions.<id> bundle on macOS, or the 3rdparty.extensions.<id> block on Linux.

Every MDM rollout below is structurally a two-step delivery: force-install via the browser-wide surface, and managed-storage values via the per-extension surface.

Windows (Intune)

Intune Settings catalog covers force-install but does not surface the per-extension 3rdparty\extensions\<id>\policy registry tree. Deliver the managed-storage values via a Custom OMA-URI profile or a PowerShell platform script.

  1. Step 1 — force-install. In the Intune admin center go to Devices > Configuration > Create > New policy > Platform: Windows 10 and later > Profile type: Settings catalog. Add Google Chrome > Extensions > Configure the list of force-installed apps and extensions (and the matching Microsoft Edge > Extensions setting) with the extension ID for that browser's store — Surface ships under different IDs on the Chrome Web Store and Edge Add-ons:

    YOUR_CHROME_EXTENSION_ID # Chrome setting
    YOUR_EDGE_EXTENSION_ID # Edge setting

    The bare ID is sufficient because each browser resolves the update URL from its own store. The <id>;<update-url> form is only required for self-hosted / air-gapped builds, in which case the URL is the URL of the update XML manifest you host yourself.

  2. Step 2 — managed-storage values. Create a second profile of type Templates > Custom and add one OMA-URI per key, or deliver the same values via a PowerShell platform script under Devices > Scripts and remediations. Either path writes to:

    HKLM\Software\Policies\Google\Chrome\3rdparty\extensions\YOUR_EXTENSION_ID\policy
    tenantId REG_SZ YOUR_TENANT_UUID
    apiEndpoint REG_SZ https://your-surfacesec.example.com
    enrollmentMode REG_SZ sso
    deviceHostname REG_SZ %ComputerName% (GPP/script-expanded — see below)

    For Edge, substitute HKLM\Software\Policies\Microsoft\Edge\....

    deviceHostname must be expanded per machine — type it as a Group Policy Preferences registry item with %ComputerName%, or set it from a PowerShell platform script ($env:COMPUTERNAME). A plain REG_SZ written verbatim is stored as the literal %ComputerName% string and is not expanded by Chrome. See Endpoint hostname.

  3. The bundled script scripts/enterprise/deploy-windows.ps1 writes the correct registry values and is the simplest way to deliver step 2 via an Intune platform script:

    .\scripts\enterprise\deploy-windows.ps1 `
    -TenantId "YOUR_TENANT_UUID" `
    -ApiEndpoint "https://your-surfacesec.example.com" `
    -ExtensionId "YOUR_CHROME_EXTENSION_ID"

macOS (Intune / Jamf Pro)

macOS Chrome is not covered by Intune Settings catalog; use a Preference file template profile. Jamf Pro uses Application & Custom Settings payloads. Both mechanisms expect a plist per preference domain.

Two preference domains are involved:

  • com.google.Chrome / com.microsoft.Edge — carries ExtensionInstallForcelist.
  • com.google.Chrome.extensions.YOUR_EXTENSION_ID / com.microsoft.Edge.extensions.YOUR_EXTENSION_ID — carries the flat managed-storage keys.

Putting ExtensionInstallForcelist inside the per-extension domain is a no-op — Chrome only reads it from the browser domain.

Jamf Pro

  1. Computers > Configuration Profiles > New.

  2. Payload A — force-install. Add an Application & Custom Settings payload per browser: preference domain com.google.Chrome with the Chrome Web Store ID, and com.microsoft.Edge with the Edge Add-ons ID. Plist body for each:

    <dict>
    <key>ExtensionInstallForcelist</key>
    <array>
    <string>YOUR_EXTENSION_ID</string>
    </array>
    </dict>

    The bare ID resolves to the browser's own store; only self-hosted / air-gapped builds need the <id>;<update-url> form.

  3. Payload B — managed-storage values. Add a second Application & Custom Settings payload, preference domain com.google.Chrome.extensions.YOUR_EXTENSION_ID (or com.microsoft.Edge.extensions.YOUR_EXTENSION_ID):

    <dict>
    <key>tenantId</key>
    <string>YOUR_TENANT_UUID</string>
    <key>apiEndpoint</key>
    <string>https://your-surfacesec.example.com</string>
    <key>enrollmentMode</key>
    <string>sso</string>
    </dict>

    Jamf wraps both plists in PayloadContent → Forced → mcx_preference_settings automatically — do not add that wrapper yourself.

    To report the endpoint hostname, add a deviceHostname string key. macOS profiles can't expand a per-machine variable, so either resolve it per machine in a script (scripts/enterprise/deploy-macos.sh) or populate it from a Jamf Extension Attribute. See Endpoint hostname.

Intune (Preference file profiles)

Create two profiles under **Devices > Configuration > Create > New policy

Platform: macOS > Profile type: Templates > Preference file**, one per preference domain, each uploading the corresponding plist above.

Helper script

scripts/enterprise/deploy-macos.sh writes all four .plist files to /Library/Managed Preferences/ directly — useful for testing locally or deploying via a shell-script payload:

sudo ./scripts/enterprise/deploy-macos.sh \
--tenant-id "YOUR_TENANT_UUID" \
--api-endpoint "https://your-surfacesec.example.com"

Group Policy (On-Premises Windows)

  1. Enable Computer Configuration > Administrative Templates > Google Chrome > Extensions > Configure the list of force-installed apps and extensions with YOUR_CHROME_EXTENSION_ID, and the matching setting under Microsoft Edge > Extensions with YOUR_EDGE_EXTENSION_ID. The bare ID is sufficient because each browser resolves the update URL from its own store; only self-hosted / air-gapped builds need the <id>;<update-url> form.

    Use the ADMX setting (not raw .reg writes) for ExtensionInstallForcelist: it is a list policy whose registry value names are sequential integers ("1", "2", ...) and a hand-rolled .reg import would silently overwrite the entry an existing corp-pushed extension occupies. The GPO editor manages indexing for you.

  2. Chrome does not ship an ADMX setting for per-extension managed-storage values (the schema is extension-specific). The per-extension registry tree is keyed by extension ID and does not collide with other extensions' policies — deliver these values directly on a representative machine:

    HKLM\Software\Policies\Google\Chrome\3rdparty\extensions\YOUR_EXTENSION_ID\policy
    tenantId REG_SZ YOUR_TENANT_UUID
    apiEndpoint REG_SZ https://your-surfacesec.example.com
    enrollmentMode REG_SZ sso
    deviceHostname REG_SZ %ComputerName%

    For Edge, substitute HKLM\Software\Policies\Microsoft\Edge\.... Export the key with reg export and deploy via Group Policy Preferences > Computer Configuration > Preferences > Windows Settings > Registry, or via a startup script. Add deviceHostname as a GPP registry item set to %ComputerName% — GPP expands it to the machine name at processing time, so each device reports its own hostname (a reg export of a literal value will not expand). See Endpoint hostname.

Linux (managed JSON policy file)

For fleet-managed Linux endpoints (Ansible, Puppet, Chef, etc.), drop a JSON file under /etc/opt/chrome/policies/managed/ (or /etc/chromium/policies/managed/ for Chromium, /etc/opt/edge/policies/managed/ for Edge). Use the Chrome Web Store ID in the Chrome file and the Edge Add-ons ID in the Edge file:

{
"ExtensionInstallForcelist": [
"YOUR_EXTENSION_ID"
],
"3rdparty": {
"extensions": {
"YOUR_EXTENSION_ID": {
"tenantId": "YOUR_TENANT_UUID",
"apiEndpoint": "https://your-surfacesec.example.com",
"enrollmentMode": "sso",
"deviceHostname": "HOST-RESOLVED-PER-MACHINE"
}
}
}
}

Restart the browser to pick up the policy.

Linux managed JSON has no variable expansion, so deviceHostname must be written per machine. Have your config-management tool (Ansible/Puppet/Chef) template the file with the node's hostname (e.g. Ansible {{ ansible_hostname }}) rather than shipping one shared file. Omit the key if you don't want to report a hostname. See Endpoint hostname.

Managed Storage Schema

The extension reads configuration from Chrome managed storage. This is set via MDM policy or GPO and takes highest priority over local settings.

PropertyTypeDefaultDescription
tenantIdstring (UUID)--Organization tenant UUID
apiEndpointstring (URI)--Backend API endpoint URL
enrollmentEndpointstring (URI)apiEndpoint + /api/v1/enroll/autoDevice enrollment endpoint
nativeHostNamestringcom.surfacesec.agentNative messaging host name
deviceHostnamestring--OS hostname of the endpoint, injected by policy (no native agent). Reported on first heartbeat; shown on the device record. See Endpoint hostname.
autoEnrollbooleantrueAuto-enroll via native host
extensionModestringlearningDefault mode: learning, warn, or block
policySyncIntervalinteger15Policy sync interval in minutes (1-1440)
eventBatchSizeinteger10Events per batch (1-100)
eventBatchIntervalinteger30Max seconds before sending batch (5-300)
sensitiveDomainsBlockedstring[]--Domains excluded from telemetry
allowedDomainsstring[]--If set, only these domains are monitored

Feature toggles (nested under features):

PropertyTypeDefaultDescription
telemetryEnabledbooleantrueCredential telemetry collection
phishingDetectionbooleantruePhishing detection and warnings
clipboardProtectionbooleanfalseClipboard paste monitoring
hibpEnabledbooleantrueHIBP breach checking
techFingerprintingbooleantrueTechnology stack detection

Configuration precedence:

  1. Managed storage (enterprise policy) -- highest priority for tenant config
  2. Local storage -- user-specific settings and certificates
  3. Default values -- fallback

The full JSON schema is defined in extension/managed_schema.json.

Endpoint hostname (without a native agent)

Browsers expose no API for the OS hostname, and a Manifest V3 extension cannot read the registry directly. The supported, agent-free way to get an endpoint's hostname into Surface is the deviceHostname managed-storage value: an admin populates it through standard Chrome/Edge enterprise policy, the extension reads it via chrome.storage.managed, and reports it to the backend on its first heartbeat. It then appears on the device record (Dashboard > Devices).

Surface can ingest the endpoint hostname through standard Chrome and Edge enterprise managed-extension policy. No local agent or background service is required. Populate the value using GPO, MDM, or your existing browser policy tooling.

The one subtlety is how the hostname gets into the policy value, because it differs per platform:

  • Windows (GPO / Intune / SCCM) — write %ComputerName%. Group Policy Preferences expand it to the machine name at processing time (a static string per machine); a PowerShell/SCCM script expands $env:COMPUTERNAME at run time. Both store the resolved name. (A raw REG_SZ typed by hand in the GPO editor is not expanded — use GPP or a script.)
  • macOS (Jamf / Intune) and Linux — managed preferences have no per-machine variable expansion. A single shared .mobileconfig / managed JSON cannot carry a per-machine hostname. Resolve the hostname per machine in a deployment script (scripts/enterprise/deploy-macos.sh writes scutil --get ComputerName) or via a Jamf Extension Attribute.

Because the value flows in on the heartbeat (not at cert issuance), it works for every enrollment path — SSO, magic-link, token, and native-host — and a later policy refresh (e.g. after a machine rename) updates it on the next heartbeat. The native companion app, when present, also supplies the real OS hostname at auto-enrollment time. deviceHostname is only read when the extension is policy-managed (force-installed); hand-installed extensions have no managed storage and leave the device hostname blank.

Native Companion App

The native companion app is an optional OS-level binary that enables automatic device enrollment without user interaction. It authenticates the user via OS-level identity (Windows Kerberos/NTLM via SSPI, macOS Keychain) and retrieves an mTLS certificate from the backend.

Supported Platforms

PlatformAuthenticationIntegration
WindowsKerberos/NTLM via SSPIRegistry-based host registration
macOSKeychain integrationplist-based host registration

Installation

Windows

# 1. Copy the native host binary to the target machine
Copy-Item surfacesec-agent.exe -Destination "C:\Program Files\SurfaceSec\"

# 2. Register the native messaging host for Chrome/Edge
.\native-host\manifest\install.ps1 -ExtensionId "YOUR_EXTENSION_ID"

# 3. (Optional) Configure auto-update via registry
reg add "HKLM\SOFTWARE\Policies\SurfaceSec\Companion" /v AutoUpdate /t REG_DWORD /d 1
reg add "HKLM\SOFTWARE\Policies\SurfaceSec\Companion" /v UpdateEndpoint /t REG_SZ /d "https://updates.yourdomain.com"

macOS

# 1. Copy the native host binary
sudo cp surfacesec-agent /usr/local/bin/surfacesec-agent
sudo chmod +x /usr/local/bin/surfacesec-agent

# 2. Run the deployment script to register native messaging host
sudo ./scripts/enterprise/deploy-macos.sh \
--native-host-path /usr/local/bin/surfacesec-agent

Note: Pre-built MSI (Windows) and PKG (macOS) installers are planned for a future release. Currently, the binary is built from source in the native-host/ directory and deployed directly.

Building from Source

Requires Go 1.21+.

cd native-host

# Build for current platform
make build

# Cross-compile for specific targets
make build-windows-amd64 # Output: build/surfacesec-agent-windows-amd64.exe
make build-macos-amd64 # Output: build/surfacesec-agent-darwin-amd64
make build-macos-arm64 # Output: build/surfacesec-agent-darwin-arm64
make build-linux-amd64 # Output: build/surfacesec-agent-linux-amd64

# Build all platforms at once
make build-all

# Inject version (recommended for production)
make build-all VERSION=1.2.3

For internal development details, see docs/internal/companion/development.md.

Native Messaging Protocol

The extension communicates with the native host via Chrome Native Messaging API.

Request from extension:

{
"action": "getConfiguration"
}

Response from native host:

{
"success": true,
"userId": "uuid",
"accessKey": "secure-key",
"deviceId": "device-uuid",
"certificateData": "base64-encoded-cert",
"error": ""
}

Auto-Enrollment Flow

When the native messaging host is installed and managed storage is configured, enrollment happens automatically without any user interaction.

Auto-Enrollment Requirements

For auto-enrollment to succeed, all of the following must be true:

  1. User must exist in Surface Security -- synced via SCIM before enrollment attempt
  2. UPN/email must match -- the user UPN in the identity provider must match their email in Surface Security
  3. Native host installed -- must be registered for Chrome/Edge on the device
  4. Managed storage configured -- tenantId and apiEndpoint set via MDM policy
  5. CA certificate uploaded -- a CA certificate must be configured in Surface Security settings for mTLS certificate signing

Deployment Artifacts

PlatformArtifactPurpose
WindowsMSI installer (planned)Install native host + register for Chrome/Edge
macOSPKG installer (planned)Install native host + configure for Chrome/Edge
AllIntune policyForce-install extension + set managed storage
AllGPO templateAlternative to Intune for on-prem AD

Deployment scripts (available now):

ScriptPlatformDescription
scripts/enterprise/deploy-windows.ps1WindowsExtension + native host deployment
scripts/enterprise/deploy-macos.shmacOSExtension + native host deployment

Extension Configuration (Post-Install)

After installation, configure the extension via the admin dashboard:

  1. Go to Settings > Extension Configuration
  2. Set your API Endpoint (e.g., https://surfacesec.yourdomain.com)
  3. Configure Mode (learning/warn/block)
  4. Set up Domain Policies

Key Files

FileDescription
extension/managed_schema.jsonChrome managed storage JSON schema
extension/manifests/Per-browser manifest files
native-host/Native companion app source
scripts/enterprise/deploy-windows.ps1Windows enterprise deployment
scripts/enterprise/deploy-macos.shmacOS enterprise deployment
backend/internal/companion/api.goNative messaging API
backend/internal/companion/updater.goCompanion app auto-update system