Insecure credential storage used by drupal_cms_ai recipe as default
>>> [!note] Migrated issue
<!-- Drupal.org comment -->
<!-- Migrated from issue #3560518. -->
Reported by: [mxr576](https://www.drupal.org/user/315522)
Related to !762 !711 !704
>>>
<h2>Problem/Motivation</h2>
<p>
The <code>drupal_cms_ai</code> recipe currently relies on configuration-based storage for third-party AI provider credentials, which results in API keys and other sensitive data being written into standard Drupal configuration. This is an insecure default because configuration is often exported to version control and shared between environments, contrary to recommended practices for secret management in Drupal and in the AI ecosystem more broadly.
</p>
<p>
When the “Configuration” key provider is used for AI provider credentials, those secrets are stored in Drupal’s active configuration and therefore end up both in the site database and in exported configuration YAML files. This means credentials are present in clear text in multiple places where they can be accidentally exposed, backed up, or committed to a public or insufficiently protected repository.
</p>
<p>
The AI initiative meta issue <span class="drupalorg-gitlab-issue-link drupalorg-gitlab-link-wrapper"><a href="https://git.drupalcode.org/project/ai_initiative/-/work_items/3559052" class="drupalorg-gitlab-link">https://git.drupalcode.org/project/ai_initiative/-/work_items/3559052</a></span> already calls out the need to define and improve default credential handling across AI-related tooling, but<strong> the <code>drupal_cms_ai</code> recipe is a concrete high-visibility case that should not normalize insecure storage of credentials out of the box.</strong>
</p>
<h2>Steps to reproduce</h2>
<ol>
<li>Apply the <code>drupal_cms_ai</code> recipe and follow the instructions for setting your OpenAI/Antropic API key.</li>
<li>Export site configuration using <code>drush cex</code> or the UI configuration export.</li>
<li>Inspect:
<ul>
<li>The exported YAML files under the configuration sync directory.</li>
<li>The active configuration storage in the database (<code>cache_config</code> entries).</li>
</ul>
</li>
</ol>
<p>
Expected result:
</p>
<ul>
<li>AI provider credentials (such as API keys or tokens) are stored using a secret-management mechanism (for example, via the Key module or other non-exported storage), and are not present in clear text in the exported configuration files or in the database-backed active configuration.</li>
</ul>
<p>
Actual result:
</p>
<ul>
<li>One or more exported configuration entities contain raw AI provider credentials in clear text.</li>
<li>The same clear-text credentials are also present in the database as part of Drupal’s active configuration (and its caches), which increases the risk of accidental disclosure via database dumps, backups, or compromised configuration exports.</li>
</ul>
<h2>Proposed resolution</h2>
<p>The drupal_cms_ai recipe now integrates the<a href="https://www.drupal.org/project/easy_encryption"> Easy Encryption</a> module to ensure that sensitive AI provider credentials (API keys) are never stored in plaintext configuration. Easy Encryption addresses the problem by intercepting the creation of new keys - either when a recipe/config action or a user initiates it on a UI - and Easy Encryption automatically switches the Configuration (plain text) provider to Easy Encrypted - which is a Key provider that encrypts key values at rest via Libsodium (sealed box).</p>
<p>Further information can be found at the module's homepage/README.md and <a href="https://project.pages.drupalcode.org/easy_encryption">Documentation</a> page.</p>
<p>P.S.: This module was specifically designed for addressing the credential security challenge detailed in <span class="drupalorg-gitlab-issue-link drupalorg-gitlab-link-wrapper"><a href="https://git.drupalcode.org/project/ai_initiative/-/work_items/3559052" class="drupalorg-gitlab-link">https://git.drupalcode.org/project/ai_initiative/-/work_items/3559052</a></span>.</p>
> Related issue: [Issue #3561213](https://www.drupal.org/node/3561213)
> Related issue: [Issue #3561380](https://www.drupal.org/node/3561380)
> Related issue: [Issue #3561668](https://www.drupal.org/node/3561668)
> Related issue: [Issue #3561674](https://www.drupal.org/node/3561674)
> Related issue: [Issue #3193591](https://www.drupal.org/node/3193591)
> Related issue: [Issue #3569548](https://www.drupal.org/node/3569548)
> Related issue: [Issue #3559052](https://www.drupal.org/node/3559052)
issue