Changelog · Changelogs
lockfile changelog
Unreleased #
v0.9.1 (2026-11-13) #
Fixed #
- A Go plugin whose manifest memory was between about 80 and 176 MiB (128 MiB, say) died at
start (exit 2): the sandbox's address-space limit (
RLIMIT_AS= memory + 1 GiB, since v0.3.0) fell in a range where the Go runtime cannot reserve its heap. The address-space headroom is now 4 GiB; the memory a plugin can use is still bounded byRLIMIT_DATA=memory, and the main-thread stack, whichRLIMIT_DATAdoes not count, is now fixed at 8 MiB (DavidGodefroid/lockfile#76).
v0.9.0 (2026-11-13) #
Builds on lockkit v0.19.0 (from v0.14.1).
Breaking #
- lockkit v0.18.0 (DavidGodefroid/lockkit#120): the status answer no longer carries
auto_approve, sostatusandsourcesno longer printauto: lockhub decides. A client that readsauto_approvegets false. - lockkit v0.18.0 (DavidGodefroid/lockkit#120): a config value that embeds a bearer token, an
Authorization:header or a URL with userinfo (https://TOKEN@host) is refused as a secret ("looks like it embeds a secret (a password or a token)"). - With a managed configuration (
/etc/lockfamily/managed.toml), a profile beyond its[lockfile.bounds], or a profile of yours named as a managed one, is refused and the console exits 3 (see Added). Without the file, nothing changes. - Plain
http://S3 endpoints are refused on a production profile, tolocalhostas well, which was accepted before (DavidGodefroid/lockfile#62).
Behaviour #
- Hub ticket limits: the console opens at most 30 tickets a human decides per 5 minutes
(was 10) and 30 pending ones (was 20); a ticket
lox run's auto mode decides leaves that count, and a safety cap of 300 tickets per window counts every one. The profile'stickets_per_window,ticket_windowandmax_pendingset those limits; more tickets, a shorter window or more pending tickets is a loosening, reviewed like the others (the typed profile name on production), a lowering applies at once. The status screen shows the limits in force (DavidGodefroid/lockkit#150). - lockkit v0.19.0: the hub screen's masking of object keys catches more phone formats and e-mail addresses with an apostrophe.
- Launcher: it mounts the machine's managed directory (
/etc/lockfamily,/Library/Application Support/lockfamilyon macOS) read-only on/etc/lockfamilyin its containers, aslox rundoes in its locks:lockfile managed show,managed check,doctor,profile,initand the other commands run from the host see the company's managed configuration. No directory: no mount; a file, a dangling link or a:in the path stops the launcher (DavidGodefroid/lockfile#69).
Added #
- Managed configuration: lockfile enforces the
[lockfile]section of the company'smanaged.toml(lockfamily#31):[lockfile.profiles.<name>]served as written (a profile of yours with that name is refused),[lockfile.defaults]for what your config leaves unset, and[lockfile.bounds]you may tighten, never loosen: production, source, hosts, buckets, credentials, timers, limits,max_workspace_bytesand marketplaces;[family] vault.requiredallows only Vault credentials. The console refuses a policy beyond them at start and at every poll, with no prompt, citing the managed key (blocked by company policy: …), and exits 3. Newlockfile managed showandlockfile managed check [--profile NAME](the checklox runruns before it starts the lock), and amanagedline inlockfile doctor. See docs/configuration.md#managed-configuration (DavidGodefroid/lockfile#63). - Placeholders typed by the human (plugin protocol 2): a plugin may declare up to 4
inputs(text,emailordigits, at most 1024 bytes) named in its question as${name}. The agent binds them to placeholders (--input 'pdf.contains.needle=${email}', MCPinputs); a literal is refusedE_LITERAL_INPUT. The human types the value inlox runat approval (no echo, sealed in the decision to a one-use key of the ticket, never auto-approved); the console holds it in memory for its session and replays it on other files without a new prompt (held_inputsin the plan answer,reusedand the replay count on the screen). A value that does not fit is refusedE_INPUT_INVALID, never echoed. The plugin reads its values on a sealed read-only memfd, fd 6; each (plugin, placeholder) pair counts as one answer. The value never reaches the agent, the audit (which names the placeholders), the hub or the disk (DavidGodefroid/lockfile#9). - Human-only detail: a protocol-2 plugin with
detail = truemay write up to 4 KiB on fd 7 (found on pages 4512, 12307), printed escaped on the console window only while clear results are on (Ctrl-O, level 0); never to the agent, the audit or the hub. - Plugins get
LOCKFILE_PLUGIN_PROTOCOLset to their manifest's protocol;statusreports plugin protocol 2. - Public-key trust roots for unofficial marketplaces (DavidGodefroid/lockfile#68, lockkit
v0.18.0's key mode):
marketplace add NAME --index URL --public-key cosign.pubtrusts a cosign public key (ECDSA P-256/P-384, Ed25519, RSA >= 2048) instead of a keyless identity, never both. The index, manifests and binaries are checked withcosign verify-blob --key, a Rekor entry required.public_keyin the config holds the key inline (the base64 body of the PEM, on one line), so the config's approval covers it; it is checked at load, never quoted in an error, and shown as its SHA-256 fingerprint (public_key_sha256in JSON). A changed key is a loosening on:review. - Local S3 store under
lox run: an S3endpointmay be plainhttp://tohost.docker.internal(the host, aslox runmaps it), as it was tolocalhost, so a MinIO or LocalStack without TLS on your machine works from the console's container;lox run's allowlist reaches it. The policy review and the status screen showWARNING the connection to the S3 endpoint HOST:PORT is NOT encrypted, andlockfile doctorandprofile add(both run bylox add) warn too. An endpoint onlocalhostor a loopback address is flagged the same way: underlox runit is the lock's own container, usehost.docker.internal(DavidGodefroid/lockfile#62).
Fixed #
- Several lockfile locks, one MCP server: when lea mounts several lockfile locks and neither
--instancenorLOCKFILE_INSTANCEchooses one,lockfile mcp(the one serverlockfile initwrites) and the agent commands no longer exit withseveral lockfile locks: they reach every lock, list the profiles of all of them, and route each call to the lock that serves its profile.lockfile mcpalso starts when the instance cannot be resolved, and every tool,lockfile_statusfirst, says why, instead of a closed connection (DavidGodefroid/lockfile#60). - Official marketplace unreachable: a failed download of the official index or of its
signature (an HTTP error such as 404, a host that does not answer) now says that the
official marketplace is unreachable or not published and gives its URL, instead of a bare
HTTP 404. A dailyofficial-indexworkflow checks that the index answers and verifies against the pinned identity (lockfile#61). - Tests: the thread-cap sandbox test no longer starves plugins started by other test binaries under the same uid (DavidGodefroid/lockfile#72).
v0.8.1 (2026-11-07) #
Builds on lockkit v0.14.1 (from v0.13.0). No behavior change: container labels, files
(up.json, .lockfamily/) and flags stay the same.
lox runreplaceslox up(deprecated since lockhub v0.10.0) in every user- and agent-facing text: the docs, the agent assets the binary wires (skill,AGENTS.mdinstructions, Cursor rule, MCP instructions), the launcher's help and errors, the Go messages (console,doctor,profile add) and lockkit's shared hints (StartHint,ErrNoHub). A wired agent now asks the user to runlox run(DavidGodefroid/lockfile#57).
v0.8.0 (2026-11-06) #
Builds on lockkit v0.13.0 (from v0.11.0); lockhub as v0.6.0. No lockfile code change; what the console's users will see:
- Production profiles: the profile name typed instead of
yfor every widening the console decides itself: the policy review at start and a loosening at:review(Type the profile name "prod" to apply them:; turningproductionoff is such a loosening) and:unmask(Type the profile name "prod" to allow it:). Outside production they still takey. When the start-up review was approved with the name, the secondType the profile name "P" to continue:prompt is no longer asked (DavidGodefroid/lockkit#8). - Idle timeout: only the human's input and the backend's methods count as activity. An
agent polling
status(orhello,pii.list,pii.add,change.request) no longer keeps an unattended console open untilmax_session(DavidGodefroid/lockkit#10). pii.addaudited with the peer: thetightenedrecord (and anyfailed/unsavedrecord of that change) carriespeer_uid,peer_pid,peer_userand thepatternadded, so an agent's addition is told apart from a tightening made in the files (DavidGodefroid/lockkit#6).- A policy change of the credentials mode or Vault path ends the console session, a tightening as well as an approved loosening, instead of keeping the old setting live (DavidGodefroid/lockkit#7).
- From lockkit v0.12.1: a plain restart of an approved, unchanged production profile no longer
asks its name again; the console records the question it waits for next to its socket, so
lox attachand lea show it.
v0.7.0 (2026-11-01) #
- Each release tag bumps the
lockfileformula of the Homebrew tap (brew install davidgodefroid/tap/lockfile): the release workflow's last job dispatches the bump once the release is signed (DavidGodefroid/lockfamily#8). It is skipped while theTAP_DISPATCH_TOKENsecret is not set. --instance NAME: reach one lock among several of this type (lea mounts each at /run/lockfile/NAME). Every agent-side command andmcptake it; it defaults toLOCKFILE_INSTANCE, then to the one lock listed inLEA_LOCKFILE_INSTANCES.credentials = "vault": the console asks the Vault token at its start (masked), underlox uptoo.
v0.6.0 #
The console is hub-attached only (DavidGodefroid/lockkit#110): lox up starts it, the
launcher reaches it through .lockfamily/up.json (lockhub#78), and the launcher verbs of
lockkit v0.10.0 (DavidGodefroid/lockkit#111). Builds on lockkit v0.10.0; lockhub v0.3.0 or
later.
-
The launcher verbs of lockkit v0.10.0 (DavidGodefroid/lockkit#111), the same in every lock, which
lox initdrives (docs/usage.md):lockfile profile add [--non-interactive] [--json] [--project DIR] [--profile NAME] [--production] [--credentials ask|vault [--vault-path P]] --source local|s3|sftp <source flags>. Breaking: the profile name is--profile NAME(defaultwithout it), no longer a positional argument, which is refused, as any positional argument is;profile addis the onlyprofilecommand. On a terminal it asks for what the flags did not give (the prompts on stderr); with--non-interactive, or without a terminal, a missing value is a usage error naming its flag.--credentialstakesaskorvault(keychain,noneand anything else are refused; the interactive form still offersnonefor an SFTP key without a passphrase); alocalsource takes no--credentials. Its arguments go throughverbs.Parse: a flag named like a secret, a value that embeds a password and a control character are refused and never quoted. A profile already in the file is never rewritten (exists).--jsonprints one line,verbs.Profileorverbs.Error(codesusage,secret,exists,failed).--project DIRwrites that workspace's.lockfile/config.toml(the launcher passes it on as an absolute path).lockfile doctor [--fix [--yes]] [--json]:--fixasks before wiring each agent unless--yes, and refuses without a terminal and without--yes;--yeswithout--fixis a usage error.--jsonprints oneverbs.Doctor(ok,fixed,checkswithfix) instead of the array of findings, or averbs.Errorwhen doctor could not check.lockfile init <agent>: the agent is required (no defaultclaude).
-
lockkit v0.10.0.
-
The profile and the plugins reach the console
lox upruns (#44).lockfile profile addandlockfile marketplace add|remove|listedit the workspace's.lockfile/config.toml, as you (the launcher mounts only.lockfile/, made0700; the file0600), wherelox upreads the lock's profile, reviews it and copies it read-only to the console's~/.config/lockfile; a.lockfileorconfig.tomlthat is a link, someone else's, or that others may write is refused, aslox upwould. The workspace is the one the agent commands reach (.lockfamily/up.json), else the nearest.lockfamily/, else the current directory.lockfile plugin,audit,forgetanddoctorrun with the home of the consolelox upruns (lockfamily-<hash>_<instance>-home) and its profile copy, read-only, once Docker shows both are lox up's (compose's labels), with--instance NAMEfor one of several; withoutlox up,plugin,auditandforgetexit 2 and say why. The fixedlockfile-homevolume is no longer used by them (docs/docker.md). The quick start installs the plugins afterlox up. -
The agent commands no longer hang when the current directory was removed: they exit 2.
-
The agent commands reach the console
lox upruns (lockhub#78). The launcher'smcp,status,sources,plugins,plan,run,answers,releaseanddoctormount the socket volume of the workspace's lockfile lock thatlox upnames in.lockfamily/up.json, read-only, once Docker confirms it is lox up's (the checks of lea; no container lox up did not start may mount it read-write), and no longer the fixedlockfile-run: withoutlox upthey exit 2 and say to run it (doctorruns without a socket and says why). With several lockfile locks,--instance NAMEpicks one (in.mcp.json:"args": ["mcp", "--instance", "NAME"])..mcp.jsonis unchanged (docs/docker.md). -
lockfile consoleis launched bylox uponly:--hub,--hub-key-fileand--instanceare required, and without them (or with only some) it refuses before the config, the plugin store or the terminal is touched ("the console is launched bylox uponly", exit 64).--autoand auto mode (:mode, Shift+Tab) are gone: plans are decided on lockhub, the console's own decisions (its policy) stay at its terminal. The standalone start that withdrew the plans hub mode had left pending is gone too. -
A run by plan id answers the fixed text
in hub mode, run the ticket file.plan answered(rpc.MsgRunTicket), now in the closed list with the pending answer (ticket, lock, instance) (docs/security-model.md). -
The quick start (README, docs, site) goes through
lox add lockfile <instance>,lox up,lox attachandlox approve; the profile and plugin setup steps are unchanged. -
Tests: every test console (
internal/testenv/consoletest, the CLI's, the MCP server's,test/e2e,test/docker) runs attached to a fake lockhub (hubtest) and approves its plans there. Builds on lockkit's hub-only console.
lockkit v0.9.1 (from v0.9.0): a console is started by lox up only, and the agent's hints say so.
- The no-console text (
lockfile_sources,lockfile statusandlockfile_statuswith no console running, a call without a profile when none runs) ends with lockkit'sclient.StartHint, "the console is started bylox uponly: ask the user to runlox up", no longerlockfile console --profile NAME. - The
unknown_profilehint no longer nameslockfile console --profile P, which refuses to start. It covers both causes: "if profile P exists, its console is not running: ask the user to runlox up; otherwise use a profile listed by lockfile sources, or ask the user to set up profile P and declare it (lox add lockfile INSTANCE --profile P), then to runlox up" (P only when it is a valid profile name, else NAME). clientcmd.StartCommandis lockkit's,lox up, and takes no profile.- The agent instructions
lockfile initwrites (skill, AGENTS.md, the Claude Code note) and the MCP instructions say plans are decided on lockhub (lox approve, lea's deck, a paired phone), answerpendingwith a ticket and run by that ticket, no longer that they are approved in the lockfile console. They,lockfile doctor's config check and the security model (docs, site) saylox upwhere they said to start or use thelockfile consoleterminal. - The quick start (README, docs/docker.md, site) declares the lock with its profile,
lox add lockfile samples --profile samples(without--profile,lox upservesdefault), andlockfile profile addprintslox add lockfile INSTANCE --profile P.
v0.5.3 #
lockkit v0.8.1: the hub URL is http(s)://host[:port] only, and lockfile prints lockkit's
hub errors as they are (no typed value in them).
- lockkit v0.8.1 (from v0.6.0). The
--hubURL ishttp(s)://host[:port]only: a path (https://h/lockhub, which never reached a working hub: lockhub serves/v1/at its root), query, fragment, IPv6 zone, user or password, a host that is not a DNS name or an IP address, or a port outside 1-65535 is refused naming--huband the allowed form, never the URL. The console's start line names the hub in that form, and a hub that cannot be reached is named by scheme and host, no longer by the whole request URL. - lockkit's hub errors are printed as they are: lockkit v0.8.1 quotes no typed
--hubURL,--instance,--require-deviceclass orhub-keyflag, so lockfile no longer replaces those values with<value>(the workaround of v0.5.2 is gone). The no-echo tests cover a token in the hub URL's userinfo, path, query, fragment, port and zone without it. One case remains, by design: a hub host that is a valid DNS name (http://some_name) is shown as the hub, in the plain-http refusal and on the console's start line, since a host cannot be told apart from a token. - The hub screen's personal data (
email,phone,iban,card) is also found right after a JSON\n,\tor\u00XXescape, read as the blank or delimiter it stands for (lockkit v0.8.0): a key such as{"msg":"tel:\t0475123456"}is now masked. lockfile implements none of lockkit v0.7.0's console product hooks and turns on none of v0.8.0's new detectors, so the console is otherwise unchanged.
v0.5.2 #
Errors never quote a typed config or CLI value (a token pasted in the wrong key, a URL carrying one).
- Errors no longer quote a value typed in the config or on the command line: a pasted
token or a URL with one (userinfo, path, query) never reaches the terminal, a log or an
agent. A download error names the marketplace and the file (
marketplace acme: index: download: HTTP 404,... connection refused), not the URL, and keeps only the kind of a transport error (its host and address are dropped); an invalid plugin name or version, marketplace name (--marketplace,marketplace remove),--since, source kind, size, or manifest name, version or media type,--allowed-uids, an unknown command or agent (lockfile init), or a profile name (profile add,forget --profile,console --profile) is refused naming the key and what is allowed. A plugin or marketplace name is quoted only once it is checked to be one, and theunknown_profilehint names the profile only when it is a valid profile name. Errors of lockkit v0.6.0 that quote a flag value (--hub URL,--instance,--require-device,hub-key --instance) are printed with that value replaced by<value>, and lockkit'sinvalid profile name %qandunknown agent %qare not reached (lockfile checks first). - A marketplace
index_urlwith a query or fragment is refused, as one with userinfo was: a token there would be shown bymarketplace addandmarketplace list(and the signature is fetched atindex_url+.sigstore.json).
v0.5.1 #
lea's agents reach the release image's console: console --allowed-uids (lea#226).
- The
ALLOWED_UIDSbuild argument refuses root (0), leading zeros, the console's uid (10031) and 65534 (nobody), as lockmq's does (it took0, which served root peers). lockfile doctoras a uid with no account here (an agent in a container of its own) warns that a console serves it only when started with--allowed-uids, instead of failing.lockfile console --allowed-uids UID,...(lea#226): these uids may connect besides the members oflockfile-clients, asallowed_uidsof the image'ssystem.toml(theALLOWED_UIDSbuild argument) does, for that console only, with the release image unchanged: an agent in a container of its own whose uid has no account in the console's image (lea's agents run as the host user's uid). Set only by whoever starts the console (the human: the launcher passes it on,lock upof lockhub v0.2.4 a lock's reviewedallowed_uids); root, leading zeros, 65534 (nobody) and the console's own uid are refused; every isolation check sees the uids (a session or terminal of one is the agent's), and the console prints them at start.
v0.5.0 #
Hub mode on lockkit v0.6.0: expired tickets are approved again, reveal from a local facade, and a console restarted without the hub withdraws its hub plans.
- Hub mode, renewals (#31): a plan whose object could not be fetched (
unfetched, nosha256input) is renewed withsha256uncheckedtoo, never shown checked and unchanged. A run whose expired ticket cannot be asked again prints and audits the cause (reason, lockkit'shubclient.AnswerCode: e.g.policy_changed,recheck_failed,not_renewable; the agent still hears the outcome only); a renewal stored but not sent to the hub yet says so.docs/usage.mdlistsunfetchedamong the plan inputs. New end-to-end tests: an unfetched plan's renewal, a renewal whose content changes before its approval (stale), a policy tightened on disk (policy_changed). lockfile console's usage and help list--hub-plain-http(the flaglock uppasses for itshttp://lockhub:8443);docs/usage.mdsays when it is needed.- Site: the plugin sandbox hardening the site still called "coming in v0.2.1" shipped in
v0.3.0 (System V IPC, POSIX message queues and signals to other processes denied;
/prochidden from other accounts, or a private pid namespace); the security page says so, the FAQ no longer lists it. - Docs and site: no stale version claims left (the
v0.2.xpre-release banners and footers,plugin_yanked"not produced yet" — it is since v0.3.0 —, "keyless signers only in v0.2", "lockkit v0.2.1" in the public-key refusal messages); the README status mentions hub mode. The seccomp lists nameexecveatandmemfd_createin place of a "secondexecve" that no filter denies (Landlock lets only the plugin's own binary execute). - Builds on lockkit v0.6.0 (was v0.3.1). Hub mode: a ticket lockkit reports expired
(
ticket.ErrExpired: an approval the console can no longer use) is no longer answeredapproval_timeout: the run asks it again (console.Hub.RenewResp) and the agent hearspendingwith a new ticket that renews the old one. Plans record their plan-hash inputs (Binding.Inputs:profile,key,handle,sha256,plugins,pins,unfetched) and the console recomputes them for a renewal (SetRecompute, the run's recheck: policy in force and installed plugins; the content's sha256 isticket.UncheckedInput, nameduncheckedon the renewal and never one click, with the stored plan hash: the run checks it when it fetches again), so the human sees which moved. The recompute is given to the hub before any request (console.Options.OnHub) and reads the policy in force as the console saved it: a renewal asked from the hub right after a start is checked too. Renewals by the agent are audited (renew); reveals and renewals asked from the hub are audited by lockkit. lockfile's tickets areread_pii, which lockkit never ages (no TTL): the path is reached by a decision the console's clock refuses as expired. - Reveal (Ctrl-O on a local facade) and
--facade-keys-filecome withlockfile console's hub flags; a console restarted without--hubwithdraws its pending hub plans and refuses their ticketswithdrawn(tested end to end).
v0.4.0 #
Hub mode: plans can be decided on lockhub instead of the console's own prompt.
Hub mode #
lockfile console --hub URL --hub-key-file FILE --instance NAME: plans are decided on lockhub.file.planfetches and hashes the content at once, stores the plan (content sha256, key or handle, plugins, pins, policy) and pushes a signed ticket with a masked screen; the agent hearspendingand runs the ticket once decided (file.runwithticket,lockfile run TICKET,lockfile_run). The run verifies the decision, the policy and the plugins, fetches again and refuses changed bytesstalebefore any plugin. Decisions are audited with the ticket, approver, facade and hub signature.lockfile hub-key --instance NAMEprints the instance key to pin. Plan and run carry the agent's lea name and the hash of its token (requester; the raw token stays in the agent's process, lockkit#71).- Builds on lockkit v0.3.1 (was v0.2.1). An
http://hub other than a loopback address needs--hub-plain-httpand must be a service name without a dot or a private address; redirects from the hub are refused and the hub state file must be private (lockkit#72).
Plans bound to a content hash (groundwork for hub mode) #
- A plan can carry the sha256 of the bytes it was approved on. The run spools the object
into the console's workspace and hashes the bytes it writes; when they differ the spool
is released before any plugin runs and the plan is refused
stale(E_STALE, retry rewrite: plan again). The plugins read only the spool, so the bytes they answer on are the hashed ones. Hub mode (above) sets it.
v0.3.0 #
Docker-only: every command runs in the image through the lockfile launcher, the image
has its own account ids, and the host install code is gone. Upgrade from v0.2.x with
scripts/install.sh (it installs the launcher), then run lockfile console once: the
launcher moves the existing volumes to the new ids (docs/docker.md, Migrating).
lockfile's own account ids #
- The image's accounts move from locksql's ids to lockfile's own: group
lockfile-clients10000 → 10030, consolelockfile10001 → 10031,agent10002 → 10032, so that each Lock has its own. - The launcher runs
lockfile migrate-idsbeforeconsole,doctor,profile,plugin,marketplace,auditandforget: in a throwaway container, as root withCAP_CHOWNandCAP_DAC_READ_SEARCHonly, under the seccomp profile, with no network and a read-only root, it gives the files oflockfile-runandlockfile-homethat hold an old id the new one, and leaves the others alone (idempotent). It refuses any account but root and any argument, and stays for one release.
Breaking, migration: volumes of a previous release are migrated by the launcher.
Without it, run migrate-ids once as docs/docker.md shows. An agent container that
reached the console with gid 10000 now needs gid 10030.
The host install code is gone #
cmd/lockfile/install.go, the binary's host self-update andinternal/separateare removed: the image refused them since the Docker-only launcher.lockfile installandlockfile updatein the image still print the hint to the launcher (scripts/install.sh,lockfile updateon the host, docs/docker.md).lockfile doctor's separation checks no longer tell you to runsudo lockfile install: they point to the launcher and its image.
lockkit v0.2.1 #
- lockfile now builds on lockkit v0.2.1 (was v0.1.0), the version locksql and lockhttp use.
No API change affects lockfile: it does not show clear results, read Vault with a held
token or use
pii, so its behaviour is unchanged.
One lockfile command, backed by Docker #
Every lockfile command now runs in Docker, through one host command: the lockfile
launcher.
scripts/lockfile, a POSIX shell launcher, runs each command inghcr.io/davidgodefroid/lockfilewith the account and volumes it needs:consoleaslockfile, one container per profile (lockfile-console-<n>,LOCKFILE_CONSOLE_NAME), under the shipped seccomp profile (it refuses to start without it), with no capability,no-new-privileges, a read-only root and a small/tmp, 512 processes, 4 GB, no log driver,host.docker.internalmapped,VAULT_ADDR/VAULT_NAMESPACEpassed andVAULT_CACERT's file mounted read-only;profile,plugin,marketplace,audit,forgetanddoctoraslockfileonlockfile-home;initanddoctor --fixas you, with your home and the project; the agent's commands,mcpincluded, asagentwithlockfile-runonly. It never mounts the Docker socket, refuses paths with:, and splitsLOCKFILE_DOCKER_ARGSwithout globbing (localsources are mounted with it).scripts/install.shinstalls the launcher andseccomp.json(/usr/local/share/lockfile,LOCKFILE_SHARE_DIR), root-owned, after the cosign checks ofchecksums.txtand of the images against the release workflow run on that exact tag; the launcher runs the image by its verified digest (the two digests must differ). A newer installed launcher is not downgraded withoutLOCKFILE_ALLOW_DOWNGRADE=1.lockfile versionprints the launcher's and the image's versions;lockfile update [vX.Y.Z](an exact tag only) replaces the launcher and the profile the same way, then pulls the image.- The release workflow builds the image (and its
-vaultvariant) for amd64 and arm64 after the "tag is on main" guard, publishes it to ghcr.io and signs it with cosign. The archives ship the launcher andseccomp.json. - The binary refuses
installandupdate("lockfile runs in Docker").lockfile initwriteslockfile mcp, which now works as is. - CI drives the launcher against the built image, and
test/dockerruns the console as the launcher confines it.
Breaking, migration: install.sh installs the launcher in place of the binary.
Replace the docker run … lockfile … commands and docker run … lockfile mcp agent entries
of the former guide with lockfile … (the old ones keep working). A host install (sudo lockfile install) leaves the lockfile account and group, /etc/lockfile/, the socket
directory and the console account's home behind: see
docs/docker.md.
Docker image #
Docker is the deployment of lockfile, at parity with locksql (#10).
Dockerfile: a console image replaces the single-container demo. Static build with-X …product.Version=${VERSION}; accountslockfile(10001) andagent(10002), grouplockfile-clients(10000); root-owned binary, cosign and/etc/lockfile/system.toml(x11 = "refuse",allowed_uidsfrom theALLOWED_UIDSbuild argument);/run/lockfile(2750) and the console's home as volumes;/srv/lockfile(0700) for read-onlylocalsources; avaulttarget with the Vault CLI. Base images pinned by digest, cosign and the Vault CLI by sha256; nosudo, no shell by default.seccomp.json: the seccomp profile every console container runs under (Docker's default, which allows the sandbox's system calls).seccomp=unconfinedis no longer needed nor documented./procin a container: Docker cannot mount it withhidepid=invisible. Where the image's root-owned/etc/lockfile/container.tomldeclares a container of its own, the console accepts a plain/proconly in a pid namespace other than the host's, whose/procit is, that lists no process of an account other than root and the console's, with a seccomp filter on the console (seccomp=unconfinedis refused); checked at start, when a plan arrives and just before it runs (the sandbox too), refused otherwise (proc_not_hidden, the failed condition named).lockfile doctorreports it.- Documentation is Docker-only:
docs/docker.md(new), the README quick start,docs/usage.mdanddocs/security-model.md. Other deployment modes (lockfile install, the session and desktop shapes) stay in the code but are no longer documented. - CI: a
dockerjob builds the image, runs the sandbox tests in a container underseccomp.json, and runstest/docker(console and agent containers; sandbox active; peer refused;ALLOWED_UIDS; no Landlock,seccomp=unconfined,--pid=hostand an agent in the console's pid namespace refused).
v0.2.0 #
The console, the agent side and the human commands, on lockkit v0.1.0.
lockfile console --profile P: lockkit's console with lockfile's backend: plans validated from the request and the local install only, approval screens without file data, one run per approved plan (same peer, 10 minutes, unchanged policy), results released on the answer quantum, audit of every fetch and plugin outcome.- Agent commands
status,sources,plugins,plan [--run],run,answers,releaseand thelockfile mcpserver (seven tools); nothing outside the closed list of the security model reaches the agent, checked end to end over the socket. lockfile initwires Claude Code, Codex, Cursor and Gemini CLI.- Human commands:
install(separated setup, Linux),profile add,plugin install|update|remove|list|verify,marketplace add|remove|list,doctor [--fix],audit,forget,update. - Plugin and release signatures checked with lockkit's
update(cosign keyless). - Releases: GoReleaser, signed
checksums.txt,scripts/install.sh(requires cosign).
Not in this release: the setup wizard, a macOS console, public-key trust roots for
unofficial marketplaces, the website.
v0.1.1, v0.1.0 #
Core: sources (S3, SFTP, local), workspace, plugin sandbox (Landlock, seccomp), plugin store, marketplace client, answer runner.