Enable invoking JS from the server in a way that is compatible with CSP and avoids XSS
See original GitHub issueThis ticket suggests a new low-level mechanism in Flow that would allow defining all JS expression to invoke as static strings with no possibility of causing XSS issues by accidentally including user-provided values in the actual JS expressions. By extension, this means that all used JS can be known when the production build is created which in turn makes it possible to compute hashes to use with CSP.
The core idea is to avoid page.executeJs("window.alert($0)", "Hello");
which cannot be known in advance and which might accidentally be written as page.executeJs("window.alert('" + greeting "')");
which might lead to XSS. Instead, all JS snippets would be defined either in separate JS module files or as oneliners in annotations.
For oneliners, one alternative might be to define an interface.
public interface GreeterJs {
@JsExpression("window.alert($0)")
void showGreeting(String greeting);
}
Flow could then provide a way of creating an instance of that interface to actually invoke the JS:
page.getJsInvoker(GreeterJs.class).showGreeting("Hello");
Internally, this could work so that generated-flow-imports.js
would contain something like this:
window.Flow.jsInvokers["com.acme.GreeterJs"] = {
"showGreeting": $0 => window.alert($0)
}
When the Java method is run, a tuple like ["com.acme.GreeterJs", "showGreeting", ["Hello"]]
could be sent to the client, based on which Flow could do window.Flow.jsInvokers[tuple[0]][tuple[1]).apply(null, tuple[2])
to invoke the JS without having to use eval
or any similar mechanism that is unfriendly to CSP.
A corresponding concept could be used to instead define the invokeable JS that is exported from separate JS module file. In that case, the module file could be defined like this:
export function showGreeting(message) { window.alert(message) };
while the corresponding Java interface would instead use an annotation to define the JS module it should be bound to:
@JsExpressionModule("./myModule")
public interface GreeterJs {
void showGreeting(String greeting);
}
Invoking from Java would be done in exactly the same way.
The code generated into generated-flow-imports.js
would in this case be something like this:
import * as generated123 from './myModule';
window.Flow.jsInvokers["com.acme.GreeterJs"] = generated123;
The same mechanism could also be used to replace Element.executeJs
with a small enhancement. What’s needed is just to pass the client-side element instance as this
(i.e. as the first argument to apply
) if invoked in this way:
someElement.getJsInvoker(GreeterJs.class).showGreeting("Hello");
For this to make sense, the targeted JS expression would of course also have to make use of this
.
An additional benefit of this approach is that @JsExpression
scripts will be included in the bundle processed by webpack, which means that newer JS features can be used without having to take browser compatibility into account.
Issue Analytics
- State:
- Created 2 years ago
- Reactions:2
- Comments:6 (6 by maintainers)
There are no specific plans at this moment - I’m just poking at things that might be interesting and seeing if there’s any wider interest among users. I plan to publish something related on vaadin.com/labs later this week to get something more tangible to refer to.
If there is enough interest within the next weeks or months, then I believe a basic implementation could be thrown together quickly enough to get into the next LTS. The challenges here are more on the conceptual side whereas an actual implementation should be quite straightforward if I’m not mistaken.
As an alternative for someone really eager to use strict CSP settings even if it adds some boilerplate to their application, there’s also the fallback mechanism in https://github.com/vaadin/flow/issues/10810 that could be implemented in a couple of hours. That approach could be added even to 14.x.
Vaadin components are typically only using hardcoded JavaScript snippets that can be known in advance. One would just have to verify whether any expressions have changed whenever updating to a new Vaadin version or starting to use a component that hasn’t been used previously. I specifically reviewed Grid and ComboBox to have two quite complex cases and I didn’t spot anything there that would dynamically generate JS expressions to run.
The one exception that I’m aware of is
ShortcutRegistration
which is dynamically building event filter expressions based on the key and modifiers to listen for. Even a case like this could still be supported. If the application only uses a handful of different shortcut keys, then it would might make sense to just hardcode for the particular expressions that are generated for those keys. Supporting arbitrary shortcut keys could also be possible by building a simple parser that recognizes the structure used by shortcut expressions to extract the variable parts and then returning a regular function that uses those values from its own closure.