Claude
Skills
Sign in
Back

os-keychain

Included with Lifetime
$97 forever

# OS Keychain Skill

General

What this skill does

# 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.md` for cross-platform patterns and `references/security-examples.md` for 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

1. **TDD First** - Write tests before implementing credential operations
2. **Performance Aware** - Cache credentials, batch operations, minimize keychain calls
3. **Platform-native storage** - Use OS keychain services for all credentials
4. **Access isolation** - Unique service names prevent cross-contamination
5. **Secure by default** - Reject insecure backends automatically
6. **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

```python
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

```python
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

```bash
# 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

```python
# 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

```python
# 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

Related in General