-
Notifications
You must be signed in to change notification settings - Fork 424
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
Have you considered supporting CSP header? #878
Comments
You are barely providing details about the problem and it looks like you're using an older version of the grid but I'm not sure since you just provided code that I don't think is part of SlickGrid... Anyway, the best would be for you to contribute because I don't think we will look into this ourselves. |
- potentially fixes #878, not sure
Searching a little bit and found this SO Javascript: Can i dynamically create a CSSStyleSheet object and insert it? and followed 1 of the answer below to create stylesheet dynamically then insert the dynamic css rules and it seems to work but I don't know how you test the CSP so you could maybe test yourself and let me know. At least it's no longer using replace the entire protected createCssRules() {
this._style = document.createElement('style');
document.head.appendChild(this._style); // must append before you can access sheet property
const sheet = this._style.sheet;
if (sheet) {
const rowHeight = (this._options.rowHeight! - this.cellHeightDiff);
sheet.insertRule(`.${this.uid} .slick-group-header-column { left: 1000px; }`);
sheet.insertRule(`.${this.uid} .slick-header-column { left: 1000px; }`);
sheet.insertRule(`.${this.uid} .slick-top-panel { height: ${this._options.topPanelHeight}px; }`);
sheet.insertRule(`.${this.uid} .slick-preheader-panel { height: ${this._options.preHeaderPanelHeight}px; }`);
sheet.insertRule(`.${this.uid} .slick-headerrow-columns { height: ${this._options.headerRowHeight}px; }`);
sheet.insertRule(`.${this.uid} .slick-footerrow-columns { height: ${this._options.footerRowHeight}px; }`);
sheet.insertRule(`.${this.uid} .slick-cell { height: ${rowHeight}px; }`);
sheet.insertRule(`.${this.uid} .slick-row { height: ${this._options.rowHeight}px; }`);
for (let i = 0; i < this.columns.length; i++) {
if (!this.columns[i] || this.columns[i].hidden) { continue; }
sheet.insertRule(`.${this.uid} .l${i} { }`);
sheet.insertRule(`.${this.uid} .r${i} { }`);
}
}
} for example here is 1 of the dynamically created css rule P.S. I have no clue why the original author of SlickGrid decided to create dynamic CSS rules, so we have to try to keep the original logic as much as possible. I think it's because by doing it this way, we don't need any external stylesheet file loaded, this css will be applied every time and will also create a css rule by the guid UID which is not possible via an external stylesheet file. @JesperJakobsenCIM All Cypress E2E tests are passing so it's already promising, I think this code might not work in IE but we don't support it anymore so we should be good anyway |
setAttribute is not a good solution because it's not going to work with everything (for example For example this simple 1 line code from Utils.createDomElement('div', { className: 'slick-cell', id: '', style: { visibility: 'hidden' }, textContent: '-' }, r); is actually 6 lines when applied const cellElm = document.createElement('div');
cellElm.className = 'slick-cell';
cellElm.id = '';
cellElm.style.visibility = 'hidden';
cellElm.textContent = '-';
r.appendChild(cellElm); I guess, but not entirely sure, the 6 lines code would pass CSP? But it would increase the code by a lot of lines. I searched for I merged the other PR that I referenced earlier, it's only a partial fix and I would suggest you start contributing since you know how to test these things and I don't know what else to change. EDIT After trying some more, I created another PR #884, it seems that CSP also doesn't like adding |
* fix: dynamically create CSS rules via JS instead of innerHTML - potentially fixes #878, not sure * chore: apply nonce property on dynamic style
For DomPurify to work you are properly not parsing in
missing this {RETURN_TRUSTED_TYPE: true} might be the reason edit: |
even if I add DOMPurify to the CSP Example (in this commit 1e13388), it still throws some errors on the lines with <script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.6/dist/purify.min.js"></script> then add the grid option var options = {
enableCellNavigation: true,
enableColumnReorder: false,
sanitizer: (dirtyHtml) => DOMPurify.sanitize(dirtyHtml, { RETURN_TRUSTED_TYPE: true })
}; I also tried to create a default policy as suggest here: https://web.dev/articles/trusted-types#use_a_default_policy but that didn't work either, even if it got slightly further EDIT 1 If I change the CSP to include only the trusted types then it works, but it would be better to make it work with <meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script'; trusted-types dompurify"> EDIT 2 So I also found that CSP doesn't like my - Utils.createDomElement('span', { className: 'slick-column-name', innerHTML: this.sanitizeHtmlString(m.name as string) }, header);
// split into 2 lines, to have explicit innerHTML assignment
+ const colNameElm = Utils.createDomElement('span', { className: 'slick-column-name' }, header);
+ colNameElm.innerHTML = this.sanitizeHtmlString(m.name as string); but again, I only got CSP working with trusted-types but without So I decided to go ahead and merge the 2nd PR #884 with the new CSP Example which uses trusted types. I think that is the best I can do with this and I will release a new version later including these fixes. If you know how to make it work with |
- to further improve CSP support for issue #878, we need to move `innerHTML` as separate assignment and not use it directly within a `createDomElement`, so for example this line `const elm = createDomElement('div', { innerHTML: '' })` should be split in 2 lines `const elm = createDomElement('div'); elm.innerHTML = '';`
* chore: add CSP header example - ref issue #878 * chore: add DOMPurify to CSP example * fix: move `innerHTML` as separate assignment outside of createDomElement - to further improve CSP support for issue #878, we need to move `innerHTML` as separate assignment and not use it directly within a `createDomElement`, so for example this line `const elm = createDomElement('div', { innerHTML: '' })` should be split in 2 lines `const elm = createDomElement('div'); elm.innerHTML = '';`
- to further improve CSP support for issue #878, we need to move `innerHTML` as separate assignment and not use it directly within a `createDomElement`, so for example this line `const elm = createDomElement('div', { innerHTML: '' })` should be split in 2 lines `const elm = createDomElement('div'); elm.innerHTML = '';`
* chore: add CSP header example - ref issue #878 * chore: add DOMPurify to CSP example * fix: move `innerHTML` as separate assignment outside of createDomElement - to further improve CSP support for issue #878, we need to move `innerHTML` as separate assignment and not use it directly within a `createDomElement`, so for example this line `const elm = createDomElement('div', { innerHTML: '' })` should be split in 2 lines `const elm = createDomElement('div'); elm.innerHTML = '';`
The 2 PRs #883 and #884 that I previously merged are now available in today's release v5.4.0. This should help with CSP header and I also added the CSP Header Example to the Examples Wiki |
I'll investigate it further to see if I can make the example work. The nonce value should also come from the options object so its the developer who controls it. As a side note, when I attempted to implement it on our website initially, I noticed that it triggered an 'unsafe-eval' operation in 'slick.dataview.ts.' The 'new Function' is considered equivalent to 'eval(),' which is unsafe. I'm unsure if there's an alternative option for this particular case.
|
The reason it doesn't work appears to be that it contains style tags in the pre-generated HTML, which conflicts with the 'unsafe-inline' directive in the 'style-src' section of the header. You can see this in the HTML example. slick.grid.ts
Example of the generated HTML
One possible solution could be to dynamically add the top style as a class, for example, 'slick-top-{x}' where 'x' represents the height (e.g., 'slick-top-900'). Alternatively, you could modify the styling after appending the HTML to the DOM using 'setStyleSize.' Here's an example of how to make it work with the first option.
my meta tag
I've moved the values out of default-src and into their proper places Another approach could involve checking if the rule doesn't exist and, if it doesn't, adding it. Regarding the concern of missing rules when adding more items later, what are your thoughts on this? |
I already know about this one, but I don't know how to replace this. The original author used for this caching and speed performance I think and we somehow replace this with something we might decrease performance by a lot.
The So anyway, I've worked quite a bit on this but I'm still waiting for you to contribute if you know how to fix some of them. For example the |
Also saw on Ag-Grid that the minimal rules they use is including
|
I'll see if I can find some time to contribute. The issue of 'unsafe-inline' is widely discussed, and you can read more about it here: Can You Get Pwned with CSS?, authored by the creator of Security Headers. Currently, the removal of 'unsafe-inline' from 'style-src' is a gradual process that many people are working towards. While it hasn't been widely exploited recently, it still poses a security risk. It's important to consider how the cybersecurity landscape is evolving. You can learn more about CSS injections in this OWASP wiki. In the case of a grid-type package, the risk can be significant because it's challenging to ensure that every developer who uses it properly sanitizes data before adding it to the grid. Providing developers with the necessary tools is essential. Ultimately, 'style-src' with 'unsafe-inline' may not be the most significant risk, but it's crucial to stay vigilant, given the evolving nature of cybersecurity threats. |
Sure but the thing that you might have missed is that SlickGrid is an Open Source project, we get no money from working on this and most of it come from our spare time which is why contributions is more than welcome. I understand the security risk but still this project is all but free in terms of usage and in terms of development, hence why contributions are even more important |
Yeah, I know you don't have to worry. This is also a reason why it's an open-source project, so people can contribute to the project. You're doing great work, and I'm sorry if I sounded aggressive. For now, just take it easy; I'll look into the issues around the new Function. We have to remember this will take time to implement anyway. |
Might be worth putting a performance benchmark against the compiled aggregates for comparison to any reworked method |
@6pac well the thing is that I don't even know how or what to replace these |
How do i commit changes to #894? fairly new in this apartment |
you need to Create a Fork like it suggested and you can't push to a PR since you're not the owner of this repo. The best we could do maybe is to maybe create a |
Okay, ill try that, the nonce commit is here I'm not sure if any of you have experience with DataView, but it seems that changing FilterFn to a Function type doesn't break anything, and the functionality still works as expected. It may only require minor adjustments in other places. I'll need to experiment with it a bit more and also figure out how to conduct some performance benchmarks afterward.
|
Your code change looks pretty good, could you create a new PR? To create a PR, you should have a screen similar to what is shown below, I made contributions to Vitest in the past and so I got the Vitest fork, from there I have the screen below and I can create a PR from there (my create PR is disabled because I have no change at the moment but yours should be enabled)
I only use the DataView myself, it looks like you remove the dynamic functions entirely but again I'm pretty sure the SlickGrid author created these dynamic function so that they can be cache by the browser and reused for performance reason. However, these were created when IE6 was the big boy and was also super slow, we might not need them anymore since browser are much optimized but we would have to compare with a grid that has a large dataset like this Example Optimized DataView with 500,000 rows and also test with multiple filters (that demo ony has 1 filter), then maybe add some |
Thanks, pull request send, yea definitely. At the moment, I'm mainly working on a working concept. It will require a bit more time to finalize the adjustments. I haven't touched the compileFilterWithCaching function yet, but it should receive the same rewrite. |
You can check out this it seems to be slightly slower however this is where i just changed the code to use the Function concept instead of new Function old version 6.256932861328125 ms Edit: Edit: |
so it's 44% difference, so it is notable even if it's in ms. Perhaps we should keep both option in the code and a provide a flag to let the user chose for strict CSP or loose & faster mode. I again think that the new function does some kind of caching which is how it gets better perf. Your new approach is still not that bad though but yeah a flag might be the best approach |
Super, I'll implement it with a flag (most of the setup is there already) and do some minor cleanup. Question tho, how do you enable it to run the cached version of the calls 'compileFilterWithCachingNew' and 'compileFilterWithCaching' since i have no idea how to make it use them instead. Would also still need to do some testing with the security header active since I don't know if how the CSP header reacts if the new Function is still present, hopefully it only blocks if you call the line with new Function. About the concept with new Function caching i have no idea how it works and makes me wonder how new Function actually works especially with the setup, since i would have assumed that calling the function would be faster, since new Function adds some overhead. |
I don't know either how that works, I'm just assuming that it's caching the function because I don't see other reasons for why it creates dynamic function. By taking a look at the commits made by the original author, in this PR and this other PR with comments like below
This also makes me think that you should also test perf when using Grouping and Filters. Personally I never use the DataView |
Ohh asked copilot why there was a difference between the 2 functions this was its answer
This makes kinda sense tho it also tells me that if the dataset is even larger 1.000.000+ the compileFilterNew function would be even slower than compileFilter |
What do you guys think about these changes #908 last two commits thought i could have split it up in 2 pull requests but github just added my new changes to already existing pull request... tho this pull request contains useCSPSafeFilter as an option and the last inline css style fix (should maybe be an opt in too or just check if nonce is set) performance wise doesnt seem to be a huge impact did 500.000 rows and it took 0.05 extra ms |
I think we can now close this issue since multiple PRs were pushed in regards to CSP
I went ahead and merged my other PR and released a new version 5.5.0 which is enough I think to become CSP compliant. Thanks a lot for helping in making this happen 🚀 I also updated the readme homepage, please let me know if there's anything wrong or missing. Thanks |
As far as I can see, the only real issue is with "style-src unsafe-inline" in the Content Security Policy (CSP). However, you can work around this by using:
These properties allow you to bypass CSP and still use inline CSS. Please don't ask me why this works, but it should enable you to use SlickGrid with a CSP header.
You can also simplify the application of CSS using a function like this:
This function makes applying CSS styles easier.
The text was updated successfully, but these errors were encountered: