Agent skill
os-keychain
Install this agent skill to your Project
npx add-skill https://github.com/martinholovsky/claude-skills-generator/tree/main/skills/os-keychain
SKILL.md
OS Keychain Skill
name: os-keychain version: 1.1.0 domain: security/credential-storage risk_level: HIGH languages: [python, typescript, rust, go] frameworks: [keyring, security-framework, libsecret] requires_security_review: true compliance: [GDPR, HIPAA, PCI-DSS, SOC2] last_updated: 2025-01-15
MANDATORY READING PROTOCOL: Before implementing credential storage, read
references/advanced-patterns.mdfor cross-platform patterns andreferences/security-examples.mdfor platform-specific implementations.
1. Overview
1.1 Purpose and Scope
This skill provides secure credential storage using OS-native keychain services:
- Windows: Credential Manager (DPAPI-backed)
- macOS: Keychain Services (Secure Enclave integration)
- Linux: Secret Service API (GNOME Keyring, KWallet)
1.2 Risk Assessment
Risk Level: HIGH
Justification:
- Master keys and sensitive credentials stored
- Compromise exposes all dependent systems
- Platform API misuse leads to insecure storage
- Privilege escalation can access all credentials
Attack Surface:
- Inter-process communication (D-Bus, XPC)
- Access control misconfigurations
- Memory disclosure attacks
- Privilege escalation to access keychain
2. Core Principles
- TDD First - Write tests before implementing credential operations
- Performance Aware - Cache credentials, batch operations, minimize keychain calls
- Platform-native storage - Use OS keychain services for all credentials
- Access isolation - Unique service names prevent cross-contamination
- Secure by default - Reject insecure backends automatically
- Cross-platform support - Unified API across Windows, macOS, Linux
2.1 Security Principles
- NEVER store secrets in environment variables or files
- NEVER log credential values or access patterns with identifiers
- ALWAYS use platform-native keychain services
- ALWAYS validate application identity before credential access
- ALWAYS use unique service names per credential type
3. Implementation Workflow (TDD)
Step 1: Write Failing Test First
import pytest
from unittest.mock import MagicMock, patch
class TestCredentialStoreOperations:
"""TDD tests for credential store - write these FIRST."""
def test_store_credential_success(self):
"""Test storing a credential in keychain."""
# Arrange
store = SecureCredentialStore("test-service")
# Act
store.store("api-key", "sk-test-12345")
# Assert
assert store.exists("api-key") is True
assert store.retrieve("api-key") == "sk-test-12345"
def test_retrieve_nonexistent_raises_keyerror(self):
"""Test retrieving nonexistent credential raises KeyError."""
store = SecureCredentialStore("test-service")
with pytest.raises(KeyError, match="Credential not found"):
store.retrieve("nonexistent-key")
def test_delete_removes_credential(self):
"""Test deletion completely removes credential."""
store = SecureCredentialStore("test-service")
store.store("temp-key", "temp-value")
store.delete("temp-key")
assert store.exists("temp-key") is False
def test_credential_isolation_between_namespaces(self):
"""Test credentials are isolated by namespace."""
store1 = SecureCredentialStore("namespace-a")
store2 = SecureCredentialStore("namespace-b")
store1.store("shared-key", "value-a")
store2.store("shared-key", "value-b")
assert store1.retrieve("shared-key") == "value-a"
assert store2.retrieve("shared-key") == "value-b"
def test_rejects_insecure_backend(self):
"""Test rejection of insecure keyring backends."""
import keyring
from keyring.backends import null
original = keyring.get_keyring()
try:
keyring.set_keyring(null.Keyring())
with pytest.raises(RuntimeError, match="Insecure"):
SecureCredentialStore("test")
finally:
keyring.set_keyring(original)
Step 2: Implement Minimum to Pass
import keyring
from keyring.errors import KeyringError
import logging
logger = logging.getLogger(__name__)
class SecureCredentialStore:
"""Minimal implementation to pass tests."""
SERVICE_PREFIX = "com.jarvis.assistant"
def __init__(self, namespace: str):
self._service = f"{self.SERVICE_PREFIX}.{namespace}"
self._verify_backend()
def _verify_backend(self):
backend = keyring.get_keyring()
backend_name = type(backend).__name__
insecure = ['PlaintextKeyring', 'NullKeyring', 'ChainerBackend']
if backend_name in insecure:
raise RuntimeError(f"Insecure keyring backend: {backend_name}")
def store(self, key: str, secret: str) -> None:
keyring.set_password(self._service, key, secret)
def retrieve(self, key: str) -> str:
secret = keyring.get_password(self._service, key)
if secret is None:
raise KeyError(f"Credential not found: {key}")
return secret
def delete(self, key: str) -> None:
keyring.delete_password(self._service, key)
def exists(self, key: str) -> bool:
return keyring.get_password(self._service, key) is not None
Step 3: Refactor with Performance Patterns
After tests pass, add caching and logging (see Performance Patterns section).
Step 4: Run Full Verification
# Run all tests with coverage
pytest tests/security/test_keychain.py -v --cov=src/security/keychain
# Run security-specific tests
pytest tests/security/ -k "keychain or credential" -v
# Verify no credential leaks in logs
grep -r "sk-\|password\|secret" logs/ && echo "FAIL: Credentials in logs"
4. Performance Patterns
4.1 Credential Caching
# BAD: Repeated keychain access
class SlowCredentialStore:
def get_api_key(self):
return keyring.get_password(self._service, "api-key") # Slow IPC every call
# GOOD: In-memory cache with TTL
from functools import lru_cache
from threading import Lock
import time
class CachedCredentialStore:
def __init__(self, namespace: str, cache_ttl: int = 300):
self._service = f"com.jarvis.{namespace}"
self._cache: dict[str, tuple[str, float]] = {}
self._lock = Lock()
self._ttl = cache_ttl
def retrieve(self, key: str) -> str:
with self._lock:
if key in self._cache:
value, timestamp = self._cache[key]
if time.time() - timestamp < self._ttl:
return value
secret = keyring.get_password(self._service, key)
if secret is None:
raise KeyError(f"Credential not found: {key}")
self._cache[key] = (secret, time.time())
return secret
def invalidate(self, key: str = None):
with self._lock:
if key:
self._cache.pop(key, None)
else:
self._cache.clear()
4.2 Batch Operations
# BAD: Individual keychain calls
def load_all_credentials():
db_pass = keyring.get_password("jarvis", "db-password")
api_key = keyring.get_password("jarvis", "api-key")
secret = keyring.get_password("jarvis", "encryption-key")
return db_pass, api_key, secret # 3 separate IPC calls
# GOOD: Batch loading with single initialization
class BatchCredentialLoader:
def __init__(self, namespace: str, keys: list[str]):
self._service = f"com.jarvis.{namespace}"
self._credentials = self._load_batch(keys)
def _load_batch(self, keys: list[str]) -> dict[str, str]:
"""Load multiple credentials in optimized batch."""
result = {}
for key in keys:
value = keyring.get_password(self._service, key)
if value:
result[key] = value
return result
def get(self, key: str) -> str:
if key not in self._credentials:
raise KeyError(f"Credential not loaded: {key}")
return self._credentials[key]
# Usage - single initialization at startup
loader = BatchCredentialLoader("secrets", ["db-password", "api-key", "encryption-key"])
4.3 Lazy Loading
# BAD: Load all credentials at import
class EagerStore:
def __init__(self):
self.db_password = keyring.get_password("jarvis", "db") # Loaded immediately
self.api_key = keyring.get_password("jarvis", "api")
# GOOD: Load only when accessed
class LazyCredentialStore:
def __init__(self, namespace: str):
self._service = f"com.jarvis.{namespace}"
self._cache: dict[str, str] = {}
def __getattr__(self, name: str) -> str:
if name.startswith('_'):
raise AttributeError(name)
if name not in self._cache:
value = keyring.get_password(self._service, name.replace('_', '-'))
if value is None:
raise KeyError(f"Credential not found: {name}")
self._cache[name] = value
return self._cache[name]
# Usage - credentials loaded on first access
store = LazyCredentialStore("api-keys")
# No keychain calls yet
key = store.openai_key # First access triggers load
4.4 Connection Reuse
# BAD: Create new backend each time
def get_credential(key: str) -> str:
store = SecureCredentialStore("service") # Backend verification each call
return store.retrieve(key)
# GOOD: Singleton pattern for store instances
class CredentialStoreFactory:
_instances: dict[str, 'SecureCredentialStore'] = {}
_lock = Lock()
@classmethod
def get_store(cls, namespace: str) -> 'SecureCredentialStore':
with cls._lock:
if namespace not in cls._instances:
cls._instances[namespace] = SecureCredentialStore(namespace)
return cls._instances[namespace]
# Usage - reuses existing store instance
store = CredentialStoreFactory.get_store("api-keys")
4.5 Memory-Safe Handling
# BAD: Credentials persist in memory
class UnsafeStore:
def get_credential(self, key: str) -> str:
secret = keyring.get_password(self._service, key)
self.last_retrieved = secret # Persists in memory
return secret
# GOOD: Secure memory handling with cleanup
import ctypes
import gc
class SecureMemoryStore:
def retrieve_and_use(self, key: str, callback) -> None:
"""Retrieve credential, use it, then clear from memory."""
secret = keyring.get_password(self._service, key)
if secret is None:
raise KeyError(f"Credential not found: {key}")
try:
callback(secret)
finally:
# Overwrite string in memory (best effort in Python)
if secret:
secret_bytes = secret.encode()
ctypes.memset(id(secret_bytes) + 32, 0, len(secret_bytes))
del secret
gc.collect()
def with_credential(self, key: str):
"""Context manager for secure credential access."""
class CredentialContext:
def __init__(ctx_self, store, key):
ctx_self._store = store
ctx_self._key = key
ctx_self._value = None
def __enter__(ctx_self):
ctx_self._value = keyring.get_password(
ctx_self._store._service, ctx_self._key
)
return ctx_self._value
def __exit__(ctx_self, *args):
if ctx_self._value:
del ctx_self._value
gc.collect()
return CredentialContext(self, key)
# Usage
store = SecureMemoryStore("secrets")
with store.with_credential("api-key") as api_key:
make_api_call(api_key)
# Credential cleared after context exits
5. Core Responsibilities
5.1 Primary Functions
- Store secrets securely using OS-native encryption
- Retrieve secrets with proper access control verification
- Manage credential lifecycle including rotation and deletion
- Abstract platform differences for cross-platform code
- Integrate with encryption skill for master key storage
6. Technology Stack
6.1 Recommended Libraries
| Platform | Library | API | Notes |
|---|---|---|---|
| Python (cross-platform) | keyring |
Unified | Auto-detects backend |
| macOS | Security.framework |
Keychain Services | Native Swift/ObjC |
| Windows | Windows.Security.Credentials |
Credential Manager | WinRT API |
| Linux | libsecret |
Secret Service D-Bus | GNOME Keyring backend |
6.2 Platform Requirements
- macOS: 10.15+ (Keychain Access improvements)
- Windows: 10 1903+ (Credential Guard support)
- Linux: libsecret 0.20+, GNOME Keyring 3.36+
7. Implementation Patterns
7.1 Cross-Platform Python Implementation
import keyring
from keyring.errors import KeyringError
import logging
logger = logging.getLogger(__name__)
class SecureCredentialStore:
"""Cross-platform credential storage using OS keychain."""
SERVICE_PREFIX = "com.jarvis.assistant"
def __init__(self, namespace: str):
self._service = f"{self.SERVICE_PREFIX}.{namespace}"
self._verify_backend()
def _verify_backend(self):
"""Verify secure keyring backend is available."""
backend = keyring.get_keyring()
backend_name = type(backend).__name__
insecure_backends = ['PlaintextKeyring', 'NullKeyring', 'ChainerBackend']
if backend_name in insecure_backends:
raise RuntimeError(f"Insecure keyring backend: {backend_name}")
logger.info("keychain.backend.initialized", extra={'backend': backend_name})
def store(self, key: str, secret: str) -> None:
"""Store a credential securely."""
keyring.set_password(self._service, key, secret)
logger.info("keychain.credential.stored", extra={'key': key})
def retrieve(self, key: str) -> str:
"""Retrieve a credential. Raises KeyError if not found."""
secret = keyring.get_password(self._service, key)
if secret is None:
raise KeyError(f"Credential not found: {key}")
return secret
def delete(self, key: str) -> None:
"""Delete a credential."""
keyring.delete_password(self._service, key)
logger.info("keychain.credential.deleted", extra={'key': key})
def exists(self, key: str) -> bool:
"""Check if credential exists."""
return keyring.get_password(self._service, key) is not None
7.2 Platform-Specific Implementations
For detailed platform-specific implementations with advanced features:
- macOS Keychain (ACLs, Touch ID, Secure Enclave): See
references/security-examples.md#macos-keychain - Windows Credential Manager (DPAPI, Credential Guard): See
references/security-examples.md#windows-credential-manager - Linux Secret Service (D-Bus, GNOME Keyring): See
references/security-examples.md#linux-secret-service
8. Security Standards
8.1 Known Vulnerabilities
| CVE | Severity | Platform | Mitigation |
|---|---|---|---|
| CVE-2023-21726 | High (7.8) | Windows | Windows Update Jan 2023 |
| CVE-2024-54490 | High | macOS | Update to macOS 15.2+ |
| CVE-2024-44162 | High | macOS | Update to macOS 14.7+ |
| CVE-2024-44243 | High | macOS | Update to macOS 15.2+ |
| CVE-2024-1086 | High (7.8) | Linux | Kernel 6.6.15+ |
8.2 OWASP Mapping
| OWASP 2025 | Implementation |
|---|---|
| A01: Broken Access Control | OS-level ACLs, app sandboxing |
| A02: Cryptographic Failures | Platform-native encryption |
| A04: Insecure Design | Defense in depth, least privilege |
| A07: Identification Failures | Credential isolation per service |
8.3 Platform Security Features
macOS: Secure Enclave, per-item ACLs, code signing, Touch ID gating
Windows: DPAPI encryption, Credential Guard, virtualization-based security
Linux: D-Bus access control, collection locking, session keyring isolation
For detailed threat analysis, see references/threat-model.md.
9. Common Mistakes
9.1 Critical Anti-Patterns
Environment Variables for Secrets
# NEVER: Visible in /proc, logs
api_key = os.environ.get('API_KEY')
# ALWAYS: OS keychain
api_key = SecureCredentialStore("api").retrieve("api-key")
Hardcoded Credentials
# NEVER: In source code
DATABASE_PASSWORD = "production-password-123"
# ALWAYS: Retrieved at runtime
password = SecureCredentialStore("database").retrieve("password")
Insecure File Storage
# NEVER: Plain files
with open('~/.config/app/credentials.json') as f:
creds = json.load(f)
# ALWAYS: Platform keychain
token = SecureCredentialStore("app").retrieve("access-token")
Logging Credentials
# NEVER: Log values
logger.info(f"Retrieved API key: {api_key}")
# ALWAYS: Log metadata only
logger.info("credential.retrieved", extra={'service': service, 'key': key})
Single Service Name
# NEVER: All credentials under one service
store = SecureCredentialStore("jarvis")
# ALWAYS: Namespace by credential type
db_store = SecureCredentialStore("database")
api_store = SecureCredentialStore("api-keys")
10. Pre-Implementation Checklist
Phase 1: Before Writing Code
- Read
references/advanced-patterns.mdfor cross-platform patterns - Read
references/security-examples.mdfor platform implementations - Review threat model in
references/threat-model.md - Identify required credential namespaces
- Design test cases for credential operations
- Plan caching strategy for performance
Phase 2: During Implementation
- Write failing tests first (TDD workflow)
- Implement minimum code to pass tests
- Add credential caching with TTL
- Implement batch loading for multiple credentials
- Use lazy loading for optional credentials
- Add memory-safe handling for sensitive operations
- Verify secure keyring backend at startup
- Log operations without credential values
Phase 3: Before Committing
- All tests pass with
pytest -v - No credentials in test fixtures or logs
- Cross-platform tests verified
- Memory leak tests pass
- Security scan shows no credential leaks
- Code review for anti-patterns complete
Platform-Specific Verification
- macOS: Code signing verified for Keychain access
- Windows: Credential Guard compatibility tested
- Linux: Secret Service daemon running, D-Bus accessible
- OS security updates applied (check CVE list above)
11. Summary
Key Objectives
- TDD workflow: Write tests before implementing credential operations
- Performance optimization: Cache credentials, batch operations, lazy loading
- Platform-native storage: Use OS keychain services for all credentials
- Access isolation: Unique service names prevent cross-contamination
- Secure by default: Reject insecure backends automatically
Security Reminders
- Credentials in environment variables are NOT secure
- File-based credential storage is NOT secure
- Always verify keyring backend at application startup
- Log credential operations but NEVER values
- Keep OS updated to address keychain vulnerabilities
References
references/advanced-patterns.md- Cross-platform patterns, migration, testingreferences/security-examples.md- Complete platform implementationsreferences/threat-model.md- Attack scenarios and mitigations
The OS keychain is your first line of defense. Misuse negates all downstream encryption.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
prompt-engineering
Expert skill for prompt engineering and task routing/orchestration. Covers secure prompt construction, injection prevention, multi-step task orchestration, and LLM output validation for JARVIS AI assistant.
windows-ui-automation
Expert in Windows UI Automation (UIA) and Win32 APIs for desktop automation. Specializes in accessible, secure automation of Windows applications including element discovery, input simulation, and process interaction. HIGH-RISK skill requiring strict security controls for system access.
accessibility-wcag
devsecops-expert
Expert DevSecOps engineer specializing in secure CI/CD pipelines, shift-left security, security automation, and compliance as code. Use when implementing security gates, container security, infrastructure scanning, secrets management, or building secure supply chains.
kanidm-expert
Expert in Kanidm modern identity management system specializing in user/group management, OAuth2/OIDC, LDAP, RADIUS, SSH key management, WebAuthn, and MFA. Deep expertise in secure authentication flows, credential policies, access control, and platform integrations. Use when implementing identity management, SSO, authentication systems, or securing access to infrastructure.
motion-design
Didn't find tool you were looking for?