safe_storage

Native credential storage for Crystal using macOS Keychain, Linux Secret Service, and Windows Credential Manager.

safe_storage

Native credential storage for Crystal applications.

safe_storage stores secrets in the credential facility provided by your operating system:

  • macOS: Keychain Services
  • Linux: Secret Service through libsecret
  • Windows: Credential Manager

There is no plaintext file fallback. Unsupported platforms raise SafeStorage::UnavailableError.

Installation

Add the shard to shard.yml:

dependencies:
  safe_storage:
    github: naqvis/safe_storage

Then run shards install.

Quick start

require "safe_storage"

vault = SafeStorage.open("com.example.my-app")

vault.store("api-token", "secret-value")
vault.fetch?("api-token") # => "secret-value"

vault.delete("api-token") # => true
vault.fetch?("api-token") # => nil

The service name scopes credentials to an application or profile. Use a stable, unique reverse-domain application identifier.

Reading and writing

store creates or replaces a credential and returns the vault for chaining:

vault
  .store("username", "alice")
  .store("token", "abc123")

Hash-style access is also supported:

vault["token"] = "replacement-token"
vault["token"]  # => String, raises KeyError when missing
vault["token"]? # => String | Nil

Use fetch? when absence is expected and fetch when it is an error:

vault.fetch?("missing") # => nil
vault.fetch("missing")  # raises KeyError

Secrets are stored verbatim as strings. Serialize structured values before storing them. Service names and keys cannot be empty. Service names, keys, and values cannot contain NUL bytes.

Listing and deleting

vault.keys             # sorted Array(String), values are never included
vault.has_key?("token")  # checks existence without retrieving the value
vault.delete("token")  # true when removed, false when absent
vault.clear            # number of credentials removed

Errors

Native failures raise SafeStorage::Error, which provides structured context:

begin
  vault.store("token", "secret")
rescue ex : SafeStorage::Error
  ex.operation   # SafeStorage::Operation
  ex.service
  ex.key         # String | Nil
  ex.native_code # Int64 | Nil
end

SafeStorage::UnavailableError indicates that secure storage is unsupported on the platform or that the credential service could not be reached for an operation that queries it. Other native failures raise SafeStorage::Error with the structured context shown above. A missing credential is not a native error: optional reads return nil, and deletion returns false.

Fiber and execution-context safety

Operations on a vault are serialized internally, so one vault can be shared by fibers on different Crystal execution contexts. Native credential calls are synchronous and may wait for an OS service or authentication prompt. Perform them outside latency-sensitive GUI event loops.

Create one vault per service and reuse it. Separate vault objects are isolated by their service identifiers.

Platform notes

macOS

Credentials are generic-password items in the user's default Keychain, written through the modern SecItem API. Storing an existing key updates its item instead of creating a duplicate. Keychain access can trigger an operating-system authentication prompt.

Linux

The shard requires libsecret-1 and an active Secret Service provider such as GNOME Keyring or KWallet with Secret Service support. Typical development packages are libsecret-1-dev on Debian/Ubuntu, libsecret-devel on Fedora, and libsecret on Arch Linux. Headless sessions without a credential service raise an error instead of writing secrets to disk.

Windows

Credentials are generic credentials in Windows Credential Manager, persisted for the local machine user. Secret strings are stored as UTF-8 bytes.

Security considerations

  • Encryption, access control, persistence, and prompts belong to the operating system.
  • The shard never falls back to plaintext files.
  • Crystal strings cannot be guaranteed to be wiped from memory immediately.
  • Never log secret values or include them in exception messages.
  • Use a unique service ID to avoid collisions with other applications.
  • Treat key enumeration as sensitive metadata even though values are omitted.

Example

The included example creates, updates, lists, reads, and deletes a credential under a dedicated example service. Its ensure block removes the test credential if an operation fails.

crystal run examples/basic.cr

Development

crystal spec

The native smoke spec (spec/native_spec.cr) exercises the real OS credential stores on their matching platform — macOS Keychain, Linux Secret Service, and Windows Credential Manager — by creating and removing a throwaway credential. macOS and Windows run it automatically; on Linux it requires a running Secret Service (for example GNOME Keyring) and is marked pending without one, so run crystal spec inside a desktop session to exercise the Linux backend.

Contributing

  1. Fork the repository and create a feature branch.
  2. Add specs for behavior changes.
  3. Run formatting and the full spec suite.
  4. Open a pull request.

License

MIT

Contributors

Repository

safe_storage

Owner
Statistic
  • 0
  • 0
  • 0
  • 0
  • 0
  • about 1 hour ago
  • August 30, 2026
License

MIT License

Links
Synced at

Sun, 30 Aug 2026 10:33:36 GMT

Languages