Skip to main content
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 NameEnvironment Variable
openai_api_keyOPENAI_API_KEY
anthropic_api_keyANTHROPIC_API_KEY
github_tokenGITHUB_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

  1. Use unique account names - e.g., myapp_openai_key rather than just api_key
  2. Rotate secrets regularly - Update keystore entries periodically
  3. Audit secret access - Check which profiles request which secrets
  4. 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"

Linux: “secret-tool: No secret found”

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:
  1. It requires D-Bus session bus
  2. The keyring is typically locked without a graphical login
  3. Unlocking the keyring interactively over SSH is cumbersome
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