Skip to content

ADR: Use state as fallback storage when file storage is unavailable#

Status#

Implemented

Context#

This module was introduced to address concrete security concerns raised in the Drupal ecosystem around provider credential storage. In particular, discussions in Drupal CMS and the AI Initiative highlighted a recurring pattern: API keys and similar secrets were stored in plain text configuration, which is routinely exported and committed to Git repositories as part of standard Drupal workflows.

The core objective of this module is simple and strict:

Plain text credentials must not be stored in configuration.

Instead, credentials are encrypted before being written to configuration. Configuration may travel through Git and between environments, but the encrypted payload is useless without the corresponding private key. This preserves the primary security boundary even when configuration is shared broadly.

The module implements a sealed box model:

  • The public key is stored in configuration and is exportable.
  • The private key is environment-specific and must never be exported with configuration.

The preferred storage for the private key is the file system, typically in a directory outside the web root and excluded from version control. This aligns with established best practices for handling sensitive key material.

During implementation, storing encryption keys in the private file system was considered a natural fit. However, having a properly configured private file system is not currently a hard requirement in Drupal core or in Drupal CMS. As discussed in the related core issue, private file system configuration cannot yet be assumed in every installation.

In addition, some hosting environments:

  • Do not provide a writable directory at install time.
  • Use ephemeral file systems in container-based setups.
  • Restrict file system access in ways that are outside the control of site builders.

If this module were to require file-based storage unconditionally, it would either fail to initialize or push users toward insecure workarounds. That would directly contradict its purpose.

A fallback mechanism was therefore required that:

  • Is always available in a standard Drupal 11.1 installation.
  • Is not exported with configuration.
  • Preserves the environment-specific nature of the private key.
  • Continues to guarantee that credentials are never stored in plain text in configuration.

The Drupal State API satisfies these constraints.

Decision#

File-based key storage remains the recommended and preferred provider for private keys.

When file storage is unavailable or cannot be initialized, this module will fall back to using the Drupal State API as the private key provider.

Even when the State provider is used:

  • Credentials are still encrypted before being written to configuration.
  • Plain text secrets are never stored in configuration.
  • Exported configuration remains safe to commit to Git, because it contains only encrypted values and the public key.

The State-based provider is explicitly defined as a fallback. It preserves the primary security goal while ensuring operability in constrained environments.

Alternatives#

Require file storage and fail if unavailable#

This option would enforce a stricter baseline by refusing to operate without a writable directory for key storage.

It was rejected because:

  • It would block usage in legitimate hosting environments where file storage is restricted or ephemeral.
  • It would block legitimate hosting scenarios.
  • It would increase friction, especially for non-technical site builders.
  • It could drive users back to storing credentials in plain text configuration.

Store the private key in configuration#

This would violate the core design principle of the module. Configuration is designed to be exported and shared across environments. Storing the private key there would collapse the security boundary between encrypted configuration and decryption capability.

This option was rejected as fundamentally incompatible with the module’s goals.

Require an external secret manager#

External secret management systems can provide stronger guarantees in advanced deployments. However, requiring them would:

  • Add operational complexity.
  • Increase the barrier to adoption.
  • Conflict with the goal of secure-by-default behavior with minimal configuration.

Such integrations remain possible as extensions, but they are not required for the baseline implementation.

Consequences#

Benefits#

  • The primary objective is upheld: plain text credentials are never stored in configuration.
  • Encrypted configuration can safely travel through Git and across environments without exposing secrets.
  • The module remains usable in environments without writable file storage.
  • The security model works consistently across Drupal CMS and non-Drupal CMS projects.

Drawbacks#

  • When using State, the private key resides in the database. Database dumps must therefore be treated as sensitive artifacts. In practice, this is already the case, because database dumps contain other security-relevant information such as hashed user passwords, active session records (when stored in the database), and environment-specific state maintained by core and contributed modules.
  • In environments where databases are cloned between stages, the private key will be cloned as well. Operational practices must account for this.
  • The State API is a pragmatic storage mechanism within Drupal’s trust model. It is not a dedicated external secret vault.

Notes#

This decision balances architectural best practice with real-world Drupal constraints.

File-based storage in a directory outside the web root remains the recommended approach for private key material. The State provider ensures that, even when that is not feasible, the module still fulfills its central promise: credentials are encrypted at rest, and plain text secrets never enter configuration management workflows.

Authors#

  • Dezső Biczó (@mxr576)
  • Adam Globus-Hoenich (@phenaproxima)

Date#

2025-12-10