fnox: one config file in front of every place a secret can live
encrypted/remote secret manager
At a glance
- What is it?
- A Rust secret manager that reads from encrypted files, cloud key managers and password managers through the same fnox.toml, so the command you run locally is the command CI runs.
- Who is it for?
- The idea fnox commits to is that secret storage should be a per-secret decision rather than a per-project one, so the same command works whether a value lives in age ciphertext in your repository or in AWS Secrets Manager. Two things deserve attention before you point it at anything real.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 8 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 28, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The one-liner that defines the tool
The whole pitch fits in a single line, which the README puts directly under the title: fnox loads secrets into your commands from encrypted files, password managers and cloud services, keeps the configuration in `fnox.toml`, and lets you use the same command in development and CI.
fnox exec -- npm startThat is the shape of the tool. There is no long-running service you have to adopt as your source of truth, no agent to install before anything works. fnox is a command that resolves values and hands them to another command as environment variables. The tag line calls it Fort Knox for your secrets, and the repository description is blunter still, just `encrypted/remote secret manager`, which is a fair summary of a tool whose job is dispatch rather than storage.
The metadata says MIT licensed, Rust, 2199 stars, 109 forks and only 2 open issues, with the last push on 2026-09-28 and version 1.36.0 released the same day. The project ships a documentation site at fnox.jdx.dev with separate quick start, providers, and CLI reference pages, which is where the depth lives.
Getting installed and the order of operations that matters
Installation is short. The README leads with mise:
mise use -g fnoxIt also gives a cargo path, `cargo install fnox --locked`, and points to an installation page for other methods. The presence of a mise plugin, a `mise.toml` in the tree, and a version manifest in the Cargo workspace all point the same way: this is a tool built by someone who uses mise as a package manager, and mise is the smoothest install route.
The order of the first commands matters more than the install. The README says to configure a provider before storing a secret:
fnox init
fnox set DATABASE_URL
fnox check --all
fnox exec -- npm start`fnox init` runs a setup wizard and selects a default provider, `fnox set` prompts for the value, and `fnox check --all` verifies access before you rely on it. That last step is worth respecting, because it turns a broken credential into a message rather than an application that fails later with a missing variable.
The warning in the README is bold for a reason: without a provider, `fnox set` writes a plaintext default. The tool will not stop you from doing this, and a file full of plaintext values in a repository is the exact outcome a secret manager exists to prevent. Configure storage before you set anything.
Same key, different backends across profiles
The configuration format is where fnox makes its actual argument. A secret entry names a provider and a value, and non-sensitive settings sit alongside them as ordinary defaults:
[providers.op]
type = "1password"
vault = "Engineering"
[secrets]
DATABASE_URL = { provider = "op", value = "Database/url" }
LOG_LEVEL = { default = "info" } # Non-sensitive configurationWith a remote provider, `value` is a reference into that store rather than the secret itself, which is what lets a developer and a CI runner resolve the same key name to the same underlying item.
Profiles then let you point one secret at different stores per environment:
[profiles.production.providers.aws]
type = "aws-sm"
region = "us-east-1"
prefix = "myapp/"
[profiles.production.secrets]
DATABASE_URL = { provider = "aws", value = "database-url", if_missing = "error" }The `if_missing = error` setting is the detail to notice. Default behaviour in a secret tool tends toward silence when a value is absent, and this lets you make absence loud for the keys that would break production. Personal overrides live in `fnox.local.toml`, which the README keeps separate so they never get committed, and the running command changes by profile:
fnox exec -- ./deploy.sh
fnox exec --profile production -- ./deploy.shThe provider table is unusually long on purpose
Providers are grouped by where the values live, and the grouping is the argument. Encrypted in your config means age, FIDO2, YubiKey, AWS KMS, Azure KMS and GCP KMS. Cloud and hosted stores covers AWS Secrets Manager, AWS Parameter Store, Azure Key Vault, Azure App Configuration, GCP Secret Manager, Vault, Doppler, FOKS, Bitwarden Secrets Manager and Keeper Secrets Manager. Password managers bring in 1Password, Bitwarden, Infisical, Passwordstate and Proton Pass, and local stores cover the OS keychain, KeePass and password-store.
Two providers in that list change how you should think about the tool. The KMS entries mean the ciphertext in your repository can be encrypted to a key that never leaves a cloud service, so a leaked repository is not a leaked secret. The hardware entries, FIDO2 and YubiKey, put the unlock step on a device you physically hold.
What changes with an encryption provider is the commit story: `fnox set` writes ciphertext into `fnox.toml`, and you commit the ciphertext and the public recipients while private keys stay outside the repository. Teammates then need either a matching private key or access to the configured vault. That is a real operational difference from the remote provider case, and the README states it plainly rather than implying the two paths are equivalent.
Short-lived credentials are handled separately through leases, with AWS STS, GitHub Apps and Vault among the backends.
A shell hook that resolves as you move between projects
Beyond `fnox exec`, the README describes a shell integration with hooks that load and unload secrets as you change directory, supporting Bash, Zsh, Fish, Nushell and PowerShell. This is the feature that changes daily behaviour most, because you stop prefixing commands.
It is also the feature with the most recent sharp edges, and the release notes are unusually candid about them. Version 1.35.2 fixed a case where the shell hook loaded a credential but a dependent secret failed to resolve, for example when a password manager exited unsuccessfully during shell startup. The partial result was recorded as an unchanged session, so later prompts skipped resolution entirely even after the provider recovered. You would get a working `fnox exec` next to an incomplete shell environment, which is a confusing pair of symptoms. The fix records incomplete loads and retries on the next invocation, and the daemon no longer caches missing values.
The release notes also name the cost of the fix: secrets that are persistently unavailable, including optional ones, are now retried on every prompt, which can add latency and repeat warnings until they resolve. That is the right trade, and it is the kind of trade-off most changelogs would not mention.
Two caching layers exist for different reasons. `fnox sync` encrypts a personal local cache for offline use against a provider such as age, refreshed when vault values change, while an optional daemon keeps resolved values in memory for the session.
What 1.36.0 fixed and how the repository is built
Version 1.36.0 is the most instructive release in the recent history for anyone evaluating the tool. It fixed the YubiKey provider, which the notes say timed out on every challenge. It batched `fnox reencrypt` by provider, because reencrypt previously encrypted each secret individually even when the provider supports batching, which for age secrets protected by Touch ID meant one unlock per value on an uncached exec. It also made the per-prompt shell hook faster and shipped a skill that teaches coding agents to set up providers and profiles, inject secrets into commands, and diagnose resolution failures without printing secret values.
That last item is a design stance worth noting. The tree shows the same instinct in the tooling: `AGENTS.md`, `CLAUDE.md`, `.agents/`, `.claude/`, `.codex/` and a `skills/` directory all sit at the root, alongside `mise-tasks/` and a `mise.toml`. fnox is being built in a way where agents are expected participants in the workflow, and the release notes are careful that generated skill links only work on your machine and should stay out of version control.
The build itself is a Cargo workspace with a root crate plus `crates/fnox-core`, on edition 2024 with an MSRV of 1.91.1 pinned deliberately behind latest stable so distribution packagers with an older rustc can still build it. The comment in `Cargo.toml` is explicit that a dependency requiring a newer rustc should be pinned to a compatible version rather than by raising the floor. Cross compilation is configured through `Cross.toml`, the docs site is VitePress under `docs/`, and the release history shows a maintenance-only 1.35.3 sitting between two functional releases, which is what a project with automated dependency updates looks like.
Editorial conclusion
The idea fnox commits to is that secret storage should be a per-secret decision rather than a per-project one, so the same command works whether a value lives in age ciphertext in your repository or in AWS Secrets Manager. Two things deserve attention before you point it at anything real. The README states plainly that without a configured provider `fnox set` writes a plaintext default, so configure a provider first. And the shell hook has known trade-offs that the 1.35.2 notes describe honestly: a persistently unavailable optional secret is retried on every prompt, which adds latency until it resolves. Start with `fnox init`, one provider, and `fnox exec -- your-command`, then add profiles only when you have a second environment that genuinely needs different values.
Frequently asked questions
Is fnox safe to use if I have not configured a provider yet?
Not for secrets you care about. The README states directly that without a provider, `fnox set` writes a plaintext default into the configuration. Run `fnox init` first to select a provider, then set values, then use `fnox check --all` to confirm access.
Can I keep one secret name and resolve it from different places per environment?
Yes, that is what profiles are for. A secret entry names a provider and a value, and a profile can override the provider for that environment, for example pointing `DATABASE_URL` at AWS Secrets Manager in production and at 1Password locally. Personal overrides go in `fnox.local.toml` so they stay out of version control.
How does fnox work with an encrypted config file?
With an encryption provider such as age, FIDO2, YubiKey or a cloud KMS, `fnox set` writes ciphertext into `fnox.toml`. You commit the ciphertext and the public recipients, while private keys stay outside the repository. Teammates need a matching private key or access to the configured vault.
Does the shell integration support the shells I use?
Bash, Zsh, Fish, Nushell and PowerShell are all supported by the directory-change hooks, which load and unload secrets as you move between projects. Two caching options sit alongside it: `fnox sync` for an encrypted offline cache and an optional daemon that keeps resolved values in memory for the session.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jdx-fnox)