[client] Support non-PTY no-command interactive SSH sessions#5093
[client] Support non-PTY no-command interactive SSH sessions#5093
Conversation
📝 WalkthroughWalkthroughRefactors SSH session dispatch and command execution: introduces logger propagation across executor/SU/S4U flows, renames handlers (handlePty → handlePtyLogin, handleCommand → handleExecution), changes non‑interactive proxy lifecycle to shell‑driven, revises PTY lifecycle/cleanup, and adds extensive PTY tests. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Proxy
participant Backend as BackendSession
participant Shell
Client->>Proxy: start non-interactive SSH session
Proxy->>Backend: create backend session
Proxy->>Backend: attach Stdin/Stdout/Stderr
Backend->>Shell: start shell
alt shell started
Note over Proxy,Shell: Proxy waits for client ctx done or shell exit
Shell-->>Backend: shell exits (code)
Backend-->>Proxy: report exit code
Proxy-->>Client: close session, return exit code
else start failed
Backend-->>Proxy: start error
Proxy-->>Client: log and propagate exit via handleProxyExitCode
end
sequenceDiagram
participant Client
participant Server
participant PTYMgr as PTY Manager / WinPTY
participant Executor
Client->>Server: SSH session (pty? / command?)
alt PTY requested and no command
Server->>PTYMgr: handlePtyLogin (create PTY, winCh)
PTYMgr->>Executor: execute login shell
Executor-->>PTYMgr: command exits
PTYMgr-->>Server: cleanup & report exit
Server-->>Client: close session
else command or non-PTY
Server->>Server: handleExecution (createCommand with logger)
Server->>Executor: createExecutorCommand / createSuCommand (with logger)
Executor-->>Server: cmd started / exit code
Server-->>Client: return output & exit code
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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: 0
🧹 Nitpick comments (9)
client/ssh/server/server_config_test.go (1)
548-551: Test validates basic functionality but could be strengthened.The test verifies that non-PTY shell sessions don't error out, which validates the core fix. However, it doesn't confirm the shell is actually functional or produces expected output.
Consider verifying the session output to ensure the shell was properly initialized and can accept/return data, especially since the PR mentions platform-specific behavior (Windows runs without ConPTY, Unix uses
su -lwith fallback).client/ssh/server/command_execution_windows.go (2)
245-258: Inconsistent logger usage in fallback path.Line 249 uses the global
log.Debugfwhile theloggerparameter is available in scope. For consistency with the logger-threading pattern introduced in this PR, use the injected logger.Suggested fix
func (s *Server) prepareCommandEnv(logger *log.Entry, localUser *user.User, session ssh.Session) []string { username, domain := s.parseUsername(localUser.Username) userEnv, err := s.getUserEnvironment(logger, username, domain) if err != nil { - log.Debugf("failed to get user environment for %s\\%s, using fallback: %v", domain, username, err) + logger.Debugf("failed to get user environment for %s\\%s, using fallback: %v", domain, username, err) env := prepareUserEnv(localUser, getUserShell(localUser.Uid))
315-320: Consider refactoring to avoid empty Server instantiation.Creating an empty
&Server{}solely to callgetUserEnvironmentWithTokenis a code smell. The method doesn't appear to use any Server state. Consider makinggetUserEnvironmentWithTokena standalone function or passing the required dependencies directly.client/ssh/server/sftp_windows.go (1)
34-38: Consider passing a logger for consistency.Unlike the Unix SFTP subprocess case, this runs in the server process where session context is available. For consistency with other Windows privilege-dropping paths that thread loggers (e.g.,
executePtyCommandWithUserToken), consider accepting and passing a logger here.client/ssh/server/command_execution_unix.go (1)
140-149: Consider removing unused logger parameter or documenting intent.The logger parameter is accepted but unused (
_). If this is for API consistency or future use, consider adding a brief comment. Otherwise, if no logging is planned here, the parameter could potentially be removed from this platform-specific implementation.client/ssh/server/command_execution.go (1)
79-80: Log message may be misleading whenUsedFallbackis true buterris nil.When
privilegeResult.UsedFallbackis true anderris nil, the log message "su command failed" is inaccurate—the su command wasn't attempted or didn't fail; rather, a fallback was already used during privilege checking.Suggested improvement
if err != nil || privilegeResult.UsedFallback { - logger.Debugf("su command failed, falling back to executor: %v", err) + if err != nil { + logger.Debugf("su command failed, falling back to executor: %v", err) + } else { + logger.Debugf("using executor due to privilege fallback") + } cmd, cleanup, err := s.createExecutorCommand(logger, session, localUser, hasPty)client/ssh/server/executor_windows.go (3)
191-214: Consider extractingisLocalUseras a standalone function.Creating a
PrivilegeDropperinstance at line 194 solely to callisLocalUseris somewhat indirect sinceisLocalUserdoesn't use the logger field. This could be simplified.Optional refactor
+// isLocalUser determines if this is a local user vs domain user +func isLocalUser(domain string) bool { + hostname, err := os.Hostname() + if err != nil { + hostname = "localhost" + } + return domain == "" || domain == "." || + strings.EqualFold(domain, hostname) +} func generateS4UUserToken(logger *log.Entry, username, domain string) (windows.Handle, error) { userCpn := buildUserCpn(username, domain) - - pd := NewPrivilegeDropper(logger) - isDomainUser := !pd.isLocalUser(domain) + isDomainUser := !isLocalUser(domain)Then update the
PrivilegeDroppermethod to call the standalone function if needed.
481-492: Minor inconsistencies: typo and global logger usage.
- Line 481: Comment has typo "useVerifier" → should be "user"
- Line 484: Uses global
log.Debugfinstead of a logger parameter, inconsistent with the rest of the file's patternSuggested fix
-// userExists checks if the target useVerifier exists on the system -func userExists(fullUsername, username, domain string) error { +// userExists checks if the target user exists on the system +func (pd *PrivilegeDropper) userExists(fullUsername, username, domain string) error { if _, err := lookupUser(fullUsername); err != nil { - log.Debugf("User %s not found: %v", fullUsername, err) + pd.log().Debugf("User %s not found: %v", fullUsername, err) if domain != "" && domain != "." { _, err = lookupUser(username) }And update the call site in
createToken:- if err := userExists(fullUsername, username, domain); err != nil { + if err := pd.userExists(fullUsername, username, domain); err != nil {
242-247: Pre-existing global logger usage remains.For future consistency,
cleanupLsaConnectionandlookupPrincipalName(lines 292, 311, 316) still use the globallog.Debugf. Consider updating these in a follow-up if full logger consistency is desired.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (18)
client/cmd/ssh_exec_unix.goclient/cmd/ssh_sftp_unix.goclient/ssh/proxy/proxy.goclient/ssh/server/command_execution.goclient/ssh/server/command_execution_js.goclient/ssh/server/command_execution_unix.goclient/ssh/server/command_execution_windows.goclient/ssh/server/executor_unix.goclient/ssh/server/executor_unix_test.goclient/ssh/server/executor_windows.goclient/ssh/server/server.goclient/ssh/server/server_config_test.goclient/ssh/server/session_handlers.goclient/ssh/server/session_handlers_js.goclient/ssh/server/sftp_windows.goclient/ssh/server/userswitching_unix.goclient/ssh/server/userswitching_windows.goclient/ssh/server/winpty/conpty.go
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: lixmal
Repo: netbirdio/netbird PR: 4015
File: client/ssh/server/server_test.go:396-406
Timestamp: 2025-11-14T11:11:50.812Z
Learning: On Windows, the NetBird SSH server only supports PowerShell as the shell (powershell.exe or pwsh.exe). cmd.exe and other shells are not supported due to parsing quirks and complexity.
📚 Learning: 2025-11-14T13:05:31.729Z
Learnt from: lixmal
Repo: netbirdio/netbird PR: 4015
File: client/ssh/server/userswitching_windows.go:89-139
Timestamp: 2025-11-14T13:05:31.729Z
Learning: In client/ssh/server/executor_windows.go, the WindowsExecutorConfig struct's Pty, PtyWidth, and PtyHeight fields are intentionally left unused for now and will be implemented in a future update.
Applied to files:
client/ssh/server/session_handlers_js.goclient/ssh/server/winpty/conpty.goclient/ssh/server/executor_unix_test.goclient/ssh/server/session_handlers.goclient/ssh/server/userswitching_windows.goclient/ssh/server/command_execution_unix.goclient/ssh/server/command_execution.goclient/ssh/server/executor_windows.goclient/ssh/server/command_execution_windows.go
📚 Learning: 2025-11-13T00:29:53.247Z
Learnt from: lixmal
Repo: netbirdio/netbird PR: 4015
File: client/cmd/ssh_exec_unix.go:53-74
Timestamp: 2025-11-13T00:29:53.247Z
Learning: In client/ssh/server/executor_unix.go, the method ExecuteWithPrivilegeDrop(ctx context.Context, config ExecutorConfig) has a void return type (no error return). It handles failures by exiting the process directly with appropriate exit codes rather than returning errors to the caller.
Applied to files:
client/ssh/server/winpty/conpty.goclient/ssh/proxy/proxy.goclient/ssh/server/sftp_windows.goclient/ssh/server/userswitching_unix.goclient/ssh/server/executor_unix_test.goclient/ssh/server/server_config_test.goclient/ssh/server/executor_unix.goclient/cmd/ssh_exec_unix.goclient/ssh/server/session_handlers.goclient/ssh/server/userswitching_windows.goclient/ssh/server/command_execution_js.goclient/ssh/server/command_execution_unix.goclient/ssh/server/command_execution.goclient/cmd/ssh_sftp_unix.goclient/ssh/server/executor_windows.goclient/ssh/server/command_execution_windows.go
📚 Learning: 2026-01-05T06:33:50.474Z
Learnt from: lixmal
Repo: netbirdio/netbird PR: 5031
File: client/ssh/proxy/proxy.go:537-559
Timestamp: 2026-01-05T06:33:50.474Z
Learning: The NetBird SSH proxy (client/ssh/proxy/proxy.go) is ephemeral and tied to a single SSH client session. It operates through stdio and exists only for the lifetime of one client connection, so session-scoped contexts are appropriate for the proxy's lifecycle.
Applied to files:
client/ssh/proxy/proxy.goclient/cmd/ssh_exec_unix.go
🧬 Code graph analysis (10)
client/ssh/server/session_handlers_js.go (2)
client/ssh/server/server.go (1)
Server(137-175)client/ssh/server/user_utils.go (1)
PrivilegeCheckResult(50-66)
client/ssh/server/userswitching_unix.go (2)
client/ssh/server/executor_unix.go (1)
NewPrivilegeDropper(43-45)client/ssh/server/executor_windows.go (1)
NewPrivilegeDropper(46-48)
client/ssh/server/executor_unix_test.go (2)
client/ssh/server/executor_unix.go (1)
NewPrivilegeDropper(43-45)client/ssh/server/executor_windows.go (1)
NewPrivilegeDropper(46-48)
client/cmd/ssh_exec_unix.go (2)
client/ssh/server/executor_unix.go (1)
NewPrivilegeDropper(43-45)client/ssh/server/executor_windows.go (1)
NewPrivilegeDropper(46-48)
client/ssh/server/userswitching_windows.go (4)
client/ssh/server/server.go (1)
Server(137-175)shared/management/status/error.go (1)
Errorf(70-75)client/ssh/server/executor_unix.go (1)
NewPrivilegeDropper(43-45)client/ssh/server/executor_windows.go (1)
NewPrivilegeDropper(46-48)
client/ssh/server/command_execution_js.go (1)
client/ssh/server/server.go (1)
Server(137-175)
client/ssh/server/command_execution_unix.go (3)
client/ssh/server/server.go (1)
Server(137-175)shared/management/status/error.go (1)
Errorf(70-75)client/ssh/server/user_utils.go (1)
PrivilegeCheckResult(50-66)
client/cmd/ssh_sftp_unix.go (2)
client/ssh/server/executor_unix.go (1)
NewPrivilegeDropper(43-45)client/ssh/server/executor_windows.go (1)
NewPrivilegeDropper(46-48)
client/ssh/server/executor_windows.go (1)
client/ssh/server/executor_unix.go (2)
NewPrivilegeDropper(43-45)PrivilegeDropper(38-40)
client/ssh/server/command_execution_windows.go (3)
client/ssh/server/executor_unix.go (1)
NewPrivilegeDropper(43-45)client/ssh/server/executor_windows.go (1)
NewPrivilegeDropper(46-48)client/ssh/server/winpty/conpty.go (1)
ExecutePtyWithUserToken(59-71)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
- GitHub Check: Client / Unit
- GitHub Check: iOS / Build
- GitHub Check: Android / Build
- GitHub Check: release_ui
- GitHub Check: release
- GitHub Check: Build Cache
- GitHub Check: release_ui_darwin
- GitHub Check: Client / Unit
- GitHub Check: Linux
- GitHub Check: JS / Lint
- GitHub Check: Client / Unit
- GitHub Check: Windows
- GitHub Check: Darwin
🔇 Additional comments (29)
client/ssh/server/server.go (1)
338-338: LGTM!The comment update correctly reflects the new behavior: with the added support for
ssh -T <host>(non-PTY interactive sessions), those sessions now get proper session handling and are tracked in thesessionsmap. Only-Nand port-forwarding-only connections remain as "authenticated connections without sessions."client/ssh/server/server_config_test.go (2)
486-487: LGTM!Test name and description accurately reflect the new purpose: verifying that non-PTY shell sessions (
ssh -T) work regardless of port forwarding configuration.
494-518: LGTM!Good test matrix covering all port forwarding configuration combinations. The simplified struct is appropriate since the expected outcome (success) is now uniform across all test cases.
client/ssh/proxy/proxy.go (1)
217-239: LGTM! Shell-driven lifecycle for non-interactive sessions.The implementation correctly:
- Attaches session I/O streams before starting the shell
- Uses a buffered channel to receive the wait result asynchronously
- Handles both client context cancellation and shell completion via select
- Propagates exit codes properly via
handleProxyExitCodeThis aligns with the PR objective of supporting
ssh -T <host>(interactive login without PTY).client/ssh/server/winpty/conpty.go (1)
59-71: LGTM! Context sourcing simplified appropriately.Removing the explicit
ctxparameter and deriving it fromsession.Context()internally is a cleaner API. This aligns with the learning that session-scoped contexts are appropriate for the SSH proxy lifecycle. The change ensures consistent context sourcing across the codebase.client/ssh/server/command_execution_windows.go (2)
270-281: LGTM! Handler correctly threads logger.The
handlePtyLoginfunction properly accepts and uses the logger parameter, maintaining consistent logging context throughout the PTY login flow.
300-342: LGTM! PTY execution with proper logger propagation.The function correctly:
- Accepts and uses the logger for tracing and debug output
- Creates a new PrivilegeDropper with the logger
- Handles token lifecycle with proper cleanup in defer
- Delegates to
winpty.ExecutePtyWithUserTokenwith the sessionclient/cmd/ssh_sftp_unix.go (1)
39-39: LGTM! Nil logger is acceptable for standalone SFTP subprocess.Passing
niltoNewPrivilegeDropperis appropriate here since this is a standalone subprocess invoked via command-line. The subprocess has its own logging vialog.Tracefand doesn't need a logger context threaded from a parent session.client/ssh/server/session_handlers_js.go (1)
12-22: LGTM! Consistent handler naming and signature.The rename from
handlePtytohandlePtyLoginaligns with the broader refactor across the PR. The logger parameter is properly utilized for error logging, maintaining API consistency with Unix and Windows implementations.client/ssh/server/executor_unix_test.go (5)
18-19: LGTM! Test adapted to new constructor signature.Passing
nilfor the logger in tests is appropriate since test assertions don't require structured logging context.
76-77: LGTM!
110-111: LGTM!
159-160: LGTM!
229-230: LGTM!client/ssh/server/session_handlers.go (1)
65-71: Clear and correct session dispatch logic.The two-path routing correctly handles:
- PTY interactive login (
ssh <host>) →handlePtyLogin- All other cases (commands,
-t,-T) →handleExecutionThis aligns well with the PR objective to support
ssh -T <host>for non-PTY interactive sessions.client/cmd/ssh_exec_unix.go (1)
55-55: LGTM -nillogger is appropriate here.This is a spawned child process for privilege dropping that communicates via stdout/stderr. Structured logging isn't needed in this context, and the executor handles failures by exiting with specific exit codes. Based on learnings, this aligns with the established pattern.
client/ssh/server/userswitching_unix.go (1)
184-195: Logger propagation looks correct.The logger parameter is properly threaded through to
NewPrivilegeDropper(logger)and used for debug logging. The cleanup function being a no-op on Unix is appropriate since there's no token handle to close (unlike Windows).client/ssh/server/userswitching_windows.go (3)
91-100: Logger propagation correctly implemented for Windows.The logger is properly threaded to
createUserSwitchCommandand debug logging uses the provided logger instance.
115-121: WindowsExecutorConfig construction simplified.The
Interactivefield has been removed as per PR objectives. Based on learnings, the PTY-related fields (Pty,PtyWidth,PtyHeight) are intentionally unused and will be implemented in a future update.
123-138: Cleanup function properly handles token lifecycle.The cleanup function correctly closes the Windows token handle and logs any errors via the provided logger. This ensures proper resource cleanup after process creation.
client/ssh/server/executor_unix.go (2)
38-53: Well-designed logger integration with safe fallback.The
log()helper method provides a clean pattern for optional logging - returns the injected logger if available, otherwise falls back to the standard logger. This ensures consistent logging behavior across all call sites.
217-233: Login shell execution enables non-PTY interactive sessions.The change to always use
-l(login shell) flag is correct for proper shell initialization. This is the key change that enablesssh -T <host>to work correctly - it spawns a login shell even without PTY allocation. The safe command logging viasafeLogCommand()properly protects sensitive command content from being exposed in logs.client/ssh/server/command_execution_js.go (1)
18-30: LGTM - Stub signatures correctly aligned with other platforms.The function signatures now accept
*log.Entryas the first parameter, maintaining consistency with Unix and Windows implementations. The logger is appropriately ignored in these stubs since the platform doesn't support SSH server command execution.client/ssh/server/command_execution_unix.go (2)
102-129: Good addition of username validation and debug logging.The username validation at the start of
createSuCommandis a good security practice, though I note thatcreateExecutorCommandinuserswitching_unix.goalso validates the username. This provides defense-in-depth but consider whether both validation points are necessary.The debug logging of the constructed
sucommand (line 124) is helpful for troubleshooting while keeping the command arguments visible (unlike the executor path which masks the command content).
162-178: Improved PTY login error handling.The
handlePtyLoginfunction now provides better user feedback by writing an error message tosession.Stderr()before exiting. This helps users understand why their session failed rather than just seeing a disconnection.client/ssh/server/command_execution.go (1)
15-57: LGTM!The function properly threads the logger through the command creation and execution paths. The PTY vs non-PTY branching is clean, and error handling appropriately logs and exits with meaningful messages.
client/ssh/server/executor_windows.go (3)
30-40: LGTM!The struct is clean. Based on learnings, the
Pty,PtyWidth, andPtyHeightfields are intentionally reserved for a future update.
42-56: LGTM!The
PrivilegeDropperimplementation with logger support is consistent with the Unix implementation pattern shown inexecutor_unix.go. Thelog()helper provides a safe fallback when no logger is configured.
575-578: LGTM!The stub correctly returns an error indicating
suis unavailable on Windows, with a signature matching the cross-platform interface.
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
client/ssh/server/executor_windows.go (1)
205-228: Incorrect PrivilegeDropper instantiation - missing WithLogger wrapper.On line 208,
NewPrivilegeDropper(logger)is called with a*log.Entrydirectly, butNewPrivilegeDropperexpects...PrivilegeDropperOptionvariadic arguments. This should use theWithLoggerfunctional option pattern as defined in lines 49-62.🐛 Proposed fix
- pd := NewPrivilegeDropper(logger) + pd := NewPrivilegeDropper(WithLogger(logger))
🧹 Nitpick comments (2)
client/ssh/server/compatibility_test.go (2)
557-576: Consider portability ofbash -cassumption.The
exit_code_preserved_with_ptytest usesbash -c 'exit 43'which assumesbashis available. While this is generally true on most Unix systems, it may fail on minimal systems or containers with onlysh.Consider using a more portable approach:
♻️ Suggested alternative
- args := append(baseArgs, "-tt", fmt.Sprintf("%s@%s", username, host), "bash -c 'exit 43'") + args := append(baseArgs, "-tt", fmt.Sprintf("%s@%s", username, host), "sh -c 'exit 43'")
526-532: Stdin write errors not checked in goroutine.The goroutine writes to stdin but doesn't handle potential write errors beyond logging them implicitly via
stdin.Write. If the connection closes unexpectedly, this could cause silent failures.This is acceptable for test code where the primary assertion is on the output, but consider using a done channel or error capture for more robust test diagnostics.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@client/ssh/server/compatibility_test.go`:
- Around line 567-579: The goroutine that writes to stdin uses t.Errorf which
may run after the test returns (race/panic); replace those t.Errorf calls with
non-test-failing logging (t.Logf) or, preferably, synchronize the goroutine with
the main test using a done channel: create a done := make(chan error) (or chan
struct{}), send any write error into it from the goroutine (or nil on success),
and defer/close stdin inside the goroutine as now; in the main test wait for the
done channel (or select with the test context/timeout) before returning so
cmd.Wait() and the goroutine are ordered and any error is reported from the main
goroutine (use t.Fatalf/t.Errorf there if needed).
🧹 Nitpick comments (2)
client/ssh/server/command_execution_windows.go (2)
23-34: Ensure logger is never nil before use.These new logger calls will panic if any call site passes a nil *log.Entry. Please confirm callers always supply a logger, or add a safe default (e.g., standard logger) at the entry points.
Also applies to: 38-48, 62-64
245-249: Use the injected logger on the fallback path.The fallback currently logs via the global logger, which drops the session context. Consider switching to the injected logger for consistency.
♻️ Suggested change
- log.Debugf("failed to get user environment for %s\\%s, using fallback: %v", domain, username, err) + logger.Debugf("failed to get user environment for %s\\%s, using fallback: %v", domain, username, err)
e72ff73 to
7ada564
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@client/ssh/server/compatibility_test.go`:
- Around line 591-602: The test "exit_code_preserved_no_pty" performs an
unchecked type assertion on err to *exec.ExitError which can be nil and cause a
panic; update the test to mirror the safe pattern used in
exit_code_preserved_with_pty by using a two-value type assertion on err (e.g.,
if exitErr, ok := err.(*exec.ExitError); ok { assert.Equal(...,
exitErr.ExitCode()) } else { t.Fatalf(...) }) so ExitCode() is only called when
the assertion succeeds and the test fails with a clear message otherwise.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@client/ssh/server/compatibility_test.go`:
- Around line 611-657: The three test cases ("exit_code_preserved_with_pty",
"stderr_works_no_pty", "stderr_merged_with_pty") assume a POSIX shell via
hardcoded "sh -c ..."; guard them by checking for shell availability (e.g. use
exec.LookPath("sh")) at the start of each t.Run and call t.Skipf when not found
(or on windows) so they don't run on systems without /bin/sh; update the test
blocks containing these names to perform that check before building args and
invoking exec.Command.
f204d92 to
80d6e42
Compare
80d6e42 to
82dc0c6
Compare
|


Describe your changes
ssh -T <host>(interactive login without PTY):-t, but without ConPTYsu -l(with fallback to the executor / privilege dropper) instead ofloginsessionfield to some log messagesInteractivefieldIssue ticket number and link
Stack
Checklist
Documentation
Select exactly one:
Docs PR URL (required if "docs added" is checked)
Paste the PR link from https://github.com/netbirdio/docs here:
https://github.com/netbirdio/docs/pull/__
Summary by CodeRabbit
New Features
Bug Fixes
Tests
✏️ Tip: You can customize this high-level summary in your review settings.