Do not expose sensitive information#978
Conversation
1e7b0b4 to
2b32da7
Compare
| * @param bool $debug The kernel debug parameter | ||
| */ | ||
| public function __construct(SerializerInterface $serializer, array $errorFormats, array $exceptionToStatus = []) | ||
| public function __construct(SerializerInterface $serializer, array $errorFormats, array $exceptionToStatus, bool $debug) |
There was a problem hiding this comment.
The $exceptionToStatus default value should be kept and debug default to false to preserve backward compatibility.
| $headers['X-Content-Type-Options'] = 'nosniff'; | ||
| $headers['X-Frame-Options'] = 'deny'; | ||
|
|
||
| if ($exception->getStatusCode() == Response::HTTP_INTERNAL_SERVER_ERROR && !$this->debug) { |
There was a problem hiding this comment.
Please use === and Yoda style. I would also invert the order because !$this->debug is cheaper.
if (!$this->debug && Response::HTTP_INTERNAL_SERVER_ERROR === $exception->getStatusCode() ) {
There was a problem hiding this comment.
Would it make sense to catch all 5xx here?
There was a problem hiding this comment.
Good catch! Indeed we should handle all errors.
There was a problem hiding this comment.
@jordscream what do you think?
| $serializer->serialize($flattenException, 'jsonproblem')->willReturn(); | ||
|
|
||
| $exceptionAction = new ExceptionAction($serializer->reveal(), ['jsonproblem' => ['application/problem+json'], 'jsonld' => ['application/ld+json']]); | ||
| $exceptionAction = new ExceptionAction($serializer->reveal(), ['jsonproblem' => ['application/problem+json'], 'jsonld' => ['application/ld+json']], [], true); |
There was a problem hiding this comment.
I would let it is it was previously (to test that there is no BC breaks and that the previous signature is still accepted).
|
Thanks for this PR, it's greatly appreciated. I left some minor comment but 👍 to change this behavior when comments will be fixed. |
2b32da7 to
af28a6c
Compare
|
@dunglas : reviews applied 👍 . You can check |
|
Is this the correct way to fix it? I hope someone who's familiar with Symfony exception handling can comment... |
|
@teohhanhui , check Symfony\Bundle\TwigBundle\Controller\ExceptionController.php |
|
Ok, sounds good then. |
af28a6c to
7eba081
Compare
|
@teohhanhui , as suggest @dunglas @dkarlovi , I modify the code to catch all 5XX exceptions. |
|
@teohhanhui @soyuka , any news ? |
|
@jordscream looks fine to me, just waiting for more approval (ping @teohhanhui @meyerbaptiste ?). Anyway I'd be able to merge this but I won't publish a new release without @dunglas. Are you in a hurry to get this patch? |
|
@soyuka we need it to push in production. We have a temporary solution without that. ;-) |
|
@jordscream even if I merge this on |
|
@soyuka , I did not know about min-stability ! Let's do that |
|
No, I meant 2.0 sorry too many things at once. Leave this one like this it's fine! Still, if I merge it won't be released (new tag) and so |
|
|
||
| $response->setContent($this->serializer->serialize($exception, $format['key'])); | ||
|
|
||
| return $response; |
There was a problem hiding this comment.
return $response->setContent(...);?
There was a problem hiding this comment.
@meyerbaptiste , Yes in fact, I need the Response object to use the method isServerError() in the Error condition just above.
It was a point I have to discuss with you. No other way to deal it
There was a problem hiding this comment.
Yes but still you can return $response->setContent() (returns Response)
There was a problem hiding this comment.
@meyerbaptiste good catch, I just pushed the modification
7eba081 to
a20a69f
Compare
|
Nice one! Merging this tomorrow if noone has comments until then :). |
|
Before merging this, what about #929 (comment)? cc @dkarlovi |
|
@meyerbaptiste it's effectively a security issue. Something to do in particular? |
| $response = new Response(null, $exception->getStatusCode(), $headers); | ||
|
|
||
| if (!$this->debug && $response->isServerError()) { | ||
| $exception->setMessage(Response::$statusTexts[$response->getStatusCode()]); |
There was a problem hiding this comment.
We should not modify the exception.
There was a problem hiding this comment.
Which means we need to look for a different way of doing this.
There was a problem hiding this comment.
@teohhanhui , you mean create a new Exception object ?
There was a problem hiding this comment.
@teohhanhui, or à simply response with message and 500 status code?
There was a problem hiding this comment.
I'd say that we could create a new HTTPException if we shouldn't change the exception message. I didn't though about this but indeed it'd be better if (in production) you'd like to log the origin exception.
There was a problem hiding this comment.
@meyerbaptiste @soyuka , Woooh, it seems better ^^, waiting for @teohhanhui approval before modification if needed
There was a problem hiding this comment.
Yes, let's avoid modifying the FlattenException. We should treat it as immutable. The current implementation can be improved in that regard.
There was a problem hiding this comment.
@meyerbaptiste @teohhanhui @soyuka , what about the comment of @meyerbaptiste "And avoid the FlattenException::setStatusCode() in the same time" (means move it in ErrorNormalizer) ?
The Response with status code is dealed by the ExceptionAction, not by ErrorNormalizer....
I feel confused, cause in the same time, We alter the exception status code in ExceptionAction and the exception message in ErrorNormalizer...
There was a problem hiding this comment.
@jordscream Have a look at https://github.com/symfony/symfony/blob/4d48b58d197cfa76046bb23ce9c2fe7bec8fbbb2/src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php#L41-L73
I see no compelling reason to modify the exception in any way whatsoever.
For example,
core/src/Action/ExceptionAction.php
Lines 58 to 84 in 8053c55
/**
* Converts a an exception to a JSON response.
*
* @param FlattenException $exception
* @param Request $request
*
* @return Response
*/
public function __invoke(FlattenException $exception, Request $request): Response
{
$exceptionClass = $exception->getClass();
$statusCode = $exception->getStatusCode();
foreach ($this->exceptionToStatus as $class => $status) {
if (is_a($exceptionClass, $class, true)) {
$statusCode = $status;
break;
}
}
$headers = $exception->getHeaders();
$format = ErrorFormatGuesser::guessErrorFormat($request, $this->errorFormats);
$headers['Content-Type'] = sprintf('%s; charset=utf-8', $format['value'][0]);
$headers['X-Content-Type-Options'] = 'nosniff';
$headers['X-Frame-Options'] = 'deny';
return new Response($this->serializer->serialize($exception, $format['key']), $statusCode, $headers);
}with no change in behaviour and no BC. (You may include this change as part of this PR since it's minor.)
The Response with status code is dealed by the ExceptionAction, not by ErrorNormalizer....
The ErrorNormalizer is responsible for the body / content of the Response. (It basically replaces the content set when creating the Response.) How is it called? https://github.com/api-platform/core/blob/8053c55311b713153179f412ea294362a9997bea/src/EventListener/SerializeListener.php
So in our case, if we want to change the message, we should change it in the respective ErrorNormalizers.
There was a problem hiding this comment.
👍 to do it directly in the normalizer. It's cleaner.
| * @param bool $debug The kernel debug parameter | ||
| */ | ||
| public function __construct(SerializerInterface $serializer, array $errorFormats, array $exceptionToStatus = []) | ||
| public function __construct(SerializerInterface $serializer, array $errorFormats, array $exceptionToStatus = [], bool $debug = false) |
There was a problem hiding this comment.
We should move $debug before $exceptionToStatus and remove its default value.
There was a problem hiding this comment.
@teohhanhui , what about BC breaks if we do this ? I do this to prevent the bc break
thanks for your feeback
There was a problem hiding this comment.
Because this is a security issue and needs to be fixed in 2.0, I think we should maintain the debug flag here.
There was a problem hiding this comment.
There is no BC break because this class is final. 😄
There was a problem hiding this comment.
@teohhanhui there is. People can (and do) instantiate this class directly.
a20a69f to
598e49e
Compare
|
@dunglas @teohhanhui , I made changes, waiting for your reviews |
That's not true. Please check Travis. There is 1 error in PHPUnit test. All Behat tests passed. |
| $message = $object->getMessage(); | ||
| if ($this->debug) { | ||
| $trace = $object->getTrace(); | ||
| } else { |
There was a problem hiding this comment.
I suggest:
if ($this->debug) {
$trace = $object->getTrace();
} elseif (Response::HTTP_INTERNAL_SERVER_ERROR <= ($code = $object->getStatusCode())) {
$message = Response::$statusTexts[$code];
}There was a problem hiding this comment.
We need to check $object instanceof FlattenException first. And the use of <= here is Yoda condition gone wrong (this "over-Yoda" is not part of Symfony CS).
There was a problem hiding this comment.
We lose the mapping between exceptions and their HTTP status code (#797). What about passing the correct HTTP status code inside the serializer context?
There was a problem hiding this comment.
If we're okay with modifying the status code in the FlattenException, like we currently do, then this is already working just fine.
There was a problem hiding this comment.
But perhaps we should determine the status code here. That may be more proper.
|
@meyerbaptiste @dunglas done, let me know ^^. (I learn a lot of things with you, guys, do not hesitate to feeback me ^^) |
| if ($this->debug) { | ||
| $trace = $object->getTrace(); | ||
| } elseif ($object instanceof FlattenException) { | ||
| $statusCode = (isset($context['statusCode'])) ? $context['statusCode'] : $object->getStatusCode(); |
There was a problem hiding this comment.
$statusCode = $context['statusCode'] ?? $object->getStatusCode();There was a problem hiding this comment.
@meyerbaptiste PHP 7 , not yet the reflex, fixed
| } elseif ($object instanceof FlattenException) { | ||
| $statusCode = (isset($context['statusCode'])) ? $context['statusCode'] : $object->getStatusCode(); | ||
| if ($statusCode >= 500 && $statusCode < 600) { | ||
| $message = Response::$statusTexts[$context['statusCode']]; |
There was a problem hiding this comment.
It should be $statusCode instead of $context['statusCode'].
There was a problem hiding this comment.
@meyerbaptiste , I push too fast, it's fixed
|
I believe we do not have to pass the HTTP status code in the serialization context, but instead moving the logic into the |
ec59f31 to
effb877
Compare
|
rebase branch with 2.0 |
|
May you also squash the commits? Use |
|
@teohhanhui , for me is totally the opposite. ErrorNormalizer->normalize is only here to normalize, not to modify... and only responsible to send a serialize data. (IMO) So always IMO, I would let the satusCode and message treatment in ExceptionAction without alter FlattenException Again, You have more experience. Tell me the right way to take. :-) |
effb877 to
ce97d9a
Compare
|
@soyuka done ^^ |
|
I agree that the normalizer should not change the state of the underlying exception. |
|
@teohhanhui @dunglas @meyerbaptiste , What about NativeNormalizer Symfony and CustomNormalizer used instead of ErrorNormalizer of API Platform? Everybody has to develop this piece of code to make it work :-D I think , we took the wrong way. Everything has to be dealed in ExceptionAction |
|
An exception being normalized into a different error message is not modifying anything in any way. 😄 |
|
@dunglas @teohhanhui @meyerbaptiste @soyuka Need to continue this PR with your advice: 1/ Let the code like this (statusCode in Action and Message in ErrorNormalizer) => implies that anybody who uses his own normalizer or a Symfony normalizer cannot benefit of the error hidding in production. 2/ Move all the logic in Action. (statusCode + Message) 3/ Move all the logic in ErrorNormalizer (satusCode + Message): same remark as 1/ 4/ Other choices ? Your choice ? :-) |
|
I'm for merging the code as is. Can you also add a unit test checking that the message is hidden when the status code is 500 (and not hidden when it's another status)? |
ce97d9a to
0635c0f
Compare
|
@dunglas , done tell me if you want more. |
|
Thank you very much for fixing this @jordscream! |
|
@dunglas , my pleasure ^^ |
|
@jordscream great work following this through, thank you! |
|
Houston, we have a problem. :) /cc @dunglas @jordscream This works as expected with |
|
Also, if I trigger a connection-type error (I turn off the DB container), the same full error is thrown with either content type: |
|
Yes, we forgot the |
|
@dunglas @meyerbaptiste @dkarlovi oups! |
…ive_information Do not expose sensitive information
This PR fixes the display of Sensitive information (SQL request displayed, Database information) when the project is in production.
No BC according to me