Skip to content

Security Profiles

Each session runs under a security profile that controls capabilities, resource limits, filesystem access, network policy, and environment enforcement. Profiles are defined as .profile files in ~/.lash/profiles/.

ProfileDescription
defaultFull access. Config, other sessions, audit log, profile management.
scriptNo plugins, no job control, no admin. Suitable for non-interactive scripts.
ciRestricted spawn via allowlist. Resource limits and env denylist.
restrictedMinimal. No arbitrary processes, no redirects, no env mutation.

Profiles use INI-style sections. Each .profile file in ~/.lash/profiles/ defines one profile:

[profile]
description = CI/CD build environment
[inherit]
from = default
[process]
spawn_any = false
spawn_allowlist = make, cmake, gcc, git, cargo, dub
spawn_denylist = rm, dd
[filesystem]
sandbox = /home/ci/builds
read_only = /usr, /lib
read_write = /tmp
include_workdir = true
tmpdir = session
[network]
allow_hosts = github.com, *.npmjs.org
allow_ports = 443, 80
block_all_others = true
enforcement = enforce
[resources]
max_memory = 2G
max_cpu_seconds = 300
max_file_size = 100M
max_processes = 64
max_open_files = 256
[environment]
deny = LD_PRELOAD, LD_LIBRARY_PATH
force = SANDBOX=1, CI=true
mutation = true
safelist = PATH, HOME, CC, CXX
[capabilities]
file_redirect = true
plugin_access = false
config_modify = false
session_manage = false
job_control_own = false
audit_access = false
profile_manage = false
profile_switch = false
[monitoring]
enabled = true

Profiles can inherit from another profile using the [inherit] section. Child values override parent values. Capabilities use explicit-bit tracking so only fields you set override the parent.

[inherit]
from = ci
profile list # list all profiles and active profile
profile review <name> # show resolved settings for a profile
profile permissions [name] # show capabilities and resource limits
profile test <name> <cmd> # dry-run: would this command be allowed?
profile create <name> # create a new profile file
profile edit <name> # open profile in $EDITOR
profile reload # reload active profile from disk

The create, edit, and reload subcommands require the profileManage capability. Profile switching is handled by the audit plugin and requires the profileSwitch capability.

CapabilityDescription
spawnAnyAllow spawning any process from PATH.
spawnAllowlistComma-separated permitted commands (when spawnAny is false).
spawnDenylistComma-separated commands that are always denied.
fileRedirectAllow >, >> file writes.
fileSandboxRestrict writes to a directory.
envMutationAllow modifying env vars.
envSafelistComma-separated env vars that can be modified.
envDenylistEnv vars stripped from child processes before exec.
envForcedKEY=VALUE pairs injected into child processes.
pluginAccessAllow calling plugin methods.
pluginAllowlistComma-separated permitted plugins.
configModifyAllow modifying config at runtime.
sessionManageAllow managing other sessions (list, kill).
jobControlOwnAllow fg/bg/jobs for own jobs.
jobControlOthersAllow managing other sessions’ jobs.
auditAccessAllow audit commands and log access.
profileManageAllow creating, editing, and reloading profiles.
profileSwitchAllow switching the active profile.

Resource limits are enforced at the OS level on spawned processes:

LimitDescription
max_memoryMaximum memory per process (e.g. 512M, 2G).
max_cpu_secondsCPU time limit in seconds.
max_file_sizeMaximum file size a process can create.
max_processesMaximum number of child processes.
max_open_filesMaximum open file descriptors.

Set to 0 or omit for unlimited.

Filesystem restrictions are enforced via Landlock (Linux) or Seatbelt (macOS) through the armor library:

  • sandbox — root directory for sandboxed writes
  • read_only — paths allowed for read-only access
  • read_write — paths allowed for read-write access
  • include_workdir — whether the current directory is automatically added to read-write paths
  • tmpdir = session — creates a per-session temporary directory

Network restrictions use a transparent proxy with TLS SNI verification:

  • allow_hosts — hostnames or wildcard patterns (e.g. *.github.com)
  • allow_ports — TCP ports allowed for outbound connections
  • block_all_others — deny all traffic not matching allow rules
  • enforcementenforce (default) or warn (log but allow)

On Linux, a TUN device provides transparent interception. DNS queries are intercepted to enforce hostname-level policy. TLS connections are verified by SNI to prevent IP-based bypass.

When profile.auto_detect = true is set in ~/.lash/config, the daemon checks the session context and suggests switching to a matching profile. Dismissals are tracked per-session.

When a profile denies an action, the shell returns a Problem/Suggestion error:

Problem: This session (profile: restricted) cannot run 'curl'.
Suggestion: 'curl' is not in the allowlist. Switch to a different profile.