Repository navigation
[client] mobile MDM bridge — iOS + Android setMDMPolicyFetcher entrypoint - #6435
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughRefactors MDM policy loading system across desktop, server, and Android from package-level global functions and test hooks to dependency-injected ChangesMDM Loader Dependency Injection Refactor
Sequence Diagram(s)sequenceDiagram
participant Kotlin as Kotlin/Native
participant AndroidClient as Android Client
participant MDMLoader as MDM Loader
participant PlatformImpl as Platform Impl
participant Config as Config
Kotlin->>AndroidClient: SetMDMPolicyFetcher(fetcher)
AndroidClient->>MDMLoader: NewLoader(jsonFetcherAdapter)
Note over AndroidClient: Stores in mdmLoader field
rect rgba(100, 150, 200, 0.5)
Note over AndroidClient,Config: Config Load Path
AndroidClient->>Config: UpdateOrCreateConfig()
Config->>Config: apply() with NewPolicy(nil)
AndroidClient->>MDMLoader: applyMDMOverlay(cfg)
MDMLoader->>MDMLoader: Load()
MDMLoader->>PlatformImpl: loadPlatform()
PlatformImpl->>AndroidClient: fetcher.Fetch()
AndroidClient->>Kotlin: FetchJSON()
Kotlin-->>AndroidClient: JSON string
AndroidClient-->>PlatformImpl: map[string]any
PlatformImpl-->>MDMLoader: policy map
MDMLoader->>Config: ApplyMDMPolicy(policy)
Config-->>AndroidClient: config with overlay
end
sequenceDiagram
participant Server as Server.Start()
participant MDMLoader as MDM Loader
participant Ticker as MDM Ticker
participant Fetcher as Platform Fetcher
Server->>MDMLoader: NewLoader(nil)
Note over Server: Stores in mdmLoader field
Server->>Ticker: NewTicker(interval, mdmLoader)
Ticker->>MDMLoader: Load() init prev
MDMLoader->>Fetcher: loadPlatform()
Fetcher-->>MDMLoader: policy map (or nil)
MDMLoader-->>Ticker: Policy
rect rgba(150, 200, 100, 0.5)
Note over Ticker,Fetcher: Ticker Run Loop (periodic)
loop Each Tick
Ticker->>MDMLoader: Load() current
MDMLoader->>Fetcher: loadPlatform()
Fetcher-->>MDMLoader: updated policy
MDMLoader-->>Ticker: Policy
Ticker->>Ticker: Compare prev vs curr
alt Policy Changed
Ticker->>Ticker: Emit diffs, invoke onChange
end
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@client/mdm/policy_mobile.go`:
- Around line 20-41: The global fetcher variable accessed by
SetMobilePolicyFetcher and loadPlatformPolicy lacks synchronization, creating a
potential data race if SetMobilePolicyFetcher is called concurrently or after
initialization. Add a sync.Mutex to protect access to the fetcher variable. In
SetMobilePolicyFetcher, acquire the lock before writing to fetcher, and in
loadPlatformPolicy, acquire the lock before reading from fetcher. This ensures
atomic registration and read operations regardless of when
SetMobilePolicyFetcher is invoked.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 21ce5b76-f5ba-4cd4-b900-185874005e16
📒 Files selected for processing (2)
client/android/mdm.goclient/mdm/policy_mobile.go
Release artifactsBuilt for PR head
GHCR images (amd64)
This comment is updated by the Release workflow. Artifact links expire according to the workflow retention policy. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
client/mdm/policy.go (1)
108-111: ⚡ Quick winAlign
NewLoaderdocumentation with actual platform behavior.Line 108–111 says the fetcher is consulted only on mobile, but desktop loaders also short-circuit to
l.fetcher.Fetch()when a fetcher is injected. Please update this comment to match the real cross-platform contract and avoid misleading call sites.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@client/mdm/policy.go` around lines 108 - 111, The comment for NewLoader function (lines 108-111) incorrectly states that the fetcher is consulted only on mobile builds and is unused on desktop. Update this documentation comment to accurately reflect that the fetcher is consulted on both mobile and desktop platforms when a fetcher is injected, not just on mobile. The comment should clarify the actual cross-platform contract to avoid misleading callers.client/internal/profilemanager/config.go (1)
187-193: ⚡ Quick winClarify
ApplyMDMPolicyreset semantics in the doc comment.The current wording implies that an empty policy clears prior overlay effects on the same
Configinstance, butapplyMDMPolicyis overlay-only and does not restore previously overridden fields. Please tighten the comment to reflect “apply on freshly resolved config” semantics (or implement explicit revert behavior).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@client/internal/profilemanager/config.go` around lines 187 - 193, The doc comment for the ApplyMDMPolicy function currently states that passing an empty Policy will clear any prior overlay, but this is misleading since the function only applies overlays and does not restore previously overridden fields. Update the doc comment to clarify the actual behavior: that ApplyMDMPolicy applies MDM policy overlays on top of the currently resolved config values and should be called on freshly resolved config (as the lifecycle owner does), rather than implying it can be used to revert or clear prior overlays by passing an empty policy. Remove or revise the wording about clearing prior overlays to accurately reflect overlay-only semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@client/mdm/policy_test.go`:
- Around line 153-161: The test TestLoader_NilFetcherReturnsEmpty is
non-deterministic because calling NewLoader(nil).Load() may read real MDM state
on Windows/macOS, causing the emptiness assertions to fail on managed hosts.
Either add build tags (such as //go:build linux) to gate this test to platforms
where nil fetcher produces a deterministic empty result, or refactor the test to
use a mock fetcher that ensures deterministic behavior regardless of actual MDM
state on the host. This ensures the assertions in the test remain valid and
consistent across all platforms.
---
Nitpick comments:
In `@client/internal/profilemanager/config.go`:
- Around line 187-193: The doc comment for the ApplyMDMPolicy function currently
states that passing an empty Policy will clear any prior overlay, but this is
misleading since the function only applies overlays and does not restore
previously overridden fields. Update the doc comment to clarify the actual
behavior: that ApplyMDMPolicy applies MDM policy overlays on top of the
currently resolved config values and should be called on freshly resolved config
(as the lifecycle owner does), rather than implying it can be used to revert or
clear prior overlays by passing an empty policy. Remove or revise the wording
about clearing prior overlays to accurately reflect overlay-only semantics.
In `@client/mdm/policy.go`:
- Around line 108-111: The comment for NewLoader function (lines 108-111)
incorrectly states that the fetcher is consulted only on mobile builds and is
unused on desktop. Update this documentation comment to accurately reflect that
the fetcher is consulted on both mobile and desktop platforms when a fetcher is
injected, not just on mobile. The comment should clarify the actual
cross-platform contract to avoid misleading callers.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 239e01a4-4e58-4765-ad8d-d153e6b35e88
📒 Files selected for processing (15)
client/android/client.goclient/android/mdm.goclient/internal/profilemanager/config.goclient/internal/profilemanager/config_mdm_test.goclient/mdm/policy.goclient/mdm/policy_darwin.goclient/mdm/policy_mobile.goclient/mdm/policy_other.goclient/mdm/policy_test.goclient/mdm/policy_windows.goclient/mdm/ticker.goclient/mdm/ticker_test.goclient/server/mdm.goclient/server/server.goclient/server/setconfig_mdm_test.go
💤 Files with no reviewable changes (1)
- client/server/mdm.go
|
- login.go: keep both new imports (mdm + nbnet + server) - ios/NetBirdSDK/client.go: additive struct-field merge (mdmLoader + stateMu/connectClient/config) - setconfig_mdm_test.go: adopt new withMDMPolicy(t, s, policy) signature; fix stray old-signature call in TestSetConfig_MDMAllow_ManagementURLPortNormalized
Aligns to other clients OSes behavior
# Conflicts: # client/android/client.go
# Conflicts: # client/cmd/up.go # client/embed/embed.go # client/ios/NetBirdSDK/client.go
The mobile bridges only carried the policy fetcher, leaving every enforcement decision to the native apps: the desktop derived its UI restrictions in the Wails service layer, the daemon kept the conflict machinery in the server package, and both mobile bridges duplicated the JSON fetch adapter. Anything the native side had to reimplement was a place for iOS and Android to drift apart. Enforcement now lives in client/mdm and is consumed identically by all three platforms: - conflicts.go holds the value-aware conflict checks lifted out of the daemon, so the same normalization (canonical URLs, PSK sentinel echo) applies wherever a config change is validated. - restrictions.go derives the UI enforcement snapshot from a policy and renders it in the JSON shape the desktop frontend already consumes. The service-layer types become aliases, keeping one source of truth. - jsonloader.go replaces the adapter that was copy-pasted into both bridges. - changedetector.go moves change detection off the native side: the caller forwards the OS notification and asks whether the managed configuration actually changed, instead of diffing dictionaries itself. The mobile bridges gain the enforcement the daemon already had. The Preferences getters resolve managed keys from the policy, so a naive UI shows the enforced value; Commit rejects a staged change that diverges from a managed key; NewAuth resolves the managed management URL before persisting the config and overlays the policy on it, so a login can no longer run against a URL the policy forbids. Android's profile mutations fail closed when disableProfiles is set. NewAuth takes the fetcher as a required argument rather than keeping a policy-blind overload: the apps consume this code as a submodule, so a compile error at the bump is the point. The mobile PSK getter is replaced by a presence check — the key has no reason to cross the bridge, and not returning it means the native side needs no redaction sentinel of its own.
# Conflicts: # client/ios/NetBirdSDK/client.go # client/ios/NetBirdSDK/login.go # client/server/mdm.go
The merge commit was recorded with the conflict markers still in the tree. Resolve them so the branch builds again: - client/ios/NetBirdSDK: keep both the mdm and mobile imports, and keep the mdmLoader/mdmDetector fields next to main's stateMu documentation. - client/server/mdm.go: drop the conflict helpers main added locally, they already live in the client/mdm package on this branch, and keep the new checks main introduced (allowRemoteJobs, enableLocalMetrics, localMetricsAddress) as calls into the package-level helpers. - client/mdm/conflicts.go: add ConflictStringPtr, the presence-aware string check main needs for the optional localMetricsAddress field. - Port the two tests main added over the per-Server loader helper and the configWithMDM helper, both of which replaced the package-level policy injection this branch removed.
There was a problem hiding this comment.
1 issue found across 38 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="client/internal/profilemanager/config.go">
<violation number="1" location="client/internal/profilemanager/config.go:219">
P2: When an already-overlaid config receives an empty policy, this call clears the enforcement metadata but leaves the prior MDM values in the config. Restore the pre-policy values, or re-resolve the base config before clearing the overlay, so the documented empty-policy behavior actually removes enforcement.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
The SetConfig, Login and mobile Commit conflict checks collapsed the PSK to a plain string, so an explicit empty value was indistinguishable from an unset field and slipped past the MDM gate, clearing the persisted key. Carry the optional field as a pointer through ConflictStringPtr, treating only the redaction sentinel as a no-op echo. ConflictString had no other callers and is removed.
Drop the unused ChangeDetector.Current, point the stale LoadPolicy comment references at Loader.Load, and move the profileEmail godoc back above its function.
…onflict gate MDMConflicts skipped allowRemoteJobs, enableLocalMetrics and localMetricsAddress even though the overlay applies all three and the daemon gate already checks them, so a mobile Commit could persist values diverging from the enforced policy. Align the list with the daemon.
There was a problem hiding this comment.
All reported issues were addressed across 9 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
…ct test The legacy LoginRequest.PreSharedKey field is deliberately exercised by the test, matching the nolint already carried by the production path.
SetMDMPolicyFetcher wrote the loader and change detector as two plain fields that Run, the OS-change callback and the restrictions getter read from other threads without synchronization. Hold both behind a single atomic pointer so a registration is published as one unit and readers always observe a matching loader and detector pair; Preferences gets the same treatment for its loader. Exported signatures are unchanged.
…ences GetRemoteJobsAllowed returned the staged or persisted value even when the policy manages allowRemoteJobs, so the native settings UI could show a value the Commit gate would reject. Resolve it through the overlay like GetManagementURL does.
NewAuth already writes the config through UpdateOrCreateConfig before the MDM policy is overlaid, and the login itself never mutates the Config. The post-login WriteOutConfig calls therefore only rewrote the same file with the enforced ManagementURL and PreSharedKey in it, so a removed or changed policy kept acting through the persisted values.
ApplyMDMPolicy promised that an empty Policy clears a prior overlay, but applyMDMPolicy only resets the enforcement metadata and the runtime-only upload URL; the enforced ManagementURL, PreSharedKey and flags stay. Every lifecycle owner resolves the base Config again before applying, so state that contract instead of the reversibility that was never implemented.
The iOS Client kept the config parsed from SetConfigFromJSON and applied the MDM overlay onto that same instance on every Run, IsLoginRequired and DebugBundle, so a key removed from the policy stayed enforced. Store the JSON instead and parse it per load through one loadConfig path. Auth serialized the overlaid config from GetConfigJSON, which tvOS then persisted to UserDefaults and fed back as the preload. Keep the resolved config as the base, run the login on a JSON round-trip copy with the overlay, and return the base from GetConfigJSON.
…nfig file on mobile Preferences.GetManagementURL resolved a managed URL by reading and overlaying the persisted config, so a corrupt file or the tvOS sandbox turned an enforced URL into a read error. Return the canonical managed value directly, the same string BuildRestrictions already hands to the UI, and only fall back to the staged or persisted value when MDM does not manage the key. NewAuth validated the caller-supplied management URL before the overlay ran, so a malformed or echoed value blocked or persisted under an MDM policy that already dictates the URL. Ignore the caller value while the key is managed; the login runs against the overlay either way.
…ke disableAdvancedView a tristate NewLoader, PolicyFetcher and the darwin/windows loadPlatform docs claimed the fetcher is unused on desktop, while every loader returns its values when one is injected. That precedence is the seam the server tests rely on across platforms, so the docs now describe it; production desktop callers still pass nil and keep the registry / plist authoritative. Fields.DisableAdvancedView collapsed "managed and false" into the same JSON as "not managed", unlike AllowServerSSH and the daemon's optional proto field. Carry it as a *bool so the UIs can tell the two apart; the desktop reflect loop skips pointer fields already, and the mobile decoders treat null as not managed.
- ResolveConflicts treats a managed key whose ConflictCheck has no Check as a conflict instead of dereferencing nil. - Ticker.Run and ChangeDetector.Changed share policyChanged so the diff semantics and the log line cannot drift apart. - TestLoader_NilFetcherReturnsEmpty skips on windows/darwin, where a nil fetcher reads the real registry / plist. - The profilemanager test loader checks GetInt before GetBool so integer keys survive the round trip, and the PSK tests use the exported redaction sentinel.
There was a problem hiding this comment.
1 issue found across 14 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="client/ios/NetBirdSDK/preferences.go">
<violation number="1" location="client/ios/NetBirdSDK/preferences.go:45">
P2: For an invalid or non-http(s) MDM management URL, GetManagementURL now returns the raw value (CanonicalURL returns the input unchanged when unparseable and doesn't check the scheme), instead of the previous behavior where ApplyMDMPolicy/parseURL validated it and fell back to the existing config URL to keep the client functional. The codebase deliberately guards this case (TestApply_MDMInvalidURL_KeepsPreviousValue); this new branch drops that safeguard. Parse the policy value with parseURL and fall back to the config URL when it fails, mirroring ApplyMDMPolicy.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
|
|
||
| // GetManagementURL read url from config file | ||
| func (p *Preferences) GetManagementURL() (string, error) { | ||
| if v, ok := p.policy().GetString(mdm.KeyManagementURL); ok { |
There was a problem hiding this comment.
P2: For an invalid or non-http(s) MDM management URL, GetManagementURL now returns the raw value (CanonicalURL returns the input unchanged when unparseable and doesn't check the scheme), instead of the previous behavior where ApplyMDMPolicy/parseURL validated it and fell back to the existing config URL to keep the client functional. The codebase deliberately guards this case (TestApply_MDMInvalidURL_KeepsPreviousValue); this new branch drops that safeguard. Parse the policy value with parseURL and fall back to the config URL when it fails, mirroring ApplyMDMPolicy.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At client/ios/NetBirdSDK/preferences.go, line 45:
<comment>For an invalid or non-http(s) MDM management URL, GetManagementURL now returns the raw value (CanonicalURL returns the input unchanged when unparseable and doesn't check the scheme), instead of the previous behavior where ApplyMDMPolicy/parseURL validated it and fell back to the existing config URL to keep the client functional. The codebase deliberately guards this case (TestApply_MDMInvalidURL_KeepsPreviousValue); this new branch drops that safeguard. Parse the policy value with parseURL and fall back to the config URL when it fails, mirroring ApplyMDMPolicy.</comment>
<file context>
@@ -42,16 +42,17 @@ func (p *Preferences) policy() *mdm.Policy {
func (p *Preferences) GetManagementURL() (string, error) {
- policy := p.policy()
- if !policy.HasKey(mdm.KeyManagementURL) && p.configInput.ManagementURL != "" {
+ if v, ok := p.policy().GetString(mdm.KeyManagementURL); ok {
+ return mdm.CanonicalURL(v), nil
+ }
</file context>
| values[k] = v | ||
| continue | ||
| } | ||
| if v, ok := policy.GetBool(k); ok { |
There was a problem hiding this comment.
here attempt order is GetString → GetBool → GetInt but in config_mdm_test.go I see String, Int and Bool.. so because GetBool accepts also an integer.. for example something like the wireguardPort: 51820 might get confused for a "true".
withMDMPolicy rebuilt the policy map by trying GetString, then GetBool, then GetInt. Policy.GetBool accepts native ints (non-zero means true), so an int-valued key such as wireguardPort round-tripped through the helper as the bool true and GetInt was never reached. Try GetInt before GetBool, as the profilemanager helper already does; GetInt does not coerce bools, so booleans still fall through to GetBool. No test sets an int key today, so this was latent: the first test to exercise the wireguardPort conflict gate would have seen ConflictInt64 report a conflict for every value, including a matching one.
|
This PR contains the following updates: | Package | Update | Change | |---|---|---| | [netbirdio/dashboard](https://github.com/netbirdio/dashboard) | minor | `v2.92.0` → `v2.94.0` | | [netbirdio/netbird](https://github.com/netbirdio/netbird) | minor | `0.78.1-rootless` → `0.80.0-rootless` | | [netbirdio/netbird-server](https://github.com/netbirdio/netbird) | minor | `0.78.1` → `0.80.0` | | [netbirdio/reverse-proxy](https://github.com/netbirdio/netbird) | minor | `0.78.1` → `0.80.0` | --- >⚠️ **Warning** > > Some dependencies could not be looked up. Check the [Dependency Dashboard](issues/12) for more information. --- ### Release Notes <details> <summary>netbirdio/dashboard (netbirdio/dashboard)</summary> ### [`v2.94.0`](https://github.com/netbirdio/dashboard/releases/tag/v2.94.0) [Compare Source](netbirdio/dashboard@v2.93.0...v2.94.0) #### What's Changed - Fix peer approval visible for self-hosted by [@​heisbrot](https://github.com/heisbrot) in [#​811](netbirdio/dashboard#811) - Hide the sidebar version card from users without settings access by [@​heisbrot](https://github.com/heisbrot) in [#​812](netbirdio/dashboard#812) - Move CI onto the Node 24 action runtime by [@​heisbrot](https://github.com/heisbrot) in [#​814](netbirdio/dashboard#814) - Show real provider logos and rework the Agent Network modals by [@​heisbrot](https://github.com/heisbrot) in [#​809](netbirdio/dashboard#809) - Add Agent Network support to the Control Center by [@​heisbrot](https://github.com/heisbrot) in [#​808](netbirdio/dashboard#808) - Add Sign-in Domains settings tab by [@​heisbrot](https://github.com/heisbrot) in [#​781](netbirdio/dashboard#781) **Full Changelog**: <netbirdio/dashboard@v2.93.0...v2.94.0> ### [`v2.93.0`](https://github.com/netbirdio/dashboard/releases/tag/v2.93.0) [Compare Source](netbirdio/dashboard@v2.92.0...v2.93.0) #### What's Changed - chore: remove dead code and unused dependencies by [@​heisbrot](https://github.com/heisbrot) in [#​787](netbirdio/dashboard#787) - Add rootless dashboard container image by [@​jnfrati](https://github.com/jnfrati) in [#​791](netbirdio/dashboard#791) - chore: update Next.js to 16.3.4 and refresh dependencies by [@​heisbrot](https://github.com/heisbrot) in [#​797](netbirdio/dashboard#797) - mark is cloud false unless explicitly overridden by [@​pascal-fischer](https://github.com/pascal-fischer) in [#​799](netbirdio/dashboard#799) - Restore the Active toggle and keep it visible on disabled rows by [@​SunsetDrifter](https://github.com/SunsetDrifter) in [#​803](netbirdio/dashboard#803) - Pick a private-capable proxy cluster when bootstrapping the agent network by [@​mlsmaycon](https://github.com/mlsmaycon) in [#​780](netbirdio/dashboard#780) - Fix group and resource order in resource group audit events by [@​SunsetDrifter](https://github.com/SunsetDrifter) in [#​804](netbirdio/dashboard#804) - fix: keep tables from scrolling horizontally when the Active toggle shows by [@​SunsetDrifter](https://github.com/SunsetDrifter) in [#​805](netbirdio/dashboard#805) - Add a deployment announcement banner set through NETBIRD\_ANNOUNCEMENT by [@​bison](https://github.com/bison) in [#​806](netbirdio/dashboard#806) - Replace the pending-approval error page with a dedicated screen by [@​heisbrot](https://github.com/heisbrot) in [#​802](netbirdio/dashboard#802) **Full Changelog**: <netbirdio/dashboard@v2.92.0...v2.93.0> </details> <details> <summary>netbirdio/netbird (netbirdio/netbird)</summary> ### [`v0.80.0`](netbirdio/netbird@v0.79.0...v0.80.0) [Compare Source](netbirdio/netbird@v0.79.0...v0.80.0) ### [`v0.79.0`](https://github.com/netbirdio/netbird/releases/tag/v0.79.0) [Compare Source](netbirdio/netbird@v0.78.2...v0.79.0) ##### Release Notes for v0.79.0 ##### New Feature: Desktop Light Mode The desktop app now gives you three appearance options: System, Light, and Dark. Follow your operating system's theme or choose the look you prefer. The new light theme covers the connection view, settings, profiles, and dialogs, with matching native window appearance on Windows, macOS, and Linux. [#​7344](netbirdio/netbird#7344) by [@​TechHutTV](https://github.com/TechHutTV) <img width="2022" height="1542" alt="image" src="https://github.com/user-attachments/assets/2081f103-f766-4f2d-92df-b304eaaa5acb" /> This release also brings shared MDM policy enforcement to the mobile SDKs, a rootless Red Hat UBI container image, and improvements to DNS, relay connections, and reverse proxy access controls. ##### What's Changed ##### Desktop Client Improvements - Fixed a Windows tray deadlock that could freeze the app when a double-click opened a window while another window was still being created. [#​7449](netbirdio/netbird#7449) by [@​pappz](https://github.com/pappz) - Fixed the MDM settings snapshot so the UI correctly reports when remote jobs are managed by policy. [#​7485](netbirdio/netbird#7485) by [@​pappz](https://github.com/pappz) ##### Client Improvements - Added MDM policy bridges and shared enforcement for the iOS and Android SDKs, keeping managed settings and profile restrictions consistent with the desktop client. [#​6435](netbirdio/netbird#6435) by [@​riccardomanfrin](https://github.com/riccardomanfrin) - Accepted MDM boolean values delivered as JSON numbers, so policies using `0` and `1` are applied correctly. [#​7471](netbirdio/netbird#7471) by [@​riccardomanfrin](https://github.com/riccardomanfrin) - Compared MDM-managed URLs by their normalized endpoints, avoiding false conflicts between equivalent URLs. [#​7472](netbirdio/netbird#7472) by [@​riccardomanfrin](https://github.com/riccardomanfrin) - Refreshed system information on every management sync reconnect, keeping local network addresses and posture information current after network changes. [#​7409](netbirdio/netbird#7409) by [@​pappz](https://github.com/pappz) - Used host prefixes for Android TUN addresses so local network protection does not classify the entire overlay as a local network. [#​7414](netbirdio/netbird#7414) by [@​pappz](https://github.com/pappz) - Replaced the eBPF DNS forwarder with UDP and TCP DNAT rules when the resolver cannot listen on port 53, with rollback and cleanup for incomplete redirects. [#​7439](netbirdio/netbird#7439) by [@​lixmal](https://github.com/lixmal) - Fixed a relay address race that could advertise a URL and IP from different connections during a reconnect. [#​7498](netbirdio/netbird#7498) by [@​pappz](https://github.com/pappz) - Returned the context cancellation or timeout error when an SSH handshake is interrupted. [#​7426](netbirdio/netbird#7426) by [@​pappz](https://github.com/pappz) - Updated wireguard-go to `8bf8fa968f1a`, fixing keepalive buffer-pool stalls, keeping timer paths non-blocking, and making netstack interface shutdown idempotent. [#​7532](netbirdio/netbird#7532) by [@​pappz](https://github.com/pappz) - Allowed buffer-pool limits to be adjusted while a device is stalled, and bounded proxy-wide updates so one stuck client does not block the others. [#​7452](netbirdio/netbird#7452) by [@​riccardomanfrin](https://github.com/riccardomanfrin) ##### Management Improvements - Restored networks using individual peers as routers in the SQLite network map when `peer_groups` is empty or null. [#​7418](netbirdio/netbird#7418) by [@​mlsmaycon](https://github.com/mlsmaycon) - Fixed SQLite network-map reads for users with null automatic groups and expanded coverage for empty and null router groups. [#​7425](netbirdio/netbird#7425) by [@​dmitri-netbird](https://github.com/dmitri-netbird) - Included offline peers when scheduling login expiration and ensured expired peers are disconnected, while protecting peers that have just logged in again. [#​7467](netbirdio/netbird#7467) by [@​pascal-fischer](https://github.com/pascal-fischer) - Applied duplicate-key sync protection to user-owned peers as well as peers registered with setup keys. [#​7427](netbirdio/netbird#7427) by [@​pascal-fischer](https://github.com/pascal-fischer) - Validated that a peer exists before adding it to a group. [#​7486](netbirdio/netbird#7486) by [@​pascal-fischer](https://github.com/pascal-fischer) - Prevented other users from deleting the account owner. [#​7456](netbirdio/netbird#7456) by [@​pascal-fischer](https://github.com/pascal-fischer) - Hardened OIDC issuer validation by requiring HTTPS, rejecting credentials, query strings, and fragments in issuer URLs, refusing discovery redirects, and limiting discovery response size. [#​7435](netbirdio/netbird#7435) by [@​bcmmbaga](https://github.com/bcmmbaga) - Cleaned up resources when WebSocket-to-gRPC proxy connections close. [#​7484](netbirdio/netbird#7484) by [@​dmitri-netbird](https://github.com/dmitri-netbird) ##### Agent Network - Added managed proxy provisioning endpoints and response types to the API specification. [#​7433](netbirdio/netbird#7433) by [@​bison](https://github.com/bison) - Prevented deletion of groups referenced by Agent Network budget rules, preserving the rules' spending limits. [#​7450](netbirdio/netbird#7450) by [@​Tyagiquamar](https://github.com/Tyagiquamar) ##### Reverse Proxy Improvements - Added `NB_PROXY_UPSTREAM_HTTP_VERSION` with `auto`, `1.1`, and `2` options. The default `auto` mode negotiates with HTTPS upstreams and falls back to HTTP/1.1 when an upstream's negotiated HTTP/2 connection fails at the protocol level. [#​7410](netbirdio/netbird#7410) by [@​lixmal](https://github.com/lixmal) - Enforced group access both when issuing session cookies and when accepting existing sessions. [#​7240](netbirdio/netbird#7240) by [@​lixmal](https://github.com/lixmal) - Required custom domain validation before creating a service or moving one to a different custom domain. [#​7341](netbirdio/netbird#7341) by [@​mlsmaycon](https://github.com/mlsmaycon) - Added a 48-hour validation window for custom domain registrations. Existing pending registrations receive a fresh window on upgrade; expired registrations are removed unless they still have services attached. [#​7497](netbirdio/netbird#7497) by [@​mlsmaycon](https://github.com/mlsmaycon) - Rejected unsupported direct-upstream IP addresses, including loopback, multicast, link-local, and IPv6 addresses with zone identifiers. [#​7400](netbirdio/netbird#7400) by [@​dmitri-netbird](https://github.com/dmitri-netbird) - Validated domain names before creating certificate lock files. [#​7501](netbirdio/netbird#7501) by [@​pascal-fischer](https://github.com/pascal-fischer) ##### Self-Hosting Improvements - Added a rootless Red Hat UBI image for AMD64 and ARM64, published with the `0.79.0-rootless-ubi` tag. [#​7469](netbirdio/netbird#7469) by [@​jnfrati](https://github.com/jnfrati) - Supported arbitrary non-root UIDs without a passwd entry, as used by OpenShift. [#​7440](netbirdio/netbird#7440) by [@​jnfrati](https://github.com/jnfrati) - Added RPM dependencies, license and documentation files, a generated changelog, and an example `/etc/sysconfig/netbird` to meet Red Hat software certification packaging requirements. [#​7562](netbirdio/netbird#7562), [#​7573](netbirdio/netbird#7573) by [@​mlsmaycon](https://github.com/mlsmaycon) - Kept deployments using the embedded identity provider on a single account, with stricter configuration checks and migration handling. [#​7380](netbirdio/netbird#7380) by [@​bcmmbaga](https://github.com/bcmmbaga) - Passed the combined server's TLS configuration through to the management listener. [#​7499](netbirdio/netbird#7499) by [@​pascal-fischer](https://github.com/pascal-fischer) - Updated peer connection-IP extraction to honor configured `TrustedPeers`, with `X-Forwarded-For` taking precedence over `X-Real-IP`. The final release preserves trust in all IPv4 and IPv6 sources when `TrustedPeers` is empty; configure your reverse proxy's address or network to restrict which sources can supply forwarded-IP headers. [#​7454](netbirdio/netbird#7454), [#​7589](netbirdio/netbird#7589) by [@​bcmmbaga](https://github.com/bcmmbaga); [#​7561](netbirdio/netbird#7561), [#​7577](netbirdio/netbird#7577) by [@​dmitri-netbird](https://github.com/dmitri-netbird) and [@​bcmmbaga](https://github.com/bcmmbaga) ##### Internal, CI, and Docs - Added atomic `SetNX` and `GetDel` cache operations. [#​7084](netbirdio/netbird#7084) by [@​bcmmbaga](https://github.com/bcmmbaga) - Allowed binaries embedding management to extend its command tree. [#​7483](netbirdio/netbird#7483) by [@​bison](https://github.com/bison) - Extracted peer update handling and added test coverage. [#​7338](netbirdio/netbird#7338) by [@​dmitri-netbird](https://github.com/dmitri-netbird) - Replaced a hard-coded temporary directory in WebSocket adapter tests with the platform's temporary directory. [#​7503](netbirdio/netbird#7503) by [@​dmitri-netbird](https://github.com/dmitri-netbird) - Switched the MinIO test image to `quay.io` after it became unavailable on Docker Hub. [#​7516](netbirdio/netbird#7516) by [@​Silex](https://github.com/Silex) - Preserved image variant suffixes in snapshot tags. [#​7511](netbirdio/netbird#7511) by [@​jnfrati](https://github.com/jnfrati) - Skipped the protobuf breaking-change check on branch-creation pushes, which have no previous commit to compare against. [#​7411](netbirdio/netbird#7411) by [@​bison](https://github.com/bison) *** **Full Changelog:** [v0.78.0...v0.79.0](netbirdio/netbird@v0.78.0...v0.79.0) ### [`v0.78.2`](https://github.com/netbirdio/netbird/releases/tag/v0.78.2) [Compare Source](netbirdio/netbird@v0.78.1...v0.78.2) ##### What's Changed - \[client] Fix the Windows tray deadlock on re-entrant window creation by [@​pappz](https://github.com/pappz) in [#​7537](netbirdio/netbird#7537) - \[client] Bump wireguard-go to [`8bf8fa9`](netbirdio/netbird@8bf8fa968f1a) by [@​pappz](https://github.com/pappz) in [#​7538](netbirdio/netbird#7538) **Full Changelog**: <netbirdio/netbird@v0.78.1...v0.78.2> </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://github.com/renovatebot/renovate/discussions) if that's undesired. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC43OS4yIiwidXBkYXRlZEluVmVyIjoiNDQuNzkuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==--> Reviewed-on: https://git.jfreudenberger.de/JuliusFreudenberger/nix-config/pulls/54
…ding (#282) The netbird submodule bump pulls in the mobile MDM bridge (netbirdio/netbird#6435), which changed two binding signatures the app calls: Android.newAuth now takes a PolicyFetcher, and Preferences.getPreSharedKey was replaced by hasPreSharedKey. Pass a null fetcher, which the Go side treats as MDM enforcement off, and switch the pre-shared key check to the new boolean getter. This is a temporary bridge until the Android MDM support in #278 wires the real fetcher through these call sites.
* Bump netbird submodule to the deadline-only session watcher The engine no longer arms expiry-warning timers on Android and the gomobile StateChangeListener drops OnSessionExpiring; the app schedules the warnings itself from the deadline. * Keep the scheduler tests from running the worker during schedule With the SynchronousExecutor a job with zero initial delay runs inside schedule(). The tests used a deadline 5 minutes out, so the T-10 job ran immediately: it marked the warning fired and finished, which broke workerSkipsOtherProfile and cancelAllKeepsFiredMarks and let the in-window test pass on the automatic run instead of its own call. Use a deadline an hour out where the test drives the worker itself, and let the late-warning test assert on the automatic run directly. * Bump netbird submodule to main with the deadline-only session watcher * Adapt the setup-key login and PSK check to the MDM-aware gomobile binding (#282) The netbird submodule bump pulls in the mobile MDM bridge (netbirdio/netbird#6435), which changed two binding signatures the app calls: Android.newAuth now takes a PolicyFetcher, and Preferences.getPreSharedKey was replaced by hasPreSharedKey. Pass a null fetcher, which the Go side treats as MDM enforcement off, and switch the pre-shared key check to the new boolean getter. This is a temporary bridge until the Android MDM support in #278 wires the real fetcher through these call sites.



Describe your changes
Adds the iOS and Android gomobile-facing MDM bridge on the daemon side.
Each native
Clientnow carries an*mdm.Loaderset viaSetMDMPolicyFetcher;applyMDMOverlay(cfg)runs the resolvedmdm.Policyon top of the loaded Config at every Run / login /DebugBundle call site — mirroring how the desktop loader plugs into
Config.apply. Nil loader is a no-op: until the Swift or Kotlin side
registers a
PolicyFetcher, the code is dormant and behavior isbyte-identical to pre-PR. Merging this without the app-side wiring is
safe.
Issue ticket number and link
Part of NET-1214 (MDM configuration profile support). Companion Swift /
Kotlin work lives in the
netbirdio/ios-clientandnetbirdio/android-clientrepos, on their respective
mdm_integrationbranches; they bump thenetbird-coresubmodule to this PR's merge commit to activate the bridge.Stack
Checklist
Documentation
Select exactly one:
Internal SDK plumbing: the public MDM key surface, key semantics, and
mdm-integration.mdxreference all remain as shipped with the desktopMDM feature. The native-side calling convention is documented in the
Go and Swift/Kotlin docstrings themselves.
Docs PR URL (required if "docs added" is checked)
Paste the PR link from https://github.com/netbirdio/docs here:
N/A