Dev.to AI 🤖 Ai 👁 0 📖 3 min read

Stop leaking API keys into Claude Code

Every time I pasted an API key into Claude Code, I'd get the little warning: "this key may have leaked — rotate it." And… yeah. I know. By then the key is already in the request, and already sitting in my session transc

Every time I pasted an API key into Claude Code, I'd get the little warning: "this key may have leaked — rotate it."

And… yeah. I know. By then the key is already in the request, and already sitting in my session transcript forever. Rotating it is the chore, not the fix.

I didn't want to rotate a key every time I referenced one, and "just be careful" stops working the moment you're moving fast — which, if you're vibecoding, is most of the time. So I made the careful part automatic.

It's called Keyward — a small, open-source Claude Code plugin. Here's the idea, plus the one genuinely tricky part of building it.

The problem, precisely

When you paste a secret into the chat:

  1. it's sent to Anthropic's API as part of your prompt,
  2. it's written to your local *.jsonl transcript,
  3. Claude (correctly) tells you it leaked.

Three places, one paste. The damage is done before you finish reading the warning.

What Keyward does

A UserPromptSubmit hook scans every message you submit. When it spots a key it:

  1. detects it (regex for ~20 providers, explicit /key markers, optional gitleaks),
  2. saves the value to ~/.claude/secrets/<name>.txt (chmod 600),
  3. blocks the original prompt so the raw value never reaches the model,
  4. re-submits a sanitized version of your message — the key replaced by <<secret:NAME stored at ~/.claude/secrets/NAME.txt>>.

You press Enter once; the model only ever sees the reference.

The tricky part: you can't modify a prompt, only block it

This is the bit worth sharing, because it surprised me.

Claude Code's UserPromptSubmit hook runs before your message reaches the model — the perfect place to catch a key. But by design, that hook cannot rewrite your prompt. It can only:

  • add context alongside it, or
  • block it entirely.

That's a deliberate safety choice: a hook that could silently rewrite your prompt would be a nasty attack surface (imagine a plugin quietly turning "delete file X" into "delete file Y").

So "detect the key and quietly swap it" isn't directly possible. Keyward's workaround:

# intercept.py (simplified)
detection = detect(prompt)
if detection["secrets"]:
    for s in detection["secrets"]:
        save_secret(s["name"], s["value"])        # atomic write, chmod 600
    sanitized = sanitize_prompt(prompt, detection["secrets"])
    write_tempfile(sanitized)                      # + put on clipboard
    spawn_paste(sanitized)                         # OS-level keystrokes
    print(json.dumps({"decision": "block",
                      "suppressOriginalPrompt": True}))

The hook blocks the leaking prompt and spawns a tiny detached process that, via OS-level automation, pastes the sanitized text and hits Enter for you. From your side: a blocked message flashes, then a clean one takes its place. One keypress.

Cross-platform paste

The paste backend is per-OS, and each one saves your clipboard, types, verifies focus hasn't changed, and restores the clipboard:

  • macOS — osascript (needs Accessibility permission)
  • Linux X11 — xdotool
  • Linux Wayland — wtype (compositor-dependent: Sway/Hyprland yes, GNOME not by default)
  • Windows — PowerShell SendKeys

No display server (SSH, Docker)? Set KEYWARD_DISABLE_PASTE=1 — it still saves and sanitizes, you paste manually.

Using a saved key without re-leaking it

A bundled skill teaches Claude to consume the saved secret without printing it:

export GITHUB_TOKEN=$(cat ~/.claude/secrets/github_pat_classic.txt) && gh api /user

…never a bare cat (which would dump the value straight back into the context). The value flows disk → process env → tool, and never through stdout.

Honestly: it's defense-in-depth, not magic

Secrets are stored as chmod 600 plaintext — the same trust model as ~/.aws/credentials or a .env file, not encrypted at rest. Keyward is a safety net for the "I need to use this key once, in chat, now" workflow, not a replacement for a real secret manager. The README and wiki document exactly what it does and doesn't protect, which feels like the right thing for a security tool.

Try it

In a Claude Code session:

/plugin marketplace add albemiglio/keyward
/plugin install keyward@keyward

Free and open source (MIT) — github.com/albemiglio/keyward. ~35 tests, CI across macOS/Linux/Windows, no network calls, no telemetry.

If you've ever pasted a key into an AI tool and hoped for the best — this is the net. 🔑

📰 Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.