diff --git a/config/app.php b/config/app.php index 1ced8bef0a14..336cf9f709f4 100644 --- a/config/app.php +++ b/config/app.php @@ -44,6 +44,19 @@ 'debug' => (bool) env('APP_DEBUG', false), + /* + |-------------------------------------------------------------------------- + | Hide Validation Errors + |-------------------------------------------------------------------------- + | + | When enabled, detailed validation error messages and field names will + | not be included in JSON error responses. This prevents attackers from + | discovering your API's expected fields by sending empty requests. + | + */ + + 'hide_validation_errors' => (bool) env('HIDE_VALIDATION_ERRORS', false), + /* |-------------------------------------------------------------------------- | Application URL diff --git a/src/Illuminate/Foundation/Exceptions/Handler.php b/src/Illuminate/Foundation/Exceptions/Handler.php index 57dfc30f480e..86f236e50e31 100644 --- a/src/Illuminate/Foundation/Exceptions/Handler.php +++ b/src/Illuminate/Foundation/Exceptions/Handler.php @@ -793,10 +793,12 @@ protected function invalid($request, ValidationException $exception) */ protected function invalidJson($request, ValidationException $exception) { - return response()->json([ - 'message' => $exception->getMessage(), - 'errors' => $exception->errors(), - ], $exception->status); + return response()->json(array_filter([ + 'message' => config('app.hide_validation_errors') + ? $exception->validator->getTranslator()->get('The given data was invalid.') + : $exception->getMessage(), + 'errors' => config('app.hide_validation_errors') ? null : $exception->errors(), + ]), $exception->status); } /** diff --git a/tests/Foundation/Configuration/ExceptionsTest.php b/tests/Foundation/Configuration/ExceptionsTest.php index fc868e6c5f4c..42b684c1f61d 100644 --- a/tests/Foundation/Configuration/ExceptionsTest.php +++ b/tests/Foundation/Configuration/ExceptionsTest.php @@ -4,10 +4,19 @@ use Exception; use Illuminate\Container\Container; +use Illuminate\Contracts\Routing\ResponseFactory as ResponseFactoryContract; +use Illuminate\Contracts\View\Factory as ViewFactory; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Exceptions\Handler; use Illuminate\Http\Request; +use Illuminate\Routing\Redirector; +use Illuminate\Routing\ResponseFactory; +use Illuminate\Translation\ArrayLoader; +use Illuminate\Translation\Translator; +use Illuminate\Validation\ValidationException; +use Illuminate\Validation\Validator; +use Mockery as m; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpKernel\Exception\HttpException; @@ -49,4 +58,38 @@ public function testShouldRenderJsonWhen() $shouldReturnJson = (fn () => $this->shouldReturnJson(new Request, new Exception()))->call($exceptions->handler); $this->assertFalse($shouldReturnJson); } + + public function testHideValidationErrors() + { + $container = Container::setInstance(new Container); + $config = new \Illuminate\Config\Repository; + $container->instance('config', $config); + $container->instance(ViewFactory::class, $viewFactory = m::mock(ViewFactory::class)); + $container->instance(ResponseFactoryContract::class, new ResponseFactory( + $viewFactory, + m::mock(Redirector::class) + )); + + $handler = new Handler($container); + + $translator = new Translator(new ArrayLoader, 'en'); + $validator = new Validator($translator, ['name' => ''], ['name' => 'required']); + $validator->fails(); + $validationException = new ValidationException($validator); + + // By default, validation errors are included. + $response = (fn () => $this->invalidJson(new Request, $validationException))->call($handler); + $data = json_decode($response->getContent(), true); + $this->assertArrayHasKey('errors', $data); + $this->assertArrayHasKey('name', $data['errors']); + + // When HIDE_VALIDATION_ERRORS is enabled, errors should be omitted. + $config->set('app.hide_validation_errors', true); + $response = (fn () => $this->invalidJson(new Request, $validationException))->call($handler); + $data = json_decode($response->getContent(), true); + $this->assertArrayNotHasKey('errors', $data); + $this->assertEquals('The given data was invalid.', $data['message']); + + Container::setInstance(null); + } }