Metadata-Version: 2.4
Name: keyward
Version: 1.1.1
Summary: Local secret broker that keeps API keys out of files AI agents can read.
Project-URL: Homepage, https://github.com/sumedhrasal/keyward
Project-URL: Repository, https://github.com/sumedhrasal/keyward
Author: sumedh
License: MIT License
        
        Copyright (c) 2026 sumedh
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: ai-agents,api-keys,proxy,secrets,security
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Requires-Python: >=3.11
Requires-Dist: aiohttp>=3.9
Requires-Dist: keyring>=24.0
Requires-Dist: tomli-w>=1.0
Requires-Dist: typer>=0.12
Provides-Extra: linux
Requires-Dist: cryptography>=42; extra == 'linux'
Requires-Dist: secretstorage>=3.3; extra == 'linux'
Description-Content-Type: text/markdown

# keyward

A local secret broker for developers who run AI coding agents on their own machines.

## The goal

Keep API keys out of any file an AI agent, co-pilot, or third-party tool can read,
without adding friction to normal development.

Your code never contains real keys. It contains opaque tokens like `kw_ab12cd34`.
A local daemon swaps the token for the real key only when the outbound request
goes to an allowlisted endpoint, and records every use.

If an agent reads your code, config, or environment, it sees tokens. Tokens are
useless off-host: they only resolve inside the daemon, which will not forward
them to destinations you have not explicitly approved.

## Why this is not just encryption

"One-way encryption you can decrypt" does not exist. What this package actually
provides is **tokenization plus a scoped, audited forward proxy**. The security
properties that matter are:

- real secrets live in the OS keychain, never on disk in plaintext
- code and config contain only tokens
- the daemon forwards to an allowlist, so a leaked token cannot exfiltrate data to a new host
- every resolution is logged
- new destinations require explicit user approval

## Intended user experience

Onboarding is the product. If any step feels heavier than `export KEY=...`,
it has failed its design goal.

```
# one-time setup
pip install keyward
keyward init

# add a key (prompts for the secret; never passed on the command line)
keyward add openai --endpoint api.openai.com

# run any program with tokens injected as env vars
keyward run -- python app.py
keyward run -- pytest
keyward run -- npm start

# rotate a key in place; tokens stay the same so no code changes
keyward rotate openai

# list, remove, inspect
keyward list
keyward rm openai
keyward log --since 1h
```

Your code stays boring:

```python
import os, openai
client = openai.OpenAI()   # reads OPENAI_API_KEY and OPENAI_BASE_URL from env
```

Under `keyward run`, those variables point at the local daemon with a token.
Outside `keyward run`, they are not set at all.

## Activating from inside your app

If you don't want to wrap every command with `keyward run`, call
`keyward.activate()` once near the top of your app. With the daemon installed
as a login agent (`keyward init`), this is all you need:

```python
# .env (or your normal env-loading mechanism)
# OPENAI_API_KEY=kw_ab12cd34
import os
from dotenv import load_dotenv
load_dotenv()

import keyward
keyward.activate()       # rewrites OPENAI_BASE_URL to point at the daemon

from openai import OpenAI
client = OpenAI()        # transparently goes through keyward
```

`activate()` looks at every registered key, and for each one whose `env_vars`
already hold its token in `os.environ`, sets the matching `base_url_env` to the
daemon URL. Real keys are left alone. It also exports `KEYWARD_DAEMON` as a
stable signal you can check from your code (`if "KEYWARD_DAEMON" in os.environ:
...`) to confirm activation.

It returns a `keyward.ActivateResult` with three lists so you can see exactly
what happened:

```python
result = keyward.activate(strict=False)
if result.skipped_no_env:
    print(f"token not found in env for: {result.skipped_no_env}")
    print("Did you load your .env file before calling activate()?")
# result.activated       — keys that are now routing through the daemon
# result.skipped_no_env  — keys whose token was not found in any env var
# result.skipped_no_base_url — keys with no base_url_env configured
```

If no daemon is running, `activate()` raises `keyward.DaemonNotRunning`. Pass
`strict=False` to return an empty result instead — useful for code that should
work both with and without keyward installed.

## What works today (v0.2)

| Area                  | Status                                                                 |
|-----------------------|------------------------------------------------------------------------|
| CLI commands          | `init`, `add`, `list`, `rm`, `rotate`, `restart`, `run` all functional |
| Keychain storage      | macOS Keychain, Windows Credential Manager, Linux libsecret via `keyring` |
| Proxy forwarding      | Authorization: Bearer and x-api-key, on both ingress and egress        |
| Streaming             | Server-Sent Events forwarded without buffering                         |
| Login agent           | macOS LaunchAgent install/uninstall/kickstart via `keyward init`       |
| Daemon reuse          | `keyward run` reuses a live daemon; else spawns ephemeral              |
| Audit log             | Stub only (prints TODO; no log is written yet)                         |
| Endpoint enforcement  | Each token is bound to one host at `keyward add` time; the daemon ignores the request host and always forwards to the stored endpoint — so a token cannot be used against a different host |
| Multi-endpoint allowlist + approval flow | Not yet — v0.3 scope; see ARCHITECTURE.md   |
| Linux systemd / Windows scheduled task | Not wired up yet                                      |
| Websocket proxying    | Returns 501; HTTP only for now                                         |
| Request body streaming| Buffered; fine for LLM chat, not for large uploads                     |
| Caller attestation    | Trust-anything on localhost; see ARCHITECTURE.md                       |

See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the full design, threat
model, and the list of deferred items.

## Verifying the key swap

The sharpest test is to point keyward at a request-echoing endpoint and look
for your raw secret (and the absence of the token) in the response.

```bash
# pick a distinctive fake secret so you can spot it in the echo
keyward add echotest --endpoint httpbin.org
# at the prompt, enter: sk-fake-secret-12345

keyward restart   # only needed if a LaunchAgent daemon is already running

keyward run -- curl -s "$ECHOTEST_BASE_URL/anything" \
    -H "Authorization: Bearer $ECHOTEST_API_KEY"
```

In the JSON response, under `headers.Authorization`:
- `Bearer sk-fake-secret-12345` means the swap worked.
- Anything starting with `Bearer kw_` means the swap did not happen (bug).

For the Anthropic-style (x-api-key):

```bash
keyward add echotestx --endpoint httpbin.org --auth-style x-api-key
keyward restart
keyward run -- curl -s "$ECHOTESTX_BASE_URL/anything" \
    -H "x-api-key: $ECHOTESTX_API_KEY"
```

Check `headers.X-Api-Key` in the response.

Clean up with `keyward rm echotest -y && keyward rm echotestx -y`.

There is also a Python equivalent that uses `keyward.activate()`:

```bash
keyward add echotest --endpoint httpbin.org
keyward restart
uv run python scripts/verify_swap.py echotest
```

## License

MIT. See [LICENSE](LICENSE).
