SeliseBlocks.Secrets.OS 4.0.2

dotnet add package SeliseBlocks.Secrets.OS --version 4.0.2
                    
NuGet\Install-Package SeliseBlocks.Secrets.OS -Version 4.0.2
                    
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="SeliseBlocks.Secrets.OS" Version="4.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SeliseBlocks.Secrets.OS" Version="4.0.2" />
                    
Directory.Packages.props
<PackageReference Include="SeliseBlocks.Secrets.OS" />
                    
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 SeliseBlocks.Secrets.OS --version 4.0.2
                    
#r "nuget: SeliseBlocks.Secrets.OS, 4.0.2"
                    
#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 SeliseBlocks.Secrets.OS@4.0.2
                    
#: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=SeliseBlocks.Secrets.OS&version=4.0.2
                    
Install as a Cake Addin
#tool nuget:?package=SeliseBlocks.Secrets.OS&version=4.0.2
                    
Install as a Cake Tool

SeliseBlocks.Secrets.OS

Configuration

Values live in Azure Key Vault, configured through the KeyVault environment section — the same keys Blocks.Genesis reads, so a host already configured for Genesis needs nothing extra.

Variable Required Purpose
KeyVault__KeyVaultUrl yes Vault URL, e.g. https://my-vault.vault.azure.net/
KeyVault__ClientId no Service-principal application id
KeyVault__ClientSecret no Service-principal secret
KeyVault__TenantId no Directory tenant id

Authentication tries DefaultAzureCredential first — managed identity on Azure, az login / Visual Studio / AZURE_* environment variables locally. When that chain has nothing available and all three of ClientId, ClientSecret, and TenantId are set, it falls back to those. Set them on hosts with no CLI login and no managed identity. A partially filled set is ignored.

Bad credentials surface on the first vault call, not at startup: resolution happens in a synchronous singleton constructor, so probing the token there would block container build on a network round trip and take the host down on a transient AAD blip.

Setup

services.AddBlocksSecrets();

builder.Services.Configure<MvcOptions>(options => options.Filters.Add<SecretExceptionFilter>());

Inject ISecretService wherever you need it.

Types

Type Access list Value readable by Shown in the Blocks OS UI
api yes the access list, the creator, root reveal, copy, edit, rotate
service no any authenticated caller in the tenant metadata only
both no any authenticated caller in the tenant reveal, copy, edit, rotate

both reads identically to service — a valid tenant context and Active status is the whole check for either. The difference is presentational: the UI offers value and edit actions for both and hides them for service. That makes an existing capability visible rather than granting a new one, because the API has always let any authenticated caller in the tenant read a service value.

The type is fixed at creation. There is no operation that rewrites it, because converting one in place would silently move an existing credential between access models — a service secret turned both would become visible to every user in the tenant without anyone re-approving it.

SecretTypes.HasAccessList is the single predicate behind the distinction; four rules depend on it (whether a create stores an access list, whether UpdateAccessAsync applies, whether a value read consults the list, and whether a metadata mutation does).

Create

var secretId = await _secrets.SetAsync(new SetSecretRequest
{
    Name  = "smtp-password",
    Value = plaintext,
    Type  = SecretTypes.Service,
    Tags  = ["mail", "env:prod"],
});
var ids = await _secrets.SetManyAsync(
[
    new SetSecretRequest { Name = "smtp-password", Value = pwd,  Type = SecretTypes.Service },
    new SetSecretRequest { Name = "smtp-host-key", Value = key,  Type = SecretTypes.Service },
]);
// ids["smtp-password"] => "3f2a...c91"

Store the returned id on your own documents. Never the value.

Read

SecretResult? secret = await _secrets.GetAsync(secretId);

SecretListResult page = await _secrets.FindAsync(new SecretFilter
{
    Search     = "smtp",
    Type       = SecretTypes.Service,
    Tags       = ["iam", "os"],   // any of them, not all
    PageNumber = 1,
    PageSize   = 20,
});
string value = await _secrets.GetValueAsync(secretId);

IReadOnlyDictionary<string, string> values = await _secrets.GetValuesAsync([id1, id2]);

Tags

Tags are free-form labels for grouping and lookup. They carry no authorization meaning — an access list is still what decides who may read a value.

They are normalized on write: trimmed, lowercased, de-duplicated and ordered. A tag must start with a letter or digit and may then contain letters, digits, ., _, - and :; the colon is there so a tag can be namespaced as env:prod. At most 20 tags per secret, 50 characters each.

UpdateAsync replaces the whole set. Null leaves the existing tags alone; an empty collection clears them.

await _secrets.UpdateAsync(secretId, new UpdateSecretRequest { Tags = ["mail", "env:staging"] });

Filtering is any-of: a secret matching at least one of the given tags is returned, so selecting a second tag widens the result rather than narrowing it. At most 10 tags per filter.

The catalogue

GetTagsAsync returns the tenant's tag catalogue — key plus display label — for a tag picker or a tag filter:

IReadOnlyList<SecretTagEntry> tags = await _secrets.GetTagsAsync();
// [{ Key = "iam", Label = "Blocks Iam" }, …]

It lives in the tenant database's keyValueStores collection under my-secret-tags, read and written through Genesis's IKeyValueStore:

{
  "Key": "my-secret-tags",
  "Value": [
    { "Key": "iam", "Label": "Blocks Iam" },
    { "Key": "os",  "Label": "Blocks Logic" }
  ]
}

Entries seeded by hand keep the labels they were given. When a create or update carries a tag the catalogue has not seen, the platform adds it with a label derived from the key (payments-team becomes "Payments Team"); an existing key is never rewritten. Nothing removes an entry, so a tag stays on offer after the last secret using it is gone.

The catalogue is a convenience list, not an authority. It never constrains what a secret may be tagged with and is never consulted to validate a tag, so a missing document means "no suggestions" rather than an error, and a failed catalogue write never fails the secret write that triggered it.

Update and rotate

await _secrets.UpdateAsync(secretId, new UpdateSecretRequest
{
    Name        = "smtp-password-primary",
    Description = "Outbound mail",
});

await _secrets.RotateAsync(secretId, new RotateSecretRequest { Value = newPlaintext });

Lifecycle

await _secrets.LockAsync(secretId);
await _secrets.UnlockAsync(secretId);
await _secrets.DeleteAsync(secretId);
await _secrets.RestoreAsync(secretId);

Access

await _secrets.UpdateAccessAsync(secretId, new SecretAccess
{
    UserIds = { "user-1", "user-2" },
    Roles   = { "admin" },
});

Audit

SecretAuditListResult logs = await _secrets.GetAuditLogsAsync(new SecretAuditFilter
{
    SecretId   = secretId,
    FromDate   = DateTime.UtcNow.AddDays(-30),
    PageNumber = 1,
    PageSize   = 20,
});

Errors

Exception HTTP
SecretValidationException 400
SecretAccessDeniedException 403
SecretNotFoundException 404
SecretStateException 409
SecretVaultException 502

HTTP

Route Method Purpose
/Secrets/gets GET List metadata; ?tags=iam&tags=os filters to secrets carrying any of them
/Secrets/tags GET The tenant's tag catalogue, for a picker or a filter
/Secrets/values POST Batch plaintext read by id
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  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
4.0.2 76 9/8/2026
4.0.1 705 8/23/2026
4.0.0 157 8/19/2026