lock family docs GitHub

lea · Teams of agents

Plugins and permissions in lea

Each projects directory's container has its own writable copy of your ~/.claude/settings.json and ~/.claude/plugins, made from yours on the host when it first starts. Inside lea, /plugin install, /plugin marketplace add and the marketplaces' auto-update work as on the host, on that copy, for every agent of that projects directory. Permission rules are the exception: they come from your host's settings.json only (below).

  • Kept across restarts. The copy lives in lea's data (state/claude/<id>/); lea stop and the next lea keep what the agents installed or allowed.

  • It follows your host while it is untouched. When you change your host's settings or install a plugin on the host, the next start takes it into a copy the agents did not change. When both changed, lea keeps the copy and says so once; to take the host's, remove the copy (the path lea prints) and restart lea.

  • Nothing is ever written back to your host. The host's settings.json and plugins are not mounted at all; lea reads them on the host to make the copy, and never copies your plugins' own data (plugins/data). To use a plugin you tried in lea on the host, install it there yourself (claude plugin install <plugin>@<marketplace>): lea has no path from an agent to your host's configuration, so a prompt-injected agent cannot plant a hook or a plugin that would run the next time you start claude outside lea.

  • lea's guards cannot be undone from there. lea's hooks (the shell guard and its hard denies, your grants, the approvals in the deck) are in Claude Code's managed settings, /etc/claude-code/managed-settings.json, root's in the image: Claude Code ranks them above the agents' settings and runs their hooks even when those set disableAllHooks. An agent that deletes lea's hooks or allows every command in its copy is still guarded. The same hooks also come in through --settings (an organisation's server-managed settings replace the file-based ones); Claude Code runs each once. lea doctor checks the managed settings in the image and in every running container, and that Claude Code is the version lea's CI verified them with.

  • Permission rules come from your host only. The managed settings make their own rules the only ones Claude Code honours (allowManagedPermissionRulesOnly), disable bypass mode (disableBypassPermissionsMode): an agent that writes allow rules into its copy or into a project's .claude/settings*.json gains nothing. lea sets no default mode of its own (#333): the agents start in the mode Claude Code starts in on your host (its own default, auto on the accounts that have it), or in yours when your settings.json sets one. A defaultMode an agent writes into its copy can change its own mode, never to bypass. At each start the container's root adds your host's own permissions (allow, ask, deny, additionalDirectories, and defaultMode unless it is bypassPermissions) to lea's, read from your host's ~/.claude/settings.json by lea on the host and mounted read-only, never from the copy. So a rule you add with /permissions inside lea, or the deck's "always", is not kept: add it with lea config claude (below) or to your host's settings.json, then restart lea (lea stop --all, then lea).

  • Your base settings for lea: lea config claude (#334). Without editing a file, and without touching your own Claude Code outside lea:

    lea config claude                           # every effective setting, with its source
    lea config claude defaultMode auto          # default, acceptEdits, plan, dontAsk or auto
    lea config claude defaultMode --unset       # back to your settings.json's, else Claude Code's
    lea config claude allow add 'Bash(make *)'  # allow, ask, deny, additionalDirectories
    lea config claude allow remove 'Bash(go *)' # lea's built-in rules and your host's can go too
    lea config claude env MY_TOOL_HOME /opt/tool
    lea config claude model opus                # also language, outputStyle, alwaysThinkingEnabled
    

    They are kept in claude.json next to your lea.conf (never in ~/.claude/settings.json), outrank your host's permissions, which outrank lea's image, and apply to the containers started from then on (lea stop --all, then lea). lea doctor lists them; lea config export never carries them (--include-personal neither). lea keeps what keeps it working and its guards effective, and refuses it with the reason: its hooks, allowManagedPermissionRulesOnly, disableBypassPermissionsMode (so no bypassPermissions: dontAsk or auto drop the prompts and keep the guards), the status line and its own variables (LEA_*, OTEL_*, SSH_AUTH_SOCK, GH_CONFIG_DIR, GLAB_CONFIG_DIR, the proxy's, telemetry and update switches). The command runs on the host only, and the agents' guard denies editing lea's configuration.

  • The guard fails closed. lea's PreToolUse hook denies a call it cannot check: lea missing, a crash or a timeout (onFailure: "block"), a bus that does not answer, a Claude Code without a lea agent identity (LEA_AGENT: run Claude in lea as an agent, from the deck or lea-agent run). The variables the hooks read that lea sets alike in every container (LEA_BUS_SOCK, SSH_AUTH_SOCK, GH_CONFIG_DIR, GLAB_CONFIG_DIR) are pinned in the managed settings and the --settings file, above an agent's own env.

The worst a compromised agent can do is change its own container's copy, which it could already do by running code in its container: there is no persistence on your host, and no wider permission. The agents of one container share one user, so they are one trust domain: an agent that names another agent of its container in LEA_AGENT is still guarded by the same hard denies and the container's grants.