Skip to content
Back to skills

Keychain Bridge

DSecurity

Manage secrets via macOS Keychain instead of plaintext files. Migrate existing secrets, read/write keychain entries, bridge to files for bash tools, audit for leaks, diagnose access issues. Use when asked about secrets, keychain, credentials, API keys, or security hardening on macOS.

  • 14 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 7, 2026
securitypythonrustgobashgitapisecurity

Works with

  • terminal
  • cli
  • api

Security analysis

D46/100
  • criticalAccesses system keychains or credential stores
  • criticalAccesses system keychains or credential stores
  • criticalSends environment variables or credentials to an external URL
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 7 files and shows the line behind each finding

Scanned September 7, 2026

npx -y skills add modbender/skill-library-mcp --skill keychain-bridge --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Keychain Bridge?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Keychain Bridge
[![Security: D โ€” Skills Directory](https://www.skillsdirectory.com/api/skills/modbender-keychain-bridge/badge)](https://www.skillsdirectory.com/skills/modbender-keychain-bridge)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: keychain-bridge
description: Manage secrets via macOS Keychain instead of plaintext files. Migrate existing secrets, read/write keychain entries, bridge to files for bash tools, audit for leaks, diagnose access issues. Use when asked about secrets, keychain, credentials, API keys, or security hardening on macOS.
homepage: https://github.com/moltbot/keychain-bridge
metadata:
  openclaw:
    emoji: "๐Ÿ”"
    requires:
      bins: ["bash", "python3"]
      env: []
    files: ["scripts/*"]
  clawmart:
    price: 99
    currency: USD
    category: security
    tags: ["keychain", "macos", "secrets", "credentials", "tahoe", "migration"]
---

# Keychain Bridge

Manage secrets via macOS Keychain instead of plaintext files. Eliminates plaintext credential storage while maintaining compatibility with bash-based tools through a file-bridge architecture.

## Prerequisites

The `keyring` Python library must be installed for each Python version that will access secrets:

```bash
pip3 install keyring
# If multiple Python versions exist (common on macOS):
/usr/bin/python3 -m pip install keyring
/opt/homebrew/opt/python@3.14/bin/python3.14 -m pip install --break-system-packages keyring
```

## Check Keychain Health

Verify the keychain bridge is working correctly:

```bash
python3 -c "
import keyring
# Test write
keyring.set_password('keychain-bridge-test', 'test', 'hello')
# Test read
val = keyring.get_password('keychain-bridge-test', 'test')
assert val == 'hello', f'Read back {val!r}, expected hello'
# Cleanup
keyring.delete_password('keychain-bridge-test', 'test')
print('Keychain health: OK')
"
```

If this fails, see **Diagnose Issues** below.

## Migrate Secrets

Migrate plaintext secret files to macOS Keychain. The migration tool:
- Auto-detects all Python versions on the system
- Injects each secret from ALL detected Python binaries (required for ACL coverage)
- Verifies the round-trip read
- Optionally deletes the original file

```bash
python3 SKILL_DIR/scripts/migrate_secrets.py --dir ~/.openclaw/secrets/ --account moltbot --dry-run
# Remove --dry-run to actually migrate
python3 SKILL_DIR/scripts/migrate_secrets.py --dir ~/.openclaw/secrets/ --account moltbot
```

After migration, secrets fall into two groups:

### Group A โ€” Keychain Only
Python scripts read directly via `keychain_helper.get_secret(service)`. No file on disk.

### Group B โ€” File Bridge
Bash scripts cannot reliably use Python keyring as a subprocess (see **Known Issues**). For these, a boot-time bridge script populates files from the keychain:

```bash
# Add to your LaunchAgent or startup script:
bash SKILL_DIR/scripts/populate_secrets.sh
```

This reads each Group B secret from keychain and writes it to a `chmod 600` file that bash scripts can `cat`.

## Read a Secret

### From Python
```python
import sys, os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from keychain_helper import get_secret

token = get_secret("my-service-name")
```

The helper tries keychain first, falls back to file read.

### From Bash (via Group B file)
```bash
MY_SECRET=$(cat ~/.openclaw/secrets/my-service-name)
```

Ensure the service is listed in `populate_secrets.sh` so the file is populated at boot.

### From Bash (via CLI helper โ€” interactive only)
```bash
# Works from terminal, but HANGS from LaunchAgent bash scripts
MY_SECRET=$(python3 path/to/get_secret.py my-service-name)
```

## Write a Secret

**Critical**: Inject from ALL Python versions on the system. Keychain ACLs are per-binary โ€” an item created by Python 3.9 cannot be read by Python 3.14 unless both binaries are in the ACL.

```bash
# Detect Python versions
PYTHONS=()
[ -x /usr/bin/python3 ] && PYTHONS+=(/usr/bin/python3)
[ -x /opt/homebrew/opt/python@3.14/bin/python3.14 ] && PYTHONS+=(/opt/homebrew/opt/python@3.14/bin/python3.14)

# Inject from each
for py in "${PYTHONS[@]}"; do
    $py -c "import keyring; keyring.set_password('SERVICE', 'ACCOUNT', 'VALUE')"
done
```

Or use the migration tool for batch operations.

## Audit for Leaks

Check for unexpected plaintext secret files and verify keychain health:

```bash
python3 SKILL_DIR/scripts/audit_secrets.py --dir ~/.openclaw/secrets/ --account moltbot
```

Reports:
- Unexpected files in the secrets directory (potential leaks)
- Keychain items that exist but can't be read (ACL issues)
- Files that exist but aren't in keychain (unmigrated)
- Keychain library installation status per Python version

## Diagnose Issues

### `security find-generic-password -w` hangs
**macOS Tahoe 26.x regression.** The `security` CLI hangs indefinitely (or returns exit code 36) when reading keychain items, even after `security unlock-keychain`. This affects ALL CLI-based keychain reads.

**Fix**: Use Python `keyring` library instead. It uses the Security framework C API via ctypes, bypassing the broken CLI entirely.

### Python keyring returns None or raises errSecInteractionNotAllowed (-25308)
This happens when running from an SSH session. The keychain requires a GUI session (SecurityAgent) context.

**Fix**: Run from a LaunchAgent (which has GUI session context), or unlock the keychain first:
```bash
security unlock-keychain -p "PASSWORD" ~/Library/Keychains/login.keychain-db
```

### Python keyring hangs when called from bash LaunchAgent
**Novel finding (macOS Tahoe 26.x).** When a bash script is the LaunchAgent program and spawns `python3 get_secret.py` as a subprocess, the Python process hangs indefinitely. The SecurityAgent session attachment is lost in the bash-to-python subprocess transition.

**Fix**: Use the Group B file-bridge pattern. Have a Python-native process populate files at boot, then bash scripts read from files.

Alternatively, make Python the direct LaunchAgent program (not a subprocess of bash).

### Different Python versions can't read each other's items
Keychain ACLs are per-binary. An item created by `/usr/bin/python3` (Python 3.9) has an ACL entry only for that binary. `/opt/homebrew/bin/python3.14` is a different binary and gets access denied.

**Fix**: Inject from both Python versions:
```python
# Read from working version, write via target version
import subprocess, keyring
value = keyring.get_password("service", "account")
subprocess.run(["/opt/homebrew/bin/python3.14", "-c",
    f"import keyring; keyring.set_password('service', 'account', '{value}')"])
```

Or use `migrate_secrets.py` which handles this automatically.

### `keyring` not installed for a Python version
Each Python binary has its own site-packages. `pip3 install keyring` only installs for one.

```bash
# Check which Python pip3 targets
pip3 --version
# Install for system Python
/usr/bin/python3 -m pip install keyring
# Install for Homebrew Python
/opt/homebrew/opt/python@3.14/bin/python3.14 -m pip install --break-system-packages keyring
```

## Architecture Reference

```
                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚   macOS Keychain     โ”‚
                    โ”‚  (login keychain)    โ”‚
                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                               โ”‚
              โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
              โ”‚                โ”‚                โ”‚
    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”‚     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
    โ”‚   Group A          โ”‚     โ”‚     โ”‚   Group B            โ”‚
    โ”‚   (keychain only)  โ”‚     โ”‚     โ”‚   (file bridge)      โ”‚
    โ”‚                    โ”‚     โ”‚     โ”‚                      โ”‚
    โ”‚ Python scripts     โ”‚     โ”‚     โ”‚ populate_secrets.sh  โ”‚
    โ”‚ import keychain_   โ”‚     โ”‚     โ”‚ runs at boot โ†’       โ”‚
    โ”‚ helper.get_secret()โ”‚     โ”‚     โ”‚ writes chmod 600     โ”‚
    โ”‚                    โ”‚     โ”‚     โ”‚ files for bash       โ”‚
    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ”‚     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                               โ”‚
                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚   Fallback           โ”‚
                    โ”‚   get_secret() tries โ”‚
                    โ”‚   keychain first,    โ”‚
                    โ”‚   then file read     โ”‚
                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
```

### When to use Group A
- The consumer is a Python script
- The script runs as a LaunchAgent (or from terminal)
- The script is NOT spawned as a subprocess from a bash LaunchAgent

### When to use Group B
- The consumer is a bash script
- The bash script runs as a LaunchAgent
- The secret is referenced by a config file that expects `file:secrets/` paths

## Known Issues (macOS Tahoe 26.x)

1. **`security` CLI broken**: `find-generic-password -w` hangs or exits 36. Use Python `keyring` instead.
2. **Keychain ACL per-binary**: Must inject from every Python version that will read the item.
3. **Bash subprocess loses SecurityAgent**: `bash LaunchAgent โ†’ python3 subprocess` hangs. Use Group B file bridge.
4. **SSH sessions lack GUI context**: Keychain reads fail with -25308. Run from LaunchAgent or unlock first.
5. **`keyring` must be installed per-Python**: Each binary's site-packages is independent.

These issues are specific to macOS Tahoe 26.x (macOS 26). Earlier versions (Sonoma 14, Sequoia 15) may not exhibit all of them, but the Group A/B architecture is safe on all versions.

## External Endpoints

None. This skill makes zero network requests. All operations are local to the macOS Keychain and filesystem.

## Security & Privacy

- All operations execute locally against the macOS login keychain
- No telemetry, analytics, or usage tracking
- No data leaves the machine under any circumstances
- Scripts request no network permissions
- Secrets are only read from and written to the local keychain or `chmod 600` files
- Migration tool never logs or displays secret values

## Trust Statement

All code is open for inspection โ€” no obfuscation, no minification, no compiled binaries. The skill operates exclusively on the local macOS Keychain and filesystem. Built and tested on a production Mac Mini M4 Pro deployment running OpenClaw 24/7 with 12+ API keys and 25 automated scripts.

Files in this skill

  • README.md3.9 KB
  • SKILL.md10.5 KB
  • scripts/audit_secrets.py6.6 KB
  • scripts/get_secret.py988 B
  • scripts/keychain_helper.py909 B
  • scripts/migrate_secrets.py7.9 KB
  • scripts/populate_secrets.sh1.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading commentsโ€ฆ