Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SecretBroker

AI agents often need authenticated access to CLIs, APIs, and browser sessions, but plaintext credentials should never enter prompts, model context, or tool results.

SecretBroker is a local macOS credential broker for AI agents. It lets agents request scoped, user-approved operations through MCP and injects credentials into an approved process or broker-owned proxy instead of returning stored values to the agent or model.

Warning

SecretBroker handles high-value secrets and has not received an independent security audit. It is not a sandbox: an approved command runs with the user's permissions and can deliberately print or exfiltrate injected credentials. Read Security and SECURITY.md before using real accounts.

Important

Build the macOS app from source. Before building or installing it, verify the repository origin and the commit you checked out. Treat third-party binaries as untrusted unless you can verify their publisher identity, notarization, and checksum.

Features

  • Imports live or snapshot cookies from Chrome, Chromium, Brave, Edge, Vivaldi, and Arc.
  • Stores secrets in a Keychain-backed AES-GCM vault.
  • Supports per-request, session, time-window, and automatic approval policies.
  • Provides MCP tools for AI agents to discover scopes and request approved credential operations.
  • Exposes a CLI and a scope-locked reverse proxy for local automation.
  • Provides Playwright storage state, Netscape cookie jars, and cookie header files.
  • Records credential operations in a hash-chained audit log.
  • Revokes grants and removes temporary credential material with one panic action.
  • Supports multiple accounts and multiple scopes per credential.
  • Loads long-lived External Secrets from secret managers or other remote stores into daemon memory.
  • Supports user-configured command providers for short-lived secrets, including visible Terminal login flows.

Requirements

  • macOS 13 or later
  • Xcode or matching Command Line Tools with Swift 6 support
  • Chrome or another supported Chromium browser for cookie import
  • A Developer ID Application certificate for an installed release build
  • An MCP-capable AI agent host, such as Codex or Claude Code, only if you want to use the MCP integration

SecretBroker has no third-party package dependencies. It uses macOS frameworks, SQLite, and CommonCrypto.

Installation

Production peer authentication requires every SecretBroker component to share one Developer ID Application signing identity. Clone the repository and run the installer with that identity:

git clone https://github.com/thonra/secretbroker.git
cd secretbroker
SECRETBROKER_SIGNING_IDENTITY="Developer ID Application: Example (TEAMID)" \
  ./install.sh

The installer builds the project, installs the binaries under ~/Library/Application Support/SecretBroker, installs the menu-bar app under ~/Applications, and registers the app as a login LaunchAgent. The app starts and owns secretbrokerd; quitting or losing the app stops the daemon and clears ephemeral broker state. The App LaunchAgent is not KeepAlive, so choosing Quit does not relaunch it. While the app remains open, it restarts an unexpectedly exited daemon; rapid startup failures stop after five retries. Quit the app to stop the daemon intentionally. The installer refuses ad-hoc signing and refuses to copy a plaintext development master key into a backup. Migrate or deliberately remove a development vault before installing. It does not change any MCP host configuration; register the installed MCP binary after installation if needed.

Migrating from KeyBroker

When only the legacy ~/Library/Application Support/KeyBroker directory exists, install.sh moves it to the SecretBroker support path before installing the new binaries. The daemon migrates the existing com.keybroker.vault Keychain key to com.secretbroker.vault, so the encrypted vault remains usable. If both support directories exist, the installer stops instead of choosing one implicitly; reconcile them before retrying. MCP host registrations are not rewritten, so update them to point to secretbroker-mcp.

For compatibility, SecretBroker still accepts legacy KEYBROKER_* environment variables, kb_* MCP tool calls, X-KeyBroker-Token, and signed com.keybroker.* clients. New names take precedence and all new configuration should use SECRETBROKER_*, sb_*, X-SecretBroker-Token, and com.secretbroker.*.

Release builds compile out every SECRETBROKER_DEV_* bypass. If you do not have a Developer ID Application certificate, limit unsigned debug builds to isolated tests with synthetic credentials:

swift build
BIN=.build/debug ./smoke.sh

The smoke test creates its own temporary support directory and never uses the production vault or Keychain item. If swift build fails because the local Command Line Tools installation has conflicting module maps, ./build.sh provides a direct swiftc fallback:

./build.sh
BIN=.build-manual ./smoke.sh

Fixing or reinstalling the matching Xcode toolchain remains the preferred solution.

Quick start

After install.sh starts the menu-bar app and its daemon, select the installed CLI:

SECRETBROKER_CLI="$HOME/Library/Application Support/SecretBroker/bin/secretbroker"
"$SECRETBROKER_CLI" status

The installer also creates a secretbroker symlink when /usr/local/bin or /opt/homebrew/bin is writable.

Add a secret. SecretBroker reads the value from standard input so it does not enter shell history:

"$SECRETBROKER_CLI" add \
  --name github \
  --scope api.github.com \
  --env-name GH_TOKEN

SecretBroker presents one non-cookie credential kind: Secret. You do not choose between a token and an API key. Configure environment-variable names for exec delivery and, when needed, a custom header for proxy delivery. Without a custom header, the proxy sends Authorization: Bearer <secret>.

For a long-lived value stored in AWS Secrets Manager, 1Password, or another external system, first open Loaders and add the code that reads that system. A Loader can use managed Bash, Zsh, Python, or Java source, a JAR, or a custom executable. Then choose Add → Connect an External Secret, select the Loader, enter the provider reference, and use Test Connection.

Loaders are reusable. Each External Secret stores an exact Loader ID and revision, so two secrets can share one approved implementation without copying code. A secret can use the Loader's default Parse command, skip parsing, or override it. SecretBroker runs the Loader out of process and keeps the loaded value only in daemon memory. For interpreter invocations such as bash loader.sh, python loader.py, or java -jar loader.jar, the daemon binds the primary code file to the approval by content hash.

Alternatively, register an on-demand provider. Commands are parsed into argv and are not implicitly executed through a shell:

"$SECRETBROKER_CLI" provider add \
  --name github-cli \
  --scope api.github.com \
  --env-name GH_TOKEN \
  --sign-in "/opt/homebrew/bin/gh auth login" \
  --credential "/opt/homebrew/bin/gh auth token"

After approval, SecretBroker runs Credential first. When Parse is configured, SecretBroker pipes at most 64 KiB of Credential stdout to Parse and uses only the Parse output. If the pipeline fails and Sign-in commands exist, the app opens Terminal, runs those commands in order, and retries Credential → Parse.

The acquired secret lasts for one exec operation or one proxy grant. It is never returned through MCP. Test Command runs the same linked flow without saving the provider or displaying the value.

If the GitHub CLI is installed, run an authenticated API call. gh reads the secret from GH_TOKEN:

"$SECRETBROKER_CLI" exec \
  --scope api.github.com \
  -- gh api user

Import a live browser session:

"$SECRETBROKER_CLI" import \
  --scope .github.com \
  --browser chrome \
  --profile Default

SecretBroker stores a reference to a live browser profile by default and reads current cookies for each approved request. Add --snapshot to freeze the imported cookies.

Credential injection and authentication support

Each request identifies a scope, delivery mode, and optional account. After policy checks and user approval, secretbrokerd issues a short-lived grant for a matching credential.

In exec mode, the CLI or MCP server starts the approved process with secret environment variables or a private cookie jar. In proxy mode, the credential stays inside SecretBroker, which injects cookies, a Bearer value, or a custom header for the approved host. Release, expiry, or panic revokes the grant and removes temporary files.

Scenario or authentication mechanism Built-in support Injection path or limitation
CLI tools and SDKs that read environment variables Yes exec / sb_exec sets SECRETBROKER_TOKEN and the configured names in the child process
Browser-cookie authentication Yes exec cookie jar, Playwright/Netscape/header state file, or reverse-proxy Cookie header
Bearer-token HTTP APIs Yes Reverse proxy adds Authorization: Bearer; exec provides the secret through environment variables
Custom API-key headers Yes Reverse proxy adds the configured header, such as X-API-Key; exec provides the value through environment variables
Ordinary HTTP or HTTPS calls Yes Use the scope-locked reverse proxy; non-local upstreams use HTTPS, redirects are not followed, and WebSockets are not supported
Git over HTTPS with a personal access token Yes secretbroker git supplies the token through GIT_ASKPASS and disables interactive credential helpers
Playwright-compatible browser automation Partial State files contain cookies but not localStorage, sessionStorage, or passwords
OAuth/device-login CLIs that can print a token Yes, via command provider Configure Sign-in commands plus a Credential command and optional Parse command; the recipe runs as the macOS user and is not sandboxed
AWS Secrets Manager, 1Password, or another remote secret store Yes, via External Secret Select an approved reusable Loader and choose default, no, or custom parsing; SecretBroker caches the result only in daemon memory
Static OAuth or JWT access tokens Yes Store the value locally or load it as an External Secret; SecretBroker does not itself implement OAuth or token refresh protocols
Username/password, form login, HTTP Basic, Digest, or NTLM No built-in adapter SecretBroker has no structured username/password credential or challenge-response implementation
SSH keys, client certificates, mTLS, passkeys, WebAuthn, or TOTP No built-in adapter SecretBroker does not manage these keys, certificates, authenticators, or interactive ceremonies
Signed or multi-value schemes such as AWS SigV4 No built-in adapter SecretBroker does not assemble multi-field credentials or sign requests

A trusted child process can implement other protocols with an injected secret, but it can also read and expose that plaintext. SecretBroker does not rewrite arbitrary command arguments, answer interactive password prompts, or place credentials on the command line.

In secretbroker exec ... -- curl ..., -- only separates SecretBroker options from curl argv. Cookie credentials add a private cookie jar automatically. Secret credentials enter environment variables; curl does not read them or receive an automatic Authorization header. Use grant for broker-managed HTTP header injection.

AI agent and MCP setup

SecretBroker sits behind an MCP host. The agent chooses a scope and operation; secretbroker-mcp translates that tool call into a request to the local broker and returns only the operation result or a short-lived capability.

Install SecretBroker first, then register the installed MCP binary with your MCP host.

Codex CLI

codex mcp add secretbroker -- \
  "$HOME/Library/Application Support/SecretBroker/bin/secretbroker-mcp"
codex mcp list

Start a new Codex session after registration. Run /mcp in the session to confirm that the SecretBroker tools are available.

Claude Code

claude mcp add -s user secretbroker -- \
  "$HOME/Library/Application Support/SecretBroker/bin/secretbroker-mcp"
claude mcp list

Start a new Claude Code session after registration.

For either host, the server provides these tools:

Tool Purpose
sb_list_scopes List available credential scopes without returning secret values
sb_exec Run a command with injected credentials and return captured output
sb_get_state_file Create a short-lived browser or cookie state file
sb_grant Create a short-lived, scope-locked reverse-proxy grant
sb_request_scope Ask the user to import a browser session

The following flow shows how an AI agent uses sb_exec. Other MCP tools use the same scope, policy, and approval checks.

sequenceDiagram
    autonumber
    actor User
    participant Agent as AI agent
    participant Host as MCP host
    participant MCP as secretbroker-mcp
    participant Daemon as secretbrokerd
    participant App as SecretBrokerApp
    participant Source as Vault or browser
    participant Child as Approved command
    participant Service as Target service

    Agent->>Host: Call sb_list_scopes
    Host->>MCP: MCP tools/call
    MCP->>Daemon: List available scopes
    Daemon-->>MCP: Scope metadata only
    MCP-->>Host: Tool result
    Host-->>Agent: Available scopes

    Agent->>Host: Call sb_exec with scope and command
    Host->>MCP: MCP tools/call
    MCP->>Daemon: Request a scoped exec grant
    Daemon->>Source: Resolve the matching credential
    Source-->>Daemon: Selected credential

    opt No exact reusable approval applies
        Daemon->>App: Send the scope and full command
        App->>User: Request approval and authentication
        User-->>App: Approve or deny
        App-->>Daemon: Return the decision
    end

    alt Request is denied or times out
        Daemon-->>MCP: Structured error
        MCP-->>Host: Failed tool result
        Host-->>Agent: Explain the failure
    else Request is approved
        Daemon-->>MCP: Short-lived grant and injection material
        MCP->>Child: Start the trusted command with injected credentials
        Child->>Service: Send an authenticated request
        Service-->>Child: Return the response
        Child-->>MCP: Return stdout, stderr, and exit code
        MCP->>Daemon: Release the grant
        MCP-->>Host: Return a bounded, exact-value-redacted result
        Host-->>Agent: Return the tool result
    end
Loading

For browser automation, the agent can call sb_get_state_file and receive a short-lived state-file path. For repeated requests to one host, it can call sb_grant and receive a scope-locked proxy endpoint and token. Both outputs are sensitive capabilities and remain subject to user approval, expiry, revocation, and audit logging.

If the selected scope uses an External Secret or command provider, sb_exec and sb_grant wait for the load automatically. An External Secret normally loads in the background. If its loader returns exit code 20, or an on-demand provider needs Sign-in commands, the app may open Terminal. The agent continues the original tool call and receives only the operation result or proxy capability.

The MCP server redacts exact injected secret and cookie values from captured command output. This is defense in depth, not data-loss prevention: transformed, encoded, or indirectly exfiltrated values may evade redaction.

Common commands

secretbroker status
secretbroker scopes
secretbroker list
secretbroker audit --since 1h
secretbroker audit --verify
secretbroker panic

Run secretbroker help for the complete command list. See docs/guide.md for installation, policy, troubleshooting, and uninstall instructions.

Architecture

SecretBroker runs as a local broker between automation clients and credential sources. Clients request an operation for a scope; they do not query the vault directly.

flowchart LR
    User["User"] -->|"reviews request and authenticates"| App["SecretBrokerApp<br/>approval and credential management"]

    subgraph Clients["Automation clients"]
        CLI["secretbroker CLI"]
        MCP["secretbroker-mcp"]
    end

    CLI -->|"JSONL over owner-only Unix socket"| Daemon["secretbrokerd<br/>scope, policy, and grant enforcement"]
    MCP -->|"JSONL over owner-only Unix socket"| Daemon
    App -->|"management requests and decisions"| Daemon
    Daemon -->|"full command or operation summary"| App

    subgraph Sources["Credential sources"]
        Vault[("Encrypted vault<br/>key protected by macOS Keychain")]
        External[("External secret manager<br/>approved reusable Loader")]
        Browser[("Chromium cookie database<br/>read-only live import")]
    end

    Vault -->|"selected credential"| Daemon
    External -->|"plaintext cached only in daemon memory"| Daemon
    Browser -->|"fresh scoped cookies"| Daemon
    Daemon --> Audit[("Hash-chained audit log")]

    subgraph Delivery["Approved delivery modes"]
        Exec["exec<br/>inject into a child process"]
        State["state<br/>write an owner-only temporary file"]
        Proxy["proxy<br/>create a scope-locked loopback endpoint"]
    end

    Daemon -->|"short-lived grant"| Exec
    Daemon -->|"short-lived grant"| State
    Daemon -->|"short-lived grant"| Proxy
Loading

How it works

  1. The CLI or MCP server asks secretbrokerd to use a credential for a specific host scope and operation.
  2. The daemon validates the request, identifies the local peer, selects a matching credential, and resolves the applicable policy.
  3. Unless an exact reusable approval applies, the menu-bar app shows the scope, credential, mode, and complete command or operation summary. The user approves or denies the request and authenticates with Touch ID or the account password.
  4. After approval, the daemon issues a short-lived grant. The CLI or MCP server applies returned environment bindings and cookie material when it starts the approved child process; the daemon can instead write a temporary state file or expose a scope-locked loopback proxy.
  5. The daemon records the decision in the audit log. Release, expiry, or panic revokes the grant and removes temporary credential material.

The broker reduces accidental plaintext exposure, but it does not make an approved process trustworthy. An approved child process can read injected values, and a state-file path acts as a temporary bearer capability.

The source tree is organized into:

Path Responsibility
Sources/SecretBrokerCore Shared protocol, models, scope parsing, and socket helpers
Sources/SecretBrokerChrome Chromium cookie discovery and decryption
Sources/SecretBrokerVault Keychain integration, biometry, encrypted local values, and encrypted External Secret definitions
Sources/secretbrokerd Broker, approval flow, audit log, proxy, and Unix socket server
Sources/secretbroker Command-line client
Sources/secretbroker-mcp MCP server
Sources/SecretBrokerApp Menu-bar approval and credential-management app
Sources/SecretBrokerTestSupport Shared core checks used by XCTest and lightweight toolchains
Tests/SecretBrokerTests Standard SwiftPM test target

Development

Build and run the checks:

swift build
swift test
swift run secretbroker-check

Run the headless integration tests against isolated temporary data:

./smoke.sh
./test-approver.sh
./test-mcp.sh
./test-proxy.sh
./test-hardening.sh
./test-request-scope.sh
./test-protocol.sh
./test-command-provider.sh
./test-external-secret.sh
./test-app-lifecycle.sh

Some integration tests set SECRETBROKER_DEV_APPROVE=1, SECRETBROKER_DEV_FILEKEY=1, and SECRETBROKER_DEV_ALLOW_UNSIGNED=1. These switches work only in debug builds. Never use them with production credentials.

Distribution

For maintainers, release.sh builds a versioned archive, applies hardened-runtime Developer ID signatures, submits it to Apple's notary service, saves the notarization log, staples the app ticket, verifies signatures, and writes a SHA-256 checksum. It refuses to create a release without both signing and notarization credentials.

xcrun notarytool store-credentials secretbroker-notary
SECRETBROKER_SIGNING_IDENTITY="Developer ID Application: Example (TEAMID)" \
SECRETBROKER_NOTARY_PROFILE=secretbroker-notary \
./release.sh 0.1.0

The tag-based release workflow requires the Apple secrets documented in CONTRIBUTING.md. Never distribute the ad-hoc signed output from package-app.sh as a publisher-verified build.

See CONTRIBUTING.md for the development workflow, test expectations, and commit rules.

Security

SecretBroker reduces accidental secret exposure; it cannot make an untrusted local process safe.

  • Approval authorizes an operation, but exec does not sandbox the approved child process.
  • A state-file path is a capability. Any same-user process that can read the file before expiry can read its contents.
  • A command provider runs approved executables with the macOS user's permissions. Configure only commands you trust.
  • An External Loader is standing, user-approved code. It runs as the macOS user at startup, on demand, or on a configured interval. SecretBroker does not sandbox it or restrict its network and filesystem access.
  • Updating a shared Loader requires one fresh approval that lists every affected External Secret. The daemon clears their caches, revokes their grants, and advances all bindings in one encrypted-registry transaction.
  • External Secret definitions are encrypted at rest. Loaded values live only in daemon memory and are cleared on panic, deletion, configuration change, or app exit.
  • Command-provider registration and use require a current, provider-capable SecretBroker app so the full recipe can be reviewed; there is no generic headless approval fallback.
  • The installed daemon is app-owned. Opening SecretBroker starts it; quitting or crashing the app stops it, cancels active acquisitions, clears proxy grants, and shreds temporary material. CLI and MCP calls are unavailable while the app is closed.
  • A Terminal launcher contains a short-lived provider-session capability and non-secret broker routing paths, but no recipe or credential. It is owner-only and removed on completion, cancellation, timeout, panic, or restart.
  • Client labels are self-reported and must not be treated as authentication.
  • The audit chain detects many edits within the retained log, but it does not prevent deletion or replacement by a process with the same filesystem access.
  • SECRETBROKER_DEV_APPROVE, SECRETBROKER_DEV_FILEKEY, and SECRETBROKER_DEV_ALLOW_UNSIGNED weaken debug builds. Release builds compile them out.
  • SECRETBROKER_ALLOW_RAW=1 enables explicit plaintext CLI output after signed caller verification and fresh user authentication. Leave it unset for routine use.

Report vulnerabilities according to SECURITY.md.

Documentation

Contributing

Contributions are welcome. Review CONTRIBUTING.md and discuss security-sensitive design changes before implementing them.

License

SecretBroker is licensed under the Apache License 2.0.

About

SecretBroker is a local macOS credential broker for scoped, user-approved AI agent operations without exposing stored credentials.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages