-
Notifications
You must be signed in to change notification settings - Fork 1.7k
JavaScript: add support for NodeJS and Electron http client libraries #14
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
JavaScript: add support for NodeJS and Electron http client libraries #14
Conversation
JavaScript: Further improve handling of destructuring patterns.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few suggestions and comments.
This adds a lot of new classes. Have you considered which of them could be made private
?
I also dislike the amount of repetition. Is there no way of merging some of these definitions or pulling out shared bits of code?
Finally, as I commented on below, you seem to use TrackedNode
s (inter-procedural flow) a lot. We have so far got along with SourceNode
s (intra-procedural flow). I assume this is because you have seen examples where inter-procedural flow is needed?
/** | ||
* A data flow node that is a Node.js HTTP or HTTPS client request. | ||
*/ | ||
abstract class ClientRequest extends DataFlow::TrackedNode { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to overlap with the existing class RequestExpr
. What's the rationale for introducing this class, and why is it a TrackedNode
? Have you seen cases where we need to track requests inter-procedurally?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RequestExpr
is used in a NodeJS server handling an incoming request, while ClientRequest
is when the NodeJS or Electron libraries are used to send a request, as in the call at line 18 of javascript/ql/test/library-tests/frameworks/NodeJSLib/src/http.js
I think I added that because I saw a case where we need to track them interprocedurally - I'll go back through the projects I was testing on and see if I actually needed it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like I added it in an attempt to handle a use of bind
to create the request callback. It didn't work, so I'll remove it and see if there's a better way to handle bind
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, that's not clear from the description or the name. Please consider whether there is a better name, and extend the qldoc with an example.
bind
is tricky; I'd say let's ignore it for now.
* A data flow node that is a Node.js HTTP or HTTPS client request. | ||
*/ | ||
abstract class ClientRequest extends DataFlow::TrackedNode { | ||
abstract DataFlow::Node getOptions(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Public predicate, so needs qldoc.
/** | ||
* A data flow node that is the parameter of a result callback for an HTTP or HTTPS request made through Node.js. | ||
*/ | ||
abstract class RequestCallbackParam extends DataFlow::TrackedNode, RemoteFlowSource { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This overlaps with RequestSource
; same questions as for ClientRequest
above.
RequestCallbackData() { | ||
exists(RequestCallbackParam rcp, DataFlow::MethodCallNode mcn, DataFlow::FunctionNode fn | | ||
rcp.flowsTo(mcn.getReceiver()) and | ||
mcn.getArgument(0).mayHaveStringValue("data") and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mcn.getMethodName() = "on"
?
} | ||
|
||
/** | ||
* A data flow node that is a data callback for an HTTP or HTTPS request made through Node.js. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here and elsewhere, could you please add a brief example code snippet to the qldoc to clarify what is being modelled?
exists(DataFlow::MethodCallNode mcn | | ||
mcn = this and | ||
DataFlow::moduleMember("electron", "net").flowsTo(mcn.getReceiver()) and | ||
mcn.getMethodName() = "request" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this = DataFlow::moduleMember("electron", "net").getAMemberCall("request")
not do what you want?
NewClientRequest() { | ||
exists(DataFlow::NewNode nn | | ||
this = nn and | ||
DataFlow::moduleMember("electron", "ClientRequest").flowsTo(nn.getCallee()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this = DataFlow::moduleMember("electron", "ClientRequest").getAnInstantiation()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for addressing the review comments! I've got a few more comments, some of them stylistic, others about opportunities for using the SourceNode
fluent API to simplify things. I didn't point out all opportunities for doing this.
Overall, I'd encourage you to make as many of these classes private as possible. If you have two very similar private classes that only exist to extend at abstract class, consider merging them for simplicity.
|
||
|
||
/** | ||
* A Node.js-style HTTP or HTTPS request made using `Electron.client`, e.g. `new client(url)`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You use electron.net
above (lower-case electron
), and Electron.client
here (upper-case Electron
). Is that intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here and below: we generally avoid "e.g." in favour of "for example" or similar.
@@ -506,4 +506,270 @@ module NodeJSLib { | |||
} | |||
} | |||
|
|||
/** | |||
* A data flow node that is an HTTP or HTTPS client request made by a Node server, e.g. `http.request(url)` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We generally use "Node.js" instead of "Node" (although we're not as consistent about it as I'd like). Also, missing a full stop at the end.
/** | ||
* A data flow node that is an HTTPS client request made by a Node process, e.g. `https.request(url)`. | ||
*/ | ||
class HttpsRequest extends ClientRequest { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have a good use case for separating this class from HttpsRequest
and exposing both of them as public API? If not, I'd prefer merging them and making them private.
/** | ||
* A data flow node that is the parameter of a result callback for an HTTPS request made by a Node process, e.g. `res` in `https.request(url, (res) => {})`. | ||
*/ | ||
class HttpsRequestCallbackParam extends RequestCallbackParam { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question as for HttpsRequest
above.
class RequestCallbackData extends RemoteFlowSource { | ||
RequestCallbackData() { | ||
exists(RequestCallbackParam rcp, DataFlow::MethodCallNode mcn, DataFlow::FunctionNode fn | | ||
rcp.flowsTo(mcn.getReceiver()) and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mcn = rcp.getAMethodCall("on")
*/ | ||
class ClientRequestDataHandler extends DataFlow::FunctionNode { | ||
ClientRequestDataHandler() { | ||
exists(DataFlow::MethodCallNode mcn, ClientRequestResponseEvent cr | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar simplifications to above can be made here.
ClientRequestLoginPassword() { | ||
exists(ClientRequestLoginCallback callback, DataFlow::CallNode call | | ||
callback.flowsTo(call.getCallee()) and | ||
this = call.getArgument(1).asExpr() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this = callback.getACall().getArgument(1).asExpr()
/** | ||
* A data flow node that is an HTTP or HTTPS client request made by a Node server, e.g. `http.request(url)` | ||
*/ | ||
abstract class ClientRequest extends DataFlow::SourceNode { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's generally safer to extend DataFlow::DefaultSourceNode
, which has (almost) the same extent as SourceNode
, but isn't abstract.
/** | ||
* A data flow node that is the parameter of a result callback for an HTTP or HTTPS request made by a Node process, e.g. `res` in `http.request(url, (res) => {})`. | ||
*/ | ||
abstract class RequestCallbackParam extends DataFlow::SourceNode, RemoteFlowSource { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As above, extend DefaultSourceNode
instead.
/** | ||
* A data flow node that is the parameter of a response callback for an HTTP or HTTPS request made by a Node process, e.g. `res` in `http.request(url).on('response', (res) => {})`. | ||
*/ | ||
class ClientRequestResponseEvent extends RemoteFlowSource, DataFlow::SourceNode { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DefaultSourceNode
19098f8
to
d966e8d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good; a couple more suggestions.
@@ -33,4 +33,53 @@ module Electron { | |||
this = DataFlow::moduleMember("electron", "BrowserView").getAnInstantiation() | |||
} | |||
} | |||
/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indentation; also, please add blank line above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This still needs addressing.
|
||
|
||
/** | ||
* A data flow node that is the parameter of a redirect callback for an HTTP or HTTPS request made by a Node process, for example `res` in `net.request(url).on('redirect', (res) => {})`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/Node/Node.js/ for consistency.
} | ||
|
||
/** | ||
* A data flow node that is an HTTPS client request made by a Node.js process, for example `https.get(url)`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has exactly the same qldoc as the previous class, apart from the code example; either merge (my preferred option) or refine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's also wrong, of course, in that the qldoc talks about HTTPS, while the implementation is about HTTP.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/HTTPS/HTTP or HTTPS/
exists(DataFlow::MethodCallNode mcn, DataFlow::FunctionNode fn | | ||
mcn = req and | ||
fn.flowsTo(mcn.getArgument(1)) and | ||
this = fn.getParameter(0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this = mcn.getCallback(1).getParameter(0)
rcp.getAMethodCall("on") = mcn and | ||
mcn.getArgument(0).mayHaveStringValue("data") and | ||
fn.flowsTo(mcn.getArgument(1)) and | ||
fn.getParameter(0) = this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this = mcn.getCallback(1).getParameter(0)
/** | ||
* A data flow node that is the parameter of a response callback for an HTTP or HTTPS request made by a Node.js process, for example `res` in `http.request(url).on('response', (res) => {})`. | ||
*/ | ||
class ClientRequestResponseEvent extends RemoteFlowSource, DataFlow::ParameterNode { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this class be private
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could it be?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few more comments about inconsistent qldoc. Also, could you add the eslint-scope attack as a test (with appropriate attribution)? With the latest extractor changes, this should now work out of the box.
} | ||
|
||
/** | ||
* A data flow node that is an HTTP client request made by a Node.js server, for example `http.request(url)`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/HTTP/HTTP or HTTPs/
} | ||
|
||
/** | ||
* A data flow node that is an HTTPS client request made by a Node.js process, for example `https.get(url)`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/HTTPS/HTTP or HTTPS/
*/ | ||
private class ClientRequestCallbackData extends RemoteFlowSource { | ||
ClientRequestCallbackData() { | ||
exists(ClientRequestCallbackParam rcp, DataFlow::MethodCallNode mcn| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing space after mcn
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still missing.
} | ||
|
||
/** | ||
* A data flow node that is the parameter of a result callback for an HTTPS request made by a Node.js process, for example `res` in `https.request(url, (res) => {})`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/HTTPS/HTTP or HTTPS/
cr.getAMethodCall("on") = mcn and | ||
mcn.getArgument(0).mayHaveStringValue("data") and | ||
handler.flowsTo(mcn.getArgument(1)) and | ||
this = handler.getParameter(0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this = mcn.getCallback(1).getParameter(0)
I'd prefer not to check in the unmodified attack to avoid antivirus trouble on Windows. Would a modified version with the URL changed be OK? |
Sure, that makes sense. Hadn't thought of the antivirus angle. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost there! A few more cosmetic things, then we should be good to go.
Could you squash everything down to one (or a handful) of commits, please?
@@ -33,4 +33,53 @@ module Electron { | |||
this = DataFlow::moduleMember("electron", "BrowserView").getAnInstantiation() | |||
} | |||
} | |||
/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This still needs addressing.
*/ | ||
private class ClientRequestCallbackData extends RemoteFlowSource { | ||
ClientRequestCallbackData() { | ||
exists(ClientRequestCallbackParam rcp, DataFlow::MethodCallNode mcn| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still missing.
/** | ||
* A data flow node that is the parameter of a response callback for an HTTP or HTTPS request made by a Node.js process, for example `res` in `http.request(url).on('response', (res) => {})`. | ||
*/ | ||
class ClientRequestResponseEvent extends RemoteFlowSource, DataFlow::ParameterNode { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could it be?
cr.getAMethodCall("on") = mcn and | ||
mcn.getArgument(0).mayHaveStringValue("data") and | ||
this = mcn.getCallback(1).getParameter(0) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spurious blank line.
Ping @rdmarsh2, could you address the few remaining superficial issues? This is basically good to go, I think. |
7fe5620
to
aaeda5d
Compare
Addressed the last few comments and squashed down to a few commits |
Extractor: fix child index values
Kotlin: Refactoring: Give diagnostic messages locations and severities
This PR adds RemoteFlowSources for both the NodeJS http/https client library and the Electron http/https client library and adds CredentialExprs for auth responses. The majority of the work is in NodeJSLib.qll; the Electron QL library simply extends the ClientRequest class from the QL NodeJS library.