Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

apollo-engine-reporting: tweaks to maskErrorDetails and rewriteError #2932

Merged
merged 3 commits into from
Jun 27, 2019

Conversation

glasser
Copy link
Member

@glasser glasser commented Jun 26, 2019

This reimplements engine.maskErrorDetails in terms of engine.rewriteError, simplifying the code path around it. It also allows rewriteError implementors slightly more control over the error they produce.

Two behavior changes:

  • If the error returned from the engine.rewriteError hook has an extensions property, that property will be used instead of the original error's extensions. We now document that changes to most other GraphQLError fields by engine.rewriteError are ignored.
  • The engine.maskErrorDetails option, deprecated by engine.rewriteError in v2.5.0, now behaves a bit more like the new option: while all error messages will be redacted, they will still show up on the appropriate nodes in a trace.

@glasser glasser requested a review from abernix June 26, 2019 23:03
@glasser
Copy link
Member Author

glasser commented Jun 26, 2019

While I think I'd like to see this merged (and am planning to build my federated metrics change on top of this, because it refactors where this code is called from) I am also raising this PR basically to try to help me understand some of the choices made in #2618

I don't understand this code in rewriteError:

      return new GraphQLError(
        rewrittenError.message,
        err.nodes,
        err.source,
        err.positions,
        err.path,
        err.originalError,
        err.extensions,
      );

This raises two questions for me:

(a) Why exactly do we have an API that's allowed to return an arbitrary error, and yet if it's a GraphQLError (the expected case?) it's not allowed to change anything but the message? I'm assuming part of the point here is that it should be OK for a rewriteError to return new GraphQLError('some new message') without having to remember to copy over the path and ensure it ends up in the right place, which seems reasonable (although all the examples encourage mutation instead), and most of the fields here don't seem too sensitive. At first glance originalError seems sensitive but it's a non-enumerable property so it's not actually reported.

But... extensions definitely seems like a property that could have arbitrary sensitive data on it! Not letting rewriteErrors sanitize extensions seems pretty harsh. While the docs and examples for rewriteError are great, I don't see anything that explains that changes to non-message properties will be ignored! Was it intentional that rewriteErrors shouldn't be able to rewrite extensions?

(b) The new feature is documented as being a replacement for maskErrorDetails, but you can't actually implement the behavior of maskErrorDetails in terms of rewriteError. Notably, maskErrorDetails errors don't have a path, location, extensions, etc, whereas rewriteError return values do. Was this an intentional change (we think those fields aren't sensitive)? It seems odd to make a new feature that doesn't provide the value of the old feature — unless we think the behavior of the old feature isn't valuable, in which case changing the old feature's behavior like I do here seems reasonable?

@glasser
Copy link
Member Author

glasser commented Jun 26, 2019

I guess there's a tension between "want people to be be able to keep the context without having to write it" (especially for path!) and "want people to be able to delete stuff".

What if at the very least, extensions was loaded from rewrittenError.extensions || err.extensions, and you could set extensions to {} on your new object if you wanted to (effectively) drop all extensions?

@glasser glasser force-pushed the glasser/mask-simple branch 2 times, most recently from 3337da4 to 3b85378 Compare June 27, 2019 00:13
@abernix
Copy link
Member

abernix commented Jun 27, 2019

I guess there's a tension between "want people to be be able to keep the context without having to write it" (especially for path!) and "want people to be able to delete stuff".

Yup! Generally, it was believed that users will want to mask sensitive things, or selectively not report an error.

The new feature is documented as being a replacement for maskErrorDetails, but you can't actually implement the behavior of maskErrorDetails

I think the lack of a more correct maskErrorDetails implementation was part of its immediate demise (rewriteError was actually supposed to come out in the version immediately after it surfaced). Further, I believe that maskErrorDetails may have resulted in the errors appearing in the wrong place in the trace tree entirely — i.e. through because of its loss the path? The overall idea of just masking all errors in such a generic way wasn't incredibly useful, overall, hence rewriteError.

As you found with originalError, it doesn't make its way to transmission, which is why we didn't copy that property over from the error returned from rewriteError, but the extensions bit seems like an oversight on my part and I think your suggestion to rewrittenError.extensions || err.extensions and provide that option is correct.

I don't see anything that explains that changes to non-message properties will be ignored!

That is an important detail and should be clarified!

unless we think the behavior of the old feature isn't valuable, in which case changing the old feature's behavior like I do here seems reasonable?

Seems fine to me.

// of GraphQLError are not enumerable and won't show up in the trace
// (even in the json field) anyway.)
// XXX I really don't understand why we don't allow rewriteError
// to rewrite extensions, which do show up in JSON.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will fix with the || solution.

) {
throw new Error('rewriteError must be a function');
} else if (engine.maskErrorDetails) {
engine.rewriteError = () => new GraphQLError('<masked>');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We had been reproducing exactly what the previous generation of maskErrorDetails had done. I would be surprised if anyone is actually using this option in its current state, to be honest. I don't think it was ever documented, and the one person who I know asked for it claimed it missed the mark.

{
json: '{"message":"<masked>"}',
json:
'{"message":"<masked>","locations":[{"line":1,"column":2}],"path":["fieldWhichWillError"]}',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the original implementation just didn't have this extra detail, I think?

@glasser
Copy link
Member Author

glasser commented Jun 27, 2019

Further, I believe that maskErrorDetails may have resulted in the errors appearing in the wrong place in the trace tree entirely

OK, my main goal here was verifying that we consider this to be a bug and not a feature. Thanks!

glasser added 3 commits June 27, 2019 10:59
This reimplements maskErrorDetails in terms of rewriteError.

It is not quite a no-op: masked errors will now contain the path and query
source string location where they occurred.

This is being done at the `new ApolloServer` level because federated metrics
will want to use the same normalization without using EngineReportingAgent or
EngineReportingExtension.
PR description has been updated to include new changes
@glasser glasser force-pushed the glasser/mask-simple branch from 3b85378 to 6efa0d6 Compare June 27, 2019 18:04
@glasser glasser changed the base branch from master to release-2.7.0 June 27, 2019 18:04
@glasser glasser changed the title apollo-engine-reporting: simplify maskErrorDetails implementation apollo-engine-reporting: tweaks to maskErrorDetails and rewriteError Jun 27, 2019
Copy link
Member

@abernix abernix left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up commits look good. Free to merge to release-2.7.0!

@glasser glasser merged commit f6172f6 into release-2.7.0 Jun 27, 2019
@glasser glasser deleted the glasser/mask-simple branch June 27, 2019 18:17
@abernix abernix added this to the Release 2.7.0 milestone Jun 27, 2019
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 22, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants