Skip to main content

Documentation Index

Fetch the complete documentation index at: https://theseventeen-2abbdf80.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

This guide walks you through a complete first integration with keychain-auth. By the end, your CLI tool will be registered, granted access to a service namespace, and able to read and write secrets through the daemon. All steps assume keychain-auth is already installed — see the installation guide if not.
1

Start the daemon

Run the daemon in your terminal:
keychain-auth start
On macOS, the daemon listens on ~/Library/Application Support/keychain-auth/agent.sock. On Linux it uses $XDG_RUNTIME_DIR/keychain-auth/agent.sock. On Windows it listens on the Named Pipe \\.\pipe\keychain-auth.You can override the socket path with the --socket flag or the KEYCHAIN_AUTH_SOCKET environment variable:
keychain-auth start --socket /run/user/1000/keychain-auth.sock
Leave the daemon running in a separate terminal, or set it up as a service so it starts automatically. See the installation guide for platform-specific service configuration.
2

Register your binary

Register the binary you want to grant keychain access to. Run this command from the shell where your tool is installed, so which resolves the correct path:
keychain-auth register $(which your-tool)
The register command:
  1. Resolves the absolute path of your binary.
  2. Computes its SHA-256 hash.
  3. Appends a new entry to ~/.config/keychain-auth/config.json.
After registration, your binary is recognized but starts in a zero-trust state — it has no permissions to read, write, or search any service namespace. You will grant those in the next step.
# Example: registering a tool called agentsecrets
keychain-auth register $(which agentsecrets)
# Output: Registered /usr/local/bin/agentsecrets with hash sha256:fd7ecae9159e6f93...
If your binary is updated (recompiled or reinstalled), its SHA-256 hash changes. Re-register it with keychain-auth upgrade $(which your-tool) to update the approved hash without losing your permission configuration.
3

Configure permissions

Open ~/.config/keychain-auth/config.json and find the entry that register created for your binary. It looks like this:
{
  "path": "/usr/local/bin/agentsecrets",
  "hash": "sha256:fd7ecae9159e6f93a25178d3489fbffa7219917ca12567d5e42b156948",
  "allowed_read_services": [],
  "allowed_write_services": [],
  "can_search": false
}
Grant the binary access to the service namespaces it needs. For example, to allow it to read and write secrets in the agentsecrets namespace and to use prefix search:
{
  "path": "/usr/local/bin/agentsecrets",
  "hash": "sha256:fd7ecae9159e6f93a25178d3489fbffa7219917ca12567d5e42b156948",
  "allowed_read_services": ["agentsecrets"],
  "allowed_write_services": ["agentsecrets"],
  "can_search": true
}
The daemon re-reads config.json on every new connection, so changes take effect immediately — no restart required.Use shared namespaces (e.g., aws or github) when you need to share secrets with other tools. Use a private namespace named after your tool (e.g., agentsecrets) for strict isolation, and segment secrets using hierarchical target names like production/workspace_abc/proj_123/OPENAI_API_KEY.
4

Connect and send your first request

Your tool connects to the daemon over the local socket, sends a JSON request, and reads a JSON response. The connection itself is the session — there are no separate tokens or handshakes.Here is a complete Go example that writes two secrets and then reads them back:
package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"net"
	"os"
	"syscall"
)

type Request struct {
	Type    string   `json:"type"`
	Action  string   `json:"action"`
	Service string   `json:"service"`
	Targets []string `json:"targets,omitempty"`
	Values  []string `json:"values,omitempty"`
}

type ResultItem struct {
	Target string `json:"target"`
	Value  string `json:"value,omitempty"`
}

type Response struct {
	Type    string       `json:"type"`
	Status  string       `json:"status"`
	Reason  string       `json:"reason,omitempty"`
	Results []ResultItem `json:"results,omitempty"`
}

func main() {
	// Use platform-specific socket path (see platform docs for details)
	socketPath := os.ExpandEnv("$XDG_RUNTIME_DIR/keychain-auth/agent.sock")

	// Open the socket with SOCK_CLOEXEC to prevent fd inheritance across fork/exec
	fd, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
	if err != nil {
		fmt.Printf("Error creating socket: %v\n", err)
		os.Exit(1)
	}
	_ = fd

	conn, err := net.Dial("unix", socketPath)
	if err != nil {
		fmt.Println("keychain-auth daemon is not running.")
		fmt.Println("Start it with: keychain-auth start")
		os.Exit(1)
	}
	defer conn.Close()

	reader := bufio.NewReader(conn)
	encoder := json.NewEncoder(conn)

	// Write two secrets
	writeReq := Request{
		Type:    "REQUEST",
		Action:  "write",
		Service: "agentsecrets",
		Targets: []string{
			"production/workspace_abc/proj_123/OPENAI_API_KEY",
			"production/workspace_abc/proj_123/AWS_SECRET_KEY",
		},
		Values: []string{
			"sk-proj-456",
			"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
		},
	}

	if err := encoder.Encode(writeReq); err != nil {
		fmt.Printf("Failed to send write request: %v\n", err)
		return
	}

	writeRespLine, _ := reader.ReadBytes('\n')
	var writeResp Response
	json.Unmarshal(writeRespLine, &writeResp)

	if writeResp.Status != "success" {
		fmt.Printf("Write denied: %s\n", writeResp.Reason)
		return
	}
	fmt.Println("Secrets written successfully.")

	// Read them back
	readReq := Request{
		Type:    "REQUEST",
		Action:  "read",
		Service: "agentsecrets",
		Targets: []string{
			"production/workspace_abc/proj_123/OPENAI_API_KEY",
			"production/workspace_abc/proj_123/AWS_SECRET_KEY",
		},
	}

	if err := encoder.Encode(readReq); err != nil {
		fmt.Printf("Failed to send read request: %v\n", err)
		return
	}

	readRespLine, _ := reader.ReadBytes('\n')
	var readResp Response
	json.Unmarshal(readRespLine, &readResp)

	for _, res := range readResp.Results {
		fmt.Printf("target: %s\nvalue:  %s\n\n", res.Target, res.Value)
	}
}
The daemon responds with a JSON object on every request:
{
  "type": "RESPONSE",
  "status": "success",
  "results": [
    {
      "target": "production/workspace_abc/proj_123/OPENAI_API_KEY",
      "value": "sk-proj-456"
    },
    {
      "target": "production/workspace_abc/proj_123/AWS_SECRET_KEY",
      "value": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
    }
  ]
}
If the binary is not registered or lacks permission for the requested service, the response looks like:
{
  "type": "RESPONSE",
  "status": "denied",
  "reason": "unregistered_binary_pending_approval"
}
Common reason values are unregistered_binary_pending_approval, action_not_in_policy, service_not_allowed, and malformed_request.
Always open the socket with the O_CLOEXEC / SOCK_CLOEXEC flag. If your process forks and executes an untrusted child binary without this flag, the child inherits the open file descriptor and can impersonate your binary for the lifetime of that connection. The daemon cannot detect descriptor inheritance across exec(). Setting SOCK_CLOEXEC at socket creation time closes the descriptor automatically before the child binary starts.
5

Verify in the audit log

Every request — whether approved or denied — is written to the structured JSON audit log. Check it to confirm your requests were recorded:
# Linux / Windows (WSL)
cat ~/.local/share/keychain-auth/audit.log

# macOS
cat ~/Library/Logs/keychain-auth/audit.log
Each line is a JSON object. A successful read entry looks like:
{
  "timestamp": "2026-05-21T10:34:02Z",
  "action": "read",
  "pid": 8412,
  "binary_path": "/usr/local/bin/agentsecrets",
  "binary_hash": "sha256:fd7ecae9159e6f93a25178d3489fbffa7219917ca12567d5e42b156948",
  "service": "agentsecrets",
  "targets": ["production/workspace_abc/proj_123/OPENAI_API_KEY"],
  "result": "GRANTED"
}
Because the audit log captures individual target names rather than opaque “search granted” entries, you can see exactly which secrets each binary accessed and when.
Secret values are never written to the audit log — only target names and metadata. This keeps the log safe to share with your security team or export to a SIEM.

Next steps

  • Read the integration guide to learn about install-time registration patterns for distributing your CLI tool to users.
  • See the protocol reference for the full request and response schema, including batch atomicity rules and prefix matching.
  • Review platform support to understand backend differences across macOS, Linux, and Windows.