Skip to content

Secrets Management

This page covers how Deepstaging handles sensitive configuration values — API keys, passwords, connection strings, and other secrets.

Overview

The [Secret] attribute marks configuration properties as sensitive. This triggers a separate handling pipeline:

  • Secret properties are excluded from deepstaging.schema.json
  • Secret properties are included in deepstaging.secrets.schema.json
  • A local deepstaging.secrets.json file is generated (and gitignored)
  • The generated source method calls AddUserSecrets to load from the .NET User Secrets store
  • A sync script (deepstaging.secrets-update.sh) bridges the JSON file and dotnet user-secrets
public class DatabaseSecrets
{
    [Secret]
    public string ConnectionString { get; init; } = "";

    [Secret]
    public string AdminPassword { get; init; } = "";
}

[ConfigRoot]
[Exposes<DatabaseSecrets>]
public sealed partial class DatabaseConfigRoot;

[Secret] Attribute

[AttributeUsage(AttributeTargets.Property)]
public sealed class SecretAttribute : Attribute;

Apply [Secret] to any property on a configuration type that contains sensitive data:

public class SmtpSecrets
{
    [Secret]
    public string Password { get; init; } = "";

    [Secret]
    public string ApiKey { get; init; } = "";

    // Non-secret — goes in deepstaging.schema.json
    public string SenderAddress { get; init; } = "";
}

What [Secret] Does

Aspect Without [Secret] With [Secret]
Schema deepstaging.schema.json deepstaging.secrets.schema.json
Settings file deepstaging.settings.json deepstaging.secrets.json
Source control Committed normally .gitignore entry added
Runtime source JSON file only JSON file + AddUserSecrets

Schema Separation

The generator produces two separate JSON Schema files, ensuring secrets never appear in the main schema:

{
  "$schema": "https://json-schema.org/draft-07/schema#",
  "$id": "deepstaging.schema.json",
  "type": "object",
  "properties": {
    "Smtp": {
      "type": "object",
      "properties": {
        "SmtpSettings": {
          "type": "object",
          "properties": {
            "Host": { "type": "string" },
            "Port": { "type": "integer" }
          }
        }
      }
    }
  }
}
{
  "$schema": "https://json-schema.org/draft-07/schema#",
  "$id": "deepstaging.secrets.schema.json",
  "type": "object",
  "properties": {
    "Smtp": {
      "type": "object",
      "properties": {
        "SmtpSecrets": {
          "type": "object",
          "properties": {
            "Password": { "type": "string" },
            "ApiKey": { "type": "string" }
          }
        }
      }
    }
  }
}

.NET User Secrets Integration

When a [ConfigRoot] exposes any [Secret] properties, the generated Configure*Sources method automatically calls AddUserSecrets:

Generated (with secrets)
public static IConfigurationBuilder ConfigureDatabaseConfigRootSources(
    this IConfigurationBuilder builder,
    string? environmentName = null,
    string? settingsPath = null)
{
    // ... load settings files ...
    builder.AddUserSecrets(typeof(DatabaseConfigRoot).Assembly, optional: true);
    return builder;
}

UserSecretsId Requirement (DSCFG07)

AddUserSecrets requires a <UserSecretsId> in your project file. If [Secret] properties exist but no UserSecretsId is configured, the analyzer reports DSCFG07 as an error.

DSCFG07 — Missing UserSecretsId

error DSCFG07: Class 'DatabaseConfigRoot' exposes [Secret] properties
but the assembly has no UserSecretsIdAttribute — add a UserSecretsId via the code fix

Fix: Use the code fix to add the element, or run manually:

dotnet user-secrets init

This adds a <UserSecretsId> GUID to your .csproj:

<PropertyGroup>
    <UserSecretsId>a1b2c3d4-e5f6-7890-abcd-ef1234567890</UserSecretsId>
</PropertyGroup>

deepstaging.secrets.json

The DSCFG06 code fix generates a deepstaging.secrets.json file at the project root. This file:

  • Contains a template with all [Secret] properties
  • Is added to .gitignore automatically
  • Serves as the source of truth for local development secrets
  • Is synced to dotnet user-secrets via the update script
deepstaging.secrets.json
{
  "$schema": "deepstaging.secrets.schema.json",
  "Database": {
    "DatabaseSecrets": {
      "ConnectionString": "",
      "AdminPassword": ""
    }
  }
}

Fill in the values for your local environment:

deepstaging.secrets.json (filled in)
{
  "$schema": "deepstaging.secrets.schema.json",
  "Database": {
    "DatabaseSecrets": {
      "ConnectionString": "Host=localhost;Database=myapp;Username=admin;Password=secret",
      "AdminPassword": "local-dev-password"
    }
  }
}

deepstaging.secrets-update.sh

The generated sync script loads deepstaging.secrets.json into the .NET User Secrets store:

./deepstaging.secrets-update.sh

The script:

  1. Clears existing user secrets for the project
  2. Reads deepstaging.secrets.json
  3. Loads all values via dotnet user-secrets set
  4. Prints the current secrets list

DSCFG09 — Sync notification

When [Secret] properties exist, the analyzer reports DSCFG09 (Info) as a reminder:

info DSCFG09: Run deepstaging.secrets-update.sh to sync user secrets for 'DatabaseConfigRoot'

Use the code fix to run the script directly from your IDE.

Automatic Secret Detection (DSCFG05)

The PotentialSecretPropertyAnalyzer inspects properties on types exposed via [Exposes<T>] on [ConfigRoot] or [ConfigModule] and flags those whose names match common secret patterns:

Pattern Examples
Password Password, AdminPassword, SmtpPassword
Secret ClientSecret, AppSecret
ApiKey ApiKey, StripeApiKey
Token AccessToken, RefreshToken, BearerToken
ConnectionString ConnectionString, DbConnectionString
Credential Credential, UserCredential
PrivateKey PrivateKey, SshPrivateKey
AccessKey AccessKey, AwsAccessKey
ClientSecret ClientSecret, OAuthClientSecret
Passphrase Passphrase, KeyPassphrase

DSCFG05

warning DSCFG05: Property 'ConnectionString' on 'DatabaseConfig' appears to
contain secrets or PII — consider adding [Secret]

Fix: Apply the code fix to add [Secret] to the property, or suppress the diagnostic if the property is intentionally non-secret.

Best Practices

  1. Always use [Secret] for passwords, API keys, tokens, and connection strings
  2. Never commit deepstaging.secrets.json — the code fix adds it to .gitignore automatically
  3. Run dotnet user-secrets init early — before adding your first [Secret] property, or let the DSCFG07 code fix handle it
  4. Run the sync script after editing secrets./deepstaging.secrets-update.sh keeps dotnet user-secrets in sync
  5. Use per-environment overrides for non-secrets — environment-specific settings go in deepstaging.settings.{Environment}.json
  6. Use CI/CD secret management for productiondeepstaging.secrets.json is for local development only; use Azure Key Vault, AWS Secrets Manager, or environment variables in production