OpenHands / OpenHands/software-agent-sdk

Support OS keyring for secret storage

Open
#3,988 6 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement proposal
Dominant language
Python
Stars
1.1k
Forks
539
Avg merge
1d 19h
Merged PRs (30d)
137

Description

Feature request: store LLM/provider secrets in the OS keyring

Design comparison:
Secrets: keyring references vs. encrypted-at-rest

Problem

Today the SDK persists secrets (LLM api_key, aws_secret_access_key, etc.) via
SecretStr + a Fernet cipher (utils/cipher.py, utils/pydantic_secrets.py): plaintext
in memory, and either plaintext or encrypted-at-rest when serialized. Encrypted-at-rest
still means the key material — plus the cipher key to unlock it — lives on disk. It's a lot
of machinery to make on-disk secrets a bit less bad.

Proposal

Add a small, pluggable SecretStore abstraction and persist only a reference
({service, account}) to a secret, not the value. Resolve the real value from the OS
keyring at use time. Provider-scoped keys by default, with per-profile overrides.

SecretStore { get(ref), set(ref, value), has(ref), delete(ref) }
SecretRef   { service = "openhands", account = "llm-provider:openai" }

Config/settings serialize the ref; the value never touches settings files.

OS support
OS Backend How Status
macOS Keychain security CLI (find/add/delete-generic-password) ✅ works, no daemon
Windows Credential Manager keyring (wincred) / native ✅ works on desktop
Linux desktop Secret Service (libsecret) GNOME Keyring / KWallet over D-Bus only if a D-Bus session + unlocked keyring is present
Linux headless / server no Secret Service by default ⚠️ needs kernel keyring (keyctl, ephemeral) or a fallback
Docker container no Keychain, no D-Bus/gnome-keyring OS keyring not available out of the box
The Docker question (and whether we can drop the old code)

We can't rely on OS keyring inside a stock container — there's no keychain and no
D-Bus session. But the right answer there isn't "keep the encrypted-file store." It's:

  • Host / desktop (macOS, Windows, Linux-with-session): OS keyring is the primary store.
    On these, the plaintext + Fernet path can be dropped.
  • Containers / headless: inject secrets at runtime (env vars / mounted Docker
    secrets / vault sidecar) and never persist them to disk in the container. If nothing
    is persisted, there's nothing to encrypt — so the Fernet machinery isn't needed here either.

So the encrypted-at-rest cipher is only "needed" for the worst case: persisting secrets to
disk in an environment that has neither a keyring nor runtime injection. That case
should be discouraged, not designed for.

Recommendation: land the SecretStore interface with (1) OS-keyring backends per the
table and (2) an env/injected backend for containers. Then the plaintext + Fernet
encrypted-at-rest code can be removed. Keep the old path only if we confirm a real
deployment needs on-disk persistence with no keyring and no injection.

Prior art

A fresh TypeScript transpilation of this SDK already implements exactly this
(SecretStore + SecretRef, keyring-only, macOS Keychain backend, provider/profile
scoping). Happy to share a design comparison.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading utils/cipher.py and utils/pydantic_secrets.py to map the current persistence path. Compare the requested SecretStore and SecretRef design with the referenced TypeScript implementation and determine the supported host and container flows. Done means the scope, backends, runtime injection behavior, and fate of the Fernet path are resolved and covered by tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
docker, python
Domain
backend, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.