Kai Ole Hartwig — Blog
Mattschwarze Kombinationsschloss-Scheibe mit gebürstetem Aluminium-Ring auf Eichentisch, drei kleine Indikator-Markierungen, kühles gerichtetes Tageslicht.
Extension · moselwal/secret-resolver

secret-resolver — secrets in site configs, at runtime.

Write %secret(API_KEY)% directly in TYPO3 site configs — resolved at runtime: first KEY_FILE env (file path), then /run/secrets/ mount. Extensible via SecretProviderInterface for Vault, AWS Secrets Manager, and other backends. Exactly what container deployments need.

  • Composer package:composer require moselwal/secret-resolver
  • TYPO3: ^14.0 · PHP: ^8.3 · MIT
Das Problem

Secrets in site configs are plain text — or not loaded at all.

With secret-resolver

  • %secret(API_KEY)% syntax directly in site config YAMLs
  • Cascading lookup: API_KEY_FILE/run/secrets/api_keyAPI_KEY env
  • Compatible with Docker and Kubernetes secrets out of the box
  • Custom providers via SecretProviderInterface (e.g. Vault, AWS SM)
  • Cache-aware — no re-lookups on every request

Until now

  • API keys hard-coded in YAML configs (in the Git repo)
  • Or: referenced via ENV variables in TypoScript (clunky)
  • Docker/K8s secrets via /run/secrets/ not usable out of the box
  • Multi-provider lookup (Vault, AWS Secrets Manager) as custom boilerplate

Four building blocks

Extensibility

Implement SecretProviderInterface — and Vault, AWS Secrets Manager, or Bitwarden Secrets are usable as an additional lookup step.

Container-ready

Works seamlessly with Docker secrets and Kubernetes mounted secrets — no image-build adjustment required.

Cascading lookup

Priority cascade for simple keys: {KEY}_FILE env (priority 30), then /run/secrets/{key} mount (priority 20). Extended keys route directly to the named provider — no cascade pass.

%secret() syntax

Usable directly in YAML site configs — no custom bootstrap logic required.

Usage

Simple keys (cascade resolution)

 

# config/sites/main/config.yaml
apiKey: '%secret(API_KEY)%'
dbPassword: '%secret(DB_PASSWORD)%'

# Inline in strings:
dsn: 'mysql://user:%secret(DB_PASSWORD)%@db:3306/app'

 

The key is resolved through all registered providers in priority order — first match wins.

Extended keys (provider-targeted)

 

# Direct vault lookup — bypasses cascade, goes to the "vault" provider
dbPassword: '%secret(vault:kv-v2/database.password)%'
apiToken: '%secret(vault:transit/api_token)%'

# AWS Secrets Manager
dbPassword: '%secret(aws-sm:prod/database.password)%'

 

Format: %secret(provider:path/to/secret.subKey)%

PartRequiredDescription
providerYesProvider name (vault, aws-sm …) — routes directly
pathNoSecret path with / separators
subKeyNoJSON sub-key after the last . in the final path segment

Sub-key extraction: if a provider returns e.g. {"password":"s3cret","username":"admin"}, the sub-key password automatically extracts "s3cret". Simple keys (without :) keep working as before — fully backwards-compatible.

Works in all TYPO3 YAML files

%secret()% hooks into TYPO3’s central YamlFileLoader — not just site configurations. The placeholder works everywhere TYPO3 loads YAML:

Packagist

composer require moselwal/secret-resolver

Package on Packagist →
Packagist

GitHub

Source code, issues, and changelogs. MIT-licensed.

View on GitHub →
GitHub
Nächster Schritt

Tidy up your container deployment?

secret-resolver is open source and compact. For Vault or AWS Secrets Manager integration, cluster setups or migration away from plain-text secrets, I am happy to support you.

Secret-Setup besprechen

Oder direkt schreiben: kontakt@moselwal.de

Where I use this …

This package carries the secret handling in TYPO3 Kubernetes and is a mandatory building block for any setup that takes Open Source & Digital Sovereignty seriously — keys stay with you, not in the image. Managed variant: AI-Ready CMS as a Service.