How Container Secures Registry Credentials Using macOS Keychain Services

Container stores Docker-compatible registry credentials in the macOS Keychain by wrapping Keychain Services API calls in a KeychainHelper class that isolates secrets under a dedicated security domain, prompts users via native UI, and deletes entries on logout.

The apple/container CLI tool manages OCI container images and requires authenticated access to private registries. To protect sensitive login data, the project implements a secure credential storage mechanism that leverages native macOS Keychain Services rather than plaintext configuration files.

The KeychainHelper Abstraction

At the core of this security model sits KeychainHelper, a Swift wrapper that encapsulates interaction with the macOS Keychain Services API (SecItemAdd, SecItemCopyMatching, SecItemDelete). This abstraction isolates Container's secrets from other applications by using a dedicated security domain defined in Constants.keychainID.

Storing Credentials During Login

When a user executes container registry login, the implementation in Sources/ContainerCommands/Registry/RegistryLogin.swift orchestrates a multi-step process to capture and persist credentials securely.

Prompting for Credentials

First, the command instantiates a keychain helper scoped to the container-specific domain:

let keychain = KeychainHelper(securityDomain: Constants.keychainID)   // Line 63

If the username is omitted from command-line arguments, the tool invokes the native keychain UI to prompt the user:

if username == "" {
    username = try keychain.userPrompt(hostname: server)              // Line 65
}

Similarly, when no password is provided via stdin, the helper requests it through a secure system dialog:

if password == "" {
    password = try keychain.passwordPrompt()                          // Line 68
    print()
}

Validating and Saving

Before persistence, Container validates the credentials by attempting an authenticated ping to the target registry. Upon successful authentication, the username and password are encrypted and stored:

try keychain.save(hostname: server, username: username, password: password)  // Line 96

This call maps directly to SecItemAdd, ensuring the data resides in the user's keychain rather than memory or disk files.

Removing Credentials During Logout

The container registry logout command triggers cleanup via Sources/ContainerCommands/Registry/RegistryLogout.swift. The implementation initializes the same keychain helper, resolves the registry domain, and deletes the corresponding entry:

let keychain = KeychainHelper(securityDomain: Constants.keychainID)   // Line 37
let r = Reference.resolveDomain(domain: registry)
try keychain.delete(hostname: r)                                      // Lines 39-40

This invocation translates to SecItemDelete, ensuring complete removal of the secret from secure storage.

Security Benefits

Using the macOS Keychain provides several security advantages over traditional credential storage:

  • Encryption at rest: Secrets are encrypted using the user's keychain password and hardware-backed encryption when available.
  • Access control: The OS mediates access to credentials, requiring user authentication for retrieval.
  • Isolation: The dedicated Constants.keychainID security domain prevents other applications from accessing Container's registry credentials.
  • No plaintext exposure: Credentials never appear in configuration files, environment variables, or shell history.

Practical Usage Examples

The following commands demonstrate the credential lifecycle.

Log in interactively, triggering native keychain prompts:

container registry login my.registry.example.com

Log in non-interactively using stdin for the password:

echo "s3cr3t" | container registry login --username alice --password-stdin my.registry.example.com

Remove stored credentials:

container registry logout my.registry.example.com

Summary

  • Container delegates credential storage to macOS Keychain Services through the KeychainHelper wrapper class.
  • Login operations in RegistryLogin.swift use native UI prompts (userPrompt, passwordPrompt) and validate credentials before calling save.
  • Logout operations in RegistryLogout.swift permanently delete entries using delete.
  • The Constants.keychainID security domain isolates secrets and prevents cross-application access.
  • Credentials remain encrypted at rest and are never written to plaintext configuration files.

Frequently Asked Questions

Where does Container store registry credentials?

Container stores credentials in the macOS system Keychain under a dedicated security domain specified by Constants.keychainID. This location is managed by the operating system's Keychain Services API, not by files in the filesystem.

How does Container protect credentials from other applications?

By initializing KeychainHelper with a unique securityDomain parameter, Container creates isolated keychain entries that other processes cannot access without explicit permission. This domain-specific scoping ensures only the Container CLI can read or modify its stored registry passwords.

Can I view my stored Container credentials in the Keychain Access app?

Yes. Because Container uses standard macOS Keychain Services, entries appear in the Keychain Access utility under the name specified by the security domain constant. You can inspect, modify, or delete these entries manually, though using container registry logout is the recommended removal method.

Does Container support credential helpers other than the macOS Keychain?

The current implementation relies exclusively on Keychain Services for macOS. There is no pluggable credential helper architecture exposed in the public source code; all credential operations route through KeychainHelper methods that wrap SecItemAdd, SecItemCopyMatching, and SecItemDelete.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →