Claude-skill-registry keyring
Cross-platform secure credential storage using system keychains
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/keyring" ~/.claude/skills/majiayu000-claude-skill-registry-keyring && rm -rf "$T"
manifest:
skills/data/keyring/SKILL.mdsource content
keyring
Cross-platform library for securely storing and retrieving passwords/credentials using the operating system's native credential storage.
Overview
keyring provides a unified API to interact with platform-specific secure credential stores:
- Each credential is identified by a
pair(service, user) - Credentials are stored securely in the OS keychain, encrypted at rest
- The library abstracts platform differences behind the
typeEntry
Key Types
Entry
The main type for interacting with credentials:
use keyring::Entry; // Create an entry for a service/user combination let entry = keyring::Entry::new("com.myapp.service", "username")?;
Error
Non-exhaustive enum covering all failure modes:
| Variant | Description |
|---|---|
| No credential exists for this entry |
| Password is not valid UTF-8 (raw bytes attached) |
| Attribute exceeded platform length limit |
| Invalid attribute (name, reason) |
| Multiple matching credentials found |
| Underlying platform error |
| Cannot access credential store (locked?) |
Result
Type alias:
Result<T, keyring::Error>
Usage in script-kit-gpui
The
env.rs module uses keyring for secure secret storage in the EnvPrompt:
// Service identifier for all script-kit secrets const KEYRING_SERVICE: &str = "com.scriptkit.env"; // Get a secret from keyring pub fn get_secret(key: &str) -> Option<String> { let entry = keyring::Entry::new(KEYRING_SERVICE, key); match entry { Ok(entry) => match entry.get_password() { Ok(value) => Some(value), Err(keyring::Error::NoEntry) => None, // Key doesn't exist yet Err(e) => { // Log error, return None None } }, Err(e) => None, // Entry creation failed } } // Set a secret in keyring pub fn set_secret(key: &str, value: &str) -> Result<(), String> { let entry = keyring::Entry::new(KEYRING_SERVICE, key) .map_err(|e| format!("Failed to create keyring entry: {}", e))?; entry .set_password(value) .map_err(|e| format!("Failed to store secret: {}", e))?; Ok(()) } // Delete a secret from keyring pub fn delete_secret(key: &str) -> Result<(), String> { let entry = keyring::Entry::new(KEYRING_SERVICE, key) .map_err(|e| format!("Failed to create keyring entry: {}", e))?; entry .delete_credential() .map_err(|e| format!("Failed to delete secret: {}", e))?; Ok(()) }
EnvPrompt Flow
- On prompt display,
checks if secret existscheck_keyring_and_auto_submit() - If found, auto-submits the stored value (user sees nothing)
- If not found, prompts user for input
- On submit, if
, stores value in keyring viasecret: trueset_secret()
CRUD Operations
Create/Update: set_password
set_passwordlet entry = Entry::new("my.service", "user")?; entry.set_password("secret_value")?;
For binary data (non-UTF8):
entry.set_secret(&[0x01, 0x02, 0x03])?;
Read: get_password
get_passwordlet entry = Entry::new("my.service", "user")?; match entry.get_password() { Ok(password) => println!("Got: {}", password), Err(keyring::Error::NoEntry) => println!("Not found"), Err(e) => eprintln!("Error: {}", e), }
For binary data:
let bytes: Vec<u8> = entry.get_secret()?;
Delete: delete_credential
delete_credentiallet entry = Entry::new("my.service", "user")?; entry.delete_credential()?; // Returns NoEntry if doesn't exist
Platform Backends
| Platform | Backend | Feature Flag |
|---|---|---|
| macOS | Keychain Services | |
| iOS | Keychain Services | |
| Windows | Windows Credential Manager | |
| Linux | Secret Service (DBus) | or |
| Linux | kernel keyutils | |
| Linux | keyutils + Secret Service | |
Cargo.toml Configuration
script-kit-gpui uses:
keyring = "3"
For explicit platform features:
[target.'cfg(target_os = "macos")'.dependencies] keyring = { version = "3", features = ["apple-native"] } [target.'cfg(target_os = "windows")'.dependencies] keyring = { version = "3", features = ["windows-native"] } [target.'cfg(target_os = "linux")'.dependencies] keyring = { version = "3", features = ["sync-secret-service"] }
Advanced Features
Target-Based Entries
Distinguish entries with same service/user:
let entry = Entry::new_with_target("production", "my.service", "user")?;
Attributes
Get/set metadata on credentials (platform-dependent):
use std::collections::HashMap; let attrs = entry.get_attributes()?; let mut updates = HashMap::new(); updates.insert("label", "My App Secret"); entry.update_attributes(&updates)?;
Custom Credential Builders
Replace the default credential builder for custom storage backends:
use keyring::{set_default_credential_builder, Entry}; // Custom builder that uses your storage set_default_credential_builder(my_custom_builder); // Now Entry::new() uses your builder let entry = Entry::new("service", "user")?;
Anti-patterns
Don't ignore NoEntry
errors
NoEntry// BAD: Crashes if key doesn't exist let password = entry.get_password().unwrap(); // GOOD: Handle missing keys gracefully match entry.get_password() { Ok(pw) => use_password(pw), Err(keyring::Error::NoEntry) => prompt_user(), Err(e) => handle_error(e), }
Don't create entries in hot paths
// BAD: Creates entry on every call fn check_auth() -> bool { let entry = Entry::new("svc", "user").unwrap(); entry.get_password().is_ok() } // GOOD: Create once, reuse struct AuthChecker { entry: Entry, }
Don't assume UTF-8 passwords
Third-party apps may store binary data:
// BAD: Fails if password isn't UTF-8 let pw = entry.get_password()?; // GOOD: Use get_secret for unknown sources match entry.get_password() { Ok(pw) => use_password(pw), Err(keyring::Error::BadEncoding(bytes)) => { // Handle raw bytes } Err(e) => handle_error(e), }
Don't ignore platform limits
// BAD: May fail on some platforms entry.set_password(&"x".repeat(10000))?; // GOOD: Handle TooLong errors match entry.set_password(&long_value) { Err(keyring::Error::TooLong(attr, limit)) => { eprintln!("{} exceeds {} byte limit", attr, limit); } // ... }
Don't access from multiple threads simultaneously
The underlying stores may not serialize concurrent access:
// BAD: Race condition on Windows/Linux threads.iter().for_each(|_| { entry.get_password(); // May fail randomly }); // GOOD: Serialize access or use per-thread entries let mutex = Mutex::new(entry);
Thread Safety
isEntrySend + Sync- However, underlying stores may not handle concurrent access
- Serialize access to the same credential from multiple threads
- Different credentials can be accessed concurrently