diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt
index 00531b8ac..ee2f31986 100644
--- a/cmd/tailscaled/depaware.txt
+++ b/cmd/tailscaled/depaware.txt
@@ -201,6 +201,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
         tailscale.com/derp/derphttp                                  from tailscale.com/net/netcheck+
         tailscale.com/disco                                          from tailscale.com/derp+
         tailscale.com/doctor                                         from tailscale.com/ipn/ipnlocal
+     💣 tailscale.com/doctor/permissions                             from tailscale.com/ipn/ipnlocal
         tailscale.com/doctor/routetable                              from tailscale.com/ipn/ipnlocal
         tailscale.com/envknob                                        from tailscale.com/control/controlclient+
         tailscale.com/health                                         from tailscale.com/control/controlclient+
@@ -346,7 +347,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
         golang.org/x/crypto/poly1305                                 from github.com/tailscale/golang-x-crypto/ssh+
         golang.org/x/crypto/salsa20/salsa                            from golang.org/x/crypto/nacl/box+
   LD    golang.org/x/crypto/ssh                                      from tailscale.com/ssh/tailssh+
-        golang.org/x/exp/constraints                                 from golang.org/x/exp/slices
+        golang.org/x/exp/constraints                                 from golang.org/x/exp/slices+
         golang.org/x/exp/maps                                        from tailscale.com/wgengine
         golang.org/x/exp/slices                                      from tailscale.com/ipn/ipnlocal+
         golang.org/x/net/bpf                                         from github.com/mdlayher/genetlink+
diff --git a/doctor/permissions/permissions.go b/doctor/permissions/permissions.go
new file mode 100644
index 000000000..f9c0de950
--- /dev/null
+++ b/doctor/permissions/permissions.go
@@ -0,0 +1,56 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+// Package permissions provides a doctor.Check that prints the process
+// permissions for the running process.
+package permissions
+
+import (
+	"context"
+	"fmt"
+	"os/user"
+	"strings"
+
+	"golang.org/x/exp/constraints"
+	"tailscale.com/types/logger"
+)
+
+// Check implements the doctor.Check interface.
+type Check struct{}
+
+func (Check) Name() string {
+	return "permissions"
+}
+
+func (Check) Run(_ context.Context, logf logger.Logf) error {
+	return permissionsImpl(logf)
+}
+
+func formatUserID[T constraints.Integer](id T) string {
+	idStr := fmt.Sprint(id)
+	if uu, err := user.LookupId(idStr); err != nil {
+		return idStr + "(<unknown>)"
+	} else {
+		return fmt.Sprintf("%s(%q)", idStr, uu.Username)
+	}
+}
+
+func formatGroupID[T constraints.Integer](id T) string {
+	idStr := fmt.Sprint(id)
+	if g, err := user.LookupGroupId(idStr); err != nil {
+		return idStr + "(<unknown>)"
+	} else {
+		return fmt.Sprintf("%s(%q)", idStr, g.Name)
+	}
+}
+
+func formatGroups[T constraints.Integer](groups []T) string {
+	var buf strings.Builder
+	for i, group := range groups {
+		if i > 0 {
+			buf.WriteByte(',')
+		}
+		buf.WriteString(formatGroupID(group))
+	}
+	return buf.String()
+}
diff --git a/doctor/permissions/permissions_bsd.go b/doctor/permissions/permissions_bsd.go
new file mode 100644
index 000000000..8b034cfff
--- /dev/null
+++ b/doctor/permissions/permissions_bsd.go
@@ -0,0 +1,23 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build darwin || freebsd || openbsd
+
+package permissions
+
+import (
+	"golang.org/x/sys/unix"
+	"tailscale.com/types/logger"
+)
+
+func permissionsImpl(logf logger.Logf) error {
+	groups, _ := unix.Getgroups()
+	logf("uid=%s euid=%s gid=%s egid=%s groups=%s",
+		formatUserID(unix.Getuid()),
+		formatUserID(unix.Geteuid()),
+		formatGroupID(unix.Getgid()),
+		formatGroupID(unix.Getegid()),
+		formatGroups(groups),
+	)
+	return nil
+}
diff --git a/doctor/permissions/permissions_linux.go b/doctor/permissions/permissions_linux.go
new file mode 100644
index 000000000..12bb393d5
--- /dev/null
+++ b/doctor/permissions/permissions_linux.go
@@ -0,0 +1,62 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build linux
+
+package permissions
+
+import (
+	"fmt"
+	"strings"
+	"unsafe"
+
+	"golang.org/x/sys/unix"
+	"tailscale.com/types/logger"
+)
+
+func permissionsImpl(logf logger.Logf) error {
+	// NOTE: getresuid and getresgid never fail unless passed an
+	// invalid address.
+	var ruid, euid, suid uint64
+	unix.Syscall(unix.SYS_GETRESUID,
+		uintptr(unsafe.Pointer(&ruid)),
+		uintptr(unsafe.Pointer(&euid)),
+		uintptr(unsafe.Pointer(&suid)),
+	)
+
+	var rgid, egid, sgid uint64
+	unix.Syscall(unix.SYS_GETRESGID,
+		uintptr(unsafe.Pointer(&rgid)),
+		uintptr(unsafe.Pointer(&egid)),
+		uintptr(unsafe.Pointer(&sgid)),
+	)
+
+	groups, _ := unix.Getgroups()
+
+	var buf strings.Builder
+	fmt.Fprintf(&buf, "ruid=%s euid=%s suid=%s rgid=%s egid=%s sgid=%s groups=%s",
+		formatUserID(ruid), formatUserID(euid), formatUserID(suid),
+		formatGroupID(rgid), formatGroupID(egid), formatGroupID(sgid),
+		formatGroups(groups),
+	)
+
+	// Get process capabilities
+	var (
+		capHeader = unix.CapUserHeader{
+			Version: unix.LINUX_CAPABILITY_VERSION_3,
+			Pid:     0, // 0 means 'ourselves'
+		}
+		capData unix.CapUserData
+	)
+
+	if err := unix.Capget(&capHeader, &capData); err != nil {
+		fmt.Fprintf(&buf, " caperr=%v", err)
+	} else {
+		fmt.Fprintf(&buf, " cap_effective=%08x cap_permitted=%08x cap_inheritable=%08x",
+			capData.Effective, capData.Permitted, capData.Inheritable,
+		)
+	}
+
+	logf("%s", buf.String())
+	return nil
+}
diff --git a/doctor/permissions/permissions_other.go b/doctor/permissions/permissions_other.go
new file mode 100644
index 000000000..7e6912b49
--- /dev/null
+++ b/doctor/permissions/permissions_other.go
@@ -0,0 +1,17 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+//go:build !(linux || darwin || freebsd || openbsd)
+
+package permissions
+
+import (
+	"runtime"
+
+	"tailscale.com/types/logger"
+)
+
+func permissionsImpl(logf logger.Logf) error {
+	logf("unsupported on %s/%s", runtime.GOOS, runtime.GOARCH)
+	return nil
+}
diff --git a/doctor/permissions/permissions_test.go b/doctor/permissions/permissions_test.go
new file mode 100644
index 000000000..941d406ef
--- /dev/null
+++ b/doctor/permissions/permissions_test.go
@@ -0,0 +1,12 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package permissions
+
+import "testing"
+
+func TestPermissionsImpl(t *testing.T) {
+	if err := permissionsImpl(t.Logf); err != nil {
+		t.Error(err)
+	}
+}
diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go
index 4c5efd9b7..5be259af2 100644
--- a/ipn/ipnlocal/local.go
+++ b/ipn/ipnlocal/local.go
@@ -35,6 +35,7 @@ import (
 	"tailscale.com/client/tailscale/apitype"
 	"tailscale.com/control/controlclient"
 	"tailscale.com/doctor"
+	"tailscale.com/doctor/permissions"
 	"tailscale.com/doctor/routetable"
 	"tailscale.com/envknob"
 	"tailscale.com/health"
@@ -4698,7 +4699,10 @@ func (b *LocalBackend) Doctor(ctx context.Context, logf logger.Logf) {
 	logf = logger.SlowLoggerWithClock(ctx, logf, 20*time.Millisecond, 60, time.Now)
 
 	var checks []doctor.Check
-	checks = append(checks, routetable.Check{})
+	checks = append(checks,
+		permissions.Check{},
+		routetable.Check{},
+	)
 
 	// Print a log message if any of the global DNS resolvers are Tailscale
 	// IPs; this can interfere with our ability to connect to the Tailscale