[client] Do not refuse a caller's upload URL that an MDM policy overrides anyway

The privilege gate ran on the URL the caller named before the resolver applied
the MDM policy, so on a managed device an unprivileged `netbird debug bundle -U
--upload-bundle-url <host>` was refused with "requires root" — even though the
policy would have discarded that URL and uploaded to the pinned destination.
Gating a value that has no effect only turns a working bundle into a denial.

Read the policy before the gate and tell the gate the destination is pinned, so
it skips the caller-URL branch. --upload-bundle-insecure stays gated either way:
relaxing TLS towards the pinned host is a real weakening, and that one is the
caller's doing.

Reported by cubic on #7514.
This commit is contained in:
riccardom
2026-09-29 14:29:27 +02:00
parent 5fb4cdc2f7
commit d5a9f5b0ff
3 changed files with 48 additions and 20 deletions
+16 -9
View File
@@ -108,13 +108,14 @@ func TestUILogOpenerBindsToRequester(t *testing.T) {
func TestRequirePrivilegeForUploadURL(t *testing.T) {
tests := []struct {
name string
url string
insecure bool
noUpload bool
unprivOK bool
invalid bool
rootAlso bool
name string
url string
insecure bool
noUpload bool
mdmPinned bool
unprivOK bool
invalid bool
rootAlso bool
}{
{name: "no upload", url: "", unprivOK: true},
// An empty URL resolves to the destination management published, so
@@ -126,6 +127,12 @@ func TestRequirePrivilegeForUploadURL(t *testing.T) {
{name: "default service, other path", url: "https://upload.debug.netbird.io/other", unprivOK: true},
{name: "loopback exfiltration endpoint", url: "https://127.0.0.1:8080/upload-url", rootAlso: true},
{name: "custom upload service", url: "https://attacker.example/upload-url", rootAlso: true},
// With MDM pinning the destination the caller's URL is discarded before
// the upload, so refusing it would only turn a bundle that was going to
// the pinned host anyway into a denial.
{name: "custom URL ignored when MDM pins the destination", url: "https://attacker.example/upload-url", mdmPinned: true, unprivOK: true},
// Transport security is still the caller's to weaken, pinned or not.
{name: "insecure still gated when MDM pins the destination", url: "https://attacker.example/upload-url", insecure: true, mdmPinned: true, rootAlso: true},
{name: "plaintext default host", url: "http://upload.debug.netbird.io/upload-url", invalid: true},
{name: "plaintext custom host", url: "http://attacker.example/upload-url", invalid: true},
{name: "unsupported scheme", url: "file:///etc/shadow", invalid: true},
@@ -140,7 +147,7 @@ func TestRequirePrivilegeForUploadURL(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
err := requirePrivilegeForUploadURL(userCtx(), tc.url, tc.insecure, !tc.noUpload)
err := requirePrivilegeForUploadURL(userCtx(), tc.url, tc.insecure, !tc.noUpload, tc.mdmPinned)
switch {
case tc.invalid:
@@ -156,7 +163,7 @@ func TestRequirePrivilegeForUploadURL(t *testing.T) {
}
if tc.rootAlso {
assertAllowed(t, requirePrivilegeForUploadURL(rootCtx(), tc.url, tc.insecure, !tc.noUpload))
assertAllowed(t, requirePrivilegeForUploadURL(rootCtx(), tc.url, tc.insecure, !tc.noUpload, tc.mdmPinned))
}
})
}