TermUI.TermUtils (TermUI v1.0.0)

View Source

Safe terminal command execution utilities.

This module provides secure wrappers for executing terminal-related external commands (stty, test, etc.) with the following protections:

  1. Absolute path resolution - Commands are executed from known-safe locations
  2. Timeout enforcement - All commands have configurable timeouts
  3. Output validation - Command output is validated against expected formats
  4. Argument sanitization - All arguments are validated before execution

Security Model

This module follows a defense-in-depth approach:

  • Allowlist: Only known-safe commands are permitted
  • Validation: All arguments are validated against expected patterns
  • Timeout: Commands are terminated if they exceed time limits
  • Sanitization: Output is sanitized before return

Example

# Safe stty execution with timeout
case TermUI.TermUtils.safe_stty(["-g"], timeout: 1000) do
  {:ok, output} -> process_output(output)
  {:error, :timeout} -> handle_timeout()
  {:error, reason} -> handle_error(reason)
end

Summary

Types

Command options

Error reasons

Command execution result

Functions

Generic safe command execution for whitelisted commands.

Executes infocmp command with safety protections.

Executes stty command with safety protections.

Executes test command with safety protections.

Validates stty -g output format.

Validates stty size output format.

Types

options()

@type options() :: [timeout: pos_integer(), validate: (binary() -> :ok | :error)]

Command options

reason()

@type reason() ::
  :timeout
  | :command_not_found
  | :invalid_arguments
  | :not_tty
  | :execution_failed
  | :output_validation_failed
  | term()

Error reasons

result()

@type result() :: {:ok, binary()} | {:error, reason()}

Command execution result

Functions

safe_command(command, args, opts \\ [])

@spec safe_command(binary(), [binary()], options()) :: result()

Generic safe command execution for whitelisted commands.

Security

  • Only whitelisted commands are permitted
  • Timeout is enforced
  • Command is executed with minimal privileges

Returns

  • {:ok, output} - Command succeeded
  • {:error, :timeout} - Command exceeded timeout
  • {:error, :command_not_found} - Command not in whitelist
  • {:error, reason} - Other error

safe_infocmp(args, opts \\ [])

@spec safe_infocmp([binary()], options()) :: result()

Executes infocmp command with safety protections.

infocmp is used to query terminal capability information.

Returns

  • {:ok, output} - Command succeeded
  • {:error, :timeout} - Command exceeded timeout
  • {:error, reason} - Other error

safe_stty(args, opts \\ [])

@spec safe_stty([binary()], options()) :: result()

Executes stty command with safety protections.

Arguments

  • args - List of string arguments to pass to stty
  • opts - Optional keyword list:
    • :timeout - Maximum milliseconds to wait (default 5000)
    • :validate - Function to validate output (default: basic validation)

Returns

  • {:ok, output} - Command succeeded with validated output
  • {:error, :timeout} - Command exceeded timeout
  • {:error, :command_not_found} - stty not found in PATH
  • {:error, :invalid_arguments} - Arguments failed validation
  • {:error, :not_tty} - No controlling terminal is available
  • {:error, reason} - Other execution error

Example

{:ok, settings} = TermUI.TermUtils.safe_stty(["-g"])
{:ok, :done} = TermUI.TermUtils.safe_stty(["raw", "-echo"])

safe_test(args, opts \\ [])

@spec safe_test([binary()], options()) :: result()

Executes test command with safety protections.

The test command is used for terminal detection (e.g., test -t 0 to check if stdin is a TTY).

Returns

  • {:ok, output} - Command succeeded
  • {:error, :timeout} - Command exceeded timeout
  • {:error, reason} - Other error

Example

{:ok, _} = TermUI.TermUtils.safe_test(["-t", "0"])

validate_stty_settings(output)

@spec validate_stty_settings(binary()) :: :ok | {:error, term()}

Validates stty -g output format.

Stty -g returns settings in format like: "speed 9600 baud; rows 24; columns 80;" This is the output we later pass to stty for restoration, so we must validate it carefully to prevent command injection.

validate_stty_size(output)

@spec validate_stty_size(binary()) :: :ok | {:error, term()}

Validates stty size output format.

Stty size returns: "rows cols" (two integers)