Version 1.0.0
Prepare the terminal's session container
Load the AppArmor profile, choose the session image, adjust the limits: what the workstation must provide when the session sandbox runs on the workstation.
A session’s sandbox runs either on a dedicated execution host, which is the reference mode, or on the workstation, in an unprivileged container with no network. This variant suits workstation baselines that allow a container engine (security white paper V3, section 4.4). With a dedicated execution host, the workstation carries only the extension, and this page does not apply.
In the workstation variant, every command the agent runs from lemni through the
Bash tool executes in that container, not in your shell. This page describes
what the workstation must provide for the container to open, and what to do about
each refusal. Where execution takes place is described in
The life cycle of an agentic session.
What runs in the container
Section titled “What runs in the container”The container opens on the session’s first Bash tool call, once only. Subsequent commands run in it without reopening a container. It is destroyed at the end of the session, and nothing survives it (security white paper V3, section 4.2).
What the command finds there:
- the repository root, mounted at the same absolute path as on the workstation.
The command runs in the current directory of
lemni; a branch switch through the SwitchBranch tool moves that directory to the branch’s workspace, and a workspace outside the mounted root is refused to the command. The repository is the only writable volume (security white paper V3, section 4.4); - the
/bin/bash --noprofile --norc -cinterpreter, without your shell profile and without the aliases and functions it defines. The environment passed in is stripped of variables that carry code (BASH_ENV,ENV,SHELLOPTS,BASHOPTS,ZDOTDIR,BASH_FUNC_*). The product does not try any other shell; - the numeric ID of your account on the workstation, as
uid:gid, so that the files written belong to you. A container running as root on the project is refused; - a writable
/tmpof 512 MiB, which disappears with the container.
What it does not find there:
- the rest of the disk: your home directory, SSH keys and other repositories are not mounted;
- the network: the container is created with no network interface and no name
resolution.
npm install,pip installandgo mod downloadfail there; the dependencies the project needs are in the session image or in the repository (security white paper V3, section 4.1); - the workstation’s environment variables, keys and tokens: none is mounted
(security white paper V3, section 4.3). The product sets
HOMEitself, on the project root, andTMPDIR, on/tmp.
The locks placed on the container are the same at every opening, and no setting
removes any of them: all capabilities dropped, no-new-privileges, read-only root, the
engine’s system call filter, the lemniscate-session-agent AppArmor profile, and limits on memory,
processor and process count.
Load the AppArmor profile
Section titled “Load the AppArmor profile”On a Debian or Ubuntu host, the engine refuses to start a container whose AppArmor profile is not loaded in the kernel. The terminal relays that refusal in the Bash tool’s result, without running the command and without opening an unconfined session:
Session refused: the AppArmor profile "lemniscate-session-agent" is not loaded on this workstation.Lemniscate opens no session without it and never runs one unconfined. Nothing was left behind.
To install it once and for all (it survives reboots), from a checkout of the Lemniscate sources: sudo install -m 0644 durcissement/apparmor/lemniscate-session-agent /etc/apparmor.d/ sudo apparmor_parser -r /etc/apparmor.d/lemniscate-session-agentOn a managed workstation, its administrator installs it.The installed lemni command carries its own copy of the profile, in a apparmor
directory next to the program. When that copy is found, the first command in the
refusal cites it by absolute path, so both commands can be copied as-is from any
directory. When no copy is found, the refusal points to the source file,
durcissement/apparmor/lemniscate-session-agent, as above.
The two commands each have a role:
sudo install -m 0644 durcissement/apparmor/lemniscate-session-agent /etc/apparmor.d/sudo apparmor_parser -r /etc/apparmor.d/lemniscate-session-agentThe first copies the profile to /etc/apparmor.d/, from where the system reloads it at every
boot. The second loads it immediately, without a reboot. A apparmor_parser -r run on its own,
on the source file, loads the profile in memory only: at the next boot, the
profile is gone and the refusal comes back. On a managed workstation, the fleet
administrator installs the profile.
The refusal is kept for the lifetime of the process: once the profile is loaded,
restart lemni to open a session.
The embedded profile is an exact copy of the source file. It describes the mount layout the product imposes, not the project’s workload: one more compiler or package manager in the image does not make it wrong.
On a host without AppArmor
Section titled “On a host without AppArmor”On a host whose engine does not apply AppArmor, RHEL and its derivatives for
example, the container still opens. The terminal then writes, once, on its error
output, a notice that begins with [CONFINEMENT] NO MANDATORY ACCESS CONTROL. It names what remains in force
(capabilities, no-new-privileges, system call filter, read-only root, resource limits) and
what is lost: the second lock that refuses writes outside the session’s working
volume, and the named refusal of mount and ptrace. The product does not ship an
SELinux policy.
When the engine is missing, or does not apply seccomp
Section titled “When the engine is missing, or does not apply seccomp”The terminal queries the engine before opening the container. Two situations produce a refusal, and in both the command is not run:
- no engine responds: the refusal begins with
No container engine answeredand asks you to install a Docker-compatible engine; - the engine applies no system call filter, or its default seccomp profile is
disabled: the refusal begins with
This container engine does not apply a system-call filterand asks you to re-enable seccomp.
The engine queried is docker. To name another one, Podman for example, set LEMNISCATE_CONTAINER_ENGINE
to its program name.
Choose the session image
Section titled “Choose the session image”The session image is part of the delivered artifacts. Like the other container images, it is signed, and it reaches you by one of two paths (security white paper V3, sections 9.1 and 9.3):
- in connected mode, it is pushed to your private registry;
- in isolated mode, it is delivered on media, in the signed archive, then loaded into the container engine by your team.
In both cases, your team verifies the signature with the key you hold before making the image available to workstations. No public registry and no external package repository is involved: the product depends on no external service (security white paper V3, section 11.3).
When a project needs an extra compiler or tool, your team builds an image derived from the delivered image and publishes it in your private registry. The tools available to the agent are those in the image: the container has no network and installs nothing.
Name the image the session uses:
export LEMNISCATE_SESSION_IMAGE=registre.exemple/outils-projet:1.4If the engine does not find the image, the session does not open; the message
gives the cause reported by the engine and names LEMNISCATE_SESSION_IMAGE.
The named image is used as-is. It must provide two programs:
/bin/bash, which each of the agent’s commands is handed to. The terminal checks for it as soon as the container opens. In an image without bash, every command is refused with a message naming the image, the expected interpreter andLEMNISCATE_SESSION_IMAGE; no command is handed to/bin/shinstead, because the command’s security analysis reasons in bash;/bin/sh, which the product uses to keep the container alive and to interrupt commands.
A command that exits with code 127 (command not found) gets a note that begins
with [lemniscate] Command not found in the session image and names the image, so the cause is not looked for in your shell
profile.
Adjust the limits
Section titled “Adjust the limits”| Variable | Default | Accepted format |
|---|---|---|
LEMNISCATE_SESSION_MEMORY | 2g | a positive integer followed by a unit: 512m, 4g |
LEMNISCATE_SESSION_CPUS | 2 | a positive number, decimals accepted: 1.5 |
LEMNISCATE_SESSION_PIDS | 512 | a positive integer; no “unlimited” value |
The memory limit includes swap. A value in any other format, including zero, is refused at opening with a message naming the variable and the expected format. The default values stop a runaway process before it reaches the workstation; a project whose build needs more raises them.
Refused mounts
Section titled “Refused mounts”The container mounts the directory lemni was opened on. Three directories are
refused as the working root, with a message explaining why:
- the account’s home directory (
HOME): mounting it would give the agent your SSH keys, your access tokens for online services and your other repositories; - the machine root;
- a relative path: where the agent works does not depend on the directory of whoever started the program.
In all three cases, open lemni on the project directory.