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 example1.2.3).pre-release: integration branch for tested features before release. Tag release candidates from this branch (for example1.2.4rc1).- PyPI publishing uses one project (limited-shell) and accepts both stable and
rcversions. - CI (
lshell-tests) runs on pushes and PRs targeting bothmainandpre-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(lpathalias)policy-sudo(lsudoalias)
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-onlyrsync-backupdeploy-minimalreadonly-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/forbiddenpathsudo_commandsoverssh,scp,sftp,scp_upload,scp_downloadallowed_shell_escapeallowed_file_extensionsmessageswarning_counter,strictumask- 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_useris tracked with lock-protected session records; stale entries are cleaned automatically.max_background_jobsdenies new&jobs once the configured active count is reached.command_timeoutenforces a per-command wall-clock timeout (foreground and background commands).max_processesis applied via POSIXRLIMIT_NPROCon spawned command processes.- Best practice: keep
command_timeoutenabled whenevermax_processesis strict (especially1).
Best practices
- Prefer an explicit
allowedallow-list instead of'all'. - Keep
allowed_shell_escapeshort and audit every entry. Never add tools that execute arbitrary commands (for examplefind,vim,xargs). - Use
allowed_file_extensionswhen users are expected to work with a known set of file types. - Keep
warning_counterenabled (avoid-1unless you intentionally want warning-only behavior). - Use
policy-showduring 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:
- User section
- Group section
- 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 lshellman ./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
Metadata
Release files for limited-shell 0.11.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| limited_shell-0.11.1.tar.gz | 149.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| limited_shell-0.11.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 264.2 kB
Release files / limited_shell-0.11.1.tar.gz
| Download URL | limited_shell-0.11.1.tar.gz |
|---|---|
| Size | 149.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f21a4d227eaf4bb8f01c3c61e835b329d9e18c9e7edbbca1e7d201e7a4b0e2b5
|
|
BLAKE2b-256 checksum How to use checksums |
31a962153dbfa1fa280e1104181444593bc664449185b8594a7d11744fe03739
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 22, 2026.
Transparency logRelease files / limited_shell-0.11.1-py3-none-any.whl
| Download URL | limited_shell-0.11.1-py3-none-any.whl |
|---|---|
| Size | 115.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d3643d7d6a51ed815ec7c63e86207e31290444ff82bffad7c3587cffc52fabc1
|
|
BLAKE2b-256 checksum How to use checksums |
b08751081dcba6efcc2f30575e9c1cb18f7efed601348814c041f71769a3c492
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 22, 2026.
Transparency log