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:
- Magic link (recommended) -- one tenant-scoped SSO link, no MDM needed. For organizations with SSO already configured.
- MDM rollout -- Intune, GPO, Jamf, or Linux managed-policy files push the extension and enrollment configuration.
- Self-install -- users install manually and type their tenant ID.
- 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.
Magic-link quickstart
The fastest way to get started with enterprise extension deployment:
- Configure SSO at Settings > SSO in the dashboard
- Generate a magic link at Onboarding > Deploy method > Magic Link and click Generate
- 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:
- Browser Extension -- Force-installed via MDM/GPO with managed storage configuration
- Native Companion App -- Optional OS-level helper for automatic enrollment via native messaging
- 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)
- Go to the Chrome Web Store Developer Dashboard
- Create a new item
- Upload
extension/distas a ZIP - Set visibility to Private and add your organization domain
- Submit for review
Extension Deployment via MDM
Chrome and Edge expose two separate policy surfaces for extensions:
- Browser-wide policies —
ExtensionInstallForcelist,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/enrollmentModevalues the extension reads viachrome.storage.managed. Lives under3rdparty\extensions\<id>\policyon Windows, thecom.google.Chrome.extensions.<id>bundle on macOS, or the3rdparty.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.
-
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 settingYOUR_EDGE_EXTENSION_ID # Edge settingThe 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. -
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\policytenantId REG_SZ YOUR_TENANT_UUIDapiEndpoint REG_SZ https://your-surfacesec.example.comenrollmentMode REG_SZ ssodeviceHostname REG_SZ %ComputerName% (GPP/script-expanded — see below)For Edge, substitute
HKLM\Software\Policies\Microsoft\Edge\....deviceHostnamemust 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 plainREG_SZwritten verbatim is stored as the literal%ComputerName%string and is not expanded by Chrome. See Endpoint hostname. -
The bundled script
scripts/enterprise/deploy-windows.ps1writes 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— carriesExtensionInstallForcelist.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
-
Computers > Configuration Profiles > New.
-
Payload A — force-install. Add an Application & Custom Settings payload per browser: preference domain
com.google.Chromewith the Chrome Web Store ID, andcom.microsoft.Edgewith 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. -
Payload B — managed-storage values. Add a second Application & Custom Settings payload, preference domain
com.google.Chrome.extensions.YOUR_EXTENSION_ID(orcom.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_settingsautomatically — do not add that wrapper yourself.To report the endpoint hostname, add a
deviceHostnamestring 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)
-
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 withYOUR_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
.regwrites) forExtensionInstallForcelist: it is a list policy whose registry value names are sequential integers ("1","2", ...) and a hand-rolled.regimport would silently overwrite the entry an existing corp-pushed extension occupies. The GPO editor manages indexing for you. -
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\policytenantId REG_SZ YOUR_TENANT_UUIDapiEndpoint REG_SZ https://your-surfacesec.example.comenrollmentMode REG_SZ ssodeviceHostname REG_SZ %ComputerName%For Edge, substitute
HKLM\Software\Policies\Microsoft\Edge\.... Export the key withreg exportand deploy via Group Policy Preferences > Computer Configuration > Preferences > Windows Settings > Registry, or via a startup script. AdddeviceHostnameas a GPP registry item set to%ComputerName%— GPP expands it to the machine name at processing time, so each device reports its own hostname (areg exportof 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.
| Property | Type | Default | Description |
|---|---|---|---|
tenantId | string (UUID) | -- | Organization tenant UUID |
apiEndpoint | string (URI) | -- | Backend API endpoint URL |
enrollmentEndpoint | string (URI) | apiEndpoint + /api/v1/enroll/auto | Device enrollment endpoint |
nativeHostName | string | com.surfacesec.agent | Native messaging host name |
deviceHostname | string | -- | OS hostname of the endpoint, injected by policy (no native agent). Reported on first heartbeat; shown on the device record. See Endpoint hostname. |
autoEnroll | boolean | true | Auto-enroll via native host |
extensionMode | string | learning | Default mode: learning, warn, or block |
policySyncInterval | integer | 15 | Policy sync interval in minutes (1-1440) |
eventBatchSize | integer | 10 | Events per batch (1-100) |
eventBatchInterval | integer | 30 | Max seconds before sending batch (5-300) |
sensitiveDomainsBlocked | string[] | -- | Domains excluded from telemetry |
allowedDomains | string[] | -- | If set, only these domains are monitored |
Feature toggles (nested under features):
| Property | Type | Default | Description |
|---|---|---|---|
telemetryEnabled | boolean | true | Credential telemetry collection |
phishingDetection | boolean | true | Phishing detection and warnings |
clipboardProtection | boolean | false | Clipboard paste monitoring |
hibpEnabled | boolean | true | HIBP breach checking |
techFingerprinting | boolean | true | Technology stack detection |
Configuration precedence:
- Managed storage (enterprise policy) -- highest priority for tenant config
- Local storage -- user-specific settings and certificates
- 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:COMPUTERNAMEat run time. Both store the resolved name. (A rawREG_SZtyped 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.shwritesscutil --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
| Platform | Authentication | Integration |
|---|---|---|
| Windows | Kerberos/NTLM via SSPI | Registry-based host registration |
| macOS | Keychain integration | plist-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:
- User must exist in Surface Security -- synced via SCIM before enrollment attempt
- UPN/email must match -- the user UPN in the identity provider must match their email in Surface Security
- Native host installed -- must be registered for Chrome/Edge on the device
- Managed storage configured --
tenantIdandapiEndpointset via MDM policy - CA certificate uploaded -- a CA certificate must be configured in Surface Security settings for mTLS certificate signing
Deployment Artifacts
| Platform | Artifact | Purpose |
|---|---|---|
| Windows | MSI installer (planned) | Install native host + register for Chrome/Edge |
| macOS | PKG installer (planned) | Install native host + configure for Chrome/Edge |
| All | Intune policy | Force-install extension + set managed storage |
| All | GPO template | Alternative to Intune for on-prem AD |
Deployment scripts (available now):
| Script | Platform | Description |
|---|---|---|
scripts/enterprise/deploy-windows.ps1 | Windows | Extension + native host deployment |
scripts/enterprise/deploy-macos.sh | macOS | Extension + native host deployment |
Extension Configuration (Post-Install)
After installation, configure the extension via the admin dashboard:
- Go to Settings > Extension Configuration
- Set your API Endpoint (e.g.,
https://surfacesec.yourdomain.com) - Configure Mode (learning/warn/block)
- Set up Domain Policies
Key Files
| File | Description |
|---|---|
extension/managed_schema.json | Chrome managed storage JSON schema |
extension/manifests/ | Per-browser manifest files |
native-host/ | Native companion app source |
scripts/enterprise/deploy-windows.ps1 | Windows enterprise deployment |
scripts/enterprise/deploy-macos.sh | macOS enterprise deployment |
backend/internal/companion/api.go | Native messaging API |
backend/internal/companion/updater.go | Companion app auto-update system |