Skip to content

Add secret resolver plumbing - #16339

Open
domenkozar wants to merge 10 commits into
NixOS:masterfrom
cachix:secret-resolver-interface
Open

Add secret resolver plumbing#16339
domenkozar wants to merge 10 commits into
NixOS:masterfrom
cachix:secret-resolver-interface

Conversation

@domenkozar

Copy link
Copy Markdown
Member

Motivation

Split out of #16300

We need a way to resolve secrets from external providers without relying on global state.

Secret resolution represents authority: different commands, evaluations, or daemon clients may have access to different secrets.

Passing that authority explicitly also makes it easier to prevent secrets from crossing remote-store and build trust boundaries accidentally.

Some providers materialize secrets as temporary files, so consumers must retain a lease for as long as they use the path.

Implementation

This adds a generic SecretResolver interface supporting:

  • Named secret requests with consumer, operation, host, and path metadata.
  • Inline secret values.
  • Materialized files whose lifetime is tied to a lease object.
  • Optional expiration metadata.

Resolver ownership is propagated explicitly through fetch, file-transfer, store, substituter, and build operations.

Stores retain the context with which they were opened and use it when opening related substituters or creating local builders. Remote and restricted stores deliberately do not forward process-local resolver authority across trust boundaries.

Compatibility overloads use an empty context where callers have no resolver authority.

Scope

This PR establishes the interface, ownership model, and propagation paths. It does not yet provide a concrete resolver implementation
or user-facing configuration, and there are currently no production calls to SecretResolver::resolve().

Those will be introduced by follow-up work.

Relationship to #16087

#16087 implements concrete HTTP username/password discovery using credential helpers.

This branch addresses a broader layer: operation-scoped authority for named inline or file-based secrets, including fetches, stores, and builds. The approaches could be combined by supplying HTTP authentication through an explicit operation context instead of a process-global authenticator.

@github-actions github-actions Bot added new-cli Relating to the "nix" command store Issues and pull requests concerning the Nix store fetching Networking with the outside (non-Nix) world, input locking c api Nix as a C library with a stable interface labels Aug 20, 2026
@domenkozar
domenkozar force-pushed the secret-resolver-interface branch from 0acbf04 to f2242a4 Compare August 21, 2026 16:27
@github-actions github-actions Bot added the with-tests Issues related to testing. PRs with tests have some priority label Aug 21, 2026
@domenkozar domenkozar changed the title libstore: add secret resolver plumbing Add secret resolver plumbing Aug 21, 2026
@domenkozar
domenkozar force-pushed the secret-resolver-interface branch 3 times, most recently from 11d8955 to 0745db1 Compare August 22, 2026 15:21
domenkozar and others added 10 commits August 23, 2026 18:32
Introduce SecretResolver, SecretRequest, and SecretContext for resolving
named secrets within a single operation. Requests specify the required
representation and describe the consumer, operation, host, and path.

Treat an unconfigured secret as an ordinary result distinct from a
resolver failure. Require resolvers to support concurrent calls because
one operation may run several transfers on different threads.

Return materialised files as SecretFile leases. Consumers keep the lease
alive while using its path, allowing the resolver to release the secret
and remove temporary material once the last consumer finishes.

Add callback-based test implementations for resolvers and leased files.

Assisted-by: Codex (GPT-5)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xod3FXfS6PFpWYLqmZLME
Fetcher APIs currently receive only global settings, so callers cannot
attach dependencies that belong to one fetch operation.

Add FetchContext to pair the existing settings with an optional secret
resolver. Pass it from evaluation and command entry points through flake
registries, input schemes, and the Git, GitHub, Mercurial, and tarball
fetchers.

This establishes the operation-scoped path for secret resolution without
changing fetch behavior yet.

Assisted-by: Codex (GPT-5)
Add FileTransferContext and context-aware variants of the enqueue,
download, upload, and delete operations. Keep the existing entry points as
convenience wrappers that use an empty context.

Pass each fetch operation's resolver into HTTP and tarball downloads,
registry fetches, Git operations, and Git LFS smudging. This carries the
resolver to the transfer item where credentials can be selected while the
owning operation is still alive.

Assisted-by: Codex (GPT-5)
Add a SecretContext overload of Store::getBuilder() and retain that
context through LocalBuilder, Worker, and DerivationBuilderParams. This
gives local builds access to the resolver owned by the build operation.

Keep the existing builder entry point as an empty-context wrapper. Remote
and legacy SSH builders explicitly discard the resolver because its
authority is local to the current process and must not cross a remote
store boundary.

Assisted-by: Codex (GPT-5)
Let openStore() accept a SecretContext and retain it in the resulting
Store. HTTP and S3 binary caches pass the resolver to their file transfers,
and local builds propagate it to the substituter stores they open.

Continue caching resolver-free substituters process-wide. Do not cache
stores opened with a resolver, so operation-scoped authority cannot leak
to unrelated callers or outlive its operation.

Update every store implementation to accept the context. Remote builders
still keep resolution on the side of the process performing the work.

Assisted-by: Codex (GPT-5)
File transfers currently read the global `netrc-file` setting directly,
even when the owning operation provides a secret resolver.

Resolve a materialised `netrc` secret from the transfer context first and
fall back to `netrc-file` when no resolver or secret is available. Include
the destination host in the request so a resolver can return only the
credentials relevant to that connection.

Perform resolution before enqueueing the transfer and retain the
SecretFile lease on the transfer item. This keeps the file available for
curl retries and releases it when the transfer finishes.

Add tests for resolver precedence, fallback behavior, request scoping, and
lease lifetime.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xod3FXfS6PFpWYLqmZLME
builtin:fetchurl writes the host's netrc data into its sandbox and then
points the process-wide `netrc-file` setting at that file. This exposes a
per-fetch credential through global state and leaves its lifetime tied to
the sandbox directory.

Serve the sandboxed netrc through a resolver owned by the fetch instead.
The resolver supports only materialised `netrc` requests, shares the file
between that fetch's transfers, and removes it after the last lease is
released.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xod3FXfS6PFpWYLqmZLME
The builtin:fetchurl child currently reads `netrc-file` itself. That
bypasses the build's resolver, may run after sandbox setup makes the file
unavailable, and would require a forked child to contact the secret broker.

Resolve the netrc in the parent and pass its contents to the child in
RunChildArgs. Use one makeRunChildArgs() path for all startChild()
implementations so parent-side preparation remains consistent.

Ask the resolver for inline data and fall back to `netrc-file`. Leave the
request unscoped by host because one fetch may try several hashed mirrors
before its primary URL.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xod3FXfS6PFpWYLqmZLME
builtin:fetchurl currently points the process-wide `ssl-cert-file` setting
at the CA bundle written into its sandbox. A choice made for one fetch can
therefore affect unrelated transfers in the same process.

Add caFile to FileTransferRequest and use it as an override for curl's CA
bundle. Requests without an override continue to use the global setting.
Pass the sandbox CA bundle directly on each builtin:fetchurl request.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xod3FXfS6PFpWYLqmZLME
InstallableFlake::getCursors() ignores its AutoCall argument, so commands
such as `nix run`, `nix search`, `nix bundle`, and `nix repl` receive a
function-valued flake output without applying its default arguments.

Auto-call the cursor when requested. Store the result under an
`<auto-call>` child in the evaluation cache so a later AutoCall::No lookup
cannot incorrectly reuse the called value. Keep the call lazy so cached
attributes do not trigger unnecessary evaluation.

Add regression coverage for the affected commands and cache behavior.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Curu4eZVEzFJZnA7ZADPz
@domenkozar
domenkozar force-pushed the secret-resolver-interface branch from 0745db1 to 9cf59cf Compare August 24, 2026 01:55
@nixos-discourse

Copy link
Copy Markdown

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/secretspec-0-20/79863/1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c api Nix as a C library with a stable interface fetching Networking with the outside (non-Nix) world, input locking new-cli Relating to the "nix" command store Issues and pull requests concerning the Nix store with-tests Issues related to testing. PRs with tests have some priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants