diff --git a/javascript/ql/lib/change-notes/2025-03-26-Hapi.md b/javascript/ql/lib/change-notes/2025-03-26-Hapi.md new file mode 100644 index 000000000000..d6d5795570f0 --- /dev/null +++ b/javascript/ql/lib/change-notes/2025-03-26-Hapi.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Added support for the newer version of `Hapi` with the `@hapi/hapi` import and `server` function. diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Hapi.qll b/javascript/ql/lib/semmle/javascript/frameworks/Hapi.qll index f1936da3a676..a6dbf40d1e06 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Hapi.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Hapi.qll @@ -11,8 +11,8 @@ module Hapi { */ class ServerDefinition extends Http::Servers::StandardServerDefinition, DataFlow::Node { ServerDefinition() { - // `server = new Hapi.Server()` - this = DataFlow::moduleMember("hapi", "Server").getAnInstantiation() + // `server = new Hapi.Server()`, `server = Hapi.server()` + this = DataFlow::moduleMember(["hapi", "@hapi/hapi"], ["Server", "server"]).getAnInvocation() or // `server = Glue.compose(manifest, composeOptions)` this = DataFlow::moduleMember("@hapi/glue", "compose").getAnInvocation() diff --git a/javascript/ql/test/library-tests/frameworks/hapi/src/hapihapi.js b/javascript/ql/test/library-tests/frameworks/hapi/src/hapihapi.js new file mode 100644 index 000000000000..45706b7a9405 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/hapi/src/hapihapi.js @@ -0,0 +1,36 @@ +var server1 = new (require('@hapi/hapi')).Server(); // HTTP::Server + +var Hapi = require('@hapi/hapi'); +var server2 = new Hapi.Server(); // HTTP::Server + +function handler1(){} // HTTP::RouteHandler +server2.route({ + handler: handler1 +}); + + +server2.route({ + handler: function handler2(request, reply){ // HTTP::RouteHandler + request.response.header('HEADER1', '') // HTTP::HeaderDefinition + }}); + +server2.ext('onPreResponse', function handler3(request, reply) { // HTTP::RouteHandler +}) + +function handler4(request, reply){ + request.rawPayload; + request.payload.foo; + request.query.bar; + request.url.path; + request.headers.baz; + request.state.token; +} +var route = {handler: handler4}; +server2.route(route); + +server2.cache({ segment: 'countries', expiresIn: 60*60*1000 }); + +function getHandler() { + return function (req, h){} +} +server2.route({handler: getHandler()}); diff --git a/javascript/ql/test/library-tests/frameworks/hapi/tests.expected b/javascript/ql/test/library-tests/frameworks/hapi/tests.expected index 4c752ee56a85..730bae77bf93 100644 --- a/javascript/ql/test/library-tests/frameworks/hapi/tests.expected +++ b/javascript/ql/test/library-tests/frameworks/hapi/tests.expected @@ -9,6 +9,11 @@ test_RouteSetup | src/hapiglue.js:17:1:18:2 | server2 ... dler\\n}) | | src/hapiglue.js:31:1:31:20 | server2.route(route) | | src/hapiglue.js:38:1:38:38 | server2 ... ler()}) | +| src/hapihapi.js:7:1:9:2 | server2 ... ler1\\n}) | +| src/hapihapi.js:12:1:15:7 | server2 ... }}) | +| src/hapihapi.js:17:1:18:2 | server2 ... dler\\n}) | +| src/hapihapi.js:29:1:29:20 | server2.route(route) | +| src/hapihapi.js:36:1:36:38 | server2 ... ler()}) | test_RequestExpr | src/hapi.js:13:32:13:38 | request | src/hapi.js:13:14:15:5 | functio ... n\\n } | | src/hapi.js:13:32:13:38 | request | src/hapi.js:13:14:15:5 | functio ... n\\n } | @@ -38,12 +43,27 @@ test_RequestExpr | src/hapiglue.js:27:3:27:9 | request | src/hapiglue.js:20:1:29:1 | functio ... oken;\\n} | | src/hapiglue.js:28:3:28:9 | request | src/hapiglue.js:20:1:29:1 | functio ... oken;\\n} | | src/hapiglue.js:36:22:36:24 | req | src/hapiglue.js:36:12:36:33 | functio ... hapi){} | +| src/hapihapi.js:13:32:13:38 | request | src/hapihapi.js:13:14:15:5 | functio ... n\\n } | +| src/hapihapi.js:13:32:13:38 | request | src/hapihapi.js:13:14:15:5 | functio ... n\\n } | +| src/hapihapi.js:14:9:14:15 | request | src/hapihapi.js:13:14:15:5 | functio ... n\\n } | +| src/hapihapi.js:17:48:17:54 | request | src/hapihapi.js:17:30:18:1 | functio ... ndler\\n} | +| src/hapihapi.js:20:19:20:25 | request | src/hapihapi.js:20:1:27:1 | functio ... oken;\\n} | +| src/hapihapi.js:20:19:20:25 | request | src/hapihapi.js:20:1:27:1 | functio ... oken;\\n} | +| src/hapihapi.js:21:3:21:9 | request | src/hapihapi.js:20:1:27:1 | functio ... oken;\\n} | +| src/hapihapi.js:22:3:22:9 | request | src/hapihapi.js:20:1:27:1 | functio ... oken;\\n} | +| src/hapihapi.js:23:3:23:9 | request | src/hapihapi.js:20:1:27:1 | functio ... oken;\\n} | +| src/hapihapi.js:24:3:24:9 | request | src/hapihapi.js:20:1:27:1 | functio ... oken;\\n} | +| src/hapihapi.js:25:3:25:9 | request | src/hapihapi.js:20:1:27:1 | functio ... oken;\\n} | +| src/hapihapi.js:26:3:26:9 | request | src/hapihapi.js:20:1:27:1 | functio ... oken;\\n} | +| src/hapihapi.js:34:22:34:24 | req | src/hapihapi.js:34:12:34:30 | function (req, h){} | test_HeaderAccess | src/hapi.js:25:3:25:21 | request.headers.baz | baz | | src/hapiglue.js:27:3:27:21 | request.headers.baz | baz | +| src/hapihapi.js:25:3:25:21 | request.headers.baz | baz | test_ResponseExpr | src/hapi.js:14:9:14:24 | request.response | src/hapi.js:13:14:15:5 | functio ... n\\n } | | src/hapiglue.js:14:9:14:24 | request.response | src/hapiglue.js:13:14:15:5 | functio ... n\\n } | +| src/hapihapi.js:14:9:14:24 | request.response | src/hapihapi.js:13:14:15:5 | functio ... n\\n } | test_RouteHandler | src/hapi.js:6:1:6:21 | functio ... er1(){} | src/hapi.js:4:15:4:31 | new Hapi.Server() | | src/hapi.js:13:14:15:5 | functio ... n\\n } | src/hapi.js:4:15:4:31 | new Hapi.Server() | @@ -55,9 +75,15 @@ test_RouteHandler | src/hapiglue.js:17:30:18:1 | functio ... ndler\\n} | src/hapiglue.js:4:15:4:69 | new Hap ... ptions) | | src/hapiglue.js:20:1:29:1 | functio ... oken;\\n} | src/hapiglue.js:4:15:4:69 | new Hap ... ptions) | | src/hapiglue.js:36:12:36:33 | functio ... hapi){} | src/hapiglue.js:4:15:4:69 | new Hap ... ptions) | +| src/hapihapi.js:6:1:6:21 | functio ... er1(){} | src/hapihapi.js:4:15:4:31 | new Hapi.Server() | +| src/hapihapi.js:13:14:15:5 | functio ... n\\n } | src/hapihapi.js:4:15:4:31 | new Hapi.Server() | +| src/hapihapi.js:17:30:18:1 | functio ... ndler\\n} | src/hapihapi.js:4:15:4:31 | new Hapi.Server() | +| src/hapihapi.js:20:1:27:1 | functio ... oken;\\n} | src/hapihapi.js:4:15:4:31 | new Hapi.Server() | +| src/hapihapi.js:34:12:34:30 | function (req, h){} | src/hapihapi.js:4:15:4:31 | new Hapi.Server() | test_HeaderDefinition | src/hapi.js:14:9:14:46 | request ... 1', '') | src/hapi.js:13:14:15:5 | functio ... n\\n } | | src/hapiglue.js:14:9:14:46 | request ... 1', '') | src/hapiglue.js:13:14:15:5 | functio ... n\\n } | +| src/hapihapi.js:14:9:14:46 | request ... 1', '') | src/hapihapi.js:13:14:15:5 | functio ... n\\n } | test_ServerDefinition | src/hapi.js:1:15:1:44 | new (re ... erver() | | src/hapi.js:4:15:4:31 | new Hapi.Server() | @@ -65,6 +91,8 @@ test_ServerDefinition | src/hapiglue.js:4:15:4:69 | new Hap ... ptions) | | src/hapiglue.js:43:19:43:24 | server | | src/hapiglue.js:44:45:44:51 | server_ | +| src/hapihapi.js:1:15:1:50 | new (re ... erver() | +| src/hapihapi.js:4:15:4:31 | new Hapi.Server() | test_RequestInputAccess | src/hapi.js:21:3:21:20 | request.rawPayload | body | src/hapi.js:20:1:27:1 | functio ... oken;\\n} | | src/hapi.js:22:3:22:21 | request.payload.foo | body | src/hapi.js:20:1:27:1 | functio ... oken;\\n} | @@ -80,6 +108,12 @@ test_RequestInputAccess | src/hapiglue.js:26:3:26:20 | request.url.origin | url | src/hapiglue.js:20:1:29:1 | functio ... oken;\\n} | | src/hapiglue.js:27:3:27:21 | request.headers.baz | header | src/hapiglue.js:20:1:29:1 | functio ... oken;\\n} | | src/hapiglue.js:28:3:28:21 | request.state.token | cookie | src/hapiglue.js:20:1:29:1 | functio ... oken;\\n} | +| src/hapihapi.js:21:3:21:20 | request.rawPayload | body | src/hapihapi.js:20:1:27:1 | functio ... oken;\\n} | +| src/hapihapi.js:22:3:22:21 | request.payload.foo | body | src/hapihapi.js:20:1:27:1 | functio ... oken;\\n} | +| src/hapihapi.js:23:3:23:19 | request.query.bar | parameter | src/hapihapi.js:20:1:27:1 | functio ... oken;\\n} | +| src/hapihapi.js:24:3:24:18 | request.url.path | url | src/hapihapi.js:20:1:27:1 | functio ... oken;\\n} | +| src/hapihapi.js:25:3:25:21 | request.headers.baz | header | src/hapihapi.js:20:1:27:1 | functio ... oken;\\n} | +| src/hapihapi.js:26:3:26:21 | request.state.token | cookie | src/hapihapi.js:20:1:27:1 | functio ... oken;\\n} | test_RouteSetup_getServer | src/hapi.js:7:1:9:2 | server2 ... ler1\\n}) | src/hapi.js:4:15:4:31 | new Hapi.Server() | | src/hapi.js:12:1:15:7 | server2 ... }}) | src/hapi.js:4:15:4:31 | new Hapi.Server() | @@ -91,9 +125,15 @@ test_RouteSetup_getServer | src/hapiglue.js:17:1:18:2 | server2 ... dler\\n}) | src/hapiglue.js:4:15:4:69 | new Hap ... ptions) | | src/hapiglue.js:31:1:31:20 | server2.route(route) | src/hapiglue.js:4:15:4:69 | new Hap ... ptions) | | src/hapiglue.js:38:1:38:38 | server2 ... ler()}) | src/hapiglue.js:4:15:4:69 | new Hap ... ptions) | +| src/hapihapi.js:7:1:9:2 | server2 ... ler1\\n}) | src/hapihapi.js:4:15:4:31 | new Hapi.Server() | +| src/hapihapi.js:12:1:15:7 | server2 ... }}) | src/hapihapi.js:4:15:4:31 | new Hapi.Server() | +| src/hapihapi.js:17:1:18:2 | server2 ... dler\\n}) | src/hapihapi.js:4:15:4:31 | new Hapi.Server() | +| src/hapihapi.js:29:1:29:20 | server2.route(route) | src/hapihapi.js:4:15:4:31 | new Hapi.Server() | +| src/hapihapi.js:36:1:36:38 | server2 ... ler()}) | src/hapihapi.js:4:15:4:31 | new Hapi.Server() | test_HeaderDefinition_defines | src/hapi.js:14:9:14:46 | request ... 1', '') | header1 | | | src/hapiglue.js:14:9:14:46 | request ... 1', '') | header1 | | +| src/hapihapi.js:14:9:14:46 | request ... 1', '') | header1 | | test_RouteSetup_getARouteHandler | src/hapi.js:7:1:9:2 | server2 ... ler1\\n}) | src/hapi.js:6:1:6:21 | functio ... er1(){} | | src/hapi.js:12:1:15:7 | server2 ... }}) | src/hapi.js:13:14:15:5 | functio ... n\\n } | @@ -109,6 +149,13 @@ test_RouteSetup_getARouteHandler | src/hapiglue.js:38:1:38:38 | server2 ... ler()}) | src/hapiglue.js:35:1:37:1 | return of function getHandler | | src/hapiglue.js:38:1:38:38 | server2 ... ler()}) | src/hapiglue.js:36:12:36:33 | functio ... hapi){} | | src/hapiglue.js:38:1:38:38 | server2 ... ler()}) | src/hapiglue.js:38:25:38:36 | getHandler() | +| src/hapihapi.js:7:1:9:2 | server2 ... ler1\\n}) | src/hapihapi.js:6:1:6:21 | functio ... er1(){} | +| src/hapihapi.js:12:1:15:7 | server2 ... }}) | src/hapihapi.js:13:14:15:5 | functio ... n\\n } | +| src/hapihapi.js:17:1:18:2 | server2 ... dler\\n}) | src/hapihapi.js:17:30:18:1 | functio ... ndler\\n} | +| src/hapihapi.js:29:1:29:20 | server2.route(route) | src/hapihapi.js:20:1:27:1 | functio ... oken;\\n} | +| src/hapihapi.js:36:1:36:38 | server2 ... ler()}) | src/hapihapi.js:33:1:35:1 | return of function getHandler | +| src/hapihapi.js:36:1:36:38 | server2 ... ler()}) | src/hapihapi.js:34:12:34:30 | function (req, h){} | +| src/hapihapi.js:36:1:36:38 | server2 ... ler()}) | src/hapihapi.js:36:25:36:36 | getHandler() | test_RouteHandler_getARequestExpr | src/hapi.js:13:14:15:5 | functio ... n\\n } | src/hapi.js:13:32:13:38 | request | | src/hapi.js:13:14:15:5 | functio ... n\\n } | src/hapi.js:13:32:13:38 | request | @@ -138,9 +185,24 @@ test_RouteHandler_getARequestExpr | src/hapiglue.js:20:1:29:1 | functio ... oken;\\n} | src/hapiglue.js:27:3:27:9 | request | | src/hapiglue.js:20:1:29:1 | functio ... oken;\\n} | src/hapiglue.js:28:3:28:9 | request | | src/hapiglue.js:36:12:36:33 | functio ... hapi){} | src/hapiglue.js:36:22:36:24 | req | +| src/hapihapi.js:13:14:15:5 | functio ... n\\n } | src/hapihapi.js:13:32:13:38 | request | +| src/hapihapi.js:13:14:15:5 | functio ... n\\n } | src/hapihapi.js:13:32:13:38 | request | +| src/hapihapi.js:13:14:15:5 | functio ... n\\n } | src/hapihapi.js:14:9:14:15 | request | +| src/hapihapi.js:17:30:18:1 | functio ... ndler\\n} | src/hapihapi.js:17:48:17:54 | request | +| src/hapihapi.js:20:1:27:1 | functio ... oken;\\n} | src/hapihapi.js:20:19:20:25 | request | +| src/hapihapi.js:20:1:27:1 | functio ... oken;\\n} | src/hapihapi.js:20:19:20:25 | request | +| src/hapihapi.js:20:1:27:1 | functio ... oken;\\n} | src/hapihapi.js:21:3:21:9 | request | +| src/hapihapi.js:20:1:27:1 | functio ... oken;\\n} | src/hapihapi.js:22:3:22:9 | request | +| src/hapihapi.js:20:1:27:1 | functio ... oken;\\n} | src/hapihapi.js:23:3:23:9 | request | +| src/hapihapi.js:20:1:27:1 | functio ... oken;\\n} | src/hapihapi.js:24:3:24:9 | request | +| src/hapihapi.js:20:1:27:1 | functio ... oken;\\n} | src/hapihapi.js:25:3:25:9 | request | +| src/hapihapi.js:20:1:27:1 | functio ... oken;\\n} | src/hapihapi.js:26:3:26:9 | request | +| src/hapihapi.js:34:12:34:30 | function (req, h){} | src/hapihapi.js:34:22:34:24 | req | test_HeaderDefinition_getAHeaderName | src/hapi.js:14:9:14:46 | request ... 1', '') | header1 | | src/hapiglue.js:14:9:14:46 | request ... 1', '') | header1 | +| src/hapihapi.js:14:9:14:46 | request ... 1', '') | header1 | test_RouteHandler_getAResponseHeader | src/hapi.js:13:14:15:5 | functio ... n\\n } | header1 | src/hapi.js:14:9:14:46 | request ... 1', '') | | src/hapiglue.js:13:14:15:5 | functio ... n\\n } | header1 | src/hapiglue.js:14:9:14:46 | request ... 1', '') | +| src/hapihapi.js:13:14:15:5 | functio ... n\\n } | header1 | src/hapihapi.js:14:9:14:46 | request ... 1', '') | diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected index 17c3e12dedda..db9c0b11a352 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected @@ -51,6 +51,7 @@ | express.js:8:20:8:32 | req.query.bar | express.js:8:20:8:32 | req.query.bar | express.js:8:20:8:32 | req.query.bar | This path depends on a $@. | express.js:8:20:8:32 | req.query.bar | user-provided value | | handlebars.js:11:32:11:39 | filePath | handlebars.js:29:46:29:60 | req.params.path | handlebars.js:11:32:11:39 | filePath | This path depends on a $@. | handlebars.js:29:46:29:60 | req.params.path | user-provided value | | handlebars.js:15:25:15:32 | filePath | handlebars.js:43:15:43:29 | req.params.path | handlebars.js:15:25:15:32 | filePath | This path depends on a $@. | handlebars.js:43:15:43:29 | req.params.path | user-provided value | +| hapi.js:15:44:15:51 | filepath | hapi.js:14:30:14:51 | request ... ilepath | hapi.js:15:44:15:51 | filepath | This path depends on a $@. | hapi.js:14:30:14:51 | request ... ilepath | user-provided value | | normalizedPaths.js:13:19:13:22 | path | normalizedPaths.js:11:14:11:27 | req.query.path | normalizedPaths.js:13:19:13:22 | path | This path depends on a $@. | normalizedPaths.js:11:14:11:27 | req.query.path | user-provided value | | normalizedPaths.js:14:19:14:29 | './' + path | normalizedPaths.js:11:14:11:27 | req.query.path | normalizedPaths.js:14:19:14:29 | './' + path | This path depends on a $@. | normalizedPaths.js:11:14:11:27 | req.query.path | user-provided value | | normalizedPaths.js:15:19:15:38 | path + '/index.html' | normalizedPaths.js:11:14:11:27 | req.query.path | normalizedPaths.js:15:19:15:38 | path + '/index.html' | This path depends on a $@. | normalizedPaths.js:11:14:11:27 | req.query.path | user-provided value | @@ -344,6 +345,8 @@ edges | handlebars.js:13:73:13:80 | filePath | handlebars.js:15:25:15:32 | filePath | provenance | | | handlebars.js:29:46:29:60 | req.params.path | handlebars.js:10:51:10:58 | filePath | provenance | | | handlebars.js:43:15:43:29 | req.params.path | handlebars.js:13:73:13:80 | filePath | provenance | | +| hapi.js:14:19:14:51 | filepath | hapi.js:15:44:15:51 | filepath | provenance | | +| hapi.js:14:30:14:51 | request ... ilepath | hapi.js:14:19:14:51 | filepath | provenance | | | normalizedPaths.js:11:7:11:27 | path | normalizedPaths.js:13:19:13:22 | path | provenance | | | normalizedPaths.js:11:7:11:27 | path | normalizedPaths.js:14:26:14:29 | path | provenance | | | normalizedPaths.js:11:7:11:27 | path | normalizedPaths.js:15:19:15:22 | path | provenance | | @@ -821,6 +824,9 @@ nodes | handlebars.js:15:25:15:32 | filePath | semmle.label | filePath | | handlebars.js:29:46:29:60 | req.params.path | semmle.label | req.params.path | | handlebars.js:43:15:43:29 | req.params.path | semmle.label | req.params.path | +| hapi.js:14:19:14:51 | filepath | semmle.label | filepath | +| hapi.js:14:30:14:51 | request ... ilepath | semmle.label | request ... ilepath | +| hapi.js:15:44:15:51 | filepath | semmle.label | filepath | | normalizedPaths.js:11:7:11:27 | path | semmle.label | path | | normalizedPaths.js:11:14:11:27 | req.query.path | semmle.label | req.query.path | | normalizedPaths.js:13:19:13:22 | path | semmle.label | path | diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/hapi.js b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/hapi.js new file mode 100644 index 000000000000..c90da206824e --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/hapi.js @@ -0,0 +1,22 @@ +const Hapi = require('@hapi/hapi'); +const fs = require('fs').promises; + +(async () => { + const server = Hapi.server({ + port: 3005, + host: 'localhost' + }); + + server.route({ + method: 'GET', + path: '/hello', + handler: async (request, h) => { + const filepath = request.query.filepath; // $ Source + const data = await fs.readFile(filepath, 'utf8'); // $ Alert + const firstLine = data.split('\n')[0]; + return firstLine; + } + }); + + await server.start(); +})();