Skip to content

ghantoos/lshell

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

531 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PyPI - Version PyPI - Downloads GitHub Actions Workflow Status

lshell

lshell is a Python-based restricted shell that limits users to a defined set of commands, enforces path and SSH transfer controls (scp, sftp, rsync, ...), logs user activity, supports session/time restrictions, and more.

PyPI project page: https://pypi.org/project/limited-shell/

Installation

Install from PyPI:

pip install limited-shell

Prepare system resources (run as root once per host):

lshell setup-system --group lshell --log-dir /var/log/lshell --owner root --mode 2770

Build/install from source:

python3 -m pip install build --user
python3 -m build
pip install . --break-system-packages

Uninstall:

pip uninstall limited-shell

Branch and release workflow

  • main: stable release branch. Tag stable versions from this branch (for example 1.2.3).
  • pre-release: integration branch for tested features before release. Tag release candidates from this branch (for example 1.2.4rc1).
  • PyPI publishing uses one project (limited-shell) and accepts both stable and rc versions.
  • CI (lshell-tests) runs on pushes and PRs targeting both main and pre-release.

Quick start

Run lshell with an explicit config:

lshell --config /path/to/lshell.conf

Default config location:

  • Linux: /etc/lshell.conf
  • *BSD: /usr/{pkg,local}/etc/lshell.conf

Set lshell as login shell:

chsh -s /usr/bin/lshell user_name

For automated setup (including /etc/shells registration + user shell assignment):

lshell setup-system --set-shell-user user_name --add-group-user user_name

Generate a hardened scoped include file for a specific group and user directly from CLI flags:

lshell harden-init \
  --profile sftp-only \
  --group sftpusers \
  --user alice \
  --output /etc/lshell.d/sftp-only.conf

If --output is omitted, harden-init writes to /etc/lshell.d/<profile>.conf.

Policy diagnostics

Explain the effective policy and decision for a command:

lshell policy-show \
  --config /path/to/lshell.conf \
  --user deploy \
  --group ops \
  --group release \
  --command "sudo systemctl restart nginx"

Inside an interactive session:

  • policy-show [<command...>]
  • policy-path (lpath alias)
  • policy-sudo (lsudo alias)

Hide these built-ins if needed:

policy_commands : 0

Hardened profile generator

harden-init ships secure-by-default templates to bootstrap restricted accounts quickly:

  • sftp-only
  • rsync-backup
  • deploy-minimal
  • readonly-support

Examples:

# Show available templates
lshell harden-init --list-templates

# Print generated profile to stdout
lshell harden-init --profile readonly-support --stdout

# Validate rendering and sanity checks without writing files
lshell harden-init --profile rsync-backup --dry-run

# Show rationale for security controls
lshell harden-init --profile deploy-minimal --stdout --explain

# Generate scoped sections (no [default] section)
lshell harden-init --profile sftp-only --group sftpusers --user alice --stdout

Configuration

Primary template: etc/lshell.conf

Key settings to review:

  • allowed / forbidden
  • path
  • sudo_commands
  • overssh, scp, sftp, scp_upload, scp_download
  • allowed_shell_escape
  • allowed_file_extensions
  • messages
  • warning_counter, strict
  • umask
  • runtime containment: max_sessions_per_user, max_background_jobs, command_timeout, max_processes

CLI overrides are supported, for example:

lshell --config /path/to/lshell.conf --log /var/log/lshell --umask 0077

Runtime containment limits

Runtime limits are optional and disabled by default when set to 0.

max_sessions_per_user : 2
max_background_jobs   : 4
command_timeout       : 30
max_processes         : 64

Operational notes:

  • max_sessions_per_user is tracked with lock-protected session records; stale entries are cleaned automatically.
  • max_background_jobs denies new & jobs once the configured active count is reached.
  • command_timeout enforces a per-command wall-clock timeout (foreground and background commands).
  • max_processes is applied via POSIX RLIMIT_NPROC on spawned command processes.
  • Best practice: keep command_timeout enabled whenever max_processes is strict (especially 1).

Best practices

  • Prefer an explicit allowed allow-list instead of 'all'.
  • Keep allowed_shell_escape short and audit every entry. Never add tools that execute arbitrary commands (for example find, vim, xargs).
  • Use allowed_file_extensions when users are expected to work with a known set of file types.
  • Keep warning_counter enabled (avoid -1 unless you intentionally want warning-only behavior).
  • Use policy-show during reviews to validate effective policy before assigning it to users.
  • For pip installs, do not rely on installation side effects for system setup. Use lshell setup-system (or distro package post-install hooks) to create groups, /var/log/lshell, and login-shell registration.

Section model and precedence

Supported section types:

  • [global] for global lshell settings
  • [default] for all users
  • [username] for a specific user
  • [grp:groupname] for a UNIX group

Precedence order:

  1. User section
  2. Group section
  3. Default section

Example configuration

For users foo and bar in UNIX group users:

# CONFIGURATION START
[global]
logpath         : /var/log/lshell/
loglevel        : 2

[default]
allowed         : ['ls','pwd']
forbidden       : [';', '&', '|']
warning_counter : 2
timer           : 0
path            : ['/etc', '/usr']
env_path        : '/sbin:/usr/foo'
scp             : 1
sftp            : 1
overssh         : ['rsync','ls']
aliases         : {'ls':'ls --color=auto','ll':'ls -l'}

[grp:users]
warning_counter : 5
overssh         : - ['ls']

[foo]
allowed         : 'all' - ['su']
path            : ['/var', '/usr'] - ['/usr/local']
home_path       : '/home/users'

[bar]
allowed         : + ['ping'] - ['ls']
path            : - ['/usr/local']
strict          : 1
scpforce        : '/home/bar/uploads/'
# CONFIGURATION END

For full option details, use:

  • man lshell
  • man ./man/lshell.1

Testing

Run test services directly:

docker compose up ubuntu_tests debian_tests fedora_tests

Run full validation:

just test-all

Run only SSH end-to-end checks:

just test-ssh-e2e

Justfile usage

List commands:

just --list

Run distro-specific tests:

just test-debian
just test-ubuntu
just test-fedora

Run sample configs interactively:

just sample-list
just sample-ubuntu 01_baseline_allowlist.conf

Fuzzing parser/policy checks

Run Atheris fuzzing in Debian Docker (dependencies installed in-container):

just test-fuzz-security-parser 20000

Optional local run (if you want to fuzz outside Docker):

pip install -r requirements-fuzz.txt
python3 fuzz/fuzz_parser_policy.py -runs=20000

Contributing

Open an issue or pull request: https://github.com/ghantoos/lshell/issues

About

lshell is a Python-based restricted shell that limits users to a defined set of commands, enforces path and SSH transfer controls (SCP/SFTP/rsync), logs user activity, supports session/time restrictions, and more.

Resources

License

Security policy

Stars

Watchers

Forks

Contributors