How to hide .env passwords in Laravel whoops output? - php

How can I hide my passwords and other sensitive environment variables on-screen in Laravel's whoops output?
Sometimes other people are looking at my development work. I don't want them to see these secrets if an exception is thrown, but I also don't want to have to keep toggling debug on and off, or spin up a dedicated site just for a quick preview.

As of Laravel 5.5.13, you can censor variables by listing them under the key debug_blacklist in config/app.php. When an exception is thrown, whoops will mask these values with asterisks * for each character.
For example, given this config/app.php
return [
// ...
'debug_blacklist' => [
'_ENV' => [
'APP_KEY',
'DB_PASSWORD',
'REDIS_PASSWORD',
'MAIL_PASSWORD',
'PUSHER_APP_KEY',
'PUSHER_APP_SECRET',
],
'_SERVER' => [
'APP_KEY',
'DB_PASSWORD',
'REDIS_PASSWORD',
'MAIL_PASSWORD',
'PUSHER_APP_KEY',
'PUSHER_APP_SECRET',
],
'_POST' => [
'password',
],
],
];
Results in this output:

First of all, love the solution by Jeff above.
2nd, if like me you wanna hide all the env variables while still use whoops, here is a solution:
'debug_blacklist' => [
'_COOKIE' => array_keys($_COOKIE),
'_SERVER' => array_keys($_SERVER),
'_ENV' => array_keys($_ENV),
],
Output:
EDIT:
Legend has it that since laravel 7x you would need debug_hide key instead

Thanks Jeff and Raheel for helping out, but I just found a little gotcha:
Even if I clear out all environment keys from _ENV, the same keys are STILL exposed through the _SERVER variables listed.
Adding the code below in config/app.php would hide all environment variables from the whoops page:
'debug_blacklist' => [
'_SERVER' => array_keys($_ENV),
'_ENV' => array_keys($_ENV),
],

I've made a package to solve this problem.
Just install it using
composer require glaivepro/hidevara
Most of the server and all the env variables will be removed. Any password-like fields in $_POST will have their values hidden.
You can also customize it in either blacklist or whitelist approach to show/obfuscate/remove fields however you like.

The solution by #jeff + #raheel is great!!! On a project recently we found we sometimes wanted to whitelist a property or two, so building on the above, you can whitelist specific properties you want to debug with something like:
'debug_blacklist' => [
'_COOKIE' => array_diff(array_keys($_COOKIE), array()),
'_SERVER' => array_diff(array_keys($_SERVER), array('APP_URL', 'QUERY_STRING')),
'_ENV' => array_diff(array_keys($_ENV), array()),
],
If you want to allow that list to be configured via .env, you can do something like:
'debug_blacklist' => [
'_COOKIE' => array_diff(
array_keys($_COOKIE),
explode(",", env('DEBUG_COOKIE_WHITELIST', ""))
),
'_SERVER' => array_diff(
array_keys($_SERVER),
explode(",", env('DEBUG_SERVER_WHITELIST', ""))
),
'_ENV' => array_diff(
array_keys($_ENV),
explode(",", env('DEBUG_ENV_WHITELIST', ""))
),
],
Then in your .env, do something like:
DEBUG_SERVER_WHITELIST="APP_URL,QUERY_STRING"
Cheers!

Usually for local development, we should set the APP_DEBUG environment variable to true. So that we can have better insights of the debugging error and warnings.
But in the production environment, this value should always be false. If the value is set to true in production, you risk exposing sensitive env passwords to your application’s end users.
As of Laravel 5.5.x also provides a solution for it.
You just need to add the debug_blacklist option in your config/app.php configuration file. After adding this option, Laravel will blacklist all the keys mentioned in debug_blacklist option with asterisk.
You can use it with two ways:
Method 1 – Blacklist selective ENV keys and passwords
return [
// ...
'debug_blacklist' => [
'_ENV' => [
'APP_KEY',
'DB_PASSWORD',
],
'_SERVER' => [
'APP_KEY',
'DB_PASSWORD',
],
'_POST' => [
'password',
],
],
];
Method 2 – Blacklist all the ENV keys and passwords
return [
// ...
'debug_blacklist' => [
'_COOKIE' => array_keys($_COOKIE),
'_SERVER' => array_keys($_SERVER),
'_ENV' => array_keys($_ENV),
],
]
Reference Taken From : https://techjeni.com/how-to-secure-and-hide-env-passwords-from-laravel-debug-output/

Laravel 5.6 not works for my.
but this works:
$envKeys = [];
$serverKeys = [];
$cookieKeys = [];
foreach ( $_ENV as $key => $value ) { if(is_string($value)) $envKeys[] = $key; }
foreach ( $_SERVER as $key => $value ) { if(is_string($value)) $serverKeys[] = $key; }
foreach ( $_COOKIE as $key => $value ) { if(is_string($value)) $cookieKeys[] = $key; }
return [
// ...
'debug_blacklist' => [
'_COOKIE' => $cookieKeys,
'_SERVER' => $serverKeys,
'_ENV' => $envKeys,
],
];
I would be grateful for a better solution.

Just Change
APP_DEBUG=true
To:
APP_DEBUG=false
In the .env file.

For Laravel 5.6-5.8:
'debug_blacklist' => [
'_COOKIE' => array_keys(array_filter($_COOKIE, function($value) {return is_string($value);})),
'_SERVER' => array_keys(array_filter($_SERVER, function($value) {return is_string($value);})),
'_ENV' => array_keys(array_filter($_ENV, function($value) {return is_string($value);})),
],

I am also facing this issue in production environment, Laravel 5.7
https://laravel.com/docs/5.7/configuration
Here 3 ways we can reslove this issue.
config/app.php file add below line of code
TIPS #1: Block List for all variable
'debug_blacklist' => [
'_COOKIE' => array_keys($_COOKIE),
'_SERVER' => array_keys($_SERVER),
'_ENV' => array_keys($_ENV),
],
TIPS #2: Block List for specific varaibles (Best Practice)
return [
// ...
'_ENV' => [
'APP_KEY',
'DB_PASSWORD',
'REDIS_PASSWORD',
'MAIL_PASSWORD',
'PUSHER_APP_KEY',
'PUSHER_APP_SECRET',
'AWS_APP_SECRET',
'S3_BUCKET_SECRET',
'SOCKET_APP_SECRET',
'TWILIO_APP_SECRET',
],
'_SERVER' => [
'APP_KEY',
'DB_PASSWORD',
],
'_POST' => [
'password',
],
]
TIPS #3: Debug variable
APP_DEBUG=true to APP_DEBUG=false
NOTE:
Production enviroment keep always Debug False

There's a lot of great answers here (credits to #Jeff and #Raheel and #Benjamin and everyone else), but I would like to have a bit more flexible and universal solution. I extended this snippet intended for the config/app.php file even further:
$debug_blacklist=array();
if(env("DEBUG_VAR_LISTING")!==null)
foreach(explode(",", env("DEBUG_VAR_LISTING", "")) as $i){
global ${"_{$i}"};
if(env("DEBUG_VAR_BLACKLIST_{$i}")!==null)
$debug_blacklist["_{$i}"]=explode(",", env("DEBUG_VAR_BLACKLIST_{$i}", ""));
elseif(env("DEBUG_VAR_WHITELIST_{$i}")!==null)
$debug_blacklist["_{$i}"]=array_diff(
array_keys(${"_{$i}"}),
explode(",", env("DEBUG_VAR_WHITELIST_{$i}", ""))
);
}
return [
'debug_blacklist' => $debug_blacklist,
];
Then you can blacklist and whitelist directly in .env and only if and what you need.
So if you don't really need anything from $_ENV you can block all variables and for example just passwords in $_POST, but show APP_URL and QUERY_STRING from $_SERVER:
DEBUG_VAR_LISTING="SERVER,ENV,POST,COOKIE"
DEBUG_VAR_WHITELIST_SERVER="APP_URL,QUERY_STRING"
DEBUG_VAR_WHITELIST_ENV=""
DEBUG_VAR_BLACKLIST_POST="password"

I struggled with this too for a bit on a dev machine. my solution was to edit vendor/filp/whoops/src/Whoops/Handler/PrettyPageHandler.php and add in:
public function sanitizePrivate($data, $badwords){
foreach ($data as $key=>$value) {
foreach ($badwords as $keyword) {
// dd($key);
if (strpos(strtolower($key), $keyword) !== FALSE) {
$data[$key] = "***************";
}
}
}
return $data;
}
This converts all the incoming data to lowercase and then searches for partial matches so you don't have to specify every variation of password variable names. Then in the handle() function, define terms you want to exclude.
$badwords = array("password", "pwd", "secret", "key", "token", "salt", "mail");
$_SERVER=$this->sanitizePrivate($_SERVER, $badwords);
$_ENV=$this->sanitizePrivate($_ENV, $badwords);

Related

Laravel log file specific to a package

I'm writing a couple of laravel packages and I'm wondering if it is possible to have the package write to a specific log file but only for messages related to the package?
I tried making a logging.php file in the packages/myorg/mypackage/config (below) but it doesn't seem to do anything.
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
return [
'default' => env('LOG_CHANNEL', 'stack'),
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['single'],
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/mypackage.log'),
'level' => env('LOG_LEVEL', 'debug'),
]
]
];
I am using "jeroen-g/laravel-packager" to set up the packages. It appears to manually load the mypackage.config in the ServiceProvider bootForConsole
protected function bootForConsole(): void
{
// Publishing the configuration file.
$this->publishes([
mypackage.'/../config/mypackage.php' => config_path('mypackage.php'),
], 'mypackage.config');
}
I'm not sure how to add custom logging to that though. I'm still learning Laravel and I'm not quite sure what or how the main applications config/logging.php is read so I'm not quite sure how to inject a custom version for an add-on package.
EDIT:
I found a post that suggested using the following in the ServiceManager boot() method:
$this->app->make('config')->set('logging.channels.mychannel', [
/* settings */
]);
I used the package config to set a 'logging' => [ 'channels' => [ 'mychannel' => [ /* settings */ ] ] ] and could then do the same thing as above with:
$this->app->make('config')->set('logging.channels.mychannel', config('mypackage.logging.channels.mychannel');
But that still required something in the code. The next best thing I have found thus far is to change my config/logging.php to config/logging.channels.php and include something like:
return [
'mychannel' => [
'driver' => 'single',
'path' => storage_path('logs/mypackage.log'),
'level' => env('LOG_LEVEL', 'debug'),
]
];
Then in the service provider register() method add:
$this->mergeConfigFrom(__DIR__ . '/../config/logging.channels.php', 'logging.channels');
I tried doing it from the original 'logging.php' with channels array nested in a 'logging' key, but array_merge doesn't appear to merge the nested elements so my channel never showed up in logging.channels.
I'm not sure if this is ideal, however. I'd still like to know if there is a 'better' or best practices way of adding custom package logging parameters and whether there is a need to publish it in any way (and how).

How to remove the environment variables from Laravel Debug?

I want to remove the environment variables, database details and the information showing on Laravel Debug.
Here is the screenshot:
Go to config/app and add the following
/**
* Debug Blacklist
*/
'debug_blacklist' => [
'_COOKIE' => array_keys($_COOKIE),
'_SERVER' => array_keys($_SERVER),
'_ENV' => array_keys($_ENV),
],
it will replace all environment variables value with an asterisks (*******)
This is from my config/app file:
'debug_blacklist' => [
'_ENV' => [
'APP_KEY',
'DB_DATABASE',
'DB_PASSWORD',
'DB_USERNAME',
'REDIS_PASSWORD',
'MAIL_PASSWORD',
'PUSHER_APP_KEY',
'PUSHER_APP_SECRET',
],
'_SERVER' => [
'APP_KEY',
'DB_DATABASE',
'DB_PASSWORD',
'DB_USERNAME',
'REDIS_PASSWORD',
'MAIL_PASSWORD',
'PUSHER_APP_KEY',
'PUSHER_APP_SECRET',
],
'_POST' => [
'password',
],
],
Add this array to our config/app file, the the system will replace real values with ***.
This way is useful if you want control on specific key items.
You can make your own error pages. (official documentation)
Make a blade file for 500 errors. (resources/views/errors/500.blade.php)
And print error messages or trace log
<h2>{{ $exception->getMessage() }}</h2>
Change from APP_DEBUG=true to APP_DEBUG=false in the .env file. Then you can rely on the laravel logs for your errors. (yourLarvelApp/storage/logs)
Go to your project file: open .env file in notepad and find out APP_DEBUGAPP_DEBUG=true you just rename false; and save. your problem is solve.

The "access_key" option must be provided to use fixer.io

For the currency conversion i am using "florianv/laravel-swap": "^1.1" library. Florianv/Laravel-swap.
As Fixer.io has changed its implementation, it is necessary to pass the access_key with the request, and because of that i am getting this error: "InvalidArgumentException: The "access_key" option must be provided to use fixer.io in /var/www/project/project-files/vendor/florianv/exchanger/src/Service/Fixer.php:51".
I registered and got the access_key.
I updated the library using composer and now i can see three constants in the vendor/florianv/exchanger/src/Service/Fixer.php.
const ACCESS_KEY_OPTION = 'access_key';
const LATEST_URL = 'http://data.fixer.io/api/latest?base=%s&access_key=%s';
const HISTORICAL_URL = 'http://data.fixer.io/api/%s?base=%s&access_key=%s';
To pass the access key i tried this:
I have a swap.php in config folder which looks something like this:
return [
'options' => [
'cache_ttl' => 86400, // 24 hours.
'cache_key_prefix' => 'currency_rate'
],
'services' => [
'fixer' => true,
],
'currency_layer' => [
'access_key' => 'asdfas7832mw3nsdfa776as8dfa', // Your app id
'enterprise' => true, // True if your AppId is an enterprise one
],
'cache' => env('CACHE_DRIVER', 'file'),
'http_client' => null,
'request_factory' => null,
'cache_item_pool' => null,
];
This had one more option which was commented, i enabled and passed the access_key in it but it doesn't work.
I also added it in services block below 'fixer => true'.
'currency_layer' => [
'access_key' => 'asdfas7832mw3nsdfa776as8dfa'
]
Also in options block:
'options' => [
'cache_ttl' => 86400, // 24 hours.
'cache_key_prefix' => 'currency_rate',
'access_key'=>'7ca208e9136c5e140d6a14427bf9ed21'
],
I tried with adding access_key in config/services.php file but it also didn't work.
'fixer' => [
'access_key' => 'asdfas7832mw3nsdfa776as8dfa'
],
Even i tried, adding to env file and calling from there, but no success. How do i pass the access_key, can anyone help me on this, what should be the approach.
vendor/florianv/exchanger/src/Service/Fixer.php -> don't touch the constant (that was my own error).
Pass the options-array by creating the Builder:
$options = ['access_key' => 'YourGeneratedAPIKeyAtCurrencyLayer'];
$this->exchangeSwap = (new Builder($options))
->add('fixer', $options )
->build();
I hope I could help ;-)

Setting/retrieving mode in Slim Framework V3

I am new to Slim Framework (PHP). I was going through tutorials and testing some code and tried the following:
$app=new App([
'mode'=>file_get_contents(INC_ROOT . '/mode.php')
]);
echo $app->config('mode');
The output was blank.
It seems the function config() works with V2, however I am using V3. What am I doing wrong?
Slim 3 no longer has a config method. Instead, you must add configuration settings through the dependency injection container:
$app = new \Slim\App([
'settings' => [
'mode' => true
]
]);
$container = $app->getContainer();
echo $container->get('settings')['mode'];
A few things worth noting:
Slim 3 no longer handles managing different versions of configuration settings through a mode setting. So, you can set a value for a setting variable called mode as I've demonstrated here, but it won't actually do anything (i.e., Slim won't use it to determine your environment).
As an alternative, you can check out userfrosting/Config, a library we've been working on that can search multiple directories and different environment configuration files, merging together their contents:
/path/to/config/default.php
return [
'contacts' => [
'housekeeper' => [
'name' => 'Alex',
'email' => 'alex#cleansthetoilet.com'
]
]
];
/path/to/config/production.php
return [
'contacts' => [
'housekeeper' => [
'email' => 'alex#istheboss.com'
]
],
'database' => [
'password' => 'sup3rC-cr3t'
]
];
index.php
$app = new \Slim\App();
$container = $app->getContainer();
// Site config object (separate from Slim settings)
$container['config'] = function ($c) {
// Create and inject new config item
$config = new \UserFrosting\Config\Config();
$config->setPaths([
'/path/to/config'
]);
$config->loadConfigurationFiles('production');
return $config;
};
This will recursively merge in the settings from development.php with those in default.php, updating settings with the same name and scope as necessary:
Running print_r($container['config']); returns:
[
'contacts' => [
'housekeeper' => [
'name' => 'Alex',
'email' => 'alex#istheboss.com'
]
],
'database' => [
'password' => 'sup3rC-cr3t'
]
]
Notice that the value of contacts.housekeeper.email has been updated to 'alex#istheboss.com', and that the database config info has been merged in. Incidentally, you can also access config settings using the more convenient "dot syntax":
$config = $container->get('config');
echo $config['contacts.housekeeper.email'];
// Easier to type instead of $config['contacts']['housekeeper']['email'];
We recommend injecting this as a separate config service in Slim, rather than using their settings array.
You can combine this with phpdotenv to load settings from your system environment, or any .env files you create:
/path/to/config/production.php
return [
'database' => [
'password' => getenv('DB_PASSWORD')
]
];

Write php array code to file without evaluating expressions

I'm trying to write some code that will generate a php file with an array, like this.
However, in order to create a correct configuration file I need to be able to leave expressions 'as is' within the array. This is because the file will be used by many users and the expressions evaluate based on environmental variables, etc. the user has set up.
So, say we have this key/value in the array I eventually want to output to the file:
[
...
'connection' => $isFork ? $sourceArray['connection'] : config('database.default'),
...
]
When this array is eventually written out to a php file (right now using var_export and file_put_contents) I will see
'connection' => config('database.default')
become
'connection' => 'default_connection',
because the expression is evaluated. What I need is a way to prevent expressions as values in the array from being evaluated but also ensuring
'connection' => $isFork ? $sourceArray['connection']
does evaluate to
'connection' => 'my_connection'
Is there any way to do this?
EDIT: I basically want to do this but in reverse and with expressions.
If I understand you correctly, your solution is to have a string representation of your array so the statements are not evaluated. I would serialize that array and put that string into the file. Tell your peeps to unserialize it right after they receive it. Better yet, json_encode your array which is going to give you a json string. You can put that in via put_file_contents and tell your peeps to json_decode the contents. They can use it as such json_decode($content, TRUE) which will give them back the associative array.
Update
So you want to write straight up PHP. I see that you have connection stuff in your array so I am thinking it is safe to think it is some sort of a configuration file that includes connection settings etc.
// filename should have the .ini at the end
function writeConfig( $filename, $yourArray ) {
$fh = fopen($filename, "w");
// making sure its available
if (!is_resource($fh)) {
return false;
}
// start dumping you array to the file
foreach ($yourArray as $key => $value) {
fwrite($fh, sprintf("%s = %s\n", $key, $value));
}
fclose($fh); // close file
return true;
}
when you want to read it
function readConfigFile( $fileThatMadeAbove ) {
return parse_ini_file($fileThatYouMadeAbove, false, INI_SCANNER_NORMAL);
}
Since it is config info, it may be better to use the ini in php.
If you want to try plain simple solution
$fp=fopen('filename.php','w');
fwrite($fp, "$yourArray");
fclose($fp);
I honestly do not know if you can do "$yourArray" or not and I do not have a place to test it. You most likely need to do a print_r($yourArray) because it is a string that you write to a file which is why I made my recommendation above.
I am out of ideas. Good luck (:
This isn't possible using var_export. The best way I can see to do this is to create a string of the output and use file_put_contents to output this to a file.
This could be achieved by replicating the array structure, e.g.
$arr_str = "[\n";
. "\t'simple_annotations' => false,\n"
. "];";
Or by creating a helper function to use instead of var_export. Something like this:
function var_str($var, $level = 0){
if(is_array($var)) return arr_str($var, $level+1);
elseif(is_string($var)) return '\''.$var.'\'';
elseif(is_numeric($var)) return $var;
elseif(is_null($var)) return 'null';
elseif(is_bool($var)) return ($var ? 'true' : 'false');
}
function arr_str($arr, $level){
$str = "[\n";
foreach($arr as $k => $e){
$str .= str_repeat("\t", $level);
$str .= "'".$k."' => ".var_str($e, $level).",\n";
}
return $str.str_repeat("\t", $level-1).']';
}
print var_str($my_array);
After searching for a few hours I came to the conclusion the only way to take full control over what I wanted to do was to use a templating engine.
The project this is for uses Laravel so I used Blade but any engine would work (I initially tried this with Twig).
I wrote out each section of my config as if it was a regular php array and then used Blade bracketing to encompass the logic needed to find the right value for each key. If the value was not an expression I evaluated the code, and if it was I wrote the expression into a string.
I ended up with this:
//example.blade.php
[
'meta' => '{{{ $isFork ? $data['metadata']['driver'] : 'annotations' }}}',
'connection' => {{{ $isFork ? '\''.$data['connection'].'\'' : 'config("database.default")' }}},
'paths' => {{ var_export(ArrayUtil::get($data['metadata']['paths'], $data['metadata']), true) }},
'repository' => '{{{ ArrayUtil::get($data['repository'], EntityRepository::class) }}}',
'proxies' => [
'namespace' => {{{ isset($data['proxy']['namespace']) ? '\'' . $data['proxy']['namespace'] .'\'' : 'false' }}},
'path' => '{{{ ArrayUtil::get($data['proxy']['directory'], storage_path('proxies')) }}}',
'auto_generate' => {{{ ArrayUtil::get($data['proxy']['auto_generate'], env('DOCTRINE_PROXY_AUTOGENERATE', 'false')) }}}
],
'events' => [
'listeners' => [],
'subscribers' => []
],
'filters' => []
]
and the output:
[
'meta' => 'yaml',
'connection' => config('database.default'),
'paths' => array(
0 => '/proj/app/Models/mappings',
),
'repository' => 'Doctrine\ORM\EntityRepository',
'proxies' => [
'namespace' => false,
'path' => '/proj/storage/proxies',
'auto_generate' => false
],
'events' => [
'listeners' => [],
'subscribers' => []
],
'filters' => []
]
You can see that where I potentially needed to expand an array I used var_export. Other than it was pretty straight forward.

Categories