secrets-management

Comprehensive guidance for securely storing, accessing, rotating, and protecting secrets.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "secrets-management" with this command: npx skills add melodic-software/claude-code-plugins/melodic-software-claude-code-plugins-secrets-management

Secrets Management

Comprehensive guidance for securely storing, accessing, rotating, and protecting secrets.

When to Use This Skill

Use this skill when:

  • Choosing a secrets management solution

  • Implementing secret rotation

  • Preventing secrets in source code

  • Configuring CI/CD pipeline secrets

  • Setting up secrets scanning

  • Reviewing credentials handling

  • Migrating from insecure secret storage

Secrets Management Solutions

Comparison Matrix

Solution Self-Hosted Cloud Dynamic Secrets Rotation Cost

HashiCorp Vault ✅ ✅ ✅ ✅ Free (OSS) / $$

AWS Secrets Manager ❌ ✅ ❌ ✅ $

Azure Key Vault ❌ ✅ ❌ ✅ $

Google Secret Manager ❌ ✅ ❌ ✅ $

Doppler ❌ ✅ ❌ ❌ $$

Environment Variables ✅ ✅ ❌ Manual Free

When to Use What

Use Case Recommended Solution

Enterprise, multi-cloud HashiCorp Vault

AWS-native applications AWS Secrets Manager

Azure-native applications Azure Key Vault

GCP-native applications Google Secret Manager

Simple applications Environment variables

Development .env files (never commit!)

HashiCorp Vault

Basic Usage

Enable secrets engine

vault secrets enable -path=secret kv-v2

Store a secret

vault kv put secret/myapp/database
username="dbuser"
password="supersecret"

Read a secret

vault kv get secret/myapp/database

Get specific field

vault kv get -field=password secret/myapp/database

Application Integration (C#)

using System.Text.Json; using VaultSharp; using VaultSharp.V1.AuthMethods.Token;

/// <summary> /// HashiCorp Vault client for secrets retrieval. /// </summary> public sealed class VaultClient { private readonly IVaultClient _client;

public VaultClient(string url, string token)
{
    var authMethod = new TokenAuthMethodInfo(token);
    var settings = new VaultClientSettings(url, authMethod);
    _client = new VaultSharp.VaultClient(settings);
}

/// &#x3C;summary>
/// Get a secret from Vault KV v2.
/// &#x3C;/summary>
public async Task&#x3C;string> GetSecretAsync(string path, string key, CancellationToken cancellationToken = default)
{
    var secret = await _client.V1.Secrets.KeyValue.V2.ReadSecretAsync(path: path);
    return secret.Data.Data[key].ToString()!;
}

/// &#x3C;summary>
/// Get database credentials.
/// &#x3C;/summary>
public async Task&#x3C;DatabaseCredentials> GetDatabaseCredentialsAsync(CancellationToken cancellationToken = default)
{
    return new DatabaseCredentials(
        Username: await GetSecretAsync("myapp/database", "username", cancellationToken),
        Password: await GetSecretAsync("myapp/database", "password", cancellationToken)
    );
}

}

public sealed record DatabaseCredentials(string Username, string Password);

// Usage var vault = new VaultClient( url: Environment.GetEnvironmentVariable("VAULT_ADDR")!, token: Environment.GetEnvironmentVariable("VAULT_TOKEN")! ); var dbCreds = await vault.GetDatabaseCredentialsAsync();

Dynamic Database Credentials

Enable database secrets engine

vault secrets enable database

Configure PostgreSQL connection

vault write database/config/mydb
plugin_name=postgresql-database-plugin
connection_url="postgresql://{{username}}:{{password}}@localhost:5432/mydb"
allowed_roles="readonly,readwrite"
username="vault"
password="vault-password"

Create a role

vault write database/roles/readonly
db_name=mydb
creation_statements="CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO "{{name}}";"
default_ttl="1h"
max_ttl="24h"

Get dynamic credentials

vault read database/creds/readonly

Returns: username=v-token-readonly-xxx, password=xxx, lease_id=xxx

For detailed Vault patterns: See Vault Patterns Reference

AWS Secrets Manager

Store and Retrieve Secrets

using Amazon.SecretsManager; using Amazon.SecretsManager.Model; using System.Text.Json;

/// <summary> /// AWS Secrets Manager client. /// </summary> public sealed class AwsSecretsClient(IAmazonSecretsManager client) { /// <summary> /// Retrieve secret from AWS Secrets Manager. /// </summary> public async Task<T> GetSecretAsync<T>(string secretName, CancellationToken cancellationToken = default) { var response = await client.GetSecretValueAsync( new GetSecretValueRequest { SecretId = secretName }, cancellationToken );

    return JsonSerializer.Deserialize&#x3C;T>(response.SecretString)!;
}

}

// Usage with DI public sealed record DbCredentials(string Username, string Password);

// In Startup/Program.cs services.AddAWSService<IAmazonSecretsManager>(); services.AddSingleton<AwsSecretsClient>();

// In application code var dbCreds = await secretsClient.GetSecretAsync<DbCredentials>("prod/myapp/database"); // Returns: DbCredentials { Username = "dbuser", Password = "secret" }

Automatic Rotation

using Amazon.SecretsManager; using Amazon.SecretsManager.Model; using System.Text.Json;

/// <summary> /// Create secret with automatic rotation enabled. /// </summary> public static async Task CreateSecretWithRotationAsync( IAmazonSecretsManager client, string secretName, object secretValue, string rotationLambdaArn, int rotationDays = 30, CancellationToken cancellationToken = default) { // Create the secret await client.CreateSecretAsync(new CreateSecretRequest { Name = secretName, SecretString = JsonSerializer.Serialize(secretValue) }, cancellationToken);

// Enable rotation (requires Lambda function)
await client.RotateSecretAsync(new RotateSecretRequest
{
    SecretId = secretName,
    RotationLambdaARN = rotationLambdaArn,
    RotationRules = new RotationRulesType
    {
        AutomaticallyAfterDays = rotationDays
    }
}, cancellationToken);

}

Environment Variables

Best Practices

Set environment variables (not in code!)

export DATABASE_URL="postgresql://user:pass@localhost/db" export API_KEY="sk_live_xxx"

In systemd service file

[Service] Environment="DATABASE_URL=postgresql://user:pass@localhost/db" EnvironmentFile=/etc/myapp/secrets.env

In Docker

docker run -e DATABASE_URL="postgresql://..." myapp

Or from file

docker run --env-file ./secrets.env myapp

In Kubernetes

kubectl create secret generic myapp-secrets
--from-literal=DATABASE_URL="postgresql://..."
--from-literal=API_KEY="sk_live_xxx"

Loading in Application

using Microsoft.Extensions.Configuration;

/// <summary> /// Application configuration loaded from environment variables. /// </summary> public sealed class AppConfig { public required string DatabaseUrl { get; init; } public required string ApiKey { get; init; } public bool Debug { get; init; } }

// In Program.cs or Startup.cs var configuration = new ConfigurationBuilder() .AddEnvironmentVariables() .AddUserSecrets<Program>(optional: true) // For development .Build();

// Bind to strongly-typed config services.Configure<AppConfig>(options => { options.DatabaseUrl = configuration["DATABASE_URL"] ?? throw new InvalidOperationException("DATABASE_URL is required"); options.ApiKey = configuration["API_KEY"] ?? throw new InvalidOperationException("API_KEY is required"); options.Debug = bool.TryParse(configuration["DEBUG"], out var debug) && debug; });

// Or use options pattern services.AddOptions<AppConfig>() .Bind(configuration.GetSection("App")) .ValidateDataAnnotations() .ValidateOnStart();

// In application code public class MyService(IOptions<AppConfig> config) { private readonly AppConfig _config = config.Value; }

.env File Security

.env (NEVER commit this!)

DATABASE_URL=postgresql://user:pass@localhost/db API_KEY=sk_live_xxx

.env.example (commit this as template)

DATABASE_URL=postgresql://user:pass@localhost/db API_KEY=your-api-key-here

.gitignore - ALWAYS include

.env .env.local .env.*.local *.pem *.key secrets/

Secret Rotation

Rotation Strategy

using System.Security.Cryptography;

/// <summary> /// Secret rotation with overlap period for zero-downtime rotation. /// </summary> public sealed class SecretRotator(ISecretsStore secrets, INotificationClient notifications) { private static readonly TimeSpan GracePeriod = TimeSpan.FromHours(24);

/// &#x3C;summary>
/// Rotate an API key with overlap period.
/// &#x3C;/summary>
public async Task&#x3C;string> RotateApiKeyAsync(string keyName, CancellationToken cancellationToken = default)
{
    // 1. Generate new key
    var newKey = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32))
        .Replace('+', '-').Replace('/', '_').TrimEnd('=');

    // 2. Store new key as pending
    await secrets.StoreAsync($"{keyName}_pending", newKey, cancellationToken);

    // 3. Update primary key (old key still valid)
    var oldKey = await secrets.GetAsync(keyName, cancellationToken);
    await secrets.StoreAsync($"{keyName}_old", oldKey, cancellationToken);
    await secrets.StoreAsync(keyName, newKey, cancellationToken);

    // 4. Notify dependent services
    await notifications.SendAsync(
        $"API key {keyName} rotated. Update your configuration.",
        cancellationToken
    );

    // 5. Schedule old key deletion (grace period)
    await secrets.ScheduleDeletionAsync($"{keyName}_old", GracePeriod, cancellationToken);

    return newKey;
}

/// &#x3C;summary>
/// Accept both old and new keys during rotation.
/// &#x3C;/summary>
public async Task&#x3C;bool> ValidateDuringRotationAsync(string keyName, string providedKey, CancellationToken cancellationToken = default)
{
    var current = await secrets.GetAsync(keyName, cancellationToken);
    if (CryptographicOperations.FixedTimeEquals(
        System.Text.Encoding.UTF8.GetBytes(providedKey),
        System.Text.Encoding.UTF8.GetBytes(current)))
    {
        return true;
    }

    var old = await secrets.GetOrDefaultAsync($"{keyName}_old", cancellationToken);
    if (old is not null &#x26;&#x26; CryptographicOperations.FixedTimeEquals(
        System.Text.Encoding.UTF8.GetBytes(providedKey),
        System.Text.Encoding.UTF8.GetBytes(old)))
    {
        return true;
    }

    return false;
}

}

// Interfaces for secrets and notifications public interface ISecretsStore { Task<string> GetAsync(string key, CancellationToken cancellationToken); Task<string?> GetOrDefaultAsync(string key, CancellationToken cancellationToken); Task StoreAsync(string key, string value, CancellationToken cancellationToken); Task ScheduleDeletionAsync(string key, TimeSpan delay, CancellationToken cancellationToken); }

public interface INotificationClient { Task SendAsync(string message, CancellationToken cancellationToken); }

Rotation Timeline

Day 0: Generate new key, deploy to secrets manager ├── Old key: ACTIVE └── New key: PENDING

Day 1: Update applications to use new key ├── Old key: ACTIVE (grace period) └── New key: ACTIVE

Day 7: Revoke old key ├── Old key: REVOKED └── New key: ACTIVE

Secrets Scanning

Pre-commit Scanning

.pre-commit-config.yaml

repos:

CI/CD Scanning

GitHub Actions

name: Security Scan on: [push, pull_request]

jobs: secrets-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 with: fetch-depth: 0 # Full history for scanning

  - name: Gitleaks scan
    uses: gitleaks/gitleaks-action@v2
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  - name: TruffleHog scan
    uses: trufflesecurity/trufflehog@main
    with:
      path: ./
      extra_args: --only-verified

Scanning Tools Comparison

Tool Strengths Weaknesses

gitleaks Fast, good regex patterns May miss custom formats

TruffleHog Verifies secrets are live Slower, network calls

detect-secrets Baseline support, plugins More false positives

git-secrets AWS patterns built-in AWS-focused

For detailed scanning setup: See Secrets Scanning Reference

CI/CD Pipeline Secrets

GitHub Actions

Store secrets in repository settings

Access via ${{ secrets.SECRET_NAME }}

jobs: deploy: runs-on: ubuntu-latest steps: - name: Deploy env: DATABASE_URL: ${{ secrets.DATABASE_URL }} API_KEY: ${{ secrets.API_KEY }} run: | # Secrets available as environment variables ./deploy.sh

  # For OIDC authentication (preferred for cloud)
  - name: Configure AWS credentials
    uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::123456789:role/GitHubActionsRole
      aws-region: us-east-1

GitLab CI

Store in Settings > CI/CD > Variables

Mark as "Masked" and "Protected"

deploy: script: - echo "Deploying with DB_PASSWORD=$DB_PASSWORD" # Never do this! - ./deploy.sh variables: # Override for this job only ENVIRONMENT: production

Best Practices for CI/CD Secrets

  • Use OIDC when possible - No long-lived credentials

  • Mask secrets in logs - CI systems should auto-mask

  • Limit secret scope - Per-environment, per-branch

  • Audit secret access - Who accessed what when

  • Rotate regularly - Especially after team changes

Quick Decision Tree

Where should I store this secret?

  • Production database credentials → Secrets Manager + rotation

  • API keys for third-party services → Secrets Manager

  • Encryption keys → HSM or Vault

  • Development credentials → .env file (gitignored)

  • CI/CD deployment credentials → CI/CD secrets + OIDC

  • Inter-service authentication → Vault dynamic secrets

  • User-submitted API keys → Encrypted database column

Anti-Patterns to Avoid

Never Do This

// WRONG: Hardcoded secrets const string ApiKey = "sk_live_abc123"; const string DatabaseUrl = "postgresql://admin:password123@prod.db.example.com/app";

// WRONG: Secrets in appsettings.json (committed to git) // { // "Database": { // "Password": "supersecret" // } // }

// WRONG: Secrets in Docker images // COPY secrets.env /app/secrets.env

// WRONG: Logging secrets _logger.LogInformation("Connecting with password: {Password}", password);

// WRONG: Secrets in error messages throw new Exception($"Failed to connect: {connectionString}");

// WRONG: Secrets in URLs await httpClient.GetAsync($"https://api.example.com?api_key={apiKey}");

Do This Instead

// RIGHT: Environment variables var apiKey = Environment.GetEnvironmentVariable("API_KEY") ?? throw new InvalidOperationException("API_KEY not configured");

// RIGHT: Secrets manager var apiKey = await secretsManager.GetSecretAsync("api-key");

// RIGHT: Configuration with User Secrets (dev) or Azure Key Vault (prod) var apiKey = configuration["ApiKey"];

// RIGHT: Masked logging (use structured logging) _logger.LogInformation("Connecting to database..."); // No credentials

// RIGHT: Generic error messages throw new InvalidOperationException("Database connection failed"); // No details

// RIGHT: Secrets in headers (for APIs) httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey); await httpClient.GetAsync("https://api.example.com");

Security Checklist

Storage

  • No hardcoded secrets in source code

  • Secrets stored in dedicated secrets manager

  • Environment variables for configuration

  • .env files gitignored

Access Control

  • Least privilege access to secrets

  • Audit logging enabled

  • Secrets scoped to environments

  • Regular access reviews

Rotation

  • Rotation policy defined

  • Automated rotation where possible

  • Grace period for old secrets

  • Notification on rotation

Detection

  • Pre-commit hooks for secret scanning

  • CI/CD pipeline scanning

  • Git history scanning

  • Regular repository audits

CI/CD

  • Using CI platform's secrets management

  • OIDC for cloud authentication

  • Secrets masked in logs

  • Limited secret scope

References

  • Vault Patterns Reference - HashiCorp Vault deep dive

  • Secrets Scanning Reference - Scanning tools setup

Related Skills

Skill Relationship

cryptography

Encryption for secrets at rest

devsecops-practices

CI/CD security integration

authentication-patterns

API key and token management

Version History

  • v1.0.0 (2025-12-26): Initial release with Vault, cloud providers, rotation, scanning

Last Updated: 2025-12-26

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

design-thinking

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

plantuml-syntax

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

system-prompt-engineering

No summary provided by upstream source.

Repository SourceNeeds Review