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

add safe eval for browser and eval option #185

Merged
merged 14 commits into from
May 5, 2024

Conversation

80avin
Copy link
Contributor

@80avin 80avin commented Dec 23, 2022

Adding a safe alternative to Function/eval in browser.
This is also required since the chrome extensions & chrome apps have already become strict about safety.

I was trying to use it in a chrome extension but couldn't because of unsafe-eval. Hence this fix tries to introduce a safe evaluation method which satisfies Content Security Policy.

eval: "safe" is effective only in browser and doesn't uses a scripting engine which supports a minimal form of javascript (no functions or complex expressions).

PR description

  • Add safe version of eval/Function for Content Security Policy
  • Update test/index.html to use compiled module
  • update demo html to include prefilled sample & prevent reload on submit
  • babel/rollup related changes
  • Add new option eval: "safe" | "native" | boolean | (code, context) => value | Script)
  • Add new option ignoreEvalError: boolean which can be used to consider the errors in eval expressions as match.

Checklist

  • - Added tests
  • - Ran npm test, ensuring linting passes
  • - Adjust README documentation if relevant

Fixes #60 , #182 , #169

@80avin
Copy link
Contributor Author

80avin commented Dec 23, 2022

@brettz9 Could you please check and see if the approach is right ?

I particularly need advice on the rollup/babel/eslint configuration changes as I'm totally new to them.

Also, if possible, how should we go with the support of "use strict" and funciton syntax in scripts ?

If I get a green flag in these, I'll work on documentation.

update test html file to use compiled module

update demo html file to include sample & prevent submit
@80avin 80avin reopened this Dec 23, 2022
@brettz9
Copy link
Collaborator

brettz9 commented Dec 23, 2022

I don't have time to look at this now, and no guarantees about getting to it, but I think an optimal solution, if you can't get the multi-line evaluation supported, would be to allow the user to supply their own evaluator, e.g., optionally passing in eval(), so that users could still maintain support without too much added effort yet without requiring eval statements which create conflicts with content service policies such as Chrome extensions apparently enforce.

@80avin
Copy link
Contributor Author

80avin commented Dec 23, 2022

user still has the choice to go with unsafe way using a new options evalType which can have values none or native or safe.
Thanks for the quick reply though. I'll move ahead with the cleanup & documentation. I should also be able to get multiline support.

@80avin 80avin marked this pull request as ready for review December 28, 2022 19:13
@80avin
Copy link
Contributor Author

80avin commented Dec 28, 2022

@brettz9 Looks like the PR is complete.
I added the tests, types, updated Readme and all tests are passing now.

I'm in no rush, so whenever you (or some other contributor) find time, please look into it and suggest changes.

Copy link
Collaborator

@brettz9 brettz9 left a comment

Choose a reason for hiding this comment

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

I plan to give a deeper review later, but wanted to first address these smaller questions, and also ask that we maintain 100% testing coverage. (I see that the coverage badge is no longer reporting 100%, so some of the new lines of code apparently aren't covered in tests.)

I'd prefer we avoid the Babel ESLint parser unless there is really a compelling reason, though it'd be fine to increase the parserOptions.ecmaVersion to use the latest standard features in source.

From what I can see, the rest all looks very good.

.eslintignore Outdated Show resolved Hide resolved
.eslintrc.cjs Outdated Show resolved Hide resolved
@jacobroschen
Copy link
Collaborator

I don't have time to look at this now, and no guarantees about getting to it, but I think an optimal solution, if you can't get the multi-line evaluation supported, would be to allow the user to supply their own evaluator, e.g., optionally passing in eval(), so that users could still maintain support without too much added effort yet without requiring eval statements which create conflicts with content service policies such as Chrome extensions apparently enforce.

@brettz9, my two unsolicited cents are in agreement with your thoughts about this PR for a few couple reasons.

  1. This adds dependencies for every consumer, even if they don't use the "safe" eval option.
  2. While this PR passes all tests, jsep does not support all functionality that eval() supports. This not only results in a breaking change, but will result in a third set of behavior that this library needs to support (default eval allow, preventEval: true, and now whatever jsep supports.
  3. Adding a new config parameter such as customEval Instead of evalType would negate these downsides while providing some benefits benefits.
    1. Prevents the burden of a third mode for the maintenance of this library
    2. Provides greater flexibility for consumers (ie. a more limited eval function, or they want to use something like QuickJS for a safer, but just as powerful eval function)
    3. Removes the extra dependency for consumers that don't use the evalType: 'safe'
    4. Removes the weird ability to disable eval with two different config options evalType and preventEval
      If we'd like to go down this path, there are two different config option styles that seem reasonable.
const customEval = (code: string, context: any): any => {
    // evaluate code against context, return any matching result(s)
};
const jsonPath = new JSONPath({ eval: customEval });
jsonPath.evaulate('$.test');

An alternative API that fits into the current architecture a bit better, which would require the consumer to pass in a class to the config.

class CustomEval {
  constructor(code: string) {
    // any compiling you need to do.
    this.code = code;
  }
  
  eval(context: any) {
    // however you'd like to evaluate the code (`this.code`) against `context`
    // return undefined, or match(es)
    return undefined;
  }
}

const jsonPath = new JSONPath({ eval: CustomEval });
jsonPath.evaluate('$.test');

In both scenarios, this library would still replace the shorthand properties(ex @parentProperty, @property, etc.) into _$_parentProperty, _$_property, etc while providing these values as context properties.

While option 2 has a bit more boilerplate, I think it provides more flexibility for consumers of this library and it enables automatic caching of the compiled script.

@brettz9
Copy link
Collaborator

brettz9 commented Jan 5, 2023

@jacobroschen : Thank you for your thoughts. They are most welcome. However, I have a few thoughts in response:

As far as adding dependencies, the bundle size is rather good, no less considering what it is doing:

And although it does not support everything eval-wise, for the much more common use case of just accessing properties, it really does deliver something important.

I agree we can drop preventEval (as part of a breaking change), but that can be done as part of this PR as well. While this technically removes some functionality, I don't know how much this is going to harm anyone since it should not compromise security nor would I expect performance to significantly suffer by allowing what is already normally allowed in a jsonpath engine.

It has been a long-time goal for us to have a safe default, and recently we had still another request for a safe default: #182 . I rejected that because I didn't want to gut the core functionality our users have become accustomed to, but only because we didn't have an alternative.

I remain open to further thoughts, as well as to input of others, but unless there are concerns with jsep specifically, it seems like a hearty compromise without coming at much cost.

@brettz9
Copy link
Collaborator

brettz9 commented Jan 5, 2023

Your API proposal looks sound (I agree with the second one too) and perhaps this PR can be adjusted to implement.

@80avin
Copy link
Contributor Author

80avin commented Jan 5, 2023

I agree with @brettz9 . The primary reason why I decided to use jsep in this library was to provide a safe & lightweight evaluation out of the box. Another similar library jsonpath achieves this by using static-eval and esprima which have minified + gzip sizes as

However, the idea of allowing the library user to provide his own eval alternative is also good. I'll update the PR with it.

@80avin
Copy link
Contributor Author

80avin commented Aug 5, 2023

I have implemented the new API eliminating preventEval and evalType in favour of eval.
Please comment your suggestions.

@80avin 80avin changed the title add safe eval for browser and evalType option add safe eval for browser and eval option Aug 6, 2023
@guoliang
Copy link

guoliang commented May 3, 2024

will this PR ever be merged?

80avin added 2 commits May 4, 2024 01:28
`ignoreEvalErrors` can be used to supress errors in eval expressions. These errors are common because the expression might be suitable for a particular value but may give error on other values. For such scenarios, we expect it to match the passing values instead of crash entirely
@80avin
Copy link
Contributor Author

80avin commented May 3, 2024

@guoliang Thanks for reminding

@brettz9 I have removed the conflicts which had appeared during the time.
To me, the PR looks complete. Can you review on your part also ?

Also, since your last review, I added a new option ignoreEvalError which can be used to suppress the errors in the eval expressions.
The usecase is that if our expression passes only on certain types but gives errors on other types ( .toLowerCase() will error on anything other than string ), in that case we would want to consider the error as no match instead of crash.

Copy link
Collaborator

@brettz9 brettz9 left a comment

Choose a reason for hiding this comment

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

If you can make the mentioned changes, I think I'll be ready for a final review. Looks good so far...

.eslintignore Outdated Show resolved Hide resolved
test/index.html Show resolved Hide resolved
src/jsonpath.js Outdated Show resolved Hide resolved
src/jsonpath.js Show resolved Hide resolved
@80avin
Copy link
Contributor Author

80avin commented May 4, 2024

@brettz9 All resolved.

@brettz9
Copy link
Collaborator

brettz9 commented May 4, 2024

I added one commit to fully pass linting and the testing scripts.

Did you mean for the ignoreEvalError option to be ignoreEvalErrors instead?

Also, please prepare a message for those upgrading to this breaking change version (e.g., what users of preventEval should now do instead).

@80avin
Copy link
Contributor Author

80avin commented May 5, 2024

ignorEvalErrors sounds good as it'll be ignoring many errors, one for each path.

I hope this message suffices.

- Breaking change: remove `preventEval` property. Prefer `eval: false` instead.
- Breaking change: changed behaviour of `eval` property. In the browser, `eval`/`Function` won't be used by default to evaluate expressions. Instead, we'll safely evaluate using a subset of JavaScript. 
- feat: add `eval` property.
- feat: add `ignoreEvalErrors` property.

@brettz9 brettz9 merged commit f08447c into JSONPath-Plus:main May 5, 2024
@brettz9
Copy link
Collaborator

brettz9 commented May 5, 2024

If you want to take one last look, I think it should be ready for a release now.

@80avin
Copy link
Contributor Author

80avin commented May 6, 2024

Tried few expressions in browser demo only, and it looks good to me.

@brettz9
Copy link
Collaborator

brettz9 commented May 9, 2024

Released as v9.0.0. Thanks for your contributions!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

For filtering, when arbitrary JavaScript is whitelisted, eval in sandbox; otherwise avoid eval()
4 participants