Choose the trust posture before a local tool gets credential-backed access.
latchkeyd is a macOS local trust broker for agent-mediated tool execution. It keeps secrets local, pins the wrapper and binary that are allowed to use them, and lets you decide whether a task should use direct handoff, a bounded one-shot path, or a brokered request path.
If you use local coding agents with real credentials,
latchkeydgives you a narrower, evidence-backed trust boundary between wrapper and tool.
LATCHKEYD_BIN="$PWD/.build/debug/latchkeyd" ./examples/bin/example-wrapper brokered-demoProof:
- current public release:
v0.1.0-alpha.3 - hosted CI and hosted release workflows are live in the repo
- published binary and checksum are part of the release shape
Two problems, one tool:
- Credential sprawl: local agents become hard to trust when real credentials leak into broad env state, direct token use, or ad-hoc tool discovery.
- Prompt-injection fallout: remote content can influence an agent to attempt unsafe local actions, especially when the workstation has no explicit handoff boundary.
latchkeyd is built for the middle:
- secrets stay local
- wrappers and binaries are trust-pinned
- secret release is explicit
- drift, hijack, and bypass fail closed
The repo is moving from one execution model to a small family of explicit trust postures.
| Mode | Best for | What the child receives | Main limit | Current state |
|---|---|---|---|---|
handoff |
maximum compatibility with existing tools | approved env vars at launch | child can retain or re-export the secret | shipped |
oneshot |
bounded publish or release commands | approved env vars for one short-lived run | still visible to that command while it runs | shipped as first slice |
brokered |
per-operation control and tighter request boundaries | session metadata at launch, secret only on approved request | first slice is narrow and not same-user isolation | shipped as first slice in the repo |
ephemeral |
provider flows that support short-lived credentials | scoped short-lived credentials | depends on provider support | planned |
proxy |
highest-risk or secretless workflows | capability access without raw secret handoff | highest implementation complexity | planned |
This is the main product shift:
- from explicit handoff before execution
- to explicit, user-chosen trust posture per task
Build, initialize trust, run the wrapper, validate the workstation:
This demonstrates the shipped brokered path: wrapper and binary are verified, the child starts with session metadata only, and one approved brokered request succeeds.
Ask for an operation that is not approved and the broker rejects it inside the active session:
This demonstrates the fail-closed brokered story: a running trusted child still does not get arbitrary broker access, and no secret value is returned for an unapproved operation.
More walkthroughs:
latchkeyd currently guarantees:
- the selected policy mode is explicit in the manifest
- the wrapper asking for access is trust-pinned
- the downstream binary is trust-pinned
- only policy-approved secret names or brokered operations are allowed
- drift, hijack, and bypass fail closed
- brokered operations are audited, and logging failure is an explicit error
handoff and oneshot do not confine a trusted child after it receives secret material.
That means:
- the child can still retain or re-export the secret while it runs
oneshotnarrows lifetime intent, not post-handoff control- the first brokered slice narrows request boundaries, but it is not same-user isolation
This repo should be read as practical defense in depth for approved local workflows, not “secure agents solved.”
status, manifest commands, validate, and structured errors emit JSON that is safe to inspect, script, or log.
exec inherits stdout and stderr from the trusted child. Review child output before relying on its shape.
Event logging is part of the enforced audit contract:
execandvalidatepreflight the event log pathLOGGING_ERRORis returned if audit logging is unavailable up frontLOGGING_ERRORis also returned if the log append fails after command processing has started
Example child output:
{
"ok": true,
"tool": "example-demo-cli",
"transport": "brokered",
"args": [
"smoke"
],
"brokeredOperation": {
"operation": "secret.resolve",
"secretName": "example-token",
"valuePreview": "la***en",
"valueLength": 19,
"policyName": "example-brokered",
"policyMode": "brokered"
}
}swift build./.build/debug/latchkeyd manifest init --force
./.build/debug/latchkeyd manifest refreshLATCHKEYD_BIN="$PWD/.build/debug/latchkeyd" ./examples/bin/example-wrapper demoLATCHKEYD_BIN="$PWD/.build/debug/latchkeyd" ./examples/bin/example-wrapper brokered-demoLATCHKEYD_BIN="$PWD/.build/debug/latchkeyd" ./.build/debug/latchkeyd validate./scripts/offline_smoke.shThe example setup uses:
For real workstation use, the intended backend is keychain. The file backend exists to make demos, tests, CI, and first-run evaluation easy.
Most local agent setups end up with one of two bad patterns:
- broad inherited env state that every tool can see
- wrapper scripts that assume the tool name alone is enough to trust
latchkeyd exists to put a local trust check in the middle of that flow and let the operator choose how narrow the release path should be.
latchkeyd is a macOS-first local trust broker for secret-scoped tool execution. It verifies:
- the wrapper asking for access
- the downstream binary that would receive access
- the manifest policy that allows that access
- the trust mode attached to that policy
Depending on the mode, it either:
- injects approved env vars and launches the tool
- enforces a bounded one-shot path
- or creates a brokered local session so the child can request an approved operation later
- local-first: no cloud control plane required
- your system, your rules: trust is defined by the local manifest you control
- user-owned trust posture: choose compatibility, bounded execution, or request-time brokerage by task
- trust-pinned execution: both wrapper and binary are verified
- fail closed on drift: path changes, hash changes, and hijacks stop the run
- built for agent workflows: this is about safer local tool use, not generic app configuration
If the safety story depends on a cloud broker, remote policy service, or hosted execution boundary, the user no longer owns the full trust root.
latchkeyd takes a different position:
- the trust root is local
- the secret store is local
- the handoff policy is local
- the operator can inspect the actual trusted paths and hashes
Offline operation is supported because every run reads local manifest and event files and negotiates with local binaries. The dedicated scripts/offline_smoke.sh proves that the core path works with every proxy pointed at invalid endpoints.
Prompt injection is not something a local broker can solve universally.
What latchkeyd does is narrow the blast radius once an agent is already allowed to run local tools:
- remote content cannot directly get secrets just by influencing model output
- wrappers can remain small, explicit, and purpose-built
- broad inherited env state is replaced with explicit handoff or explicit brokered request boundaries
- a tool name alone is not trusted; the real path and hash must match
This is defense in depth for approved local workflows, not a blanket claim of secure agents.
- The agent calls a wrapper.
- The wrapper normalizes the request and calls
latchkeyd. latchkeydverifies the trusted wrapper path and hash.latchkeydverifies the trusted downstream binary path and hash.latchkeydresolves only the secret entries approved by policy.latchkeydinjects only the approved env vars and launches the command.
- The agent calls a wrapper.
- The wrapper calls
latchkeyd execfor a brokered policy. latchkeydverifies the wrapper, binary, policy, and operation set.latchkeydlaunches the child without raw secret env vars.- The child receives only session metadata.
- The child asks the broker for an approved operation such as
secret.resolve. - The broker checks the live session, operation allowlist, and secret binding before returning a result.
Examples:
- edit the example wrapper and run
latchkeyd manifest verifybefore refresh to see wrapper drift denial - prepend a fake
example-demo-cliearlier inPATHto trigger PATH hijack denial - call
latchkeyd execdirectly with an untrusted--callerpath to trigger caller denial - point the file backend at a missing file to trigger backend configuration denial
- request an unsupported brokered operation to trigger
OPERATION_NOT_ALLOWED
When trust checks fail, the intended operator loop is simple:
- inspect the failing wrapper, binary, backend path, or brokered operation
- decide whether the change is expected
- if it is expected, re-pin with
latchkeyd manifest refresh - if it is not expected, stop and investigate instead of weakening the policy
The secure path should be the shortest path, but it should never silently self-heal.
latchkeyd |
Broad env vars | Cloud broker only | |
|---|---|---|---|
| Secrets stay local | Yes | Yes | No |
| Wrapper trust-pinning | Yes | No | Varies |
| Binary trust-pinning | Yes | No | Varies |
| Fail-closed on drift | Yes | No | Policy-dependent |
| Works offline | Yes (scripts/offline_smoke.sh proves the core path) |
Yes | No |
| Operator can inspect the trust root | Yes | Partial | No |
| Mode-specific trust posture | Yes | No | Varies |
latchkeyd statuslatchkeyd manifest initlatchkeyd manifest refreshlatchkeyd manifest verifylatchkeyd execlatchkeyd validate
Default manifest path:
~/Library/Application Support/latchkeyd/manifest.json
Default event log path:
~/Library/Application Support/latchkeyd/events.jsonl
Use --manifest PATH to override the manifest location.
Current alpha scope:
- macOS-only SwiftPM package with a real
latchkeydCLI - versioned trust manifests with explicit policy modes
handoff,oneshot, and a first brokered slicefileandkeychainsecret backends- a reference Bash wrapper plus a harmless demo CLI
- JSONL event logging with enforced audit preflight and
LOGGING_ERROR scripts/offline_smoke.shfor dedicated offline proofscripts/local_workflow_parity.shfor local release-prep parity- GitHub Actions CI and release workflows
Accepted limits for this alpha:
- no long-running daemon mode
- no provider-specific integrations yet
- no same-user compromise claim
- no full secretless capability model yet
- no cross-platform backend story yet
- stronger
oneshotlifetime enforcement - broader
brokeredoperations and session controls ephemeralandproxymodes for stricter workflows
- engineers using local coding agents with real credentials
- advanced developers building wrapper-first local automations
- teams exploring safer trust boundaries for internal agent tooling
- same-user full compromise
- browser or session-store compromise
- generic endpoint policy for every API
- OS-level isolation
- "secure agents solved"
This project should be understood as local defense in depth for approved workflows.
docs/ARCHITECTURE.mddocs/THREAT_MODEL.mddocs/ROADMAP.mddocs/REPO_METADATA.mddocs/TRUST_MODES_SPEC.mddocs/MANIFEST_EVOLUTION_SPEC.mddocs/CLI_MODE_UX_SPEC.mddocs/WRAPPER_MODE_GUIDE.mdexamples/wrapper-contract.md
The broker core is a Swift command-line tool.
Current distribution shape:
- source build via Swift Package Manager
- GitHub Actions workflows for CI and tagged releases
- release binaries and checksums from GitHub Releases once tags are cut
Later possibilities:
- Homebrew distribution
- signed and notarized binaries
- broader wrapper ecosystem
Release candidates should pass scripts/local_workflow_parity.sh and scripts/offline_smoke.sh locally before cutting a tag, but the hosted GitHub release workflow remains the authoritative gate for public assets.
latchkeyd gives local agents a narrower, auditable, fail-closed way to use real tools with real credentials while letting the operator choose the trust posture per task.





