<p>The <a href="https://putyourlightson.com/plugins/secrets" rel="nofollow noreferrer">Secrets plugin</a> allows you to store and manage secrets in an encrypted file.</p>
<p>There are some benefits to storing secrets this way, instead of the conventional approach of storing them as plaintext in the <code>.env</code> file.</p>
<ol>
<li>Secrets are encrypted and cannot be revealed without the encryption key.</li>
<li>The encrypted file can be committed to your version control repository, meaning you avoid having to send secrets in plaintext to other developers. You also end up with a history of changes to the file.</li>
<li>If secrets change or are rotated then there is only one file that needs to be updated.</li>
</ol>

CC BY-SA 4.0
2021-08-10T08:11:00.117Z
2021-08-10T08:11:00.117Z
Ben Croker
<p>First, full disclosure here, the company I work for is a customer of Fort Rabbit. The <code>secrets.json</code> approach is something I think that strikes the balance between flexibility and security for really sensitive keys like API, OAuth, Amazon S3 and other sensitive secrets. dotenv is great, but having these in plain text is a risk, which I think you are mindful of.</p>

<p>The approach I take is to use the <code>secrets.json</code> concept. With Craft 3, you can make your secrets available as environment variables, but they won't be visible when dumping out phpinfo().</p>

<p><strong>secrets.json</strong></p>

<pre><code>{
  "CUSTOM": {
      "SECRET_KEY_1": "secretValue",
      "SECRET_KEY_2": "anotherSecretValue"
      ....
  }
}
</code></pre>

<p><strong>config/app.php</strong></p>

<pre><code>// Create secrets as env variables
if (getenv('APP_SECRETS')) {

    $secrets = json_decode(file_get_contents(getenv('APP_SECRETS')), true);

    if (isset($secrets['CUSTOM'])) {
        foreach($secrets['CUSTOM'] as $k =&gt; $v) {
            putenv("$k=$v");
        }
    }
}
</code></pre>

<p><strong>.env</strong></p>

<pre><code>APP_SECRETS="/app/secrets.json"
</code></pre>

<p>You set the <code>APP_SECRETS</code> environment variable which will be the file path to your JSON file.</p>

<p>You can then reference the values in <code>secrets.json</code> as environment variables with <code>getenv()</code> and even the Craft <code>$SECRET_KEY_1</code> syntax if a plugin supports <code>Craft::parseEnv</code> on a field/setting.</p>

<p>This is however a bit bespoke and tailored to Fort Rabbit's environment, but the concept can be applied generally.</p>

<p>There is also the whole other argument about dotenv in production due to the overhead and performance impact, but that's another story!</p>

<p>I'm not sure there is a perfect solution, but I hope that gives you one example or what I'd consider "good practice".</p>

CC BY-SA 4.0
2019-03-30T01:40:59.687Z
I'm not sure I understand how this helps anything? It looks like just one more indirection, but ultimately the `$_ENV` variables are still set (otherwise `getenv()` wouldn't work).
andrew.welch
CC BY-SA 4.0
2019-03-30T07:17:02.477Z
@andrew.welch In this case, if you were to dump out the contents of `phpinfo()` or the `$_SERVER` `$_ENV` globals, they wouldn't show up, given the way they are initialised. In the Fort Rabbit environment secrets are encrypted in their dashboard and everywhere in the system, except during runtime. Not saying it's perfect, but it's better than plain .env.
James White
CC BY-SA 4.0
2019-04-04T09:37:28.380Z
Thanks for the suggestion James, but I must be missing something. 
I just tried this, and all of the content in secrets.json was available when checking the ENV section of phpinfo(), together with the stuff som .env.
Knut Erik
CC BY-SA 4.0
2019-04-04T09:44:56.620Z
[Screenshot from phpinfo()](https://imgur.com/NoFHjcW)
Knut Erik
CC BY-SA 4.0
2019-04-04T09:48:40.727Z
@KnutErik Interesting. When I do phpinfo() on my local environment (Nanobox). I do not see them at all, so might be a false sense of security. I haven't tried having phpinfo() dumped on Fort Rabbit, but maybe they do show up.

I think the clarity would be Fort Rabbit's approach probably doesn't answer your question, but the storage of them is a bit more secure, so they aren't just exposed in plain text within their platform.
James White
CC BY-SA 4.0
2019-04-04T10:01:22.080Z
Interesting:
 
While everything shows up in phpinfo(), the keys and values from secrets.json do not show up in the phperrors.log or web.log. 

In those logs, the only thing that shows up is the APP_SECRETS key and value.

Calling getenv('SECRET_KEY_1'), does work.
Knut Erik
CC BY-SA 4.0
2019-03-29T10:27:22.340Z
2019-03-30T14:48:05.293Z
2019-03-30T14:48:05.293Z
James White
<p><strong>Background:</strong></p>

<p>I'm running Craft Pro 3.1.19 on PHP 7.2.15</p>

<p>Craft CMS, by default, uses vlucas/phpdotenv to get the configuration (containing secrets / credentials and lots of other stuff) from .env</p>

<p>Craft CMS logs PHP errors to storage/logs/phperrors.log</p>

<p>In the PHP error log, the all the data from .env is logged.</p>

<p>The data is available in the $_SERVER superglobal, and is appended to stack traces.</p>

<p>Example:</p>

<pre><code>Error: Call to undefined function this_is_a_fatal_error() in /srv/app/web/index.php:24
Stack trace:
#0 {main}
...
...
$_SERVER = [
    ...
    ...
    'SECURITY_KEY' =&gt; 'exposed-secret'
    'DB_USER' =&gt; 'my-db-user',
    'DB_PASSWORD' =&gt; 'exposed-secret',
    'DB_DATABASE' =&gt; 'my-db-name',
    ...
]
</code></pre>

<p>In the Craft CMS web.log, sensitive data is masked before writing the log. Yay!</p>

<p>Example:</p>

<pre><code>Stack trace:
#0 {main}
...
...
$_SERVER = [
    ...
    ...
    'SECURITY_KEY' =&gt; '**************'
    'DB_USER' =&gt; 'my-db-user',
    'DB_PASSWORD' =&gt; '**************',
    'DB_DATABASE' =&gt; 'my-db-name',
    ...
]
</code></pre>

<p><strong>Problem:</strong></p>

<p>How do I prevent secrets beeing logged in the PHP error log?</p>

<p>I know this is not a Craft CMS specific problem.</p>

<p>The secrets could ofcourse also be exposed in other ways than in the log, with a careless phpinfo() or something similar.</p>

<p>So, I've searched a lot, and read a lot, and found various options / suggestions.</p>

<p>I'd like to ask for your opinion on the options / solutions (listed below), or if you have any more options I've not found yet...</p>

<p>Some of the stuff I've read:</p>

<p><a href="http://movingfast.io/articles/environment-variables-considered-harmful/" rel="nofollow noreferrer">http://movingfast.io/articles/environment-variables-considered-harmful/</a></p>

<p><a href="https://blog.fortrabbit.com/how-to-keep-a-secret" rel="nofollow noreferrer">https://blog.fortrabbit.com/how-to-keep-a-secret</a></p>

<p><a href="https://nystudio107.com/blog/multi-environment-config-for-craft-cms" rel="nofollow noreferrer">https://nystudio107.com/blog/multi-environment-config-for-craft-cms</a></p>

<p><a href="https://12factor.net/config" rel="nofollow noreferrer">https://12factor.net/config</a></p>

<p>(I have more, but I don't have the rep to post more links)</p>

<p>A general conclusion I've made so far is that configuration in the environment is a good idea, but credentials / secrets in the environment is a bad idea.
Is this a general consensus?</p>

<p><strong>Option 1 - turn off error logging:</strong></p>

<p>This has been brought up before; <a href="https://github.com/craftcms/cms/issues/3619" rel="nofollow noreferrer">https://github.com/craftcms/cms/issues/3619</a> - <a href="https://docs.craftcms.com/v3/config/php-constants.html#craft-log-php-errors" rel="nofollow noreferrer">https://docs.craftcms.com/v3/config/php-constants.html#craft-log-php-errors</a></p>

<p>So, the solution here is to tell Craft to turn off PHP error logging (you also have to turn of logging in php.ini).</p>

<p>I really do not want to turn of error logging unless all the other options are not possible...</p>

<p><strong>Option 2 - Move production configration to the webserver:</strong></p>

<p>The phpdotenv documentation used to say "phpdotenv is made for development environments, and generally should not be used in production."</p>

<p><a href="https://github.com/vlucas/phpdotenv/issues/207" rel="nofollow noreferrer">https://github.com/vlucas/phpdotenv/issues/207</a></p>

<p>Even though the documentation does not say this anymore, it might still be a good idea to move the config from .env to the webserver config (in production).</p>

<p>For Nginx - you can add a fastcgi_param for each key/value to the vhost config. </p>

<p>You can use a tool like nystudio107/dotenvy to help getting the correct config.</p>

<p>This does not really solve anything, as the secrets are still loaded into the environment.</p>

<p>Bonus: This has a small performance benefit.</p>

<p><strong>Option 3 - Modify the way Craft CMS uses phpdotenv:</strong></p>

<p>Laravel also uses vlucas/phpdotenv, but not in the same way.
It reads the .env, and stores the config in a cache file.</p>

<p>This way, the environment is not "polluted" with sensitive data.
See <a href="https://laravel.com/docs/5.8/configuration#configuration-caching" rel="nofollow noreferrer">https://laravel.com/docs/5.8/configuration#configuration-caching</a></p>

<p>Could this be a viable option for Craft CMS as well?
Would probably need to add some more steps when deploying code?</p>

<p>Sounds great to me, but may break backwards compatibility.
Also, this requires P&amp;T to do some magic.</p>

<p><strong>Option 4 - Roll your own?</strong></p>

<p>Remove vlucas/phpdotenv, and use something else to encrypt the .env-file, or implement option 3 myself.</p>

<p>I've found these (all are on github, but I don't have the reputation to post enough complete links):</p>

<ul>
<li>arrilot/dotenv-php  </li>
<li>johnathanmiller/secure-env-php</li>
<li>psecio/secure_dotenv  </li>
<li>josegonzalez/php-dotenv</li>
</ul>

<p>Has anyone done something like this?
The environment is still "polluted", but it won't be in plain text when it's logged.</p>

<p>This is good enough for me, but "roll your own security/crypto" isn't the best idea... </p>

<p><strong>Option 5 - Something I do not know about yet?</strong></p>

<p>Do you know something I don't? Please tell me!
Do you have any suggestions for further reading?</p>

<p>Thanks!</p>

<p><strong>Update / clarification:</strong></p>

<p>To give a bit more context, that I left out of the original post to try to keep it a bit more simple:</p>

<ul>
<li>I'm writing a lot about "I" and "me", but I'm part of a bigger team of developers and ops-people from a provider.</li>
<li>We're using AWS CloudWatch, and all our logs are sent to this service.</li>
<li>We're following a set of company imposed security guidelines that requires us to evaluate the storage and "containment" of secrets.</li>
</ul>

<p>I'm not really afraid of the people having access to the server.
If they have access, they most likley know about, and can access the secrets in .env directly anyway.</p>

<p>I'm more concerned about a rouge debug-statment in our code, a bug in Craft / Yii / a plugin or something like that, that exposes the secrects inadvertently. So option 1, turning off logging, doesn't really solve the problem either. </p>

<p>More people have access to the AWS dashboards / CloudWatch, also the ops-people that are on rotation in the evening/nights, who may or may not have in depth knowledge about our application, or what data not to send around.</p>

<p>It would also be nice to have some sort of dashboard on a giant screen of our own in the office, showing the status and latest errors on the servers, without having to worry about random guests or colleagues from other departments seeing our precious credentials.</p>

<p>If this was my own personal server setup, with just me as a user/developer, I'd not think of this whole thing as a problem.</p>

CC BY-SA 4.0
2019-03-30T01:42:20.527Z
The first question I'd ask is... does it matter? Who can access the webserver in such a way that they can read the PHP logs? And if they have access to that, they can probably view any non-encrypted file that the webserver can view.
andrew.welch
CC BY-SA 4.0
2019-04-01T07:56:12.720Z
I've added an update with more info about the context @andrew.welch
Knut Erik
CC BY-SA 4.0
2019-09-27T08:46:51.297Z
Did you ever figure out a way to fix this? This problem is not related to dotenv at all, I'm having the same problem when running it containerized in AWS Fargate since the only way to configure the setup is via ENV variables. Dumping them blindly without sanitation on every error seems utterly stupid. Maybe the best way would be to run the same sanitizer for stderr as for stdout, but I can't figure out how.
jishi
<craft3><config><php><security><logging>
How do I prevent secrets beeing logged in the PHP error log?
# How do I prevent secrets beeing logged in the PHP error log?

(Asked by: username_0 on 2019-03-29)

Background:
I'm running Craft Pro 3.1.19 on PHP 7.2.15
Craft CMS, by default, uses vlucas/phpdotenv to get the configuration (containing secrets / credentials and lots of other stuff) from .env
Craft CMS logs PHP errors to storage/logs/phperrors.log
In the PHP error log, the all the data from .env is logged.
The data is available in the $_SERVER superglobal, and is appended to stack traces.
Example:
Error: Call to undefined function this_is_a_fatal_error() in /srv/app/web/index.php:24
Stack trace:
#0 {main}
...
...
$_SERVER = [
    ...
    ...
    'SECURITY_KEY' => 'exposed-secret'
    'DB_USER' => 'my-db-user',
    'DB_PASSWORD' => 'exposed-secret',
    'DB_DATABASE' => 'my-db-name',
    ...
]

In the Craft CMS web.log, sensitive data is masked before writing the log. Yay!
Example:
Stack trace:
#0 {main}
...
...
$_SERVER = [
    ...
    ...
    'SECURITY_KEY' => '**************'
    'DB_USER' => 'my-db-user',
    'DB_PASSWORD' => '**************',
    'DB_DATABASE' => 'my-db-name',
    ...
]

Problem:
How do I prevent secrets beeing logged in the PHP error log?
I know this is not a Craft CMS specific problem.
The secrets could ofcourse also be exposed in other ways than in the log, with a careless phpinfo() or something similar.
So, I've searched a lot, and read a lot, and found various options / suggestions.
I'd like to ask for your opinion on the options / solutions (listed below), or if you have any more options I've not found yet...
Some of the stuff I've read:
http://movingfast.io/articles/environment-variables-considered-harmful/
https://blog.fortrabbit.com/how-to-keep-a-secret
https://nystudio107.com/blog/multi-environment-config-for-craft-cms
https://12factor.net/config
(I have more, but I don't have the rep to post more links)
A general conclusion I've made so far is that configuration in the environment is a good idea, but credentials / secrets in the environment is a bad idea.
Is this a general consensus?
Option 1 - turn off error logging:
This has been brought up before; https://github.com/craftcms/cms/issues/3619 - https://docs.craftcms.com/v3/config/php-constants.html#craft-log-php-errors
So, the solution here is to tell Craft to turn off PHP error logging (you also have to turn of logging in php.ini).
I really do not want to turn of error logging unless all the other options are not possible...
Option 2 - Move production configration to the webserver:
The phpdotenv documentation used to say "phpdotenv is made for development environments, and generally should not be used in production."
https://github.com/vlucas/phpdotenv/issues/207
Even though the documentation does not say this anymore, it might still be a good idea to move the config from .env to the webserver config (in production).
For Nginx - you can add a fastcgi_param for each key/value to the vhost config. 
You can use a tool like nystudio107/dotenvy to help getting the correct config.
This does not really solve anything, as the secrets are still loaded into the environment.
Bonus: This has a small performance benefit.
Option 3 - Modify the way Craft CMS uses phpdotenv:
Laravel also uses vlucas/phpdotenv, but not in the same way.
It reads the .env, and stores the config in a cache file.
This way, the environment is not "polluted" with sensitive data.
See https://laravel.com/docs/5.8/configuration#configuration-caching
Could this be a viable option for Craft CMS as well?
Would probably need to add some more steps when deploying code?
Sounds great to me, but may break backwards compatibility.
Also, this requires P&T to do some magic.
Option 4 - Roll your own?
Remove vlucas/phpdotenv, and use something else to encrypt the .env-file, or implement option 3 myself.
I've found these (all are on github, but I don't have the reputation to post enough complete links):

arrilot/dotenv-php  
johnathanmiller/secure-env-php
psecio/secure_dotenv  
josegonzalez/php-dotenv

Has anyone done something like this?
The environment is still "polluted", but it won't be in plain text when it's logged.
This is good enough for me, but "roll your own security/crypto" isn't the best idea... 
Option 5 - Something I do not know about yet?
Do you know something I don't? Please tell me!
Do you have any suggestions for further reading?
Thanks!
Update / clarification:
To give a bit more context, that I left out of the original post to try to keep it a bit more simple:

I'm writing a lot about "I" and "me", but I'm part of a bigger team of developers and ops-people from a provider.
We're using AWS CloudWatch, and all our logs are sent to this service.
We're following a set of company imposed security guidelines that requires us to evaluate the storage and "containment" of secrets.

I'm not really afraid of the people having access to the server.
If they have access, they most likley know about, and can access the secrets in .env directly anyway.
I'm more concerned about a rouge debug-statment in our code, a bug in Craft / Yii / a plugin or something like that, that exposes the secrects inadvertently. So option 1, turning off logging, doesn't really solve the problem either. 
More people have access to the AWS dashboards / CloudWatch, also the ops-people that are on rotation in the evening/nights, who may or may not have in depth knowledge about our application, or what data not to send around.
It would also be nice to have some sort of dashboard on a giant screen of our own in the office, showing the status and latest errors on the servers, without having to worry about random guests or colleagues from other departments seeing our precious credentials.
If this was my own personal server setup, with just me as a user/developer, I'd not think of this whole thing as a problem.

**Tags:** craft3, config, php, security, logging

> **Comments:**
> **username_1**: The first question I'd ask is... does it matter? Who can access the webserver in such a way that they can read the PHP logs? And if they have access to that, they can probably view any non-encrypted file that the webserver can view.  
> **username_0**: I've added an update with more info about the context @username_1  
> **username_2**: Did you ever figure out a way to fix this? This problem is not related to dotenv at all, I'm having the same problem when running it containerized in AWS Fargate since the only way to configure the setup is via ENV variables. Dumping them blindly without sanitation on every error seems utterly stupid. Maybe the best way would be to run the same sanitizer for stderr as for stdout, but I can't figure out how.  

---

## 2 answers

### Answer by username_3 on 2021-08-10. Score: 2

The [Secrets plugin](https://putyourlightson.com/plugins/secrets) allows you to store and manage secrets in an encrypted file.

There are some benefits to storing secrets this way, instead of the conventional approach of storing them as plaintext in the `.env` file.

1. Secrets are encrypted and cannot be revealed without the encryption key.
2. The encrypted file can be committed to your version control repository, meaning you avoid having to send secrets in plaintext to other developers. You also end up with a history of changes to the file.
3. If secrets change or are rotated then there is only one file that needs to be updated.


---

### Answer by username_4 on 2019-03-29. Score: 2

First, full disclosure here, the company I work for is a customer of Fort Rabbit. The secrets.json approach is something I think that strikes the balance between flexibility and security for really sensitive keys like API, OAuth, Amazon S3 and other sensitive secrets. dotenv is great, but having these in plain text is a risk, which I think you are mindful of.
The approach I take is to use the secrets.json concept. With Craft 3, you can make your secrets available as environment variables, but they won't be visible when dumping out phpinfo().
secrets.json
{
  "CUSTOM": {
      "SECRET_KEY_1": "secretValue",
      "SECRET_KEY_2": "anotherSecretValue"
      ....
  }
}

config/app.php
// Create secrets as env variables
if (getenv('APP_SECRETS')) {

    $secrets = json_decode(file_get_contents(getenv('APP_SECRETS')), true);

    if (isset($secrets['CUSTOM'])) {
        foreach($secrets['CUSTOM'] as $k => $v) {
            putenv("$k=$v");
        }
    }
}

.env
APP_SECRETS="/app/secrets.json"

You set the APP_SECRETS environment variable which will be the file path to your JSON file.
You can then reference the values in secrets.json as environment variables with getenv() and even the Craft $SECRET_KEY_1 syntax if a plugin supports Craft::parseEnv on a field/setting.
This is however a bit bespoke and tailored to Fort Rabbit's environment, but the concept can be applied generally.
There is also the whole other argument about dotenv in production due to the overhead and performance impact, but that's another story!
I'm not sure there is a perfect solution, but I hope that gives you one example or what I'd consider "good practice".

> **Comments:**
> **username_1**: I'm not sure I understand how this helps anything? It looks like just one more indirection, but ultimately the `$_ENV` variables are still set (otherwise `getenv()` wouldn't work).  
> **username_4**: @username_1 In this case, if you were to dump out the contents of `phpinfo()` or the `$_SERVER` `$_ENV` globals, they wouldn't show up, given the way they are initialised. In the Fort Rabbit environment secrets are encrypted in their dashboard and everywhere in the system, except during runtime. Not saying it's perfect, but it's better than plain .env.  
> **username_0**: Thanks for the suggestion James, but I must be missing something.  I just tried this, and all of the content in secrets.json was available when checking the ENV section of phpinfo(), together with the stuff som .env.  
> **username_0**: [Screenshot from phpinfo()](https://imgur.com/NoFHjcW)  
> **username_4**: @KnutErik Interesting. When I do phpinfo() on my local environment (Nanobox). I do not see them at all, so might be a false sense of security. I haven't tried having phpinfo() dumped on Fort Rabbit, but maybe they do show up.  I think the clarity would be Fort Rabbit's approach probably doesn't answer your question, but the storage of them is a bit more secure, so they aren't just exposed in plain text within their platform.  
> **username_0**: Interesting:   While everything shows up in phpinfo(), the keys and values from secrets.json do not show up in the phperrors.log or web.log.   In those logs, the only thing that shows up is the APP_SECRETS key and value.  Calling getenv('SECRET_KEY_1'), does work.  

---

