Security#
This page describes how Lockpin is built, where it keeps secrets, what protects the ledger and the policy, and what is not done yet. It is for IT and security reviewers. It describes v0.3.0.
How it works#
Lockpin is a local proxy. Your agent sends its calls to Lockpin on the same machine
(127.0.0.1:8787 by default), and Lockpin sends them on to the provider you chose. Calls do not
pass through our servers.
The decision to allow or refuse a call is made on that machine, from its own ledger file. It never waits on our servers, and it works with the network to us cut off.
There is no telemetry and no automatic update check. Lockpin contacts our servers only for features you turn on: reporting to the dashboard, joining a team, asking about held calls on your phone or in chat, and
lockpin upgrade.The hosted dashboard is optional. With reporting on, a background reporter uploads spend totals once a minute, off the call path: a slow or failed upload changes no decision.
A team can run one Lockpin as a shared node that holds the provider keys. The node also decides from its own ledger.
For every host Lockpin calls and every file it stores, see What leaves your machine.
Reporting#
With reporting off (the default), no spend data that we can read goes to us. With reporting on,
Lockpin uploads a closed list of fields at a detail level an admin picks for the whole
organization (totals, tags or tasks).
A machine’s own
-report-detailcan only lower the level. The server rejects a batch with fields above the organization’s level.The task and user ids are pseudonyms, not anonymous: we hold the salt they are made with.
Uploads go over HTTPS to
ingest.lockpin.dev, each signed with an HMAC of the workspace credential.lockpin report previewprints the exact next upload, and a copy of every upload is kept next to the ledger.
Never uploaded, at any level: prompts, responses, tool calls and results, file contents, file names or paths, provider API keys, Lockpin key secrets or their hashes, spend capability tokens, webhook or Slack URLs, alert message text, operating-system user names, host names or environment variables.
A machine that joins a team (lockpin enroll) also sends a status report once a minute, even with
reporting off. It is encrypted on the machine to a key only the team’s admins’ browsers hold, so our
server stores it but cannot read it, unless the dashboard’s own page code were altered to send the
decrypted report elsewhere. A signed policy can turn off reporting and team status for a whole
machine.
Provider keys#
On a developer’s own machine, the agent sends its API key to Lockpin, which passes it to the provider with the call. Lockpin does not write it to disk, the ledger or its logs.
With a team node and spend capabilities, the node holds the real provider keys and agents never see
them. An agent holds a spend capability instead: a token starting lpc_ that names a task, a dollar
limit, the models and tools allowed, and an expiry (24 hours by default). An agent can hand a
smaller token to a sub-agent without the node, but can never widen it. The node refuses a call whose
worst case does not fit the token or any budget above it. See Run a team node.
The node keeps the keys in a protected store:
System |
Stored by hand ( |
Node set up by |
|---|---|---|
Windows |
Encrypted with DPAPI for this machine |
Encrypted with DPAPI for this machine; the file readable only by the node’s account, SYSTEM and Administrators |
macOS |
Encrypted in the Keychain of the account that ran it |
Not encrypted: a file readable only by the node’s account and root (a service account has no Keychain) |
Linux |
Encrypted with systemd-creds, where it is installed and works |
Not encrypted: a file readable only by the node’s account and root |
Where the system has no store, the keys go in an unencrypted file only its owner can read, and the command says so. For Amazon Bedrock and Google Vertex AI, the node signs each call or makes short-lived access tokens itself: the AWS secret access key and the Google service-account key never leave it.
Limits of the key store#
Where the store is a plain file (a node on macOS or Linux, or a system with no store), the keys are in clear on disk, protected only by file permissions. Backups and disk images of that machine hold them in clear.
On Windows, DPAPI encrypts for the machine, not for one account. Any process on that machine that can read the file can decrypt it, so the file’s access list is the real protection. A copy is useless on another machine.
While the node runs, the keys are in its memory. There is no hardware key store that keeps them out of reach of the node’s own account.
Anyone with administrator or root rights on the node can read the keys.
On Windows the node runs as LocalService, a built-in account other Windows services share. Any service running as LocalService can read the node’s keys.
A node refuses to start as an account a person signs in with, unless you allow it for a one-person setup. Then capabilities are bookkeeping, not a boundary, and Lockpin says so.
Limits of spend capabilities#
A capability is a bearer token: whoever holds it can spend what it allows until it expires or is revoked.
Revoking applies within a second on the issuing node, within about a minute on nodes that follow it, and within the token’s freshness window (10 minutes by default) anywhere.
The revocation list beside the store is protected only by file permissions. Deleting it un-revokes every token at the next heartbeat.
After a node restarts, a recent heartbeat can be replayed, so a token revoked shortly before the restart may work again for up to its freshness window.
The ledger and audit export#
Every charged call, every call refused at the cap, every budget change and alert, and every start and stop of Lockpin is appended to a ledger file on the machine.
Every line carries the SHA-256 hash of the line before it. The hash has no key. It catches accidental damage and casual edits, but anyone who can write the file can recompute the chain, and lines cut from the end leave no trace.
A team node also adds a checkpoint every 1,000 lines and when it stops, signed with its Ed25519 node key. Lines up to the last checkpoint cannot then be edited without breaking a signature. Lines after it are covered only by the unkeyed chain. Only a team node writes checkpoints.
lockpin ledger verifychecks the chain, and with-keyand the node’s public key, the checkpoint signatures.If Lockpin crashes or is killed, the cap can only get stricter: nothing it reported as written is lost. A power cut is different: it can lose about the last second of lines, and with them the spend of a call made in that second.
lockpin ledger verify lockpin-ledger.jsonl
lockpin ledger export writes the ledger as JSON Lines or CSV. CSV cells that a spreadsheet would
run as formulas are escaped. For a SIEM, -audit-export DIR (or the signed policy) keeps writing
the ledger to a folder your log agent collects. Each record carries its hash and the previous one’s,
so a gap shows. Lockpin sends the export nowhere itself. Exports hold no prompt, response, key or
alert message text. A full or unwritable export folder stops the export, never the cap.
Signed policy#
IT can push one policy file to every machine with the tool it already uses (Intune, Jamf, Ansible). Lockpin reads it at start and within five seconds of a change. See Signed policy.
The file is signed with an Ed25519 key that IT holds. Each machine pins IT’s public key. A file that is unsigned, signed by another key, edited after signing or damaged is refused and logged, and the last good policy stays in force.
It can set task budgets, a hard ceiling per task, dollar limits per machine, key, user and tag, allowed and denied providers and models, the reporting level (or no reporting), a private root CA and the audit export folder.
It is a floor for every Lockpin on the machine: flags, local settings, the dashboard and an agent’s headers can only make it stricter.
A team node can be set to refuse every call while no signed policy is in force.
The file has no field for a key, a token or a URL, and nothing in it is uploaded.
Important
The policy binds Lockpin, not the user. A user with their own provider key can call the provider another way, or run their own copy of Lockpin, and can delete the per-user policy file in their own folder. The policy is a hard wall only where the provider keys sit in a team node the user cannot read. It also does not protect against local administrators, who can replace the pinned key.
The hosted dashboard#
The dashboard is optional. The cap works the same without it. See Hosted dashboard and reporting.
Sign-in. With a code sent by email, a password, or Google or GitHub. An organization can turn on company sign-in with Microsoft Entra ID, Google Workspace or Okta, after proving its email domains with a DNS record, and can then require it for admins and viewers. Lockpin asks the identity provider only for name, email address and a stable ID. A company sign-in lasts at most 12 hours.
SCIM. With SCIM provisioning from Okta or Entra ID, someone IT deactivates is removed from the organization as soon as their identity provider tells Lockpin (Okta sends it at once; Entra in its next cycle, about every 40 minutes). An Okta app’s client secret is stored encrypted; a SCIM token is stored only as a hash.
Owners. Owners never act through company sign-in: a company sign-in acts as an admin at most, and owner actions need the owner’s own Lockpin sign-in. Whoever controls an owner’s mailbox, or the Google or GitHub account they sign in with, can still sign in as that owner. To remove an owner, remove them on the Members page: disabling them at your identity provider does not.
Audit log. Changes to members, roles, keys, sign-in, billing and export tokens are logged. Owners and admins can download the log as CSV or JSON Lines.
Read-only SIEM tokens. Owners and admins can make a token that reads the audit log, spend, or both. It is shown once and only its SHA-256 hash is kept. It cannot change anything and is not a sign-in. Revoking it stops it at once.
Honest numbers. Every number says when it was read (“as of”). Each machine enforces its own cap, so a budget or total across several machines is soft: the machines together can pass it, and the dashboard labels it soft.
Budgets only tighten. A dashboard budget can only lower a machine’s own limit, never raise it. Keys made in the dashboard are accepted only by a machine started with
-managed-keys, and each is still held to that machine’s own monthly ceiling ($100 unless changed).Retention. Hourly spend is kept 35 days and daily spend 90 days or 13 months, depending on the plan.
Network and install#
Corporate proxy. Every outbound request honours
HTTPS_PROXY,HTTP_PROXYandNO_PROXY. Lockpin’s own connections to localhost are always direct.Private root CA.
-ca-file,LOCKPIN_CA_FILEor the signed policy adds your roots to the system’s. No setting turns certificate checks off.lockpin doctorshows the settings in effect, with any proxy password hidden.Install. The one-line installers install for one user without admin rights, and refuse a download that does not match the release’s
SHA256SUMS.lockpin upgraderuns the same installer.
How the code is built and checked#
The proxy uses only Go’s standard library, and CI fails if it gains a dependency. The hosted service has one direct dependency: pgx, the Postgres driver.
CI is set up to run, on every change, the tests with the race detector,
go vet, andgovulncheck, which flags known vulnerabilities only where the code reaches them. The proxy’s parsers of untrusted input (request bodies, event streams, price catalogs, keys files, hook payloads, transcripts) have fuzz targets that CI runs too.GitHub Actions is unavailable for now. The same jobs run locally while each change is reviewed, and the weekly
govulncheckrun does not happen.Dependabot opens weekly update requests for Go modules, npm packages, GitHub Actions and Docker images.
Lockpin is proprietary software: the program and the editor extension are free to use, and the source code is not published.
What is not done yet#
Area |
Status |
|---|---|
Certifications |
No SOC 2 report, ISO 27001 certificate or similar, and no independent audit or third-party penetration test. |
Release signing |
v0.3.0 carries |
Code signing |
The programs and packages are not yet Authenticode-signed or notarized by Apple, so Windows SmartScreen and macOS may warn. |
Package managers |
The MSI, |
Source |
Closed source, so you cannot rebuild a release yourself or read its code, and no CodeQL scanning runs. |
Dashboard sign-in |
No SAML, and no identity providers besides Microsoft Entra ID, Google Workspace and Okta. Without SCIM (which Google Workspace does not offer here), there is no instant sign-out when an account is disabled: a company sign-in lasts up to 12 hours, and emailed-code access lasts until the person is removed on the Members page. |
Audit export |
No Windows Event Log or syslog output. The export files are not signed themselves; the hash chain and checkpoint signatures inside them are the proof. The Splunk, Sentinel, Elastic and Datadog examples have not been tested against those products. |
Report a vulnerability#
Email security@lockpin.dev. Do not describe the problem in a public issue. You hear back within 3
business days. Once the problem is confirmed you get a plan for the fix. Once the fix is released,
we email you, and describe it in the release notes and in a security advisory on
github.com/Lockpin/lockpin, crediting you if you like.
Include:
The version (
lockpin version).How to reproduce it.
What an attacker gains.
We especially want to hear about a way to spend past a cap, to read or use a key you were not given,
to reach the local meter from another machine or a web page, or to read another account’s data on
the dashboard. Only the latest release gets fixes. The same contact is in
/.well-known/security.txt (RFC 9116).