Skip to content

[client] Restrict debug bundle log path and upload destinations - #6975

Merged
lixmal merged 1 commit into
mainfrom
debug-bundle-hardening
Jul 31, 2026
Merged

[client] Restrict debug bundle log path and upload destinations#6975
lixmal merged 1 commit into
mainfrom
debug-bundle-hardening

Conversation

@lixmal

@lixmal lixmal commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Describe your changes

Tightens how the daemon handles debug-bundle inputs that come over the local control socket. The UI log path is now validated and opened on behalf of the caller that registered it, and upload destinations are constrained so the root daemon does not fetch or send bundles to arbitrary endpoints.

  • Validate the registered UI log path (must be an absolute gui-client.log) and open it as the registering caller, skipping files it does not own
  • Require the debug-bundle upload URL to be https and, for anything other than the default upload service, restrict it to privileged callers
  • Add --upload-bundle-insecure for self-hosted upload servers on http or with untrusted TLS (privileged only)
  • Surface the upload refusal through the same privilege-guidance path the SSH gates use

Issue ticket number and link

Stack

Checklist

  • Is it a bug fix
  • Is a typo/documentation fix
  • Is a feature enhancement
  • It is a refactor
  • Created tests that fail without the change (if possible)
  • This change does not modify the public API, gRPC protocols, functionality behavior, CLI / service flags, or introduce a new feature — OR I have discussed it with the NetBird team beforehand (link the issue / Slack thread in the description). See CONTRIBUTING.md.

By submitting this pull request, you confirm that you have read and agree to the terms of the Contributor License Agreement.

Documentation

Select exactly one:

  • I added/updated documentation for this change
  • Documentation is not needed for this change (explain why)

Docs PR URL (required if "docs added" is checked)

Paste the PR link from https://github.com/netbirdio/docs here:

netbirdio/docs#894

Summary by CodeRabbit

  • New Features

    • Added --upload-bundle-insecure for privileged, opt-in uploads to non-HTTPS destinations.
    • UI logs are included in debug bundles only when requester access is authorized.
  • Bug Fixes

    • Hardened upload validation with HTTPS enforcement, redirect protection, and destination permissions.
    • Improved debug bundle generation, upload handling, and UI log file safety across supported platforms.
  • Tests

    • Added coverage for secure uploads, UI log access, destination authorization, and cross-platform file safety.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d297fb27-d6e1-4940-93d2-945440d39b66

📥 Commits

Reviewing files that changed from the base of the PR and between e223595 and 9f36909.

⛔ Files ignored due to path filters (1)
  • client/proto/daemon.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (23)
  • client/android/client.go
  • client/cmd/debug.go
  • client/configs/configs.go
  • client/internal/debug/debug.go
  • client/internal/debug/debug_ios.go
  • client/internal/debug/debug_logfiles_test.go
  • client/internal/debug/uilog_test.go
  • client/internal/debug/upload.go
  • client/internal/debug/upload_test.go
  • client/internal/ipcauth/ownedfile.go
  • client/internal/ipcauth/ownedfile_test.go
  • client/internal/ipcauth/ownedfile_unix.go
  • client/internal/ipcauth/ownedfile_unix_test.go
  • client/internal/ipcauth/ownedfile_windows.go
  • client/internal/ipcauth/ownedfile_windows_test.go
  • client/ios/NetBirdSDK/client.go
  • client/jobexec/executor.go
  • client/proto/daemon.proto
  • client/server/debug.go
  • client/server/debug_gate.go
  • client/server/debug_gate_test.go
  • client/server/server.go
  • client/ui/uilogpath.go
🚧 Files skipped from review as they are similar to previous changes (18)
  • client/ui/uilogpath.go
  • client/android/client.go
  • client/internal/ipcauth/ownedfile_unix.go
  • client/internal/ipcauth/ownedfile.go
  • client/configs/configs.go
  • client/jobexec/executor.go
  • client/server/server.go
  • client/internal/debug/uilog_test.go
  • client/internal/debug/debug_ios.go
  • client/proto/daemon.proto
  • client/internal/ipcauth/ownedfile_unix_test.go
  • client/server/debug_gate.go
  • client/ios/NetBirdSDK/client.go
  • client/internal/debug/upload_test.go
  • client/server/debug.go
  • client/internal/debug/debug_logfiles_test.go
  • client/cmd/debug.go
  • client/internal/debug/debug.go

📝 Walkthrough

Walkthrough

Debug bundle handling now validates upload destinations and transport security, restricts UI log registration and access by caller identity, adds ownership-checked file opening, and propagates insecure-upload selection through CLI, daemon, platform, and job-execution paths.

Changes

Debug bundle security

Layer / File(s) Summary
Owned file access
client/internal/ipcauth/*
Adds cross-platform regular-file opening with caller ownership checks, symlink and FIFO protections, and tests.
Ownership-aware UI log collection
client/configs/configs.go, client/internal/debug/*, client/server/debug.go, client/server/debug_gate.go, client/server/server.go, client/ui/uilogpath.go
Threads caller-provided UI log openers through bundle generation and validates registered UI log paths and owners.
Secure upload validation and transport
client/proto/daemon.proto, client/internal/debug/upload.go, client/internal/debug/upload_test.go, client/server/debug.go, client/server/debug_gate.go
Adds the insecure-upload request option, HTTPS validation, selectable TLS behavior, and upload URL privilege checks with tests.
CLI and client propagation
client/cmd/debug.go, client/android/client.go, client/ios/NetBirdSDK/client.go, client/jobexec/executor.go
Adds --upload-bundle-insecure, propagates it through both debug commands, and updates upload call sites for the expanded API.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant DebugClient
  participant DebugBundleRPC
  participant BundleGenerator
  participant UploadDebugBundle
  participant UploadService
  DebugClient->>DebugBundleRPC: submit DebugBundleRequest
  DebugBundleRPC->>BundleGenerator: generate bundle with caller UI log opener
  BundleGenerator-->>DebugBundleRPC: bundle path and management URL
  DebugBundleRPC->>UploadDebugBundle: upload with secure or insecure mode
  UploadDebugBundle->>UploadService: request upload URL and upload bundle
  UploadService-->>DebugBundleRPC: upload key
  DebugBundleRPC-->>DebugClient: debug bundle response
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the changes and tests, but it omits the required issue or agreement link for behavior, gRPC, and CLI changes. Add the approved issue or discussion link and correct the checklist to acknowledge the gRPC, functionality, and CLI flag changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 46.81% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes to restrict debug-bundle log paths and upload destinations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch debug-bundle-hardening

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/server/debug_gate.go`:
- Around line 57-60: Update the insecure upload guidance in denyPrivileged and
avoid concatenating rawURL directly into the ElevatedCommand string. Build the
command using separate arguments or apply the project’s established
shell-quoting utility to rawURL, while preserving the existing command and flag
behavior.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 710f34c0-e690-466c-a96b-9a419b5deb3e

📥 Commits

Reviewing files that changed from the base of the PR and between 1bf54dd and d774f50.

⛔ Files ignored due to path filters (1)
  • client/proto/daemon.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (21)
  • client/android/client.go
  • client/cmd/debug.go
  • client/internal/debug/debug.go
  • client/internal/debug/debug_ios.go
  • client/internal/debug/debug_logfiles_test.go
  • client/internal/debug/uilog_test.go
  • client/internal/debug/upload.go
  • client/internal/debug/upload_test.go
  • client/internal/ipcauth/ownedfile.go
  • client/internal/ipcauth/ownedfile_test.go
  • client/internal/ipcauth/ownedfile_unix.go
  • client/internal/ipcauth/ownedfile_unix_test.go
  • client/internal/ipcauth/ownedfile_windows.go
  • client/internal/ipcauth/ownedfile_windows_test.go
  • client/ios/NetBirdSDK/client.go
  • client/jobexec/executor.go
  • client/proto/daemon.proto
  • client/server/debug.go
  • client/server/debug_gate.go
  • client/server/debug_gate_test.go
  • client/server/server.go

Comment thread client/server/debug_gate.go Outdated
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown

Release artifacts

Built for PR head 9f36909 in workflow run #17355.

Artifact Link
All release artifacts Download
Linux packages Download
Windows packages Download
macOS packages Download
UI artifacts Download
UI macOS artifacts Download

GHCR images (amd64)

This comment is updated by the Release workflow. Artifact links expire according to the workflow retention policy.

@lixmal
lixmal force-pushed the debug-bundle-hardening branch from d774f50 to 8fe6af7 Compare July 30, 2026 10:37

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 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/internal/debug/upload.go`:
- Around line 36-48: Update uploadClient so secure mode configures CheckRedirect
to reject any redirect target whose scheme is not https, while preserving normal
HTTPS redirects and the existing insecure-client behavior. Ensure both
presigned-URL GET and bundle PUT, which use uploadClient(false), apply this
protection.

In `@client/server/debug.go`:
- Line 79: Update the bundle construction flow around UILogOpener and
uiLogOpener so UI-log collection is authorized against the current bundle
requester rather than always uiLogOwner; permit it only when the requester
matches uiLogOwner or is privileged, otherwise omit the opener. Add a regression
test covering a second local user requesting a default/no-upload bundle and
verifying the first user’s UI log is not collected or uploaded.
- Around line 163-165: Update the UI log path validation around filepath.Clean,
filepath.IsAbs, and filepath.Base to reject Windows UNC paths and \\.\ or \\?\
device prefixes before RegisterUILog can invoke OpenOwnedFile. Preserve
acceptance of valid absolute paths ending in uiLogFileName, and add
Windows-specific tests covering each rejected path form.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7a06a226-d6e1-4c73-b1f9-a50806b41ddb

📥 Commits

Reviewing files that changed from the base of the PR and between d774f50 and 8fe6af7.

⛔ Files ignored due to path filters (1)
  • client/proto/daemon.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (21)
  • client/android/client.go
  • client/cmd/debug.go
  • client/internal/debug/debug.go
  • client/internal/debug/debug_ios.go
  • client/internal/debug/debug_logfiles_test.go
  • client/internal/debug/uilog_test.go
  • client/internal/debug/upload.go
  • client/internal/debug/upload_test.go
  • client/internal/ipcauth/ownedfile.go
  • client/internal/ipcauth/ownedfile_test.go
  • client/internal/ipcauth/ownedfile_unix.go
  • client/internal/ipcauth/ownedfile_unix_test.go
  • client/internal/ipcauth/ownedfile_windows.go
  • client/internal/ipcauth/ownedfile_windows_test.go
  • client/ios/NetBirdSDK/client.go
  • client/jobexec/executor.go
  • client/proto/daemon.proto
  • client/server/debug.go
  • client/server/debug_gate.go
  • client/server/debug_gate_test.go
  • client/server/server.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • client/internal/debug/upload_test.go

Comment thread client/internal/debug/upload.go
Comment thread client/server/debug.go Outdated
Comment thread client/server/debug.go
@lixmal
lixmal force-pushed the debug-bundle-hardening branch from 8fe6af7 to 94db225 Compare July 30, 2026 10:54

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
client/server/debug.go (2)

163-166: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

UNC paths still accepted by RegisterUILog.

filepath.IsAbs treats Windows UNC paths (\\attacker\share\gui-client.log) and device prefixes (\\.\..., \\?\...) as absolute, so they pass this check and reach OpenOwnedFile before the regular-file/ownership guard runs, letting a local caller point the root daemon at a remote or device namespace.

🐛 Proposed fix
 	path := filepath.Clean(req.GetPath())
-	if !filepath.IsAbs(path) || filepath.Base(path) != uiLogFileName {
+	if !filepath.IsAbs(path) || filepath.Base(path) != uiLogFileName || strings.HasPrefix(path, `\\`) {
 		return nil, gstatus.Errorf(codes.InvalidArgument, "UI log path must be an absolute path ending in %s", uiLogFileName)
 	}

Add Windows-specific test coverage for UNC and device-prefix paths.

🤖 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/server/debug.go` around lines 163 - 166, Update RegisterUILog’s path
validation to reject Windows UNC and device-prefix paths before OpenOwnedFile,
while retaining acceptance of valid local absolute paths ending in
uiLogFileName. Add Windows-specific tests covering both UNC forms and
device-prefix paths, including assertions that they return InvalidArgument.

72-92: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

UI-log bundle still not bound to the requesting caller.

UILogOpener: uiLogOpener(s.uiLogOwner) always authorizes with the registering identity, regardless of who is calling DebugBundle now. A second local user requesting a bundle (no upload URL needed, so no privilege check applies) still causes the daemon to collect the first user's registered UI log, since uiLogOpener only checks that the file belongs to s.uiLogOwner, never that the current callerCtx matches it.

🐛 Proposed fix
 func (s *Server) DebugBundle(callerCtx context.Context, req *proto.DebugBundleRequest) (resp *proto.DebugBundleResponse, err error) {
 	if err := requirePrivilegeForUploadURL(callerCtx, req.GetUploadURL(), req.GetUploadInsecure()); err != nil {
 		return nil, err
 	}
 
+	callerID, hasCaller := ipcauth.CallerIdentity(callerCtx)
+
 	s.mutex.Lock()
 	defer s.mutex.Unlock()
+
+	var uiLogOwner *ipcauth.Identity
+	if hasCaller && s.uiLogOwner != nil &&
+		(ipcauth.IsPrivilegedCaller(callerID) || callerID.String() == s.uiLogOwner.String()) {
+		uiLogOwner = s.uiLogOwner
+	}
-			UILogOpener:    uiLogOpener(s.uiLogOwner),
+			UILogOpener:    uiLogOpener(uiLogOwner),

Also add a regression test covering a second caller requesting a bundle after a different caller registered a UI log path.

🤖 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/server/debug.go` around lines 72 - 92, Bind UI-log authorization to
the requesting caller by updating the DebugBundle flow and uiLogOpener usage so
the opener validates callerCtx against the current caller rather than only
s.uiLogOwner. Preserve collection for the registering caller while preventing a
different local caller from accessing that UI log, and add a regression test
where one caller registers the path and another requests a bundle.
🤖 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/server/debug_gate.go`:
- Around line 57-70: Update the upload URL validation flow around parsed and
rawURL so url.Parse, non-empty host validation, and http/https scheme validation
occur before the insecure denyPrivileged branch. Allow the insecure override to
relax only HTTPS enforcement for HTTP or untrusted TLS URLs, while rejecting
file, mailto, hostless, and other unsupported schemes with InvalidArgument.

---

Duplicate comments:
In `@client/server/debug.go`:
- Around line 163-166: Update RegisterUILog’s path validation to reject Windows
UNC and device-prefix paths before OpenOwnedFile, while retaining acceptance of
valid local absolute paths ending in uiLogFileName. Add Windows-specific tests
covering both UNC forms and device-prefix paths, including assertions that they
return InvalidArgument.
- Around line 72-92: Bind UI-log authorization to the requesting caller by
updating the DebugBundle flow and uiLogOpener usage so the opener validates
callerCtx against the current caller rather than only s.uiLogOwner. Preserve
collection for the registering caller while preventing a different local caller
from accessing that UI log, and add a regression test where one caller registers
the path and another requests a bundle.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6284c0fd-9e9d-49dc-971d-dc99acc5d60e

📥 Commits

Reviewing files that changed from the base of the PR and between 8fe6af7 and 94db225.

⛔ Files ignored due to path filters (1)
  • client/proto/daemon.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (21)
  • client/android/client.go
  • client/cmd/debug.go
  • client/internal/debug/debug.go
  • client/internal/debug/debug_ios.go
  • client/internal/debug/debug_logfiles_test.go
  • client/internal/debug/uilog_test.go
  • client/internal/debug/upload.go
  • client/internal/debug/upload_test.go
  • client/internal/ipcauth/ownedfile.go
  • client/internal/ipcauth/ownedfile_test.go
  • client/internal/ipcauth/ownedfile_unix.go
  • client/internal/ipcauth/ownedfile_unix_test.go
  • client/internal/ipcauth/ownedfile_windows.go
  • client/internal/ipcauth/ownedfile_windows_test.go
  • client/ios/NetBirdSDK/client.go
  • client/jobexec/executor.go
  • client/proto/daemon.proto
  • client/server/debug.go
  • client/server/debug_gate.go
  • client/server/debug_gate_test.go
  • client/server/server.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • client/internal/debug/upload_test.go
  • client/proto/daemon.proto

Comment thread client/server/debug_gate.go
@lixmal
lixmal force-pushed the debug-bundle-hardening branch from 94db225 to b01c113 Compare July 30, 2026 11:10

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/internal/ipcauth/ownedfile_windows.go`:
- Around line 24-32: Release the security-descriptor buffer returned by
GetSecurityInfo with deferred windows.LocalFree immediately after each
successful call; add this cleanup in
client/internal/ipcauth/ownedfile_windows.go:24-32 and the test helper in
client/internal/ipcauth/ownedfile_windows_test.go:22-26, using the sd pointer
converted to windows.Handle while preserving sd.Owner() usage before function
return.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 88b52a7c-8e03-4c92-bdaa-b55051daac2c

📥 Commits

Reviewing files that changed from the base of the PR and between 94db225 and b01c113.

⛔ Files ignored due to path filters (1)
  • client/proto/daemon.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (21)
  • client/android/client.go
  • client/cmd/debug.go
  • client/internal/debug/debug.go
  • client/internal/debug/debug_ios.go
  • client/internal/debug/debug_logfiles_test.go
  • client/internal/debug/uilog_test.go
  • client/internal/debug/upload.go
  • client/internal/debug/upload_test.go
  • client/internal/ipcauth/ownedfile.go
  • client/internal/ipcauth/ownedfile_test.go
  • client/internal/ipcauth/ownedfile_unix.go
  • client/internal/ipcauth/ownedfile_unix_test.go
  • client/internal/ipcauth/ownedfile_windows.go
  • client/internal/ipcauth/ownedfile_windows_test.go
  • client/ios/NetBirdSDK/client.go
  • client/jobexec/executor.go
  • client/proto/daemon.proto
  • client/server/debug.go
  • client/server/debug_gate.go
  • client/server/debug_gate_test.go
  • client/server/server.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • client/jobexec/executor.go
  • client/proto/daemon.proto

Comment thread client/internal/ipcauth/ownedfile_windows.go
@lixmal
lixmal force-pushed the debug-bundle-hardening branch from b01c113 to 051d656 Compare July 30, 2026 11:52

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
client/server/debug.go (2)

78-79: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

UI-log collection still authorized by registering identity, not the current requester.

UILogOpener: uiLogOpener(s.uiLogOwner) opens the UI log as whoever last called RegisterUILog, regardless of who is calling DebugBundle now (callerCtx isn't consulted here). A second local user requesting a bundle can still cause the daemon to collect (and, with an upload URL, exfiltrate) the first user's UI log.

This was already raised on a previous commit of this PR and appears unresolved in the current code.

🤖 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/server/debug.go` around lines 78 - 79, Update the DebugBundle setup
around UILogOpener and uiLogOpener so UI-log collection is authorized using the
current requester’s callerCtx rather than the registered s.uiLogOwner identity.
Ensure a requester cannot collect or upload another user’s UI log, while
preserving the existing behavior for authorized callers.

163-166: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

UNC paths still bypass the absolute-path check in RegisterUILog.

filepath.IsAbs treats Windows UNC paths (e.g. \\attacker\share\gui-client.log) as absolute, so they pass this check before being opened via OpenOwnedFile, letting a local caller point the privileged daemon at a remote/device namespace. Block \\...\ and \\.\/\\?\ prefixes here.

Raised previously on this PR; the code is unchanged since then.

🤖 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/server/debug.go` around lines 163 - 166, Update RegisterUILog’s path
validation after filepath.Clean to reject Windows UNC and device namespace
prefixes (\\...\, \\.\, and \\?\) before allowing the existing absolute-path and
basename checks. Preserve acceptance of valid local absolute paths ending in
uiLogFileName and the current InvalidArgument response.
🧹 Nitpick comments (1)
client/internal/debug/upload_test.go (1)

19-54: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Consider adding negative-path coverage for HTTPS enforcement.

This file only exercises the insecure=true path. Nothing here verifies that insecure=false rejects an http:// service URL, an http:// presigned response URL, or a redirect to a non-https target (requireHTTPS/rejectInsecureRedirect in client/internal/debug/upload.go). Since these are the core security guarantees this PR introduces, a couple of focused unit tests would meaningfully increase confidence without much effort.

🤖 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/debug/upload_test.go` around lines 19 - 54, Extend the upload
tests around TestUpload and the HTTPS validation helpers in upload.go to cover
insecure=false rejecting an http service URL, an http presigned response URL,
and redirects to non-HTTPS targets. Add focused negative-path tests that assert
each request fails with the expected HTTPS enforcement error, while preserving
the existing insecure=true success coverage.
🤖 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/internal/ipcauth/ownedfile_windows.go`:
- Around line 12-17: Update openForRead in
client/internal/ipcauth/ownedfile_windows.go:12-17 to validate the opened
descriptor using its returned handle’s Stat metadata, rejecting non-regular
files instead of resolving and checking the caller-supplied path. In
client/internal/debug/debug.go:1144-1186, sort rotated log candidates using Stat
results from handles returned by the supplied opener, not path-based os.Stat;
apply the regular-file validation to those descriptor-derived results.

---

Duplicate comments:
In `@client/server/debug.go`:
- Around line 78-79: Update the DebugBundle setup around UILogOpener and
uiLogOpener so UI-log collection is authorized using the current requester’s
callerCtx rather than the registered s.uiLogOwner identity. Ensure a requester
cannot collect or upload another user’s UI log, while preserving the existing
behavior for authorized callers.
- Around line 163-166: Update RegisterUILog’s path validation after
filepath.Clean to reject Windows UNC and device namespace prefixes (\\...\,
\\.\, and \\?\) before allowing the existing absolute-path and basename checks.
Preserve acceptance of valid local absolute paths ending in uiLogFileName and
the current InvalidArgument response.

---

Nitpick comments:
In `@client/internal/debug/upload_test.go`:
- Around line 19-54: Extend the upload tests around TestUpload and the HTTPS
validation helpers in upload.go to cover insecure=false rejecting an http
service URL, an http presigned response URL, and redirects to non-HTTPS targets.
Add focused negative-path tests that assert each request fails with the expected
HTTPS enforcement error, while preserving the existing insecure=true success
coverage.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6add1e9b-f55c-4b8a-8160-7deacb5de9b2

📥 Commits

Reviewing files that changed from the base of the PR and between b01c113 and 051d656.

⛔ Files ignored due to path filters (1)
  • client/proto/daemon.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (21)
  • client/android/client.go
  • client/cmd/debug.go
  • client/internal/debug/debug.go
  • client/internal/debug/debug_ios.go
  • client/internal/debug/debug_logfiles_test.go
  • client/internal/debug/uilog_test.go
  • client/internal/debug/upload.go
  • client/internal/debug/upload_test.go
  • client/internal/ipcauth/ownedfile.go
  • client/internal/ipcauth/ownedfile_test.go
  • client/internal/ipcauth/ownedfile_unix.go
  • client/internal/ipcauth/ownedfile_unix_test.go
  • client/internal/ipcauth/ownedfile_windows.go
  • client/internal/ipcauth/ownedfile_windows_test.go
  • client/ios/NetBirdSDK/client.go
  • client/jobexec/executor.go
  • client/proto/daemon.proto
  • client/server/debug.go
  • client/server/debug_gate.go
  • client/server/debug_gate_test.go
  • client/server/server.go

Comment thread client/internal/ipcauth/ownedfile_windows.go Outdated
@lixmal
lixmal force-pushed the debug-bundle-hardening branch 2 times, most recently from 8dac459 to 734ff2e Compare July 30, 2026 13:37

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
client/server/debug_gate.go (1)

20-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Export the UI log filename constant instead of duplicating it.

uiLogFileName duplicates debug.uiLogFile ("gui-client.log") in a separate package. The comment itself notes it "must stay in sync"; since debug.uiLogFile is unexported, nothing enforces that today; a future edit to one literal without the other would silently break UI log registration/validation or rotated-log discovery.

♻️ Proposed fix
-// uiLogFileName is the only file name the daemon accepts as a UI log path. It
-// must stay in sync with the name the desktop UI writes (client/ui/uilogpath.go)
-// and with the prefix the bundle globs for rotated siblings.
-const uiLogFileName = "gui-client.log"
+// uiLogFileName is the only file name the daemon accepts as a UI log path.
+// It must stay in sync with the name the desktop UI writes
+// (client/ui/uilogpath.go).
+const uiLogFileName = debug.UILogFile

And in client/internal/debug/debug.go, export the constant:

-	uiLogFile     = "gui-client.log"
+	UILogFile     = "gui-client.log"

(update the package-local uses of uiLogFile to UILogFile accordingly)

🤖 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/server/debug_gate.go` around lines 20 - 23, Export the debug package’s
uiLogFile constant as UILogFile, update all package-local references
accordingly, and replace the duplicate uiLogFileName declaration in the daemon
code with the exported constant. Preserve the existing filename and
validation/globbing behavior while ensuring both packages use the single shared
symbol.
client/server/debug_gate_test.go (1)

81-96: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a positive case: the legitimate owner (or a privileged caller) can open the file.

Both assertions here are refusals. Nothing confirms uiLogOpener actually succeeds for a requester who owns the file, so a future regression that over-tightens the check would slip through.

✅ Suggested addition
 	// No verified identity: refused.
 	if _, err := uiLogOpener(ipcauth.Identity{}, false)(path); err == nil {
 		t.Fatal("expected an unidentified requester to be refused")
 	}
+
+	// The file's actual owner: allowed.
+	if _, err := uiLogOpener(ownerIdentity(), true)(path); err != nil {
+		t.Fatalf("expected the owning requester to be allowed, got %v", err)
+	}
 }
🤖 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/server/debug_gate_test.go` around lines 81 - 96, Add a
successful-opening assertion to TestUILogOpenerBindsToRequester using the file
owner identity or a privileged caller, and verify uiLogOpener returns the file
without an error. Keep the existing refusal assertions for an unprivileged
different user and an unverified identity.
🤖 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/server/debug.go`:
- Around line 36-37: Bound the network work in the debug-bundle upload path
before acquiring s.mutex: ensure UploadDebugBundle receives a context with a
finite deadline, matching the mobile callers’ 2-minute timeout, so presigned-url
discovery and PUT cannot hold the lock indefinitely. Update the relevant debug
handler around UploadDebugBundle while preserving the existing mutex-protected
state handling.

---

Nitpick comments:
In `@client/server/debug_gate_test.go`:
- Around line 81-96: Add a successful-opening assertion to
TestUILogOpenerBindsToRequester using the file owner identity or a privileged
caller, and verify uiLogOpener returns the file without an error. Keep the
existing refusal assertions for an unprivileged different user and an unverified
identity.

In `@client/server/debug_gate.go`:
- Around line 20-23: Export the debug package’s uiLogFile constant as UILogFile,
update all package-local references accordingly, and replace the duplicate
uiLogFileName declaration in the daemon code with the exported constant.
Preserve the existing filename and validation/globbing behavior while ensuring
both packages use the single shared symbol.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3f26a4c0-917e-49a2-aecc-6c04f1fe0d21

📥 Commits

Reviewing files that changed from the base of the PR and between b01c113 and 734ff2e.

⛔ Files ignored due to path filters (1)
  • client/proto/daemon.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (21)
  • client/android/client.go
  • client/cmd/debug.go
  • client/internal/debug/debug.go
  • client/internal/debug/debug_ios.go
  • client/internal/debug/debug_logfiles_test.go
  • client/internal/debug/uilog_test.go
  • client/internal/debug/upload.go
  • client/internal/debug/upload_test.go
  • client/internal/ipcauth/ownedfile.go
  • client/internal/ipcauth/ownedfile_test.go
  • client/internal/ipcauth/ownedfile_unix.go
  • client/internal/ipcauth/ownedfile_unix_test.go
  • client/internal/ipcauth/ownedfile_windows.go
  • client/internal/ipcauth/ownedfile_windows_test.go
  • client/ios/NetBirdSDK/client.go
  • client/jobexec/executor.go
  • client/proto/daemon.proto
  • client/server/debug.go
  • client/server/debug_gate.go
  • client/server/debug_gate_test.go
  • client/server/server.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • client/internal/debug/debug_ios.go
  • client/server/server.go

Comment thread client/server/debug.go
@lixmal
lixmal force-pushed the debug-bundle-hardening branch from 734ff2e to e324a96 Compare July 30, 2026 14:17

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
client/internal/debug/upload_test.go (1)

19-53: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Cover the secure-mode rejection path.

This test only verifies insecure=true. Add a companion assertion that UploadDebugBundle(..., false) rejects the HTTP service URL, preventing an accidental weakening of the secure default.

🤖 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/debug/upload_test.go` around lines 19 - 53, The TestUpload
test only exercises the insecure upload path; add a companion call to
UploadDebugBundle with insecure set to false against the HTTP testURL and assert
that it returns an error, while preserving the existing successful upload
assertions.
🤖 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.

Nitpick comments:
In `@client/internal/debug/upload_test.go`:
- Around line 19-53: The TestUpload test only exercises the insecure upload
path; add a companion call to UploadDebugBundle with insecure set to false
against the HTTP testURL and assert that it returns an error, while preserving
the existing successful upload assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 852e5f51-f184-4df2-8989-c190d104fd1d

📥 Commits

Reviewing files that changed from the base of the PR and between 734ff2e and e324a96.

⛔ Files ignored due to path filters (1)
  • client/proto/daemon.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (21)
  • client/android/client.go
  • client/cmd/debug.go
  • client/internal/debug/debug.go
  • client/internal/debug/debug_ios.go
  • client/internal/debug/debug_logfiles_test.go
  • client/internal/debug/uilog_test.go
  • client/internal/debug/upload.go
  • client/internal/debug/upload_test.go
  • client/internal/ipcauth/ownedfile.go
  • client/internal/ipcauth/ownedfile_test.go
  • client/internal/ipcauth/ownedfile_unix.go
  • client/internal/ipcauth/ownedfile_unix_test.go
  • client/internal/ipcauth/ownedfile_windows.go
  • client/internal/ipcauth/ownedfile_windows_test.go
  • client/ios/NetBirdSDK/client.go
  • client/jobexec/executor.go
  • client/proto/daemon.proto
  • client/server/debug.go
  • client/server/debug_gate.go
  • client/server/debug_gate_test.go
  • client/server/server.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • client/server/server.go

@riccardomanfrin riccardomanfrin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. gui-client.log is defined in three places.** debug.UILogFile in
    client/internal/debug/debug.go:233, the uiLogFileName alias in
    client/server/debug_gate.go:23
  2. LogOpener is threaded through four methods but only one call site needs
    a non-default opener. maybe can be resolved in place where needed and have reduced signature args.
  3. getUploadURL builds the query by string concatenation.


err = upload(ctx, filePath, response)
if !insecure {
if err := requireHTTPS("upload URL from service", response.URL); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should apply the other remediations of GHSA-7rcm (prefix/host match, reject
RFC1918, signed URL)

Comment thread client/server/debug.go
Comment thread client/internal/debug/upload_test.go
Comment thread client/server/debug.go
@lixmal
lixmal force-pushed the debug-bundle-hardening branch 2 times, most recently from 3175d59 to e223595 Compare July 31, 2026 14:50

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
client/internal/ipcauth/ownedfile_test.go (1)

46-56: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Safely reject symlinks in OpenOwnedFile.

openForRead already uses O_NOFOLLOW on Unix, and TestOpenOwnedFileRefusesSymlink covers a symlink owned by the caller that points to another user’s file. Add or keep a comparable Windows coverage so this platform path has the same protection instead of using plain os.Open.

🤖 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/ipcauth/ownedfile_test.go` around lines 46 - 56, Update
OpenOwnedFile and its openForRead platform implementations to reject symlinks
consistently on Windows, matching the Unix O_NOFOLLOW behavior instead of using
plain os.Open. Add or retain a Windows-specific TestOpenOwnedFileRefusesSymlink
covering a caller-owned symlink, while preserving existing ownership and
regular-file validation.
🤖 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/server/debug_gate_test.go`:
- Around line 31-64: Update the "traversal onto another file" case in
TestRegisterUILogRefusesForeignPath so it constructs a path that reliably
escapes the temporary directory and reaches the intended foreign target while
retaining the uiLogFileName suffix. Ensure the test exercises traversal
protection rather than being rejected only for an incorrect filename.

---

Nitpick comments:
In `@client/internal/ipcauth/ownedfile_test.go`:
- Around line 46-56: Update OpenOwnedFile and its openForRead platform
implementations to reject symlinks consistently on Windows, matching the Unix
O_NOFOLLOW behavior instead of using plain os.Open. Add or retain a
Windows-specific TestOpenOwnedFileRefusesSymlink covering a caller-owned
symlink, while preserving existing ownership and regular-file validation.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 389d42fd-a1fc-4621-9d7f-fb79b18a5b48

📥 Commits

Reviewing files that changed from the base of the PR and between e324a96 and 3175d59.

⛔ Files ignored due to path filters (1)
  • client/proto/daemon.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (21)
  • client/android/client.go
  • client/cmd/debug.go
  • client/internal/debug/debug.go
  • client/internal/debug/debug_ios.go
  • client/internal/debug/debug_logfiles_test.go
  • client/internal/debug/uilog_test.go
  • client/internal/debug/upload.go
  • client/internal/debug/upload_test.go
  • client/internal/ipcauth/ownedfile.go
  • client/internal/ipcauth/ownedfile_test.go
  • client/internal/ipcauth/ownedfile_unix.go
  • client/internal/ipcauth/ownedfile_unix_test.go
  • client/internal/ipcauth/ownedfile_windows.go
  • client/internal/ipcauth/ownedfile_windows_test.go
  • client/ios/NetBirdSDK/client.go
  • client/jobexec/executor.go
  • client/proto/daemon.proto
  • client/server/debug.go
  • client/server/debug_gate.go
  • client/server/debug_gate_test.go
  • client/server/server.go
🚧 Files skipped from review as they are similar to previous changes (17)
  • client/server/server.go
  • client/jobexec/executor.go
  • client/internal/debug/debug_ios.go
  • client/android/client.go
  • client/proto/daemon.proto
  • client/internal/ipcauth/ownedfile_windows.go
  • client/ios/NetBirdSDK/client.go
  • client/internal/ipcauth/ownedfile_unix.go
  • client/internal/ipcauth/ownedfile.go
  • client/internal/ipcauth/ownedfile_unix_test.go
  • client/cmd/debug.go
  • client/internal/debug/uilog_test.go
  • client/internal/debug/debug_logfiles_test.go
  • client/server/debug_gate.go
  • client/server/debug.go
  • client/internal/debug/debug.go
  • client/internal/ipcauth/ownedfile_windows_test.go

Comment thread client/server/debug_gate_test.go
@lixmal

lixmal commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator Author

@riccardomanfrin

gui-client.log is defined in three places.** debug.UILogFile in
client/internal/debug/debug.go:233, the uiLogFileName alias in
client/server/debug_gate.go:23

Fixed

LogOpener is threaded through four methods but only one call site needs
a non-default opener. maybe can be resolved in place where needed and have reduced signature args.

I'm not sure I have a good solution for that without making the code worse

getUploadURL builds the query by string concatenation.

Fixed

@lixmal
lixmal force-pushed the debug-bundle-hardening branch from e223595 to 9f36909 Compare July 31, 2026 15:40
@sonarqubecloud

Copy link
Copy Markdown

@lixmal
lixmal merged commit feecb99 into main Jul 31, 2026
47 checks passed
@lixmal
lixmal deleted the debug-bundle-hardening branch July 31, 2026 17:12
@fosskar fosskar Bot mentioned this pull request Aug 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants