locklog

module
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 7, 2026 License: Apache-2.0

README

CI Latest release Apache 2.0

Let Claude Code, Codex, Cursor or Gemini CLI investigate your production logs.
The personal data and secrets locklog detects never reach the model without your approval.

Quick start · How it works · Safety model · What is not masked · Sources · Install · Docs · Website


locklog lets an AI agent find when and where something broke in your logs without reading them in clear. A console held by a human, in its own account and container, reads the logs. The agent asks over MCP and gets masked, bounded, typed answers: counts, patterns, traces, lines around a hit. Emails, IPs, card numbers, national ids and the other values locklog detects become pseudonyms (⟦email:7F3KQ2M9ZD4X⟧), and secrets become ⟦secret⟧, before anything leaves the console. Reads are free. Handing a clear value to the agent waits for your approval at lox run (lockhub) and is off by default on production.

[!NOTE] Early release. v0.2.1 is the current release: see the CHANGELOG for what it holds and what is still open. Built on lockkit. The design is public in docs/security-model.md; what the spikes measured is in docs/sources.md and docs/performance.md.

Why locklog?

Logs are where personal data hides in free text: an email in an error message, a client IP in an access log, a token in a header someone logged. An agent that runs journalctl, docker logs or grep, or queries Loki with its own credentials, sends all of it to the model provider.

Agent reads logs directly Agent with a log backend's own AI locklog
The agent can answer "when did it start?" ✅ ✅ ✅
Personal values and secrets masked before the model ❌ rarely ✅
The agent never holds the log backend's credentials ❌ ✅ ✅
Self-hosted, any agent that speaks MCP ✅ ❌ ✅
A clear value reaches the agent only with your approval ❌ ❌ ✅
Every call audited, without clear values ❌ varies ✅

Highlights

  • Masked by construction. Every record is masked inside the console before any predicate runs, and a predicate holding a clear email is refused (E_PII_IN_QUERY): the agent only ever works on the masked view. Equality on a pseudonym works, so "every record of this user" still does. (The opt-in prefilter, refused on production, is the documented exception.)
  • Investigation tools, not a query language. locklog_count (with buckets and first_last), locklog_patterns (new templates against a baseline), locklog_trace (one request across sources), locklog_context, locklog_search, locklog_tail. Text never goes to a backend.
  • Free within budgets, approved beyond. Reads are bounded per call and per hour. A call over a budget is refused before it touches data, and locklog_scan asks you once for the whole plan.
  • Your sources. Files (globs, rotation, gzip, zstd), journald (a pure-Go reader), Docker container logs and Loki; Elasticsearch and OpenSearch as a preview (their incident gates come with v0.3.0).
  • Read-only. Read-only mounts, a read-only root filesystem, an allowlist of backend endpoints, no Docker socket in any locklog container, no write path to any backend.

Quick start

Prerequisites: a Linux host with the logs, Docker ≥ 19.03 and cosign. locklog is one lock of the Lock Family: its console is started by lox run (lockhub), which you keep open in a terminal of its own, outside the agent; it starts the hub and the locks, shows every ticket and decides it, by its rules or by you.

brew trust davidgodefroid/tap                # Homebrew asks you to trust a third-party tap once
brew install davidgodefroid/tap/lockfamily   # or only this tool: brew install davidgodefroid/tap/locklog
cd ~/projects/shop                           # the project whose logs the agent investigates
lox init                                      # or: lox add locklog logs
lox run                                       # in a terminal of its own, kept open

lox add locklog and the locklog console in lox run come with the lockhub release that adds the locklog type (lockhub#84, #86, #87; lockhub v0.9.0 does not know it yet), and the tap's locklog formula, and locklog in lockfamily, with it. Until then, scripts/install.sh (Install) installs the launcher, whose verbs you can run yourself (docs/setup.md).

lox add locklog <name> runs locklog's three launcher verbs from the project (docs/setup.md):

  • locklog profile add --non-interactive --json --profile NAME: a profile of .locklog/config.toml from the logs this host has (files, journald, containers); never a secret;
  • locklog init claude: the agent's .mcp.json, skill and allow list (the free tools only);
  • locklog doctor --fix --yes --json: what an agent here can reach.

lox run then reviews the profile, starts the console (attach with lox attach <name> for its own questions: the policy review at its first start) and decides the tickets. Then, in another terminal, ask your agent: "when did the 502s start?". It calls locklog_count, locklog_patterns and locklog_trace: free, masked, audited. A scan, an unmask or an approved lookup becomes a ticket that lox run shows you and never approves on its own.

On the host, locklog mcp (the server .mcp.json names) refuses every call on a production profile (E_PRODUCTION_HOST_AGENT): the host agent is a trial for non-production data, since your agent runs on your own account, where it could still read the logs directly. Without lox run, it says to run lox run first.

For production

On production the agent must not be able to read the logs, the Docker socket or the backends itself (precondition P1 below). Run it:

  • with lea, which runs the agent in a container of its own with the lock's socket mounted;
  • or in its own container: docs/setup.md has a tested Dockerfile snippet for your agent image and the docker run line. The agent user must not have sudo.

Your agent CLI's own login (Anthropic, OpenAI, Google) is yours to set up.

How it works

 host (you)
   lox run (lockhub): the hub and the decisions, in a terminal of its own
     └─ console container (one per lock)   uid 10051 "locklog", no capability,
          read-only root, cpus min(2,nproc), memory 1g, logging none, hub-attached
          read-only, non-recursive binds of the approved log directories
          home volume:   pseudonym keys, local index, audit, rules (console only)
          socket volume: /run/locklog (2750 locklog:locklog-clients), one socket
                         per project and profile, <project-hash>-<profile>.sock (0660)
          network: the hub's; and the lock's egress network for Loki, Elasticsearch

 agent (yours: lea, your own container, or the host for the trial)
   MCP server `locklog mcp` → its project's socket, the lock's socket volume
   read-only (peer uid checked by the kernel)
  1. The console reads the approved sources, and only those. The sources are reviewed at the console's first start and on every loosening.
  2. For each call it builds a byte plan from the time range and the trusted selectors only (local index, Loki label stats, ES _count). Admission, cursors and stopping points depend on the plan, never on what matched.
  3. Every record of a planned chunk is masked first: secrets (⟦secret⟧, final), then your rules, personal field names, value detectors (email, phone, IBAN, card, BE/FR/NL/US national ids, public IP; person names and loose phone numbers opt-in) and the structured or text mode. Then the agent's predicates run on the masked view.
  4. The answer leaves through render caps as JSON under an "untrusted content" header, with the pseudonym epoch and a sealed cursor to continue for free.
Operation Who decides
status, sources, search, count, patterns, context, trace, tail within budgets free, audited
lookup (clear value → pseudonym, detector kinds only) free, rate-limited, audited; or approved each time with lookup = "approved"
pii_list / pii_add (agent masking rules, tighten only) free, capped, audited
locklog_scan (beyond a budget, approved once for its whole plan; its cursor continues free for 30 minutes, or its plan's bound when longer, at most 4 hours) you, a ticket at lox run (or the deck, a phone); auto-approved only if the profile's auto rules say so (auto.read)
locklog_unmask (the clear value of exact spans of one record, once) you, a ticket at lox run, the deck or a phone; auto-approved only if the profile's auto rules say so (auto.unmask); off by default on production
locklog_request_change (any loosening) you, at the console's :review
write, delete, admin does not exist

An unmask ticket shows the record masked, with the asked spans marked » «, the agent's reason and what a yes lets the agent link. To read a record yourself without sending anything to the agent, turn clear results on with Ctrl-O at the console (lox attach <name>): the records the agent's calls return are then printed in clear on the console's terminal (secrets kept redacted). Secrets are never unmasked. By default lox run's auto mode approves no locklog ticket: scans, unmasks and approved lookups wait for you. A profile's auto rules (auto = "full-auto", or read, unmask, lookup set to auto, ask or deny) change that, as a policy loosening you review; see docs/usage.md.

Safety model

locklog enforces what it owns:

  • the console's peer check: only the clients group (10050), the image's agent (10052) and the uids you list in allowed_uids reach the socket, never root or nobody;
  • its mounts (read-only, non-recursive), its network, and its image hardening; no Docker socket in any locklog container;
  • on production, backends with authentication over TLS, and a refusal when an endpoint answers a read without credentials;
  • the masking pipeline, the budgets, and approvals bound to the exact plan (single use; an unmask re-reads the record and is refused if anything moved since you approved it).

Like the other Locks, it relies on three preconditions it does not enforce:

  • P1 The agent cannot read the logs, the Docker socket, the backends' unauthenticated ports or the cloud metadata service directly, cannot become the console's account, and cannot write lox run's state. That is lea's job, or your own container setup's.
  • P2 Agents of one uid are one trust domain.
  • P3 Backend read credentials are held only by the console. Log shippers use write-only tokens.

locklog doctor, run where the agent runs, checks the common ways P1 fails. It reports and never prints a response body. Details: docs/security-model.md.

What is not masked

locklog masks the values it detects. It does not detect:

  • person names unless you enable the opt-in person_name detector (and names outside its heuristics when you do), usernames and logins in free text, postal addresses;
  • private and loopback IPs (infrastructure, never detected), user agents and other quasi-identifiers;
  • business ids (order and customer ids, kept so trace works);
  • base64 or hex-encoded content;
  • secrets without a known shape: short or low-entropy tokens, passwords in prose, session ids in URL paths, connection strings without userinfo.

It also leaks, by design:

  • pseudonyms link a person's records within an epoch (7 days by default), and lookup or an approved unmask links a known value to them;
  • how far a call got before its deadline reflects content density, rounded to plan granules, and so do aggregate byte volumes.

Pseudonymised data is still personal data under the GDPR: the model provider receives pseudonyms, timestamps and the fields locklog does not mask. Tighten with redact-unknown (the production default for structured logs), your own rules, and lookup = "approved".

Sources

Source Since Notes
Files v0.1.0 globs under approved roots, rotation, .gz, .zst; formats auto, json, logfmt, syslog, cri, docker-json, combined, plain; multi-line events
journald v0.1.0 pure-Go reader, field allowlist; core dumps, command lines and audit fields are never served
Docker containers v0.1.0 json-file logs by container name glob; the journald driver through journald. Podman through journald
Loki v0.1.0 trusted labels and structured metadata, server-side metric counts; text never sent to Loki
Elasticsearch, OpenSearch v0.1.0 (preview) trusted keyword fields, search_after, a read-only privilege check; their incident gates come with v0.3.0

docker compose up deletes a recreated container's json-file logs: use the journald driver or Loki for history across deploys.

Loki 3 users: see docs/loki.md on the default detected_level field: what Loki puts in it, and how locklog selects on it (locally, never as a Loki filter).

Masking benchmark

The leak harness plants canaries of every detected kind, in every format and position and in encoded forms (percent, JSON \u, HTML entities, look-alikes, zero-width characters), and checks every byte that leaves the console: what the engine returns (CI job suites, with the oracle and equivalence suites), and the IPC socket's replies, the audit file, the MCP tools' outputs and the hub tickets' screens (test/hub). 0 leaks, checked on every pull request. Throughput and the budgets derived from it are in docs/performance.md.

Install

The shortest path is the Lock Family's: install it with Homebrew, then let lox add locklog (or lox init) set up the lock from the project and lox run start it, in a terminal of its own, outside the agent:

brew trust davidgodefroid/tap                # Homebrew asks you to trust a third-party tap once
brew install davidgodefroid/tap/lockfamily   # or only this tool: brew install davidgodefroid/tap/locklog
cd ~/projects/shop
lox add locklog logs                          # or: lox init
lox run                                       # approvals live there

Both need the lockhub release that knows locklog (see Quick start). Without Homebrew, install.sh installs locklog's launcher alone:

curl -fsSL https://locklog.dev/install.sh | sh

install.sh installs the latest published release, or LOCKLOG_VERSION=vX.Y.Z. It verifies the release with cosign before it installs a root-owned /usr/local/bin/locklog, and the launcher runs the image by its signed digest. To read the script first:

curl -fsSLO https://locklog.dev/install.sh   # or raw.githubusercontent.com/DavidGodefroid/locklog/main/scripts/install.sh
less install.sh && sh install.sh

locklog update moves to another release; locklog version shows the launcher's and the image's versions.

Status

In active development: see the CHANGELOG. v0.1.0 shipped files, journald, Docker and Loki, and Elasticsearch and OpenSearch as a preview. v0.2.0 makes the console hub-only, started by lox run, with per-profile auto rules; v0.3.0 adds the Elasticsearch and OpenSearch incident gates and the masked export.

The Lock family

locklog is one of the Locks: locksql (databases), lockmq (message queues), lockfile (files), lockhttp (the web) and locklog (logs), on the shared engine lockkit, with lockhub to run them together and lea to run the agents.

License

Apache License 2.0: see LICENSE and NOTICE. The demo is generated by python3 scripts/brand/demo.py > site/assets/demo.svg.

Directories

Path Synopsis
cmd
locklog command
Command locklog lets an AI agent search and count across logs it never reads in clear: a console held by a human reads the logs, and the agent gets masked, bounded answers over MCP.
Command locklog lets an AI agent search and count across logs it never reads in clear: a console held by a human reads the logs, and the agent gets masked, bounded answers over MCP.
internal
agentassets
Package agentassets holds locklog's agent texts (skill, instructions, Cursor rule, example config) and their wiring through lockkit's agentwire.
Package agentassets holds locklog's agent texts (skill, instructions, Cursor rule, example config) and their wiring through lockkit's agentwire.
authproxy
Package authproxy generates an authenticating TLS proxy (Caddy) for a Loki that runs without auth in the user's Compose stack, so that a production profile can read it (design §2.5).
Package authproxy generates an authenticating TLS proxy (Caddy) for a Loki that runs without auth in the user's Compose stack, so that a production profile can read it (design §2.5).
config
Package config loads locklog profiles from the project and user config files (`.locklog/config.toml`, then the user's), applies the defaults, refuses anything that looks like a secret or that the production rules forbid, and computes the security-relevant policy of a profile.
Package config loads locklog profiles from the project and user config files (`.locklog/config.toml`, then the user's), applies the defaults, refuses anything that looks like a secret or that the production rules forbid, and computes the security-relevant policy of a profile.
doctor
Package doctor is `locklog doctor`: an advisory check of what the agent's account can reach where it runs, around the console.
Package doctor is `locklog doctor`: an advisory check of what the agent's account can reach where it runs, around the console.
egress
Package egress is the console's only way out to a remote log backend: an HTTP client whose dialer is pinned to the configured host and port, that uses no proxy and follows no redirect, sends only the requests of an allowlist (method and path, segment by segment), caps every response, and checks the server's certificate as the profile's tls mode says.
Package egress is the console's only way out to a remote log backend: an HTTP client whose dialer is pinned to the configured host and port, that uses no proxy and follows no redirect, sends only the requests of an allowlist (method and path, segment by segment), caps every response, and checks the server's certificate as the profile's tls mode says.
engine
Package engine answers the agent's log queries over masked records: it validates a query, builds a byte plan from the range and trusted selectors only, admits it against the per-call budgets, masks every record of each planned granule before any predicate runs, and stops at plan-chunk boundaries at its deadline, with a sealed cursor to continue.
Package engine answers the agent's log queries over masked records: it validates a query, builds a byte plan from the range and trusted selectors only, admits it against the per-call budgets, masks every record of each planned granule before any predicate runs, and stops at plan-chunk boundaries at its deadline, with a sealed cursor to continue.
engine/session
Package session opens the log engine of an approved policy for the console (logconsole.Opener): the masker of each source, the local index in the console's state directory, the sources the policy names (given only the mount roots the launcher checked), the keys (K_ref, pseudonym epochs), the cost model and the budgets, and serves the agent's methods over it.
Package session opens the log engine of an approved policy for the console (logconsole.Opener): the masker of each source, the local index in the console's state directory, the sources the policy names (given only the mount roots the launcher checked), the keys (K_ref, pseudonym epochs), the cost model and the budgets, and serves the agent's methods over it.
hostfacts
Package hostfacts holds what `locklog profile add` and `locklog doctor` know of the machine the agent runs on: its account (euid, groups, sudo), Docker's reach, the log files and journals, the containers and the installed agents.
Package hostfacts holds what `locklog profile add` and `locklog doctor` know of the machine the agent runs on: its account (euid, groups, sudo), Docker's reach, the log files and journals, the containers and the installed agents.
index
Package index is the local index of file streams: for each plan granule of a stream, its record count, its time span, and per-minute counts of the tuples of the source's trusted low-cardinality fields.
Package index is the local index of file streams: for each plan granule of a stream, its record count, its time span, and per-minute counts of the tuples of the source's trusted low-cardinality fields.
landlock
Package landlock confines the console's own file access with Landlock (Linux >= 5.13): once Restrict ran, the process (every thread) may only read beneath the read-only roots, read and execute beneath the exec roots, and write only beneath the read-write ones.
Package landlock confines the console's own file access with Landlock (Linux >= 5.13): once Restrict ran, the process (every thread) may only read beneath the read-only roots, read and execute beneath the exec roots, and write only beneath the read-write ones.
logconsole
Package logconsole is locklog's console backend for lockkit: the agent's log methods (log.status, log.sources, log.search, log.count, ...), the checks the console makes before it opens a log (the mount roots and the .locklog directory the launcher checked, no network it was not given), the policy of a profile, and the audit record of every call.
Package logconsole is locklog's console backend for lockkit: the agent's log methods (log.status, log.sources, log.search, log.count, ...), the checks the console makes before it opens a log (the mount roots and the .locklog directory the launcher checked, no network it was not given), the policy of a profile, and the audit record of every call.
mask
Package mask turns raw events into masked records, the only form of log content that leaves the console (design §4).
Package mask turns raw events into masked records, the only form of log content that leaves the console (design §4).
mask/masktest
Package masktest generates seeded log corpora with planted canaries (synthetic personal values and secrets, in every position and encoding) and scans bytes for any of them: the leak harness of design §4.7.
Package masktest generates seeded log corpora with planted canaries (synthetic personal values and secrets, in every position and encoding) and scans bytes for any of them: the leak harness of design §4.7.
mcptools
Package mcptools is locklog's MCP server: one tool per log method of the console, each a thin adapter over the console's socket.
Package mcptools is locklog's MCP server: one tool per log method of the console, each a thin adapter over the console's socket.
mountplan
Package mountplan turns a profile's local sources into the mount plan the launcher applies on the host: one line per directory to bind read-only into the console, with the globs the console may open under it.
Package mountplan turns a profile's local sources into the mount plan the launcher applies on the host: one line per directory to bind read-only into the console, with the globs the console may open under it.
parse
Package parse turns raw log bytes into events: it reads bounded lines, assembles multi-line events (stack traces) under the event caps, and decodes each event's format into a structured tree with exact numbers.
Package parse turns raw log bytes into events: it reads bounded lines, assembles multi-line events (stack traces) under the event caps, and decodes each event's format into a structured tree with exact numbers.
product
Package product declares locklog's names for lockkit.
Package product declares locklog's names for lockkit.
setup
Package setup is `locklog profile add`: a profile of the project's .locklog/config.toml from what the host holds (log files with their rotated and compressed siblings, the journal, the running containers, a Compose Loki), the production question, and the authenticating proxy of a Compose Loki.
Package setup is `locklog profile add`: a profile of the project's .locklog/config.toml from what the host holds (log files with their rotated and compressed siblings, the journal, the running containers, a Compose Loki), the production question, and the authenticating proxy of a Compose Loki.
source
Package source defines the internal interface of log sources: the byte plan of a range, the scan of one plan chunk, and the re-read of one record by its raw position.
Package source defines the internal interface of log sources: the byte plan of a range, the scan of one plan chunk, and the re-read of one record by its raw position.
source/docker
Package docker is the Docker log source: the json-file logs of the containers whose names the human approved, read from the containers root (`docker info .DockerRootDir` + /containers, bound read-only and non-recursive by the launcher), through a name -> id map the launcher keeps current.
Package docker is the Docker log source: the json-file logs of the containers whose names the human approved, read from the containers root (`docker info .DockerRootDir` + /containers, bound read-only and non-recursive by the launcher), through a name -> id map the launcher keeps current.
source/elastic
Package elastic is the Elasticsearch (8.x) and OpenSearch (2.x) log source.
Package elastic is the Elasticsearch (8.x) and OpenSearch (2.x) log source.
source/elastic/elastictest
Package elastictest is a fake Elasticsearch 8 / OpenSearch 2 for the tests of the packages that open an Elasticsearch source (the session, the engine through it): an http.Handler over in-memory documents that answers the requests locklog sends (the root, the privilege checks, _resolve/index, _field_caps, _mapping, _settings, _count and _search with bool filters, sort, search_after and the two aggregation kinds), behind basic or API-key authentication unless Open.
Package elastictest is a fake Elasticsearch 8 / OpenSearch 2 for the tests of the packages that open an Elasticsearch source (the session, the engine through it): an http.Handler over in-memory documents that answers the requests locklog sends (the root, the privilege checks, _resolve/index, _field_caps, _mapping, _settings, _count and _search with bool filters, sort, search_after and the two aggregation kinds), behind basic or API-key authentication unless Open.
source/file
Package file is the file log source: globs of plain, gzip and zstd files under the console's read-only mount roots, read beneath their root without following symbolic links, planned in fixed granules, and indexed locally.
Package file is the file log source: globs of plain, gzip and zstd files under the console's read-only mount roots, read beneath their root without following symbolic links, planned in fixed granules, and indexed locally.
source/journald
Package journald is the journald log source: systemd journal files under the console's read-only journal roots, read by the pure-Go reader in ./journal, with a field allowlist applied before any value is read.
Package journald is the journald log source: systemd journal files under the console's read-only journal roots, read by the pure-Go reader in ./journal, with a field allowlist applied before any value is read.
source/journald/journal
Package journal reads systemd journal files (the format of systemd's JOURNAL_FILE_FORMAT.md): regular and compact layouts, XZ, LZ4 and ZSTD compressed data objects, keyed or Jenkins hashes.
Package journal reads systemd journal files (the format of systemd's JOURNAL_FILE_FORMAT.md): regular and compact layouts, XZ, LZ4 and ZSTD compressed data objects, keyed or Jenkins hashes.
source/journald/journal/journaltest
Package journaltest builds small journal files in memory for tests and fuzz seeds: header, DATA, ENTRY and chained ENTRY_ARRAY objects, regular or compact, plain or ZSTD/LZ4/XZ compressed (no hash tables: the reader does not use them).
Package journaltest builds small journal files in memory for tests and fuzz seeds: header, DATA, ENTRY and chained ENTRY_ARRAY objects, regular or compact, plain or ZSTD/LZ4/XZ compressed (no hash tables: the reader does not use them).
source/loki
Package loki is the Loki log source.
Package loki is the Loki log source.
source/loki/lokitest
Package lokitest is a fake Loki 3 for the tests of the packages that open a Loki source (the session, the engine through it): an http.Handler over in-memory entries that answers the requests locklog sends (labels, label values, query_range for streams and for the metric form, the instant metric query), with Loki's paging (limit over all streams, forward, end excluded) and categorized labels, behind basic authentication unless Open.
Package lokitest is a fake Loki 3 for the tests of the packages that open a Loki source (the session, the engine through it): an http.Handler over in-memory entries that answers the requests locklog sends (labels, label values, query_range for streams and for the metric form, the instant metric query), with Loki's paging (limit over all streams, forward, end excluded) and categorized labels, behind basic authentication unless Open.
test
hub
Package hub runs locklog's hub mode end to end in one process, as an agent meets it: the MCP tools, the console server (lockkit) with the logconsole backend and a real engine session over a log file, and lockkit's fake lockhub (hubtest), which decides the tickets.
Package hub runs locklog's hub mode end to end in one process, as an agent meets it: the MCP tools, the console server (lockkit) with the logconsole backend and a real engine session over a log file, and lockkit's fake lockhub (hubtest), which decides the tickets.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL