nono can securely load API keys and other secrets from the system keystore (macOS Keychain or Linux Secret Service) and inject them as environment variables into the sandboxed process.
Why Use Keystore Secrets?
Storing secrets in environment variables or .env files is common but risky:
- Environment variables can leak via process listings
.env files can be accidentally committed to git
- Credentials scattered across multiple files are hard to audit
Using the system keystore provides:
- Encryption at rest - Secrets encrypted by the OS
- Access control - Keystore access requires user authentication
- Centralization - All secrets in one auditable location
- Memory safety - Secrets zeroized from memory after use
Quick Start
1. Store a Secret
macOS:
security add-generic-password -s "nono" -a "openai_api_key" -w "sk-..."
Linux:
secret-tool store --label="nono: openai_api_key" service nono username openai_api_key
2. Use the Secret
nono run --allow . --secrets openai_api_key -- my-agent
The secret is loaded from the keystore and injected as $OPENAI_API_KEY.
How It Works
1. nono loads secrets from keystore BEFORE sandbox is applied
2. Sandbox is applied (blocks keystore access)
3. Secrets injected as environment variables
4. Command executed with secrets available
5. Secrets zeroized from memory after exec()
This ensures the sandboxed process cannot access the keystore directly - it only receives the specific secrets you authorized.
Storing Secrets
All nono secrets are stored under the service name nono in the system keystore.
macOS Keychain
# Interactive (prompts for password)
security add-generic-password -s "nono" -a "openai_api_key" -w
# Non-interactive (password on command line - less secure)
security add-generic-password -s "nono" -a "openai_api_key" -w "sk-..."
# Update an existing secret
security add-generic-password -s "nono" -a "openai_api_key" -w "new-value" -U
# Delete a secret
security delete-generic-password -s "nono" -a "openai_api_key"
# List all nono secrets
security dump-keychain | grep -A5 "nono"
Linux Secret Service
Requires secret-tool (part of libsecret-tools) and a running keyring daemon (GNOME Keyring or KWallet).
# Store (prompts for password)
secret-tool store --label="nono: openai_api_key" service nono username openai_api_key
# Retrieve (for testing)
secret-tool lookup service nono username openai_api_key
# Delete
secret-tool clear service nono username openai_api_key
Using Secrets
CLI Flag
Specify comma-separated account names to load:
# Load single secret
nono run --allow . --secrets openai_api_key -- claude
# Load multiple secrets
nono run --allow . --secrets openai_api_key,anthropic_api_key -- claude
The environment variable name is auto-generated by uppercasing the account name:
| Account Name | Environment Variable |
|---|
openai_api_key | OPENAI_API_KEY |
anthropic_api_key | ANTHROPIC_API_KEY |
github_token | GITHUB_TOKEN |
Profile-Based Secrets
Profiles can declare which secrets to load in a [secrets] section:
# ~/.config/nono/profiles/my-agent.toml
[meta]
name = "my-agent"
[filesystem]
allow = ["$WORKDIR"]
[secrets]
openai_api_key = "OPENAI_API_KEY"
anthropic_api_key = "ANTHROPIC_API_KEY"
custom_token = "MY_CUSTOM_TOKEN"
Then use the profile with --secrets:
nono run --profile my-agent --secrets -- my-agent
The [secrets] section maps keystore account names to environment variable names, giving you full control over naming.
Error Handling
Secret Not Found
nono: Secret not found in keystore: openai_api_key
The secret doesn’t exist in the keystore. Store it first:
# macOS
security add-generic-password -s "nono" -a "openai_api_key" -w
# Linux
secret-tool store --label="nono: openai_api_key" service nono username openai_api_key
Keystore Locked
Keystore access failed for 'openai_api_key': ...
Please unlock your keystore and press Enter to retry (or Ctrl+C to abort):
The keystore is locked. Unlock it (typically by entering your login password) and press Enter.
Multiple Entries
nono: Failed to access system keystore: Multiple entries (2) found for 'api_key' - please resolve manually
The keystore has duplicate entries. Delete the duplicates using your OS keystore manager.
Security Considerations
What nono Protects
- Keystore file access - Sandbox blocks direct access to
~/Library/Keychains (macOS) and keyring files
- Memory exposure - Secrets wrapped in
Zeroizing<String> and cleared after use
- Credential sprawl - Encourages centralized secret storage
Limitations
- Environment variable visibility - On Linux,
/proc/PID/environ is readable by same-user processes
- Malicious use of credentials - nono cannot prevent a sandboxed process from misusing legitimately obtained credentials
- Keystore security - Relies on OS keystore security
Best Practices
- Use unique account names - e.g.,
myapp_openai_key rather than just api_key
- Rotate secrets regularly - Update keystore entries periodically
- Audit secret access - Check which profiles request which secrets
- Minimal secrets - Only grant secrets that are actually needed
Examples
Claude Code with API Keys
# Store secrets
security add-generic-password -s "nono" -a "anthropic_api_key" -w
security add-generic-password -s "nono" -a "openai_api_key" -w
# Run with secrets
nono run --profile claude-code --secrets anthropic_api_key,openai_api_key -- claude
Custom Agent Profile
# ~/.config/nono/profiles/my-agent.toml
[meta]
name = "my-agent"
[filesystem]
allow = ["$WORKDIR"]
read = ["$HOME/.config/my-agent"]
[network]
block = false
[secrets]
openai_api_key = "OPENAI_API_KEY"
database_url = "DATABASE_URL"
nono run --profile my-agent --trust-unsigned --secrets -- my-agent serve
Dry Run to Check Secrets
nono run --allow . --secrets openai_api_key --dry-run -- my-agent
Output includes:
Would inject 1 secret(s) as environment variables
Troubleshooting
macOS: “security: SecKeychainSearchCopyNext: The specified item could not be found”
The secret doesn’t exist. Verify the service and account names:
# List all generic passwords
security dump-keychain | grep -B5 "nono"
Ensure the secret-service daemon is running:
# Check if keyring is available
secret-tool search --all service nono
Linux: “Cannot autolaunch D-Bus without X11”
You’re running on a headless system without a graphical session. Secret Service requires a D-Bus session. See the next section for solutions.
Linux: “Cannot create an item in a locked collection”
The keyring is locked. This is common on headless servers or SSH sessions. See the next section for solutions.
Headless Linux Environments
Secret Service (GNOME Keyring) can be problematic on headless servers and SSH sessions because:
- It requires D-Bus session bus
- The keyring is typically locked without a graphical login
- Unlocking the keyring interactively over SSH is cumbersome
Option 1: Use pass (Recommended for Headless)
pass is the standard Unix password manager. It uses GPG encryption and works well in headless environments.
Setup:
# Install pass
sudo apt install pass # Debian/Ubuntu
sudo dnf install pass # Fedora
# Initialize pass store (requires GPG key)
gpg --gen-key # If you don't have a key
pass init "your-gpg-key-id"
# Store a secret
pass insert nono/openai_api_key
Use with nono (wrapper script):
#!/bin/bash
# ~/bin/nono-with-pass
export OPENAI_API_KEY=$(pass show nono/openai_api_key)
export ANTHROPIC_API_KEY=$(pass show nono/anthropic_api_key)
exec nono run "$@"
Then run:
nono-with-pass --allow . -- my-agent
Native pass backend support is planned for a future release.
Option 2: Environment Variables via Wrapper
For simple setups, export secrets from a protected file:
# Create secure secrets file (readable only by you)
mkdir -p ~/.config/nono
touch ~/.config/nono/secrets.env
chmod 600 ~/.config/nono/secrets.env
# Add secrets
cat > ~/.config/nono/secrets.env << 'EOF'
export OPENAI_API_KEY="sk-..."
export ANTHROPIC_API_KEY="sk-ant-..."
EOF
Wrapper script:
#!/bin/bash
# ~/bin/nono-env
source ~/.config/nono/secrets.env
exec nono run "$@"
File-based secrets are less secure than a proper keystore. Ensure the file has strict permissions (chmod 600) and is not backed up to insecure locations.
Option 3: Set Up Headless Keyring
If you prefer to use Secret Service in headless mode:
1. Create an unlock script:
#!/bin/bash
# unlock-keyring.sh
# Run this once per session to unlock the keyring
# Read password securely
read -s -p "Keyring password: " KEYRING_PASSWORD
echo
# Start dbus if needed
if [ -z "$DBUS_SESSION_BUS_ADDRESS" ]; then
eval $(dbus-launch --sh-syntax)
export DBUS_SESSION_BUS_ADDRESS
fi
# Unlock the keyring
echo -n "$KEYRING_PASSWORD" | gnome-keyring-daemon --unlock --components=secrets
unset KEYRING_PASSWORD
2. Source the D-Bus environment in your shell:
# Add to ~/.bashrc or ~/.zshrc
if [ -n "$SSH_CONNECTION" ] && [ -z "$DBUS_SESSION_BUS_ADDRESS" ]; then
export $(dbus-launch)
fi
3. Store secrets:
# After unlocking the keyring
secret-tool store --label="nono: openai_api_key" service nono username openai_api_key
Option 4: systemd User Service
For servers with systemd, you can run gnome-keyring as a user service:
# ~/.config/systemd/user/gnome-keyring.service
[Unit]
Description=GNOME Keyring daemon
[Service]
Type=simple
ExecStart=/usr/bin/gnome-keyring-daemon --foreground --components=secrets
[Install]
WantedBy=default.target
systemctl --user enable gnome-keyring
systemctl --user start gnome-keyring
Next Steps