Deneblab.StashLock.Client 0.3.121

There is a newer version of this package available.
See the version list below for details.
dotnet add package Deneblab.StashLock.Client --version 0.3.121
                    
NuGet\Install-Package Deneblab.StashLock.Client -Version 0.3.121
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Deneblab.StashLock.Client" Version="0.3.121" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Deneblab.StashLock.Client" Version="0.3.121" />
                    
Directory.Packages.props
<PackageReference Include="Deneblab.StashLock.Client" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Deneblab.StashLock.Client --version 0.3.121
                    
#r "nuget: Deneblab.StashLock.Client, 0.3.121"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Deneblab.StashLock.Client@0.3.121
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Deneblab.StashLock.Client&version=0.3.121
                    
Install as a Cake Addin
#tool nuget:?package=Deneblab.StashLock.Client&version=0.3.121
                    
Install as a Cake Tool

Deneblab.StashLock.Client

Async-first .NET secrets management client for StashLock vault.

Installation

dotnet add package Deneblab.StashLock.Client

Quick Start

using Deneblab.StashLock.Client;

// From environment variable STASHLOCK_VAULT_KEY
var store = await StashLock.CreateClient()
    .FromEnvironment()
    .OpenAsync();

var dbPassword = store["Database:Password"];

Fluent API

All access starts with StashLock.CreateClient() which returns a fluent builder.

Connection string

The simplest way to configure StashLock — a single string with all connection parameters:

// Explicit connection string
var store = await StashLock.CreateClient()
    .WithConnectionString("Url=https://vault.example.com;ApiKey=slk_xxx;PrivateKey=base64key;Box=myapp;Tag=prod")
    .OpenAsync();

// From STASHLOCK_CONNECTION_STRING env var
var store = await StashLock.CreateClient()
    .FromConnectionString()
    .OpenAsync();

// Zero-config: auto-reads STASHLOCK_CONNECTION_STRING when no source is configured
var store = await StashLock.CreateClient()
    .OpenAsync();

// Partial override: CS provides defaults, fluent calls override specific values
var store = await StashLock.CreateClient()
    .WithBox("myapp", "staging")   // overrides Box+Tag from CS env var
    .OpenAsync();

Connection string keys: Url, ApiKey, PrivateKey, Box, Tag. Supports optional base64 wrapping for safe transport.

Remote sealed-box (X25519)

var store = await StashLock.CreateClient()
    .WithBox("myapp", "prod")
    .WithPrivateKey(key)              // optional, falls back to env var
    .OpenAsync();

Sealed-box with offline cache

var store = await StashLock.CreateClient()
    .WithBox("myapp", "prod")
    .WithPrivateKey(key)
    .WithCache(ttl: TimeSpan.FromHours(2))
    .OpenAsync();

// With custom cache directory
var store = await StashLock.CreateClient()
    .WithBox("myapp", "prod")
    .WithCache(cacheDir: "/app/cache", ttl: TimeSpan.FromHours(4))
    .OpenAsync();

Cache-first strategy

By default, the client tries the server first and falls back to cache. Use CacheFirst to prioritize cached secrets (useful for low-latency startup or unreliable networks):

var store = await StashLock.CreateClient()
    .WithBox("myapp", "prod")
    .WithCache(
        ttl: TimeSpan.FromHours(2),
        strategy: CacheStrategy.CacheFirst,      // use cache if valid, refresh in background
        serverTimeout: TimeSpan.FromSeconds(5))   // timeout for server requests (default: 10s)
    .OpenAsync();
Strategy Behavior
ServerFirst (default) Try server, fall back to cache on failure
CacheFirst Use cache if valid, fall back to server if expired/missing

Request timeout

var store = await StashLock.CreateClient()
    .WithBox("myapp", "prod")
    .WithTimeout(TimeSpan.FromSeconds(15))   // default: 30s (or 10s when cache is enabled)
    .OpenAsync();

Local files

// Plain JSON file (development)
var store = await StashLock.CreateClient()
    .FromDevFile()                    // auto-discovers dev/secrets/secrets.json
    .OpenAsync();

// Explicit plain JSON file
var store = await StashLock.CreateClient()
    .FromFile("./secrets.json")
    .OpenAsync();

// Local encrypted file (SOPS or whole-file mode)
var store = await StashLock.CreateClient()
    .FromEncryptedFile("./stashlock.enc.prod.secrets.json")
    .WithPrivateKey(key)
    .OpenAsync();

Configuration validation

var result = await StashLock.CreateClient()
    .WithBox("myapp", "prod")
    .ValidateAsync();

if (!result.IsValid)
    foreach (var issue in result.Issues)
        Console.WriteLine($"ERROR: {issue}");

Builder options

Method Description
.WithConnectionString(string) Parse an ADO.NET-style connection string (Key=Value;Key=Value)
.FromConnectionString() Read connection string from STASHLOCK_CONNECTION_STRING env var
.WithBox(box, tag, version?) Sealed-box mode with box/tag/version
.FromDevFile(path?) Plain JSON file (auto-discovers if no path)
.FromFile(path) Explicit plain JSON file
.FromEncryptedFile(path) Local encrypted file (SOPS or whole-file)
.WithApiUrl(url) Override API URL
.WithApiKey(key) Override API key
.WithPrivateKey(base64) X25519 private key for sealed-box decryption
.WithCache(cacheDir?, ttl?, machineId?, strategy?, serverTimeout?) Enable encrypted offline cache
.WithTimeout(TimeSpan) Set request timeout (default: 30s, or 10s with cache)
.OpenAsync() Open the secrets store
.ValidateAsync() Validate configuration without opening

Reading Secrets

// Indexer (synchronous — secrets are pre-loaded)
string value = store["Database:Password"];

// Async
string value = await store.GetAsync("Database:Password");

// TryGet (no exception on missing key)
if (store.TryGet("OptionalKey", out var value))
{
    // use value
}

// Section as dictionary
var dbConfig = await store.GetSectionAsDictionaryAsync("Database");
// Returns: { "Host": "localhost", "Password": "secret", ... }

// Typed section (deserializes into a POCO)
var dbConfig = store.GetSection<DatabaseConfig>("Database");

IConfiguration Integration

Single AddStashLock extension method with the same fluent builder:

using Deneblab.StashLock.Client.Configuration;

var builder = WebApplication.CreateBuilder(args);

// Remote sealed-box vault with cache
builder.Configuration.AddStashLock(cfg => cfg
    .WithBox("myapp", "prod")
    .WithCache(cacheDir: "/app/cache", ttl: TimeSpan.FromHours(4))
);

var app = builder.Build();

// Access secrets via standard IConfiguration
var connString = app.Configuration["Database:ConnectionString"];
var apiKey = app.Configuration["ExternalService:ApiKey"];

Dev/prod branching

builder.ConfigureAppConfiguration((ctx, config) =>
{
    if (ctx.HostingEnvironment.IsDevelopment())
    {
        config.AddStashLock(cfg => cfg
            .FromDevFile("dev/secrets/secrets.json")
        );
    }
    else
    {
        config.AddStashLock(cfg => cfg
            .WithBox("myapp", ctx.HostingEnvironment.EnvironmentName.ToLower())
            .WithCache(ttl: TimeSpan.FromHours(2))
        );
    }
});

Encrypted local file

builder.Configuration.AddStashLock(cfg => cfg
    .FromEncryptedFile("secrets.enc.json")
    .WithPrivateKey(key)
);

Dependency Injection

Register ISecretsStore as a singleton in your DI container:

using Deneblab.StashLock.Client.Configuration;

builder.Services.AddStashLock(cfg => cfg
    .WithBox("myapp", "prod")
    .WithCache(ttl: TimeSpan.FromHours(2))
);

// Inject anywhere
public class MyService(ISecretsStore secrets)
{
    public string GetDbPassword() => secrets["Database:Password"];
}

Environment Variables

Variable Purpose Behavior
STASHLOCK_CONNECTION_STRING Full connection string (Url=...;ApiKey=...;Box=...;Tag=...) Auto-read as defaults in OpenAsync() if set; explicit via .FromConnectionString()
STASHLOCK_PRIVATE_KEY Base64-encoded X25519 private key Silent fallback for .WithBox(), .FromEncryptedFile()
STASHLOCK_API_URL Vault API base URL Silent fallback
STASHLOCK_API_KEY API authentication key (Bearer token) Silent fallback

Error Handling

try
{
    var store = await StashLock.CreateClient()
        .FromEnvironment()
        .OpenAsync();
}
catch (VaultConfigurationException ex)
{
    // Missing or invalid configuration (env var, key format)
}
catch (VaultNotFoundException ex)
{
    // Vault entry not found on server
}
catch (DecryptionException ex)
{
    // Decryption failed — wrong password or key
}

Development Secrets File

Create a secrets.json in dev/secrets/ under your project root:

{
  "Database:ConnectionString": "Server=localhost;Database=dev",
  "Database:Password": "dev-password",
  "ExternalApi:Key": "dev-api-key"
}

FromDevFile() (without a path) auto-discovers this file when running in Dev or Test mode.

License

MIT License - see LICENSE file for details

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.4.2 0 3/12/2026
0.3.122 0 3/12/2026
0.3.121 0 3/12/2026
0.3.119 0 3/12/2026
0.3.118 0 3/12/2026
0.3.117 0 3/12/2026
0.3.115 0 3/12/2026
0.3.112 4 3/12/2026
0.3.111 4 3/12/2026
0.3.110 4 3/12/2026
0.3.109 4 3/12/2026
0.3.108 31 3/11/2026
0.3.107 25 3/11/2026
0.3.106 27 3/11/2026
0.3.105 31 3/11/2026
0.3.103 24 3/11/2026
0.3.102 30 3/11/2026
0.3.100 32 3/11/2026
0.3.99 29 3/11/2026
0.3.98 30 3/11/2026
Loading failed