Skip to content

fix(set): fall back to current provider when updating secrets#439

Merged
jdx merged 2 commits intojdx:mainfrom
rpendleton:rpendleton/fallback-to-current-provider-on-set
Apr 25, 2026
Merged

fix(set): fall back to current provider when updating secrets#439
jdx merged 2 commits intojdx:mainfrom
rpendleton:rpendleton/fallback-to-current-provider-on-set

Conversation

@rpendleton
Copy link
Copy Markdown
Contributor

When multiple providers were configured without a default_provider, running fnox set on an existing secret without --provider stored the new value as plaintext while leaving the original provider key in place. Subsequent fnox get calls then failed, since fnox would try to decrypt a value that was no longer encrypted.

Before falling back to default_provider or plaintext, we now reuse the secret's existing provider. This allows updates to stay encrypted and readable without requiring --provider on every call.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 25, 2026

Greptile Summary

Fixes a bug where fnox set on an existing encrypted secret (without --provider) would overwrite the value as plaintext while leaving the original provider key intact, breaking subsequent fnox get calls. The fix adds a new fallback step that checks the secret's current provider before consulting default_provider or defaulting to plaintext.

The new get_secret helper correctly mirrors the lookup precedence of get_secrets, and the existing provider-write guard (secret_config.provider().is_none()) at line 215 interacts cleanly with the new branch — no double-write or unintended override.

Confidence Score: 5/5

Safe to merge — targeted fix with a clear regression test and no observable behaviour change for callers that already pass --provider.

No P0 or P1 findings. The new get_secret lookup correctly mirrors get_secrets precedence, the borrow-checker-friendly ordering (immutable lookup before mutable write) is sound, and the provider-write guard at line 215 correctly handles both the profile-specific and top-level secret scenarios.

No files require special attention.

Important Files Changed

Filename Overview
src/commands/set.rs Adds a new fallback branch that reuses the secret's existing provider when --provider is not supplied; correctly placed before the default_provider lookup.
src/config.rs Adds get_secret helper that mirrors get_secrets precedence (profile-first → top-level fallback, respecting no_defaults) without cloning the map.
test/set_no_provider.bats New bats test exercises the exact bug scenario: two providers configured, no default_provider, secret initially set with --provider provider1, then updated without --provider, verified to round-trip correctly.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[fnox set KEY VALUE] --> B{--provider flag?}
    B -- yes --> C[Use explicit provider]
    B -- no --> D{Existing secret\nhas provider?}
    D -- yes --> E[Reuse existing provider\n🆕 new fallback]
    D -- no --> F{default_provider\nconfigured?}
    F -- yes --> G[Use default provider]
    F -- no --> H[Store as plaintext]
    C & E & G --> I[Encrypt / store with provider]
    H --> J[Store as default value]
    I & J --> K[Save config file]
Loading

Reviews (3): Last reviewed commit: "perf(config): add get_secret for non-clo..." | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the set command to automatically reuse a secret's existing provider when updating its value if no provider is explicitly specified. It also adds a comprehensive integration test to ensure this behavior works correctly with multiple providers. A performance concern was raised regarding the inefficient cloning of the secrets map during the provider lookup, suggesting a more direct access method instead.

Comment thread src/commands/set.rs
@rpendleton rpendleton force-pushed the rpendleton/fallback-to-current-provider-on-set branch from 3acd925 to 018faf1 Compare April 25, 2026 00:45
rpendleton and others added 2 commits April 24, 2026 20:56
When multiple providers were configured without a `default_provider`,
running `fnox set` on an existing secret without `--provider` stored the
new value as plaintext while leaving the original `provider` key in
place. Subsequent `fnox get` calls then failed, since fnox would try to
decrypt a value that was no longer encrypted.

Before falling back to `default_provider` or plaintext, we now reuse the
secret's existing provider. This allows updates to stay encrypted and
readable without requiring `--provider` on every call.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Avoid cloning the entire secrets map in `set` just to read the existing
secret's provider.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@rpendleton rpendleton force-pushed the rpendleton/fallback-to-current-provider-on-set branch from 018faf1 to 40830ab Compare April 25, 2026 02:56
@jdx jdx merged commit bf3177b into jdx:main Apr 25, 2026
14 checks passed
@rpendleton rpendleton deleted the rpendleton/fallback-to-current-provider-on-set branch April 26, 2026 03:57
jdx pushed a commit that referenced this pull request Apr 26, 2026
### 🚀 Features

- **(library)** top-level Fnox::discover() / get / list convenience API
by [@bglusman](https://github.com/bglusman) in
[#442](#442)

### 🐛 Bug Fixes

- **(docs)** stack banner and pin close button on mobile by
[@jdx](https://github.com/jdx) in
[#437](#437)
- **(set)** fall back to current provider when updating secrets by
[@rpendleton](https://github.com/rpendleton) in
[#439](#439)

### 📚 Documentation

- **(site)** show release version and github stars by
[@jdx](https://github.com/jdx) in
[#443](#443)
- add cross-site announcement banner by [@jdx](https://github.com/jdx)
in [#434](#434)
- respect banner expires field by [@jdx](https://github.com/jdx) in
[#436](#436)

### 🛡️ Security

- **(build)** deterministic provider ordering in generated schema by
[@jdx](https://github.com/jdx) in
[#432](#432)

### 🔍 Other Changes

- **(release)** append en.dev sponsor blurb to release notes by
[@jdx](https://github.com/jdx) in
[#431](#431)

### 📦️ Dependency Updates

- bump communique to 1.0.3 by [@jdx](https://github.com/jdx) in
[#435](#435)
- bump communique 1.0.3 → 1.0.4 by [@jdx](https://github.com/jdx) in
[#438](#438)

### New Contributors

- @bglusman made their first contribution in
[#442](#442)
jdx pushed a commit that referenced this pull request Apr 26, 2026
### 🚀 Features

- **(library)** top-level Fnox::discover() / get / list convenience API
by [@bglusman](https://github.com/bglusman) in
[#442](#442)

### 🐛 Bug Fixes

- **(docs)** stack banner and pin close button on mobile by
[@jdx](https://github.com/jdx) in
[#437](#437)
- **(set)** fall back to current provider when updating secrets by
[@rpendleton](https://github.com/rpendleton) in
[#439](#439)

### 📚 Documentation

- **(site)** show release version and github stars by
[@jdx](https://github.com/jdx) in
[#443](#443)
- add cross-site announcement banner by [@jdx](https://github.com/jdx)
in [#434](#434)
- respect banner expires field by [@jdx](https://github.com/jdx) in
[#436](#436)

### 🛡️ Security

- **(build)** deterministic provider ordering in generated schema by
[@jdx](https://github.com/jdx) in
[#432](#432)

### 🔍 Other Changes

- **(release)** append en.dev sponsor blurb to release notes by
[@jdx](https://github.com/jdx) in
[#431](#431)

### 📦️ Dependency Updates

- bump communique to 1.0.3 by [@jdx](https://github.com/jdx) in
[#435](#435)
- bump communique 1.0.3 → 1.0.4 by [@jdx](https://github.com/jdx) in
[#438](#438)
- bump communique to 1.1.2 by [@jdx](https://github.com/jdx) in
[#444](#444)

### New Contributors

- @bglusman made their first contribution in
[#442](#442)
fullerzz pushed a commit to fullerzz/fnox-py that referenced this pull request Apr 26, 2026
## Upstream release

Bumps bundled fnox binary from 1.20.0 to 1.22.0.

**Release**: https://github.com/jdx/fnox/releases/tag/v1.22.0

## Release notes

v1.22.0 introduces a top-level library API for embedding fnox in Rust
applications, and fixes a sharp edge in `fnox set` that could turn an
encrypted secret into plaintext.

## Added

**Top-level `Fnox` library API**
([#442](jdx/fnox#442)) -- @bglusman

Downstream Rust consumers can now use fnox as a library in three lines
instead of replicating the internals of `GetCommand::run`:

```rust
use fnox::Fnox;

let fnox = Fnox::discover()?;          // walks up + merges parent + local + global config
let value = fnox.get("MY_KEY").await?;
let names = fnox.list()?;
```

The new `Fnox` type lives in `src/library.rs` and is re-exported from
the crate root. Highlights:

- `Fnox::discover()` mirrors the binary's full config-discovery and
merge chain via `Config::load_smart`, including the `FNOX_PROFILE` env
var.
- `Fnox::open(path)` loads an explicit config without the
upward-search/merge behavior.
- `Fnox::with_profile("staging")` builder for non-default profiles.
- `get()` returns `FnoxError::SecretNotFound` with a populated "Did you
mean…" suggestion, matching the CLI's UX so callers don't need to
recompute it.
- `Fnox` is cheap to clone (`Config` is held behind an `Arc`) and safe
to hold across `.await`.

`set()` is intentionally not part of this first cut; it'll get its own
design pass.

## Fixed

**`fnox set` no longer silently downgrades encrypted secrets to
plaintext** ([#439](jdx/fnox#439)) --
@rpendleton

When multiple providers were configured without a `default_provider`,
running `fnox set` on an existing secret without `--provider` would
write the new value as plaintext while leaving the original `provider =
"..."` key in place. The next `fnox get` then failed trying to "decrypt"
a value that was no longer encrypted.

`fnox set` now reuses the secret's existing provider before falling back
to `default_provider` or plaintext, so updates stay encrypted and
readable without having to pass `--provider` on every call:

```bash
fnox set --provider age MY_SECRET "original-value"   # encrypted with age
fnox set MY_SECRET "new-value"                       # still encrypted with age
```

**Deterministic provider ordering in the generated schema**
([#432](jdx/fnox#432)) -- @jdx

Within-category provider ordering in `build/generate_providers.rs` was
inheriting `fs::read_dir` order, which is OS- and filesystem-dependent.
That non-determinism flowed into `docs/public/schema.json` and caused
autofix.ci to keep reshuffling 100+ lines between runs. A secondary sort
by provider name fixes the churn; running `fnox schema` twice now
produces byte-identical output.

**Mobile docs banner layout**
([#437](jdx/fnox#437)) -- @jdx

At `<=640px` the announcement banner now switches to a column layout
with the close button pinned to the top-right corner, instead of
cramming the message and "Read more" link onto one squeezed line.

## Changed

- Docs site nav now shows the current release version (read from
`Cargo.toml` at build time) and a GitHub star count, matching the
mise/aube docs ([#443](jdx/fnox#443)) -- @jdx
- Added a dismissible cross-site announcement banner that fetches its
config from `jdx.dev/banner.json` and respects the `expires` field
([#434](jdx/fnox#434),
[#436](jdx/fnox#436)) -- @jdx

## New Contributors

* @bglusman made their first contribution in
[#442](jdx/fnox#442)

**Full Changelog**:
jdx/fnox@v1.21.0...v1.22.0

## 💚 Sponsor fnox

fnox is maintained by [@jdx](https://github.com/jdx) under
[**en.dev**](https://en.dev) — a small independent studio building
developer tooling like [mise](https://mise.jdx.dev/),
[aube](https://aube.en.dev/), hk, and more. Keeping fnox secure,
maintained, and free is funded by sponsors.

If fnox is handling secrets or config for you or your team, please
consider [sponsoring at en.dev](https://en.dev). Sponsorships are what
let fnox stay independent and the project keep moving.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
NorthIsUp pushed a commit to NorthIsUp/fnox that referenced this pull request Apr 28, 2026
When multiple providers were configured without a `default_provider`,
running `fnox set` on an existing secret without `--provider` stored the
new value as plaintext while leaving the original `provider` key in
place. Subsequent `fnox get` calls then failed, since fnox would try to
decrypt a value that was no longer encrypted.

Before falling back to `default_provider` or plaintext, we now reuse the
secret's existing provider. This allows updates to stay encrypted and
readable without requiring `--provider` on every call.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
NorthIsUp pushed a commit to NorthIsUp/fnox that referenced this pull request Apr 28, 2026
### 🚀 Features

- **(library)** top-level Fnox::discover() / get / list convenience API
by [@bglusman](https://github.com/bglusman) in
[jdx#442](jdx#442)

### 🐛 Bug Fixes

- **(docs)** stack banner and pin close button on mobile by
[@jdx](https://github.com/jdx) in
[jdx#437](jdx#437)
- **(set)** fall back to current provider when updating secrets by
[@rpendleton](https://github.com/rpendleton) in
[jdx#439](jdx#439)

### 📚 Documentation

- **(site)** show release version and github stars by
[@jdx](https://github.com/jdx) in
[jdx#443](jdx#443)
- add cross-site announcement banner by [@jdx](https://github.com/jdx)
in [jdx#434](jdx#434)
- respect banner expires field by [@jdx](https://github.com/jdx) in
[jdx#436](jdx#436)

### 🛡️ Security

- **(build)** deterministic provider ordering in generated schema by
[@jdx](https://github.com/jdx) in
[jdx#432](jdx#432)

### 🔍 Other Changes

- **(release)** append en.dev sponsor blurb to release notes by
[@jdx](https://github.com/jdx) in
[jdx#431](jdx#431)

### 📦️ Dependency Updates

- bump communique to 1.0.3 by [@jdx](https://github.com/jdx) in
[jdx#435](jdx#435)
- bump communique 1.0.3 → 1.0.4 by [@jdx](https://github.com/jdx) in
[jdx#438](jdx#438)

### New Contributors

- @bglusman made their first contribution in
[jdx#442](jdx#442)
NorthIsUp pushed a commit to NorthIsUp/fnox that referenced this pull request Apr 28, 2026
### 🚀 Features

- **(library)** top-level Fnox::discover() / get / list convenience API
by [@bglusman](https://github.com/bglusman) in
[jdx#442](jdx#442)

### 🐛 Bug Fixes

- **(docs)** stack banner and pin close button on mobile by
[@jdx](https://github.com/jdx) in
[jdx#437](jdx#437)
- **(set)** fall back to current provider when updating secrets by
[@rpendleton](https://github.com/rpendleton) in
[jdx#439](jdx#439)

### 📚 Documentation

- **(site)** show release version and github stars by
[@jdx](https://github.com/jdx) in
[jdx#443](jdx#443)
- add cross-site announcement banner by [@jdx](https://github.com/jdx)
in [jdx#434](jdx#434)
- respect banner expires field by [@jdx](https://github.com/jdx) in
[jdx#436](jdx#436)

### 🛡️ Security

- **(build)** deterministic provider ordering in generated schema by
[@jdx](https://github.com/jdx) in
[jdx#432](jdx#432)

### 🔍 Other Changes

- **(release)** append en.dev sponsor blurb to release notes by
[@jdx](https://github.com/jdx) in
[jdx#431](jdx#431)

### 📦️ Dependency Updates

- bump communique to 1.0.3 by [@jdx](https://github.com/jdx) in
[jdx#435](jdx#435)
- bump communique 1.0.3 → 1.0.4 by [@jdx](https://github.com/jdx) in
[jdx#438](jdx#438)
- bump communique to 1.1.2 by [@jdx](https://github.com/jdx) in
[jdx#444](jdx#444)

### New Contributors

- @bglusman made their first contribution in
[jdx#442](jdx#442)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants