How witr Detects and Identifies SSH Sessions and Remote Terminal Connections from Source Code

witr detects SSH sessions by combining process ancestry inspection (checking for sshd in parent processes) with fallback environment variable checks (SSH_CONNECTION, SSH_CLIENT, SSH_TTY) to reliably identify remote terminal connections even when environment data is partially stripped.

The witr project implements a robust source detection system written in Go that answers the question "Where am I running?" The SSH detection logic in this repository demonstrates how to programmatically distinguish local shells from remote sessions. This article examines the actual implementation in pranshuparmar/witr, revealing how the tool inspects process trees and environment variables to build accurate session descriptions.

Core SSH Detection Strategy

The detection algorithm follows a two-tier approach designed for reliability across different execution contexts:

  1. Primary check: Walk the process ancestry looking for sshd
  2. Secondary check: Parse SSH-specific environment variables when ancestry fails

This dual-method design handles common scenarios where sudo, su, or containerization might strip environment variables while preserving enough process metadata for detection.

How Process Ancestry Detection Works

The detectSSH function in internal/source/ssh.go implements the primary detection mechanism by examining the process tree that launched the current program.

Ancestry Walking Implementation

// Conceptual flow based on witr's internal/source/ssh.go
func detectSSH(ancestry []model.Process) *model.Source {
    hasSSHD := false
    
    for _, proc := range ancestry {
        if proc.Executable == "sshd" {
            hasSSHD = true
            break
        }
    }
    
    if !hasSSHD {
        // Fall back to environment variable detection
        return detectSSHEnv()
    }
    
    // Build source description from SSH connection data
    return &model.Source{
        Type:        model.SourceSSH,
        Description: buildSSHDescription(),
    }
}

The ancestry slice is populated by proc.GetAncestry(), implemented in internal/proc/process.go with operating-system-specific variants. This function walks from the current process ID up to the init system, collecting executable names at each step.

Why Ancestry Detection Matters

Scenario Environment Variables Ancestry Detection
Direct SSH login Present ✓ Works
ssh → sudo -i Stripped ✓ Still detects sshd parent
ssh → su Modified ✓ Still detects sshd parent
Container with --pid=host Missing ✓ Detects host's sshd
Container without host PID Missing ✗ Falls back to env vars

Environment Variable Fallback Detection

When process ancestry does not reveal sshd, the detection logic examines three environment variables defined by OpenSSH:

Variable Format Information Extracted
SSH_CONNECTION client_ip server_ip client_port server_port Remote IP address, connection endpoints
SSH_CLIENT client_ip client_port server_port Remote IP address (simpler format)
SSH_TTY /dev/pts/N Pseudo-terminal device name

According to the source code analysis, detectSSH uses SSH_CONNECTION preferentially when building session descriptions because it provides the most complete connection metadata, including the remote IP address that witr displays to users.

Environment Parsing Logic

// Based on detection flow in internal/source/ssh.go
func detectSSHEnv() *model.Source {
    conn := os.Getenv("SSH_CONNECTION")
    client := os.Getenv("SSH_CLIENT")
    tty := os.Getenv("SSH_TTY")
    
    // Must have at least one SSH indicator
    if conn == "" && client == "" && tty == "" {
        return nil
    }
    
    // Prefer SSH_CONNECTION for IP extraction
    remoteIP := extractIP(conn)
    if remoteIP == "" {
        remoteIP = extractIP(client)
    }
    
    user := os.Getenv("USER")
    host, _ := os.Hostname()
    
    return &model.Source{
        Type: model.SourceSSH,
        Description: fmt.Sprintf("SSH session from %s (%s@%s)", 
            remoteIP, user, host),
    }
}

Integration with the Detection Pipeline

The SSH detector integrates into witr's broader source detection system through internal/source/detect.go. This orchestration file calls multiple specialized detectors in priority order and returns the first successful match.

Detection Orchestration

// Representative structure from internal/source/detect.go
func Detect(ancestry []model.Process) *model.Source {
    // Priority-ordered detection attempts
    detectors := []func([]model.Process) *model.Source{
        detectContainer,  // Docker, containerd, etc.
        detectSSH,        // SSH sessions
        detectSystemd,    // systemd units
        detectWSL,        // Windows Subsystem for Linux
        // ... additional detectors
    }
    
    for _, detector := range detectors {
        if src := detector(ancestry); src != nil {
            return src
        }
    }
    
    return nil // Unknown source
}

SSH detection typically executes after container detection because containers propagated over SSH may want to identify as the container environment rather than the SSH transport.

Source Model and Type Constants

The detection results are typed through model.Source, defined in pkg/model/source.go. This structure standardizes how witr represents execution contexts across all detector implementations.

// From pkg/model/source.go
package model

type SourceType string

const (
    SourceSSH       SourceType = "ssh"
    SourceDocker    SourceType = "docker"
    SourceSystemd   SourceType = "systemd"
    SourceWSL       SourceType = "wsl"
    // ... additional types
)

type Source struct {
    Type        SourceType
    Description string
    // Additional metadata fields
}

The SourceSSH constant enables type-safe switching throughout the codebase, while the Description field carries human-readable context like "SSH session from 203.0.113.42 (alice@myhost)".

Practical Usage and Verification

CLI Output Example

When running witr with verbose output, users see automatically detected session information:

$ ssh alice@remote-server
alice@remote-server:~$ witr --verbose
...
Source: SSH session from 198.51.100.22 (alice@remote-server)

Programmatic Verification

For applications building on witr's detection logic:

package main

import (
    "fmt"
    "log"
    "os"
    
    "github.com/pranshuparmar/witr/internal/proc"
    "github.com/pranshuparmar/witr/internal/source"
)

func main() {
    ancestry, err := proc.GetAncestry()
    if err != nil {
        log.Fatalf("Failed to get process ancestry: %v", err)
    }
    
    src := source.Detect(ancestry)
    if src == nil {
        fmt.Println("Running in unknown/localhost environment")
        os.Exit(0)
    }
    
    fmt.Printf("Detected source type: %s\n", src.Type)
    fmt.Printf("Description: %s\n", src.Description)
    
    if src.Type == "ssh" {
        fmt.Println("⚠️  Running via remote connection - " +
            "consider connection stability for long operations")
    }
}

Platform Coverage and Edge Cases

The detection implementation accounts for platform differences in process inspection:

  • Linux: Parses /proc/[pid]/stat and /proc/[pid]/status for parent relationships
  • macOS: Uses sysctl with KERN_PROCINFO and libproc
  • Windows: Implements alternative detection primarily through environment variables due to different process semantics
  • BSD variants: Similar to Linux with /proc or kvm interfaces

The environment variable fallback proves especially valuable on Windows, where OpenSSH's native port sets the same SSH_CONNECTION and SSH_CLIENT variables as Unix systems.

Summary

  • Process ancestry is the primary detection method: internal/source/ssh.go walks parent processes seeking sshd, making detection resilient against sudo and su environment stripping.
  • Environment variables provide reliable fallback: SSH_CONNECTION, SSH_CLIENT, and SSH_TTY supply session metadata when process inspection is unavailable or inconclusive.
  • internal/source/detect.go orchestrates priority-ordered detection: SSH detection integrates into a broader system that prioritizes more specific contexts like containers.
  • model.Source standardizes results: The type system in pkg/model/source.go ensures consistent handling across witr's CLI, API, and library interfaces.

Frequently Asked Questions

How does witr handle SSH sessions inside containers?

When a container runs with --pid=host or equivalent, witr's process ancestry detection can see the host's sshd and identify the SSH context. Without host PID namespace access, the container appears isolated and detection falls back to environment variable inspection, which will fail if variables were not propagated during docker exec or similar operations.

Can witr detect other remote terminal protocols like Mosh or Telnet?

The current implementation specifically targets OpenSSH's process and environment signatures. Mosh uses different environment variables (MOSH_CONNECTION) and a distinct wrapper architecture that would require separate detection logic. Telnet sessions lack standardized environment markers, making reliable programmatic detection substantially more difficult.

What happens when SSH_CONNECTION contains IPv6 addresses?

According to the source structure, witr extracts the remote IP component from SSH_CONNECTION using standard string parsing. IPv6 addresses in bracketed notation (e.g., [2001:db8::1]) are handled by the formatting logic in buildSSHDescription(), ensuring the display description remains readable regardless of address family.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →