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>
4.1 KiB
checkId, plugin, severity, tags
| checkId | plugin | severity | tags | |||
|---|---|---|---|---|---|---|
| check.security.secrets | stellaops.doctor.security | fail |
|
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:
- Is at least 8 characters long.
- Contains both uppercase and lowercase letters.
- Contains digits or special characters.
- 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.jsoninstead 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
Related Checks
check.security.jwt.config— JWT signing key securitycheck.security.encryption— encryption key managementcheck.security.apikey— API key security practices