Metadata-Version: 2.4
Name: sandbroker
Version: 0.1.1
Summary: A typed-return secret broker: clients USE secrets over a unix socket, they never SEE the plaintext.
Author: Grayson Adams
License: MIT
Project-URL: Homepage, https://github.com/312-dev/sandbroker
Project-URL: Documentation, https://github.com/312-dev/sandbroker/blob/master/DEPLOY.md
Keywords: secrets,broker,credentials,claude,1password,azure-key-vault,webauthn
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: No Input/Output (Daemon)
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Topic :: Security
Classifier: Topic :: System :: Systems Administration
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: push
Requires-Dist: pywebpush<3,>=2.0; extra == "push"
Requires-Dist: py-vapid<2,>=1.9; extra == "push"
Requires-Dist: cryptography>=41; extra == "push"
Provides-Extra: webauthn
Requires-Dist: cryptography>=41; extra == "webauthn"
Provides-Extra: mcp
Requires-Dist: mcp<2,>=1.12; extra == "mcp"
Provides-Extra: all
Requires-Dist: sandbroker[push,webauthn]; extra == "all"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: build>=1.0; extra == "dev"
Dynamic: license-file

# sandbroker

[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/13912/badge)](https://www.bestpractices.dev/projects/13912)
[![PyPI](https://img.shields.io/pypi/v/sandbroker)](https://pypi.org/project/sandbroker/)
[![CI](https://github.com/312-dev/sandbroker/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/312-dev/sandbroker/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

A typed-return secret executor. Claude Code causes secrets to be **used**
without ever learning what they **are**.

**Full design, threat model, and hardening guide** live in [`docs/`](docs/):
start with [`docs/SECURITY.md`](docs/SECURITY.md),
[`docs/THREAT-MODEL.md`](docs/THREAT-MODEL.md), and [`DEPLOY.md`](DEPLOY.md).
This README is the short version.

## The claim

> No plaintext secret resolved by sandbroker appears in Claude's context, in the
> Anthropic API request body, in the local session transcript, or in any prompt
> cache, because no channel exists through which it could travel.

It does **not** claim anything about secrets arriving through doors sandbroker
doesn't mediate. "100% secure" is not on offer; see the known limits in
[`docs/SECURITY.md`](docs/SECURITY.md).

## Why not a redaction hook

A `PostToolUse` redaction hook is a filter, and filters **fail open**: unmatched
pattern, unexpected encoding, or an unanticipated error string, and the raw value
proceeds to the wire. sandbroker is not a filter. The response schema has no field
a secret can occupy.

## How it holds

Four structural properties, all asserted in `tests/test_guarantee.py` (30 tests):

1. **The child cannot talk to us.** Targets spawn with stdout/stderr bound to
   `/dev/null` at the fd level, so no output on any path reaches the caller.
2. **Secrets never enter `argv`.** Rejected at registry load, because
   `/proc/PID/cmdline` exposes argv to the same uid.
3. **There is no shell.** Argv vectors only, so arguments can never become code.
4. **Egress is allowlisted.** Response keys outside `{ok, error, verb}` are
   dropped and unknown error tokens collapsed.

## Install

Two commands on Linux/WSL2 or macOS:

```bash
pipx install sandbroker        # puts sandbroker, sandbrokerd, and the bin/ helpers on PATH
sandbroker setup               # guided wizard: base + daemon, secret store, approver, Claude Code
```

`sandbroker setup` runs unprivileged (user mode) by default; add `--hardened` for
the dedicated-uid `/opt/sandbroker` system install. See
[`docs/integration-notes/setup-wizard.md`](docs/integration-notes/setup-wizard.md).

## Quick start from a checkout (Linux/WSL, systemd)

```bash
python3 tests/test_guarantee.py                 # expect OK
sudo ./install.sh                               # creates sandbroker uid + claude-broker
install -m 0755 bin/sandbroker ~/.local/bin/sandbroker
install -m 0755 hooks/deny_secret_reads.py ~/.claude/hooks/

# new login session for group membership, then:
sudo ./canary/canary_provision.sh
sandbroker canary.probe --ref file://canary      # expect {"ok": true}
sudo ./canary/canary_sweep.sh                   # expect clean
```

Then register the `PreToolUse` hook in your Claude Code
`settings.json` (see [`docs/`](docs/) for the snippet).

## Ref schemes

| Scheme | Backend |
| --- | --- |
| `file://canary` | local 0400 file, verification only |
| `akv://<vault>/<name>` | Azure Key Vault via `az` |
| `op://<vault>/<item>/<field>` | 1Password via the `op` CLI |

## Layout

```
sandbroker/sandbrokerd.py   the daemon: exec core, registry validator, egress filter
sandbroker/verbs.json    capability grants; installed root:root 0444
bin/sandbroker          the only interface Claude uses
hooks/                 PreToolUse deny for doors sandbroker doesn't mediate
canary/                provision + sweep: the leak test that must keep passing
tests/                 the guarantee, as executable assertions
install.sh             every sudo step (Linux/systemd)
```

## Verification note

**A sweep only means something when Claude runs the probe.** Running it yourself
proves nothing, because the canary was never near the model's context. Have
Claude exercise the verbs and attempt extraction, *then* sweep.
