Skip to main content

Local secret broker that keeps API keys out of files AI agents can read.

Project description

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:

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:

# .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:

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 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.

# 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):

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():

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

License

MIT. See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

keyward-1.1.1.tar.gz (99.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

keyward-1.1.1-py3-none-any.whl (19.0 kB view details)

Uploaded Python 3

File details

Details for the file keyward-1.1.1.tar.gz.

File metadata

  • Download URL: keyward-1.1.1.tar.gz
  • Upload date:
  • Size: 99.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for keyward-1.1.1.tar.gz
Algorithm Hash digest
SHA256 e9a712d936979f106a9df76875e9b424a4a7d33e04f586460616a37789d1a657
MD5 939eb0de60796cf2d05a22884f087700
BLAKE2b-256 6dc224c69e619803e644d460097a643a0a138b94ac16c6b81b0d2025c40a51d8

See more details on using hashes here.

Provenance

The following attestation bundles were made for keyward-1.1.1.tar.gz:

Publisher: release.yml on sumedhrasal/keyward

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file keyward-1.1.1-py3-none-any.whl.

File metadata

  • Download URL: keyward-1.1.1-py3-none-any.whl
  • Upload date:
  • Size: 19.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for keyward-1.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8174193f8ec0ca2492e657f3f6057a72d0be158d5e469c4707ada20b158a02a0
MD5 f35ee373d9c1f064ee6e0f2d0f53fdda
BLAKE2b-256 2bafe26c7e5e7b385e4e3642e8829de1ecc026932df676d74d9c0bc0c49a12b2

See more details on using hashes here.

Provenance

The following attestation bundles were made for keyward-1.1.1-py3-none-any.whl:

Publisher: release.yml on sumedhrasal/keyward

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page