Public beta · v0.1.0 →

Open-source protection from leaked API keys

Apps get virtual keys. The real key never leaves Keman, so a leaked copy is locked, watched, and killed in one command.

$ curl -fsSL https://raw.githubusercontent.com/kirank55/keman/main/scripts/install.sh | sh

One Go binary · Linux, macOS and Windows · Apache-2.0 · No account, no telemetry

keman
keman · http://127.0.0.1:8081                                                   1 keys  2 detections  3 audit  4 setup
! 2 keys work from any address · 1 key never expires · open one and press p
  name              status     kind      lock        alerts 7d  expires    last seen
› billing-svc       active     standard  ip          3          in 81d     <1m ago · billing-eu 192.0.2.30 DE
  ci-github         active     standard  ip          0          in 21d     <1m ago · github-actions 198.51.100.7 US
  laptop            active     standard  ip          0          in 29d     <1m ago · dev-laptop 203.0.113.10 US
  laptop (rotated)  active     standard  ip          0          in 23h     5m ago · office-mac 203.0.113.11 US
  old-intern        disabled   standard  open        4          in 81d     <1m ago · 198.51.100.200 SG
  prod-api          active     standard  ip+country  4          in 81d     <1m ago · 203.0.113.200 BR
  repo-decoy        active     canary    decoy       1          never      <1m ago · 192.0.2.200 NL
  staging-worker    active     standard  open        3          in 81d     <1m ago · staging 192.0.2.40 DE
  support-bot       active     standard  open        14         never      <1m ago · 192.0.2.200 NL

enter open · n new · p policy · r rotate · d disable · x revoke · / filter · ? help · q quit

API keys leak.

Committedto a repo, then “removed” while it stays in history
Printedin a CI log or an error report
Bakedinto a container image or a mobile app
Copiedoff a laptop, a backup or a shared .env
A leaked provider keyA leaked Keman key
Works fromanywhere, for anyoneonly the addresses and countries you allow
Works untilsomeone noticesit expires on its own, after 90 days by default
Can calleverything the account canonly the endpoints and models you allow, up to a dollar cap
You find outfrom the bill, or the provider’s emailfrom an alert with who, where, and the command to run
To stop itrotate the real key and redeploy every app that uses itkeman key rotate app --grace 0, and nothing else changes

How it works

A leaked key only works where you allow it

Keman sits between your apps and the provider. Apps send a kmn_… virtual key. Keman checks it, then forwards the request with the real key, which never leaves Keman.

Your app

Holds a virtual key

kmn_x7Kq…

A leaked copy

Used from a network or country you never allowed

Keman

  • Checks address, country, expiry, paths and models
  • Limits requests per minute and dollars per day
  • Watches for new countries, spikes and decoys
  • Swaps in the real key, encrypted at rest

✕ Leaked copy refused, alert sent

Provider

21 platform presets, from OpenAI, Anthropic and Groq to Stripe, GitHub and Twilio, or any HTTP API that takes a key in a header

Try it

Try to use a leaked key

Pick who is calling. Every response here is real: recorded from Keman running the demo install, where prod-api is locked to its German network and two models, and billing-svc may only create charges and read customers.

request through Keman
# prod-api, called from 192.0.2.20 (DE, AS64502)
POST /v1/chat/completions
Authorization: Bearer kmn_MjI3yW5z_…

{"model": "gpt-4o-mini", "messages": [{"role": "user", "content": "hi"}]}

HTTP/1.1 200 OK

{
  "id": "chatcmpl-demo",
  "object": "chat.completion",
  "model": "gpt-4o-mini",
  "choices": [
    {
      "index": 0,
      "message": {"role": "assistant", "content": "Hello from the demo upstream."},
      "finish_reason": "stop"
    }
  ],
  "usage": {"prompt_tokens": 2316, "completion_tokens": 448, "total_tokens": 2764}
}

Forwarded. Keman swaps in the real key and streams the provider’s answer back untouched. In the demo, the provider is a local mock.

A leak, start to finish

From the first stolen call to a dead key

This is the demo install’s own incident, captured from the real terminal UI, CLI and Slack alert.

  1. 1

    A copy is used from somewhere new

    support-bot was open to every address. A copy of it starts calling Claude Opus from a residential network in Brazil. Keman records every call: address, network, model, tokens and cost.

    keman · support-bot
    keman · http://127.0.0.1:8081                                                     1 keys  2 detections  3 audit  4 setup
    ╭─ support-bot ────────────────────────────────────╮╭─ requests · 15 recent · UTC · POST /v1/messages ─────────────────╮
    │ qWmeK8lJ                                  active ││ 07:46  192.0.2.200    claude-opus-4-5        1,382 → 99  $0.0094 │
    │                                                  ││ 07:46  192.0.2.200    claude-opus-4-5         843 → 469    $0.02 │
    │  open network  anyone who has it can call it     ││ 07:46  192.0.2.200    claude-opus-4-5       2,695 → 177    $0.02 │
    │   set --cidr <egress>/32                         ││ 07:46  203.0.113.200  claude-opus-4-5    54,089 → 6,844    $0.44 │
    │                                                  ││ 07:46  203.0.113.200  claude-opus-4-5    47,920 → 7,447    $0.43 │
    │  no expiry  a leaked copy keeps working          ││ 07:46  203.0.113.200  claude-opus-4-5    46,727 → 6,783    $0.40 │
    │   key expiry 90d                                 ││ 07:46  203.0.113.200  claude-opus-4-5    65,321 → 7,976    $0.53 │
    │                                                  ││ 07:46  203.0.113.200  claude-opus-4-5    54,428 → 7,489    $0.46 │
    │ 3,546 requests · last 07:46                      ││ 07:46  203.0.113.200  claude-opus-4-5    54,115 → 4,423    $0.38 │
    │ prod-eu-1  192.0.2.20 DE AS64502  3,525          ││ 07:46  203.0.113.200  claude-opus-4-5    69,124 → 7,335    $0.53 │
    │ 203.0.113.200 BR AS64510  18                     ││ 07:46  203.0.113.200  claude-opus-4-5    41,837 → 4,719    $0.33 │
    │ 192.0.2.200 NL AS64511  3                        ││ 07:46  203.0.113.200  claude-opus-4-5    59,220 → 7,544    $0.48 │
    │ spend $21.80                                     ││ 07:46  203.0.113.200  claude-opus-4-5    56,144 → 4,718    $0.40 │
    │ in 7,378,616 · out 1,625,702                     ││ 07:46  203.0.113.200  claude-opus-4-5    54,984 → 5,176    $0.40 │
    │                                                  ││ 07:46  203.0.113.200  claude-opus-4-5    64,804 → 6,150    $0.48 │
    │ detections                                       ││                                                                  │
    │ 2026-10-29 07:46 UTC  high  spend_spike ×10      ││                                                                  │
    │ 2026-10-29 07:46 UTC  low  new_asn ×2            ││                                                                  │
    │ 2026-10-29 07:46 UTC  medium  new_country ×2     ││                                                                  │
    ╰──────────────────────────────────────────────────╯╰──────────────────────────────────────────────────────────────────╯
    
    p policy · r rotate · n name · d disable · e enable · x revoke · j/k scroll · esc back · q quit
  2. 2

    The alert lands in Slack

    Spend in the last hour crossed $5, far above this key’s usual hourly peak. The alert names the address, the network and the model, and leads with the command to run.

    # security-alertsKeman alerts
    KemanAPP4:31 PM
    [high] spend_spike on virtual key support-bot (qWmeK8lJ) from 203.0.113.200 (BR, AS64510) calling POST /v1/messages evidence: {"hour_microusd":5285189,"model":"claude-opus-4-5","p95_microusd":147195,"threshold_microusd":5000000} what to do: Not you: issue a replacement and revoke the leaked key now keman key rotate qWmeK8lJ --grace 0 Not sure: turn it off until you know (reversible) keman key disable qWmeK8lJ It was you: allow this address and nothing else keman policy set qWmeK8lJ --cidr 203.0.113.200/32 It was you: allow this country and nothing else keman policy set qWmeK8lJ --country BR
  3. 3

    Every rule that fired, in one place

    New country, new network and a spend spike, grouped into one incident per key and rule, so a burst of abuse is one line instead of a hundred.

    keman · detections
    keman · http://127.0.0.1:8081                                                   1 keys  2 detections  3 audit  4 setup
      time                     severity  key                        rule
    › 2026-10-29 07:46 UTC     medium    support-bot (qWmeK8lJ)     new_country ×2
      2026-10-29 07:46 UTC     low       support-bot (qWmeK8lJ)     new_asn ×2
      2026-10-29 07:46 UTC     high      support-bot (qWmeK8lJ)     spend_spike ×10
      2026-10-29 07:46 UTC     medium    prod-api (MjI3yW5z)        new_country
      2026-10-29 07:46 UTC     low       prod-api (MjI3yW5z)        new_asn
      2026-10-29 07:46 UTC     medium    prod-api (MjI3yW5z)        allowlist_violations ×2
      2026-10-29 07:46 UTC     medium    billing-svc (2M1MxJLa)     allowlist_violations ×3
      2026-10-29 07:46 UTC     high      old-intern (a2gxDT24)      disabled_key_used ×4
      2026-10-29 07:46 UTC     medium    staging-worker (YQqpsBqV)  upstream_auth_burst ×3
      2026-10-29 07:46 UTC     critical  repo-decoy (ZNDo8sy2)      canary_used
    
    loading…
    j/k move · enter open key · / filter · 1-4 views · ? help · q quit
  4. 4

    One command kills it

    keman key rotate support-bot --grace 0 issues a new key with the same policy and revokes the old one. The real Anthropic key doesn’t change, and no other app notices.

    acme-api
    acme-api $ keman key rotate support-bot --grace 0
    store the new virtual key (token) now; it is not shown again
    the old key is revoked and renamed "support-bot (rotated)"
    {
      "credential_id": "cred_Ua1jK69zd3vM",
      "expires_at": "2027-01-27T07:47:09Z",
      "id": "74U2UU5J",
      "kind": "standard",
      "name": "support-bot",
      "replaced": {
        "expires_at": null,
        "id": "qWmeK8lJ",
        "name": "support-bot (rotated)",
        "status": "revoked"
      },
      "status": "active",
      "token": "kmn_74U2UU5J_a16MAbXkL2rW3fTHoCzx20l8fPGc0kpYARbnxLlxsL4"
    }
  5. 5

    The stolen copy is worthless

    The next call from Brazil gets 401 key_revoked, and still raises an alert, so you know if they keep trying. Then lock the new key to your own network with keman policy set.

    request through Keman
    # support-bot, called from 203.0.113.200 (BR, AS64510)
    POST /v1/messages
    x-api-key: kmn_qWmeK8lJ_…
    
    {"model": "claude-opus-4-5", "max_tokens": 1024, "messages": [{"role": "user", "content": "hi"}]}
    
    HTTP/1.1 401 Unauthorized
    X-Keman-Error: key_revoked
    
    {
      "error": {
        "type": "keman_error",
        "code": "key_revoked",
        "message": "This virtual key has been revoked."
      }
    }

Deceive

Plant a decoy, catch the thief

Canary keys look like real ones and never reach the provider. Plant one in a repo, a CI config or a backup. Its first use raises a critical alert, so you know what else was stored next to it.

acme-api
acme-api $ keman canary create --credential openai-main --name decoy --base-url https://keman.example.com >> .env.bak
canary decoy (Dh98cXlL) created. Plant the lines above where a thief would look: a .env in a repo,
CI variables, an old config or a backup. Any use raises a critical alert and never reaches the upstream.
Keep the snippet free of comments that mention keman or canaries.
acme-api $ cat .env.bak
OPENAI_API_KEY=kmn_Dh98cXlL_eLGVO5FpIj1FQ3bUnO7qemo9pAhOwvBlAh831fo6MFP
OPENAI_BASE_URL=https://keman.example.com/v1
# security-alertsKeman alerts
KemanAPP4:32 PM
[critical] canary_used on virtual key repo-decoy (ZNDo8sy2) from 192.0.2.200 (NL, AS64511) evidence: {"count":1} what to do: A decoy was used. Whoever has it also saw what was stored next to it. Rotate those real secrets. See every use of the decoy, with addresses keman detection list --key ZNDo8sy2

Find

Find the keys you already leaked

keman scan checks your files and every commit on every branch for OpenAI, Anthropic, OpenRouter, Groq, xAI, Stripe, GitHub, Slack, SendGrid, Linear, Google and AWS keys, and names the preset that moves each one behind Keman. It runs offline, needs no setup, and exits 3 so it can gate CI.

acme-api
acme-api $ keman scan . --history
found 2 secrets in 3 places

Stripe  sk_live_…E48v
  config/settings.py:1
  config/settings.py:1  (commit 46e6d5a30d14)
  Move it behind Keman so apps only ever hold a virtual key:
    keman upstream add --preset stripe    (once)
    keman credential add --upstream stripe --name stripe-main --secret-env STRIPE_SECRET_KEY
    keman key create --credential stripe-main --name <app> --expires 90d
  It is in git history, so treat it as leaked: rotate it at Stripe too.

OpenAI  sk-proj-…vVNf
  .env:1  (commit 92f4f0f32a58)
  Move it behind Keman so apps only ever hold a virtual key:
    keman upstream add --preset openai    (once)
    keman credential add --upstream openai --name openai-main --secret-env OPENAI_API_KEY
    keman key create --credential openai-main --name <app> --expires 90d
  It is in git history, so treat it as leaked: rotate it at OpenAI too.

Harden

Know which keys would hurt if they leaked

keman exposure lists the keys that work from any address, never expire, or sit unused, with the command that fixes each. Add --strict to fail a CI job until they’re fixed.

acme-api
acme-api $ keman exposure
staging-worker (YQqpsBqV)
  high    Works from any address. A leaked copy works for anyone who finds it.
          fix: keman policy set YQqpsBqV --cidr <your egress IP>/32

support-bot (qWmeK8lJ)
  high    Works from any address. A leaked copy works for anyone who finds it.
          fix: keman policy set qWmeK8lJ --cidr <your egress IP>/32
  medium  Never expires. A copy leaked years ago still works.
          fix: keman key expiry qWmeK8lJ 90d

2 of 7 active keys would be easy to abuse if they leaked.

In the box

Everything a key needs to be safe to lose

Address and country locks

Allow CIDRs and countries per key. Everything else is refused.

Expiry by default

New keys expire after 90 days. Set a date, a duration, or never.

Endpoint and model lists

Every preset ships a path allowlist, and a custom API requires one. LLM keys can also be held to a list of models.

Dollar caps and rate limits

Daily and monthly caps on LLM keys, reserved before each request, and requests per minute on any key.

21 platform presets

keman upstream add --preset groq fills in the host, TLS, paths and auth.

Auto-disable, with a dry run

Let spikes and new countries turn a key off, after a 7-day dry run shows what would have tripped.

Planned rotation

Rotate with a 1-hour, 24-hour or 7-day grace period, so both keys work while you deploy.

Swap the real key in place

keman credential rotate changes the provider key. Virtual keys keep working.

Slack and signed webhooks

HMAC-SHA256 signed, with the evidence and the commands to respond.

Terminal UI, CLI and API

Everything the UI does is also a one-shot command for scripts.

Prometheus metrics

Requests, latency, alerts and queue depth on /metrics.

Test your detections

keman detection test replays abuse scenarios, or your own events.

Trust

Built to be trusted with your keys

Self-hosted

Runs on your machine or your network. No account, no cloud service, and it never phones home.

Real keys stay put

Provider keys are encrypted at rest with AES-256-GCM and never sent to apps. Virtual keys are stored only as hashes.

Your data is not logged

Request bodies, headers and query strings are never logged. For LLM calls, Keman reads only the model and token settings, and the usage counts.

Fast

About 0.07 ms added at the median on loopback. Responses stream back unbuffered, including SSE from the official SDKs.

One binary

A single Go binary with pure-Go SQLite for Linux, macOS and Windows, plus a distroless multi-arch image.

Verifiable

Apache-2.0. Every release ships SHA-256 sums, an SPDX SBOM and a keyless cosign signature.

Get started

Up and running in a few minutes

  1. 1

    Install

    One static binary. No runtime, no CGO.

    $ curl -fsSL https://raw.githubusercontent.com/kirank55/keman/main/scripts/install.sh | sh

    Picks the binary for your OS and CPU, checks its SHA-256 sum (and its cosign signature if cosign is installed), and installs to /usr/local/bin or ~/.local/bin. Read the script.

  2. 2

    Start it

    init prints the admin token once. keman opens the terminal UI and starts the proxy. Press a, pick your platform, and paste its key. The form for your first virtual key opens next.

    $ keman init # writes keman.yaml and master.key, prints the admin token $ keman # opens the terminal UI and starts the proxy

    Prefer scripts? Every step is a command too: keman upstream add --preset openai, keman credential add --upstream openai --name primary --secret-env OPENAI_API_KEY, keman key create --credential primary --name laptop.

  3. 3

    Point your app at it

    Use the kmn_… key wherever the app expects its API key, and change the base URL. Then lock the key to the addresses your app runs from.

    OpenAI Python / Node SDKhttp://127.0.0.1:8080/v1
    OpenRouter through Kemanhttp://127.0.0.1:8080/api/v1
    Anthropic SDKhttp://127.0.0.1:8080
    Groq, through the OpenAI SDKhttp://127.0.0.1:8080/openai/v1
    Every other platformBase URLs for all 21 presets

Or see it all without a provider key

The demo seeds seven keys, a week of normal traffic, and the incidents on this page, against a local mock upstream. Every address is from a documentation range.

$ git clone https://github.com/kirank55/keman && cd keman $ ./scripts/demo.sh $ KEMAN_ADMIN_TOKEN="$(cat .demo/admin.token)" .demo/keman --config .demo/keman.yaml

FAQ

Questions

Does Keman see my prompts?

It forwards them, the way any proxy does, but it doesn’t keep them. For LLM APIs it reads only model, stream, max_tokens and stream_options from the request, and only the usage counts from the response. Bodies, headers and query strings are never logged. It runs on your own machine, so nothing goes to a third party.

How much latency does it add?

On loopback, about 71 µs at the median and 555 µs at p99 (benchmark). Responses, including streams, are passed back unbuffered.

Which APIs does it work with?

Keman ships presets for 21 platforms: OpenAI, Anthropic, OpenRouter, Gemini, Groq, Mistral, DeepSeek, xAI, Together AI, Fireworks AI, Cerebras, Stripe, GitHub, Slack, Twilio, SendGrid, Resend, Postmark, Cloudflare, Linear and Notion. Any other HTTP API that takes a key in a header or HTTP Basic works too. LLM platforms get spend tracking, dollar caps and model allowlists. Every platform gets a path allowlist, so a leaked key can only call the endpoints you allow.

Can’t I just use provider-scoped keys or a secret scanner?

Use both if you can. Scanners tell you a key leaked. Keman makes the leaked copy hard to use and gives you one command to kill it. Provider scoping differs from provider to provider. Keman gives every key the same locks, alerts, decoys and one-step rotation, including on APIs that have none. It also ships a scanner: keman scan . --history.

Isn’t Keman a single point of compromise?

It holds the real keys, so it’s the thing to protect. That trade is deliberate: one hardened place instead of a real key in every app, CI job and laptop. Run it isolated from the apps that use it, and keep the admin listener off the network. It does not protect you if someone can read its memory. The threat model covers the rest.

Do country and network rules need anything extra?

Yes. Country and ASN rules, and country locks, need a GeoLite2 or DB-IP .mmdb file. Keman does not bundle one. Address locks, expiry, caps, decoys and the other rules work without it.

Will normal traffic set off alerts?

A new key learns for 7 days or 500 requests before the spike and new-location rules alert. Decoys, disabled keys and repeated refusals alert from the first request. keman detection test shows what would fire before it matters.

Is it ready for production?

It’s a beta. It runs as a single instance on SQLite and proxies HTTP only (no WebSockets yet, so no OpenAI Realtime). Please try it and open an issue for anything that gets in your way.

What does it cost?

Nothing. Keman is free and open source under Apache-2.0, and runs on your own hardware.

Your API keys will leak.
Keman is the protection.