From c49ffa01eebc42df13a904d9fa1e2d972d0a0a0d Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 3 Apr 2025 12:27:42 +0200 Subject: [PATCH 1/2] JS: Enable post-processed inline expectations for query predicates --- .../util/test/InlineExpectationsTest.qll | 49 ++++++++++++++++--- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/shared/util/codeql/util/test/InlineExpectationsTest.qll b/shared/util/codeql/util/test/InlineExpectationsTest.qll index f67f54da9378..5fe8932808c9 100644 --- a/shared/util/codeql/util/test/InlineExpectationsTest.qll +++ b/shared/util/codeql/util/test/InlineExpectationsTest.qll @@ -858,17 +858,26 @@ module TestPostProcessing { bindingset[result] string getARelevantTag() { any() } - predicate tagMatches = PathProblemSourceTestInput::tagMatches/2; + bindingset[expectedTag, actualTag] + predicate tagMatches(string expectedTag, string actualTag) { + PathProblemSourceTestInput::tagMatches(expectedTag, actualTag) + or + not exists(getQueryKind()) and + expectedTag = actualTag + } bindingset[expectedTag] predicate tagIsOptional(string expectedTag) { - // ignore irrelevant tags - not expectedTag.regexpMatch(getTagRegex()) - or - // ignore tags annotated with a query ID that does not match the current query ID - exists(string queryId | - queryId = expectedTag.regexpCapture(getTagRegex(), 3) and - queryId != getQueryId() + exists(getQueryKind()) and + ( + // ignore irrelevant tags + not expectedTag.regexpMatch(getTagRegex()) + or + // ignore tags annotated with a query ID that does not match the current query ID + exists(string queryId | + queryId = expectedTag.regexpCapture(getTagRegex(), 3) and + queryId != getQueryId() + ) ) } @@ -911,6 +920,28 @@ module TestPostProcessing { not hasPathProblemSink(row, location, _, _) } + /** + * Holds if a custom query predicate implies `tag=value` at the given `location`. + * + * Such query predicates are only allowed in kind-less queries, usually in the form + * of a `.ql` file in a test folder, with a same-named `.qlref` file to enable + * post-processing for that test. + */ + private predicate hasCustomQueryPredicateResult( + int row, TestLocation location, string element, string tag, string value + ) { + not exists(getQueryKind()) and + queryResults(tag, row, 0, location.getRelativeUrl()) and + queryResults(tag, row, 1, element) and + ( + queryResults(tag, row, 2, value) and + not queryResults(tag, row, 3, _) // ignore if arity is greater than expected + or + not queryResults(tag, row, 2, _) and + value = "" // allow value-less expectations for unary predicates + ) + } + /** * Gets the expected value for result row `row`, if any. This value must * match the value at the corresponding path-problem source (if it is @@ -939,6 +970,8 @@ module TestPostProcessing { or value = getValue(row) ) + or + hasCustomQueryPredicateResult(_, location, element, tag, value) } } From 14c5495b4c48b9d53f1e6e38c9d2e386bdca623e Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 3 Apr 2025 13:21:26 +0200 Subject: [PATCH 2/2] JS: Use in SensitiveActions test as an example --- .../SensitiveActions/tests.expected | 22 ++++++++-------- .../library-tests/SensitiveActions/tests.ql | 2 +- .../SensitiveActions/tests.qlref | 2 ++ .../library-tests/SensitiveActions/tst.js | 26 +++++++++---------- 4 files changed, 27 insertions(+), 25 deletions(-) create mode 100644 javascript/ql/test/library-tests/SensitiveActions/tests.qlref diff --git a/javascript/ql/test/library-tests/SensitiveActions/tests.expected b/javascript/ql/test/library-tests/SensitiveActions/tests.expected index 2c0dfff14f75..cbbe7c1a0db8 100644 --- a/javascript/ql/test/library-tests/SensitiveActions/tests.expected +++ b/javascript/ql/test/library-tests/SensitiveActions/tests.expected @@ -31,14 +31,14 @@ sensitiveAction | tst.js:23:1:23:25 | require ... .exit() | | tst.js:24:1:24:21 | global. ... .exit() | sensitiveExpr -| tst.js:1:1:1:8 | password | -| tst.js:2:1:2:8 | PassWord | -| tst.js:3:1:3:21 | myPassw ... eartext | -| tst.js:4:1:4:10 | x.password | -| tst.js:5:1:5:11 | getPassword | -| tst.js:5:1:5:13 | getPassword() | -| tst.js:6:1:6:13 | x.getPassword | -| tst.js:6:1:6:15 | x.getPassword() | -| tst.js:7:1:7:15 | get("password") | -| tst.js:8:1:8:17 | x.get("password") | -| tst.js:21:1:21:6 | secret | +| tst.js:1:1:1:8 | password | password | +| tst.js:2:1:2:8 | PassWord | password | +| tst.js:3:1:3:21 | myPassw ... eartext | password | +| tst.js:4:1:4:10 | x.password | password | +| tst.js:5:1:5:11 | getPassword | password | +| tst.js:5:1:5:13 | getPassword() | password | +| tst.js:6:1:6:13 | x.getPassword | password | +| tst.js:6:1:6:15 | x.getPassword() | password | +| tst.js:7:1:7:15 | get("password") | password | +| tst.js:8:1:8:17 | x.get("password") | password | +| tst.js:21:1:21:6 | secret | secret | diff --git a/javascript/ql/test/library-tests/SensitiveActions/tests.ql b/javascript/ql/test/library-tests/SensitiveActions/tests.ql index e1cd2cbd095b..d7273fe13b7d 100644 --- a/javascript/ql/test/library-tests/SensitiveActions/tests.ql +++ b/javascript/ql/test/library-tests/SensitiveActions/tests.ql @@ -20,4 +20,4 @@ query predicate processTermination(NodeJSLib::ProcessTermination term) { any() } query predicate sensitiveAction(SensitiveAction ac) { any() } -query predicate sensitiveExpr(SensitiveNode e) { any() } +query predicate sensitiveExpr(SensitiveNode e, string kind) { kind = e.getClassification() } diff --git a/javascript/ql/test/library-tests/SensitiveActions/tests.qlref b/javascript/ql/test/library-tests/SensitiveActions/tests.qlref new file mode 100644 index 000000000000..8581a3f8b748 --- /dev/null +++ b/javascript/ql/test/library-tests/SensitiveActions/tests.qlref @@ -0,0 +1,2 @@ +query: tests.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql diff --git a/javascript/ql/test/library-tests/SensitiveActions/tst.js b/javascript/ql/test/library-tests/SensitiveActions/tst.js index c4a627b742c5..c6fa5aa873b7 100644 --- a/javascript/ql/test/library-tests/SensitiveActions/tst.js +++ b/javascript/ql/test/library-tests/SensitiveActions/tst.js @@ -1,11 +1,11 @@ -password; -PassWord; -myPasswordInCleartext; -x.password; -getPassword(); -x.getPassword(); -get("password"); -x.get("password"); +password; // $ cleartextPasswordExpr sensitiveExpr=password +PassWord; // $ cleartextPasswordExpr sensitiveExpr=password +myPasswordInCleartext; // $ cleartextPasswordExpr sensitiveExpr=password +x.password; // $ cleartextPasswordExpr sensitiveExpr=password +getPassword(); // $ cleartextPasswordExpr sensitiveExpr=password +x.getPassword(); // $ cleartextPasswordExpr sensitiveExpr=password +get("password"); // $ cleartextPasswordExpr sensitiveExpr=password +x.get("password"); // $ cleartextPasswordExpr sensitiveExpr=password hashed_password; password_hashed; @@ -15,13 +15,13 @@ hashedPassword; var exit = require('exit'); var e = process.exit; -e(); -exit(); +e(); // $ processTermination sensitiveAction +exit(); // $ processTermination sensitiveAction -secret; +secret; // $ sensitiveExpr=secret -require("process").exit(); -global.process.exit(); +require("process").exit(); // $ processTermination sensitiveAction +global.process.exit(); // $ processTermination sensitiveAction get("https://example.com/news?password=true") get("https://username:password@example.com")