Files
git.stella-ops.org/docs/doctor/articles/security/secrets.md
master c58a236d70 Doctor plugin checks: implement health check classes and documentation
Implement remediation-aware health checks across all Doctor plugin modules
(Agent, Attestor, Auth, BinaryAnalysis, Compliance, Crypto, Environment,
EvidenceLocker, Notify, Observability, Operations, Policy, Postgres, Release,
Scanner, Storage, Vex) and their backing library counterparts (AI, Attestation,
Authority, Core, Cryptography, Database, Docker, Integration, Notify,
Observability, Security, ServiceGraph, Sources, Verification).

Each check now emits structured remediation metadata (severity, category,
runbook links, and fix suggestions) consumed by the Doctor dashboard
remediation panel.

Also adds:
- docs/doctor/articles/ knowledge base for check explanations
- Advisory AI search seed and allowlist updates for doctor content
- Sprint plan for doctor checks documentation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 12:28:00 +02:00

4.1 KiB

checkId, plugin, severity, tags
checkId plugin severity tags
check.security.secrets stellaops.doctor.security fail
security
secrets
configuration

Secrets Configuration

What It Checks

Validates that secrets are properly managed and not exposed as plain text in configuration. The check scans the following configuration keys for potential plain-text secrets:

Key What it protects
Jwt:SigningKey JWT token signing
Jwt:Secret JWT secret (alternative key)
ApiKey API authentication key
ApiSecret API secret
S3:SecretKey Object storage credentials
Smtp:Password Email server credentials
Ldap:Password Directory service credentials
Redis:Password Cache/message broker credentials
Valkey:Password Cache/message broker credentials

A value is considered a plain-text secret if it:

  1. Is at least 8 characters long.
  2. Contains both uppercase and lowercase letters.
  3. Contains digits or special characters.
  4. Does NOT start with a secrets provider prefix: vault:, azurekv:, aws:, gcp:, ${, or @Microsoft.KeyVault.

The check also examines whether a secrets management provider is configured (Secrets:Provider, KeyVault:Provider, Secrets:VaultUrl, KeyVault:Url, Vault:Address). A missing secrets manager is only flagged if plain-text secrets are also found.

Note: Connection strings are intentionally excluded from this check as they are DSNs (host/port/db) and are expected in configuration.

Why It Matters

Plain-text secrets in configuration files are a critical security risk. Configuration files are often committed to version control, stored in CI artifacts, or readable by anyone with filesystem access. Leaked secrets enable:

  • Token forgery (JWT signing keys).
  • Unauthorized API access (API keys).
  • Data access via backend services (database, SMTP, LDAP passwords).
  • Lateral movement within the infrastructure.

Common Causes

  • Secrets stored directly in appsettings.json instead of using a secrets provider
  • Environment variables containing secrets not sourced from a secrets manager
  • Development secrets left in production configuration
  • No secrets management provider configured (HashiCorp Vault, Azure Key Vault, etc.)

How to Fix

Docker Compose

Use Docker secrets or reference an external secrets manager:

services:
  platform:
    environment:
      Jwt__SigningKey: "vault:secret/data/stellaops/jwt#signing_key"
      Secrets__Provider: "vault"
      Secrets__VaultUrl: "http://vault:8200"
    secrets:
      - jwt_signing_key

secrets:
  jwt_signing_key:
    file: ./secrets/jwt_signing_key.txt

Or use dotnet user-secrets for development:

dotnet user-secrets set "Jwt:SigningKey" "<your-secret>"

Bare Metal / systemd

Configure a secrets provider in appsettings.json:

{
  "Secrets": {
    "Provider": "vault",
    "VaultUrl": "https://vault.internal:8200",
    "UseSecretManager": true
  }
}

Store secrets in the provider instead of config files:

# HashiCorp Vault
vault kv put secret/stellaops/jwt signing_key="<key>"

# dotnet user-secrets (development)
dotnet user-secrets set "Jwt:SigningKey" "<key>"

Kubernetes / Helm

Store secrets as Kubernetes Secrets:

kubectl create secret generic stellaops-secrets \
  --from-literal=jwt-signing-key="<key>" \
  --from-literal=smtp-password="<password>"

Reference in Helm values:

secrets:
  provider: "kubernetes"
  existingSecret: "stellaops-secrets"

Or use an external secrets operator (e.g., External Secrets Operator with Vault):

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: stellaops-secrets
spec:
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: stellaops-secrets
  data:
    - secretKey: jwt-signing-key
      remoteRef:
        key: secret/stellaops/jwt
        property: signing_key

Verification

stella doctor run --check check.security.secrets
  • check.security.jwt.config — JWT signing key security
  • check.security.encryption — encryption key management
  • check.security.apikey — API key security practices