# How witr Detects SSH Sessions and Remote IP Addresses

> witr detects SSH sessions by traversing the process tree and extracts remote IPs from environment variables. Learn how witr monitors your SSH connections.

- Repository: [Pranshu Parmar/witr](https://github.com/pranshuparmar/witr)
- Tags: deep-dive
- Published: 2026-08-09

---

**witr detects SSH sessions by traversing the process tree to locate an sshd parent and extracts the remote IP from SSH_CLIENT or SSH_CONNECTION environment variables found in the process ancestry.**

The open-source tool **witr** (by pranshuparmar) identifies the origin of running processes by analyzing their execution context. Understanding how it detects SSH sessions and remote IP addresses reveals a robust methodology based on process ancestry inspection and environment variable parsing. This analysis examines the detection logic implemented in the Go source code to show exactly how remote connections are fingerprinted.

## Process Ancestry Inspection Strategy

witr determines whether a process belongs to an SSH session by walking its **process ancestry chain** and searching for an sshd parent. The detection algorithm requires at least two processes in the ancestry—the target process and one parent—otherwise it immediately returns `nil` (lines 11–14 of [`internal/source/ssh.go`](https://github.com/pranshuparmar/witr/blob/main/internal/source/ssh.go)).

The core loop iterates through the ancestry looking for process names matching `sshd`, `sshd.exe`, or entries prefixed with `sshd:`:

```go
for i := 0; i < len(ancestry)-1; i++ {
    base := filepath.Base(ancestry[i].Command)
    if base == "sshd" || base == "sshd.exe" || strings.HasPrefix(base, "sshd:") {
        hasSSHD = true
        break
    }
}

```

If no sshd parent is found after scanning the entire chain, the function returns `nil` and the session is not classified as SSH (lines 25–27).

## Extracting Remote IP from Environment Variables

Once an sshd parent is confirmed, witr extracts connection metadata by scanning the **environment variables** of each process from the target upwards. The code specifically hunts for `SSH_CLIENT`, `SSH_CONNECTION`, and `SSH_TTY` (lines 35–57 of [`internal/source/ssh.go`](https://github.com/pranshuparmar/witr/blob/main/internal/source/ssh.go)).

The resolution priority works as follows:

- **SSH_CLIENT**: The first field contains the remote IP address.
- **SSH_CONNECTION**: Used as a fallback if `SSH_CLIENT` is absent.
- **SSH_TTY**: Captured optionally to identify the terminal device.

The implementation captures these values only while `remoteIP` remains empty, ensuring the **closest ancestor's** environment takes precedence. This handles nested sessions correctly by using the nearest sshd context.

## Source Detection Implementation

### Locating the sshd Parent Process

The detection logic validates the ancestry length before entering the name-matching loop. This prevents false positives on single-process trees and ensures sufficient context exists for SSH classification.

### Parsing SSH Environment Variables

The environment scan extracts three specific OpenSSH variables. According to the witr source code, `SSH_CLIENT` typically contains `<remote_ip> <remote_port> <local_port>`, while `SSH_CONNECTION` provides `<remote_ip> <remote_port> <local_ip> <local_port>`. The parser splits these strings and extracts the first field as the remote IP.

### Building the Session Description

witr constructs a human-readable description based on available data (lines 60–68):

- `"SSH session"` (IP unknown)
- `"SSH session from <IP>"`
- `"SSH session from <IP> (<user>)"`
- `"SSH session from <IP> (<user>@<tty>)"`

The function returns a `model.Source` struct with `Type: model.SourceSSH`, the process name `"sshd"`, and the computed description (lines 71–75).

## Integration with the Detection Pipeline

The SSH detector integrates into the broader source classification system through [`internal/source/detect.go`](https://github.com/pranshuparmar/witr/blob/main/internal/source/detect.go). The generic `Detect` function calls the SSH detection routine **after** container detection, establishing SSH as the second detection priority (lines 60–62). This ordering ensures containerized environments are identified first, while SSH detection catches remote shell sessions running on bare metal or virtual machines.

## Practical Usage Examples

### Programmatic Detection

You can leverage witr's detection logic directly in Go applications:

```go
package main

import (
	"fmt"

	"github.com/pranshuparmar/witr/internal/source"
	"github.com/pranshuparmar/witr/pkg/model"
)

func main() {
	// Assume `ancestry` has been populated elsewhere (e.g. via witr's proc package)
	var ancestry []model.Process

	// Detect the source type
	src := source.Detect(ancestry)

	if src.Type == model.SourceSSH {
		fmt.Printf("Detected SSH session: %s\n", src.Description)
	}
}

```

### Command-Line Output

When running the `witr` binary on a process started via SSH, the tool displays the detected source in the SOURCE column:

```bash
$ witr
PID   USER   COMMAND   SOURCE
1234  alice  bash      SSH session from 203.0.113.42 (alice@pts/0)

```

This output demonstrates the successful extraction of the remote IP (`203.0.113.42`), username (`alice`), and TTY (`pts/0`) from the SSH environment.

## Summary

- **witr** locates SSH sessions by walking the process tree and identifying an `sshd` parent process in [`internal/source/ssh.go`](https://github.com/pranshuparmar/witr/blob/main/internal/source/ssh.go).
- Remote IP addresses are extracted from the `SSH_CLIENT` or `SSH_CONNECTION` environment variables found in the process ancestry.
- The detection requires at least two processes in the ancestry chain and supports cross-platform naming (`sshd`, `sshd.exe`, `sshd:` prefixes).
- The `model.SourceSSH` type is returned with a human-readable description indicating the remote endpoint and optional user/TTY information.
- SSH detection executes after container detection in the classification pipeline defined in [`internal/source/detect.go`](https://github.com/pranshuparmar/witr/blob/main/internal/source/detect.go).

## Frequently Asked Questions

### What happens if SSH_CLIENT is not set?

If the `SSH_CLIENT` variable is absent from the environment, witr falls back to parsing `SSH_CONNECTION` to obtain the remote IP address. If neither variable is present, the tool reports a generic "SSH session" without IP attribution.

### Does witr support Windows SSH detection?

Yes, the detection logic explicitly checks for `sshd.exe` in addition to Unix-style `sshd` process names, ensuring compatibility with Windows OpenSSH server implementations.

### How does witr handle nested SSH sessions?

witr uses the **closest ancestor's** environment variables by stopping at the first match while traversing from the target process upward. This ensures that when jumping between multiple SSH hops, the immediate parent session's details are captured rather than the outermost connection.

### Can witr detect SSH sessions inside containers?

Container detection takes priority in the witr pipeline and is evaluated before SSH detection. If a process runs inside a container that was accessed via SSH, witr will report the container source rather than the SSH session, as implemented in the detection ordering within [`internal/source/detect.go`](https://github.com/pranshuparmar/witr/blob/main/internal/source/detect.go).