From ad6f800022beae4d874adfe224dc6ace5965221a Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Thu, 29 Jan 2026 13:52:10 +0000 Subject: [PATCH 1/5] Pretty print model numbers in tests --- python/ql/lib/utils/test/PrettyPrintModels.ql | 6 ++ .../UnsafeUnpack.expected | 32 +++++---- .../UnsafeUnpack.qlref | 3 +- .../CWE-409/DecompressionBombs.expected | 70 ++++++++++--------- .../Security/CWE-409/DecompressionBombs.qlref | 3 +- .../SqlInjection.expected | 8 ++- .../SqlInjection.qlref | 3 +- .../Tests1/HeaderInjection.expected | 23 +++--- .../Tests1/HeaderInjection.qlref | 3 +- 9 files changed, 85 insertions(+), 66 deletions(-) create mode 100644 python/ql/lib/utils/test/PrettyPrintModels.ql diff --git a/python/ql/lib/utils/test/PrettyPrintModels.ql b/python/ql/lib/utils/test/PrettyPrintModels.ql new file mode 100644 index 000000000000..91c398543289 --- /dev/null +++ b/python/ql/lib/utils/test/PrettyPrintModels.ql @@ -0,0 +1,6 @@ +/** + * @kind test-postprocess + */ + +import semmle.python.frameworks.data.internal.ApiGraphModels +import codeql.dataflow.test.ProvenancePathGraph::TestPostProcessing::TranslateProvenanceResults diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.expected b/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.expected index 69bb8d30e8f4..de8721382bf3 100644 --- a/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.expected +++ b/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.expected @@ -1,3 +1,17 @@ +#select +| UnsafeUnpack.py:19:35:19:41 | ControlFlowNode for tarpath | UnsafeUnpack.py:5:26:5:32 | ControlFlowNode for ImportMember | UnsafeUnpack.py:19:35:19:41 | ControlFlowNode for tarpath | Unsafe extraction from a malicious tarball retrieved from a remote location. | +| UnsafeUnpack.py:34:23:34:38 | ControlFlowNode for local_ziped_path | UnsafeUnpack.py:33:50:33:65 | ControlFlowNode for local_ziped_path | UnsafeUnpack.py:34:23:34:38 | ControlFlowNode for local_ziped_path | Unsafe extraction from a malicious tarball retrieved from a remote location. | +| UnsafeUnpack.py:48:23:48:37 | ControlFlowNode for compressed_file | UnsafeUnpack.py:47:20:47:34 | ControlFlowNode for compressed_file | UnsafeUnpack.py:48:23:48:37 | ControlFlowNode for compressed_file | Unsafe extraction from a malicious tarball retrieved from a remote location. | +| UnsafeUnpack.py:52:23:52:37 | ControlFlowNode for compressed_file | UnsafeUnpack.py:51:19:51:36 | ControlFlowNode for Attribute() | UnsafeUnpack.py:52:23:52:37 | ControlFlowNode for compressed_file | Unsafe extraction from a malicious tarball retrieved from a remote location. | +| UnsafeUnpack.py:66:23:66:37 | ControlFlowNode for compressed_file | UnsafeUnpack.py:65:19:65:31 | ControlFlowNode for Attribute | UnsafeUnpack.py:66:23:66:37 | ControlFlowNode for compressed_file | Unsafe extraction from a malicious tarball retrieved from a remote location. | +| UnsafeUnpack.py:87:23:87:29 | ControlFlowNode for tarpath | UnsafeUnpack.py:79:16:79:28 | ControlFlowNode for Attribute | UnsafeUnpack.py:87:23:87:29 | ControlFlowNode for tarpath | Unsafe extraction from a malicious tarball retrieved from a remote location. | +| UnsafeUnpack.py:105:35:105:42 | ControlFlowNode for savepath | UnsafeUnpack.py:103:32:103:44 | ControlFlowNode for Attribute | UnsafeUnpack.py:105:35:105:42 | ControlFlowNode for savepath | Unsafe extraction from a malicious tarball retrieved from a remote location. | +| UnsafeUnpack.py:112:35:112:43 | ControlFlowNode for file_path | UnsafeUnpack.py:108:22:108:34 | ControlFlowNode for Attribute | UnsafeUnpack.py:112:35:112:43 | ControlFlowNode for file_path | Unsafe extraction from a malicious tarball retrieved from a remote location. | +| UnsafeUnpack.py:120:41:120:58 | ControlFlowNode for uploaded_file_path | UnsafeUnpack.py:116:27:116:39 | ControlFlowNode for Attribute | UnsafeUnpack.py:120:41:120:58 | ControlFlowNode for uploaded_file_path | Unsafe extraction from a malicious tarball retrieved from a remote location. | +| UnsafeUnpack.py:142:49:142:51 | ControlFlowNode for tar | UnsafeUnpack.py:140:23:140:35 | ControlFlowNode for Attribute | UnsafeUnpack.py:142:49:142:51 | ControlFlowNode for tar | Unsafe extraction from a malicious tarball retrieved from a remote location. | +| UnsafeUnpack.py:167:67:167:72 | ControlFlowNode for result | UnsafeUnpack.py:158:32:158:44 | ControlFlowNode for Attribute | UnsafeUnpack.py:167:67:167:72 | ControlFlowNode for result | Unsafe extraction from a malicious tarball retrieved from a remote location. | +| UnsafeUnpack.py:176:1:176:34 | ControlFlowNode for Attribute() | UnsafeUnpack.py:79:16:79:28 | ControlFlowNode for Attribute | UnsafeUnpack.py:176:1:176:34 | ControlFlowNode for Attribute() | Unsafe extraction from a malicious tarball retrieved from a remote location. | +| UnsafeUnpack.py:201:29:201:36 | ControlFlowNode for Attribute | UnsafeUnpack.py:194:53:194:55 | ControlFlowNode for tmp | UnsafeUnpack.py:201:29:201:36 | ControlFlowNode for Attribute | Unsafe extraction from a malicious tarball retrieved from a remote location. | edges | UnsafeUnpack.py:5:26:5:32 | ControlFlowNode for ImportMember | UnsafeUnpack.py:5:26:5:32 | ControlFlowNode for request | provenance | | | UnsafeUnpack.py:5:26:5:32 | ControlFlowNode for request | UnsafeUnpack.py:11:18:11:24 | ControlFlowNode for request | provenance | | @@ -75,7 +89,7 @@ edges | UnsafeUnpack.py:161:19:161:21 | ControlFlowNode for tar | UnsafeUnpack.py:163:33:163:35 | ControlFlowNode for tar | provenance | | | UnsafeUnpack.py:161:25:161:46 | ControlFlowNode for Attribute() | UnsafeUnpack.py:161:19:161:21 | ControlFlowNode for tar | provenance | | | UnsafeUnpack.py:161:38:161:45 | ControlFlowNode for savepath | UnsafeUnpack.py:161:25:161:46 | ControlFlowNode for Attribute() | provenance | Config | -| UnsafeUnpack.py:161:38:161:45 | ControlFlowNode for savepath | UnsafeUnpack.py:161:25:161:46 | ControlFlowNode for Attribute() | provenance | MaD:70 | +| UnsafeUnpack.py:161:38:161:45 | ControlFlowNode for savepath | UnsafeUnpack.py:161:25:161:46 | ControlFlowNode for Attribute() | provenance | MaD:1 | | UnsafeUnpack.py:163:23:163:28 | ControlFlowNode for member | UnsafeUnpack.py:166:37:166:42 | ControlFlowNode for member | provenance | | | UnsafeUnpack.py:163:33:163:35 | ControlFlowNode for tar | UnsafeUnpack.py:163:23:163:28 | ControlFlowNode for member | provenance | | | UnsafeUnpack.py:166:23:166:28 | [post] ControlFlowNode for result | UnsafeUnpack.py:167:67:167:72 | ControlFlowNode for result | provenance | | @@ -92,6 +106,8 @@ edges | UnsafeUnpack.py:176:17:176:23 | ControlFlowNode for tarpath | UnsafeUnpack.py:176:1:176:34 | ControlFlowNode for Attribute() | provenance | Config | | UnsafeUnpack.py:194:53:194:55 | ControlFlowNode for tmp | UnsafeUnpack.py:201:29:201:31 | ControlFlowNode for tmp | provenance | | | UnsafeUnpack.py:201:29:201:31 | ControlFlowNode for tmp | UnsafeUnpack.py:201:29:201:36 | ControlFlowNode for Attribute | provenance | Config | +models +| 1 | Summary: tarfile; Member[open]; Argument[0,name:,2,fileobj:]; ReturnValue; taint | nodes | UnsafeUnpack.py:5:26:5:32 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember | | UnsafeUnpack.py:5:26:5:32 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | @@ -188,17 +204,3 @@ nodes | UnsafeUnpack.py:201:29:201:31 | ControlFlowNode for tmp | semmle.label | ControlFlowNode for tmp | | UnsafeUnpack.py:201:29:201:36 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | subpaths -#select -| UnsafeUnpack.py:19:35:19:41 | ControlFlowNode for tarpath | UnsafeUnpack.py:5:26:5:32 | ControlFlowNode for ImportMember | UnsafeUnpack.py:19:35:19:41 | ControlFlowNode for tarpath | Unsafe extraction from a malicious tarball retrieved from a remote location. | -| UnsafeUnpack.py:34:23:34:38 | ControlFlowNode for local_ziped_path | UnsafeUnpack.py:33:50:33:65 | ControlFlowNode for local_ziped_path | UnsafeUnpack.py:34:23:34:38 | ControlFlowNode for local_ziped_path | Unsafe extraction from a malicious tarball retrieved from a remote location. | -| UnsafeUnpack.py:48:23:48:37 | ControlFlowNode for compressed_file | UnsafeUnpack.py:47:20:47:34 | ControlFlowNode for compressed_file | UnsafeUnpack.py:48:23:48:37 | ControlFlowNode for compressed_file | Unsafe extraction from a malicious tarball retrieved from a remote location. | -| UnsafeUnpack.py:52:23:52:37 | ControlFlowNode for compressed_file | UnsafeUnpack.py:51:19:51:36 | ControlFlowNode for Attribute() | UnsafeUnpack.py:52:23:52:37 | ControlFlowNode for compressed_file | Unsafe extraction from a malicious tarball retrieved from a remote location. | -| UnsafeUnpack.py:66:23:66:37 | ControlFlowNode for compressed_file | UnsafeUnpack.py:65:19:65:31 | ControlFlowNode for Attribute | UnsafeUnpack.py:66:23:66:37 | ControlFlowNode for compressed_file | Unsafe extraction from a malicious tarball retrieved from a remote location. | -| UnsafeUnpack.py:87:23:87:29 | ControlFlowNode for tarpath | UnsafeUnpack.py:79:16:79:28 | ControlFlowNode for Attribute | UnsafeUnpack.py:87:23:87:29 | ControlFlowNode for tarpath | Unsafe extraction from a malicious tarball retrieved from a remote location. | -| UnsafeUnpack.py:105:35:105:42 | ControlFlowNode for savepath | UnsafeUnpack.py:103:32:103:44 | ControlFlowNode for Attribute | UnsafeUnpack.py:105:35:105:42 | ControlFlowNode for savepath | Unsafe extraction from a malicious tarball retrieved from a remote location. | -| UnsafeUnpack.py:112:35:112:43 | ControlFlowNode for file_path | UnsafeUnpack.py:108:22:108:34 | ControlFlowNode for Attribute | UnsafeUnpack.py:112:35:112:43 | ControlFlowNode for file_path | Unsafe extraction from a malicious tarball retrieved from a remote location. | -| UnsafeUnpack.py:120:41:120:58 | ControlFlowNode for uploaded_file_path | UnsafeUnpack.py:116:27:116:39 | ControlFlowNode for Attribute | UnsafeUnpack.py:120:41:120:58 | ControlFlowNode for uploaded_file_path | Unsafe extraction from a malicious tarball retrieved from a remote location. | -| UnsafeUnpack.py:142:49:142:51 | ControlFlowNode for tar | UnsafeUnpack.py:140:23:140:35 | ControlFlowNode for Attribute | UnsafeUnpack.py:142:49:142:51 | ControlFlowNode for tar | Unsafe extraction from a malicious tarball retrieved from a remote location. | -| UnsafeUnpack.py:167:67:167:72 | ControlFlowNode for result | UnsafeUnpack.py:158:32:158:44 | ControlFlowNode for Attribute | UnsafeUnpack.py:167:67:167:72 | ControlFlowNode for result | Unsafe extraction from a malicious tarball retrieved from a remote location. | -| UnsafeUnpack.py:176:1:176:34 | ControlFlowNode for Attribute() | UnsafeUnpack.py:79:16:79:28 | ControlFlowNode for Attribute | UnsafeUnpack.py:176:1:176:34 | ControlFlowNode for Attribute() | Unsafe extraction from a malicious tarball retrieved from a remote location. | -| UnsafeUnpack.py:201:29:201:36 | ControlFlowNode for Attribute | UnsafeUnpack.py:194:53:194:55 | ControlFlowNode for tmp | UnsafeUnpack.py:201:29:201:36 | ControlFlowNode for Attribute | Unsafe extraction from a malicious tarball retrieved from a remote location. | diff --git a/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.qlref b/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.qlref index 90e5db651a0d..564ed9515aa9 100644 --- a/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.qlref +++ b/python/ql/test/experimental/query-tests/Security/CWE-022-UnsafeUnpacking/UnsafeUnpack.qlref @@ -1 +1,2 @@ -experimental/Security/CWE-022bis/UnsafeUnpack.ql \ No newline at end of file +query: experimental/Security/CWE-022bis/UnsafeUnpack.ql +postprocess: utils/test/PrettyPrintModels.ql diff --git a/python/ql/test/experimental/query-tests/Security/CWE-409/DecompressionBombs.expected b/python/ql/test/experimental/query-tests/Security/CWE-409/DecompressionBombs.expected index e32edeb702bb..c9f584c987e5 100644 --- a/python/ql/test/experimental/query-tests/Security/CWE-409/DecompressionBombs.expected +++ b/python/ql/test/experimental/query-tests/Security/CWE-409/DecompressionBombs.expected @@ -1,23 +1,52 @@ +#select +| test.py:11:5:11:52 | ControlFlowNode for Attribute() | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:11:5:11:52 | ControlFlowNode for Attribute() | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:12:5:12:48 | ControlFlowNode for Attribute() | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:12:5:12:48 | ControlFlowNode for Attribute() | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:15:14:15:29 | ControlFlowNode for Attribute() | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:15:14:15:29 | ControlFlowNode for Attribute() | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:19:14:19:39 | ControlFlowNode for Attribute() | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:19:14:19:39 | ControlFlowNode for Attribute() | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:22:5:22:60 | ControlFlowNode for Attribute() | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:22:5:22:60 | ControlFlowNode for Attribute() | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:24:5:24:52 | ControlFlowNode for Attribute() | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:24:5:24:52 | ControlFlowNode for Attribute() | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:25:5:25:55 | ControlFlowNode for Attribute() | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:25:5:25:55 | ControlFlowNode for Attribute() | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:26:5:26:57 | ControlFlowNode for Attribute() | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:26:5:26:57 | ControlFlowNode for Attribute() | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:27:5:27:50 | ControlFlowNode for Attribute() | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:27:5:27:50 | ControlFlowNode for Attribute() | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:28:5:28:60 | ControlFlowNode for Attribute() | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:28:5:28:60 | ControlFlowNode for Attribute() | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:35:27:35:35 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:35:27:35:35 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:39:15:39:23 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:39:15:39:23 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:40:19:40:27 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:40:19:40:27 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:44:14:44:22 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:44:14:44:22 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:45:17:45:25 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:45:17:45:25 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:49:15:49:23 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:49:15:49:23 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:50:19:50:27 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:50:19:50:27 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:54:15:54:23 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:54:15:54:23 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:55:19:55:27 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:55:19:55:27 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:59:40:59:48 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:59:40:59:48 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:61:23:61:31 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:61:23:61:31 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:62:21:62:29 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:62:21:62:29 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:64:40:64:48 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:64:40:64:48 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:65:22:65:30 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:65:22:65:30 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:66:21:66:29 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:66:21:66:29 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:67:42:67:50 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:67:42:67:50 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:68:23:68:31 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:68:23:68:31 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | +| test.py:69:36:69:44 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:69:36:69:44 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | edges | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:11:21:11:29 | ControlFlowNode for file_path | provenance | | | test.py:11:5:11:35 | ControlFlowNode for Attribute() | test.py:11:5:11:52 | ControlFlowNode for Attribute() | provenance | Config | -| test.py:11:21:11:29 | ControlFlowNode for file_path | test.py:11:5:11:35 | ControlFlowNode for Attribute() | provenance | MaD:87 | +| test.py:11:21:11:29 | ControlFlowNode for file_path | test.py:11:5:11:35 | ControlFlowNode for Attribute() | provenance | MaD:1 | | test.py:11:21:11:29 | ControlFlowNode for file_path | test.py:11:5:11:52 | ControlFlowNode for Attribute() | provenance | Config | | test.py:11:21:11:29 | ControlFlowNode for file_path | test.py:12:21:12:29 | ControlFlowNode for file_path | provenance | | | test.py:12:5:12:35 | ControlFlowNode for Attribute() | test.py:12:5:12:48 | ControlFlowNode for Attribute() | provenance | Config | -| test.py:12:21:12:29 | ControlFlowNode for file_path | test.py:12:5:12:35 | ControlFlowNode for Attribute() | provenance | MaD:87 | +| test.py:12:21:12:29 | ControlFlowNode for file_path | test.py:12:5:12:35 | ControlFlowNode for Attribute() | provenance | MaD:1 | | test.py:12:21:12:29 | ControlFlowNode for file_path | test.py:12:5:12:48 | ControlFlowNode for Attribute() | provenance | Config | | test.py:12:21:12:29 | ControlFlowNode for file_path | test.py:14:26:14:34 | ControlFlowNode for file_path | provenance | | | test.py:14:10:14:35 | ControlFlowNode for Attribute() | test.py:15:14:15:29 | ControlFlowNode for Attribute() | provenance | Config | -| test.py:14:26:14:34 | ControlFlowNode for file_path | test.py:14:10:14:35 | ControlFlowNode for Attribute() | provenance | MaD:87 | +| test.py:14:26:14:34 | ControlFlowNode for file_path | test.py:14:10:14:35 | ControlFlowNode for Attribute() | provenance | MaD:1 | | test.py:14:26:14:34 | ControlFlowNode for file_path | test.py:15:14:15:29 | ControlFlowNode for Attribute() | provenance | Config | | test.py:14:26:14:34 | ControlFlowNode for file_path | test.py:18:26:18:34 | ControlFlowNode for file_path | provenance | | | test.py:18:10:18:35 | ControlFlowNode for Attribute() | test.py:19:14:19:39 | ControlFlowNode for Attribute() | provenance | Config | -| test.py:18:26:18:34 | ControlFlowNode for file_path | test.py:18:10:18:35 | ControlFlowNode for Attribute() | provenance | MaD:87 | +| test.py:18:26:18:34 | ControlFlowNode for file_path | test.py:18:10:18:35 | ControlFlowNode for Attribute() | provenance | MaD:1 | | test.py:18:26:18:34 | ControlFlowNode for file_path | test.py:19:14:19:39 | ControlFlowNode for Attribute() | provenance | Config | | test.py:18:26:18:34 | ControlFlowNode for file_path | test.py:22:21:22:29 | ControlFlowNode for file_path | provenance | | | test.py:22:5:22:30 | ControlFlowNode for Attribute() | test.py:22:5:22:60 | ControlFlowNode for Attribute() | provenance | Config | -| test.py:22:21:22:29 | ControlFlowNode for file_path | test.py:22:5:22:30 | ControlFlowNode for Attribute() | provenance | MaD:87 | +| test.py:22:21:22:29 | ControlFlowNode for file_path | test.py:22:5:22:30 | ControlFlowNode for Attribute() | provenance | MaD:1 | | test.py:22:21:22:29 | ControlFlowNode for file_path | test.py:22:5:22:60 | ControlFlowNode for Attribute() | provenance | Config | | test.py:22:21:22:29 | ControlFlowNode for file_path | test.py:24:18:24:26 | ControlFlowNode for file_path | provenance | | | test.py:24:18:24:26 | ControlFlowNode for file_path | test.py:24:5:24:52 | ControlFlowNode for Attribute() | provenance | Config | @@ -47,6 +76,8 @@ edges | test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:67:42:67:50 | ControlFlowNode for file_path | provenance | | | test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:68:23:68:31 | ControlFlowNode for file_path | provenance | | | test.py:28:26:28:34 | ControlFlowNode for file_path | test.py:69:36:69:44 | ControlFlowNode for file_path | provenance | | +models +| 1 | Summary: zipfile.ZipFile!; Subclass.Call; Argument[0,file:]; ReturnValue; taint | nodes | test.py:10:16:10:24 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path | | test.py:11:5:11:35 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | @@ -93,32 +124,3 @@ nodes | test.py:68:23:68:31 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path | | test.py:69:36:69:44 | ControlFlowNode for file_path | semmle.label | ControlFlowNode for file_path | subpaths -#select -| test.py:11:5:11:52 | ControlFlowNode for Attribute() | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:11:5:11:52 | ControlFlowNode for Attribute() | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:12:5:12:48 | ControlFlowNode for Attribute() | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:12:5:12:48 | ControlFlowNode for Attribute() | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:15:14:15:29 | ControlFlowNode for Attribute() | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:15:14:15:29 | ControlFlowNode for Attribute() | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:19:14:19:39 | ControlFlowNode for Attribute() | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:19:14:19:39 | ControlFlowNode for Attribute() | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:22:5:22:60 | ControlFlowNode for Attribute() | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:22:5:22:60 | ControlFlowNode for Attribute() | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:24:5:24:52 | ControlFlowNode for Attribute() | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:24:5:24:52 | ControlFlowNode for Attribute() | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:25:5:25:55 | ControlFlowNode for Attribute() | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:25:5:25:55 | ControlFlowNode for Attribute() | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:26:5:26:57 | ControlFlowNode for Attribute() | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:26:5:26:57 | ControlFlowNode for Attribute() | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:27:5:27:50 | ControlFlowNode for Attribute() | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:27:5:27:50 | ControlFlowNode for Attribute() | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:28:5:28:60 | ControlFlowNode for Attribute() | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:28:5:28:60 | ControlFlowNode for Attribute() | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:35:27:35:35 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:35:27:35:35 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:39:15:39:23 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:39:15:39:23 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:40:19:40:27 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:40:19:40:27 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:44:14:44:22 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:44:14:44:22 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:45:17:45:25 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:45:17:45:25 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:49:15:49:23 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:49:15:49:23 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:50:19:50:27 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:50:19:50:27 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:54:15:54:23 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:54:15:54:23 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:55:19:55:27 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:55:19:55:27 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:59:40:59:48 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:59:40:59:48 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:61:23:61:31 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:61:23:61:31 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:62:21:62:29 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:62:21:62:29 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:64:40:64:48 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:64:40:64:48 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:65:22:65:30 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:65:22:65:30 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:66:21:66:29 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:66:21:66:29 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:67:42:67:50 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:67:42:67:50 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:68:23:68:31 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:68:23:68:31 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | -| test.py:69:36:69:44 | ControlFlowNode for file_path | test.py:10:16:10:24 | ControlFlowNode for file_path | test.py:69:36:69:44 | ControlFlowNode for file_path | This uncontrolled file extraction is $@. | test.py:10:16:10:24 | ControlFlowNode for file_path | depends on this user controlled data | diff --git a/python/ql/test/experimental/query-tests/Security/CWE-409/DecompressionBombs.qlref b/python/ql/test/experimental/query-tests/Security/CWE-409/DecompressionBombs.qlref index 5d425772f9a7..c4bb4019f740 100644 --- a/python/ql/test/experimental/query-tests/Security/CWE-409/DecompressionBombs.qlref +++ b/python/ql/test/experimental/query-tests/Security/CWE-409/DecompressionBombs.qlref @@ -1 +1,2 @@ -experimental/Security/CWE-409/DecompressionBombs.ql \ No newline at end of file +query: experimental/Security/CWE-409/DecompressionBombs.ql +postprocess: utils/test/PrettyPrintModels.ql diff --git a/python/ql/test/query-tests/Security/CWE-089-SqlInjection-local-threat-model/SqlInjection.expected b/python/ql/test/query-tests/Security/CWE-089-SqlInjection-local-threat-model/SqlInjection.expected index d59e639d641b..8cf2ec7b8266 100644 --- a/python/ql/test/query-tests/Security/CWE-089-SqlInjection-local-threat-model/SqlInjection.expected +++ b/python/ql/test/query-tests/Security/CWE-089-SqlInjection-local-threat-model/SqlInjection.expected @@ -1,8 +1,10 @@ +#select +| test.py:6:14:6:24 | ControlFlowNode for Subscript | test.py:6:14:6:21 | ControlFlowNode for Attribute | test.py:6:14:6:24 | ControlFlowNode for Subscript | This SQL query depends on a $@. | test.py:6:14:6:21 | ControlFlowNode for Attribute | user-provided value | edges -| test.py:6:14:6:21 | ControlFlowNode for Attribute | test.py:6:14:6:24 | ControlFlowNode for Subscript | provenance | Src:MaD:18 | +| test.py:6:14:6:21 | ControlFlowNode for Attribute | test.py:6:14:6:24 | ControlFlowNode for Subscript | provenance | Src:MaD:1 | +models +| 1 | Source: sys; Member[argv]; commandargs | nodes | test.py:6:14:6:21 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | | test.py:6:14:6:24 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript | subpaths -#select -| test.py:6:14:6:24 | ControlFlowNode for Subscript | test.py:6:14:6:21 | ControlFlowNode for Attribute | test.py:6:14:6:24 | ControlFlowNode for Subscript | This SQL query depends on a $@. | test.py:6:14:6:21 | ControlFlowNode for Attribute | user-provided value | diff --git a/python/ql/test/query-tests/Security/CWE-089-SqlInjection-local-threat-model/SqlInjection.qlref b/python/ql/test/query-tests/Security/CWE-089-SqlInjection-local-threat-model/SqlInjection.qlref index d1d02cbe8d37..b6916bd2cd46 100644 --- a/python/ql/test/query-tests/Security/CWE-089-SqlInjection-local-threat-model/SqlInjection.qlref +++ b/python/ql/test/query-tests/Security/CWE-089-SqlInjection-local-threat-model/SqlInjection.qlref @@ -1 +1,2 @@ -Security/CWE-089/SqlInjection.ql +query: Security/CWE-089/SqlInjection.ql +postprocess: utils/test/PrettyPrintModels.ql diff --git a/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests1/HeaderInjection.expected b/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests1/HeaderInjection.expected index 6c5f8363c487..4043b9332435 100644 --- a/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests1/HeaderInjection.expected +++ b/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests1/HeaderInjection.expected @@ -1,3 +1,11 @@ +#select +| flask_tests.py:13:17:13:26 | ControlFlowNode for rfs_header | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | flask_tests.py:13:17:13:26 | ControlFlowNode for rfs_header | This HTTP header is constructed from a $@. | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | user-provided value | +| flask_tests.py:20:36:20:61 | ControlFlowNode for Subscript | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | flask_tests.py:20:36:20:61 | ControlFlowNode for Subscript | This HTTP header is constructed from a $@. | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | user-provided value | +| flask_tests.py:33:11:33:20 | ControlFlowNode for rfs_header | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | flask_tests.py:33:11:33:20 | ControlFlowNode for rfs_header | This HTTP header is constructed from a $@. | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | user-provided value | +| flask_tests.py:35:12:35:21 | ControlFlowNode for rfs_header | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | flask_tests.py:35:12:35:21 | ControlFlowNode for rfs_header | This HTTP header is constructed from a $@. | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | user-provided value | +| http_test.py:12:40:12:50 | ControlFlowNode for input_value | http_test.py:5:16:5:19 | ControlFlowNode for self | http_test.py:12:40:12:50 | ControlFlowNode for input_value | This HTTP header is constructed from a $@. | http_test.py:5:16:5:19 | ControlFlowNode for self | user-provided value | +| wsgiref_tests.py:8:17:8:22 | ControlFlowNode for h_name | wsgiref_tests.py:4:14:4:20 | ControlFlowNode for environ | wsgiref_tests.py:8:17:8:22 | ControlFlowNode for h_name | This HTTP header is constructed from a $@. | wsgiref_tests.py:4:14:4:20 | ControlFlowNode for environ | user-provided value | +| wsgiref_tests.py:8:42:8:46 | ControlFlowNode for h_val | wsgiref_tests.py:4:14:4:20 | ControlFlowNode for environ | wsgiref_tests.py:8:42:8:46 | ControlFlowNode for h_val | This HTTP header is constructed from a $@. | wsgiref_tests.py:4:14:4:20 | ControlFlowNode for environ | user-provided value | edges | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | flask_tests.py:1:29:1:35 | ControlFlowNode for request | provenance | | | flask_tests.py:1:29:1:35 | ControlFlowNode for request | flask_tests.py:9:18:9:24 | ControlFlowNode for request | provenance | | @@ -14,10 +22,10 @@ edges | http_test.py:5:16:5:19 | ControlFlowNode for self | http_test.py:6:45:6:53 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | | http_test.py:6:9:6:19 | ControlFlowNode for parsed_path | http_test.py:7:40:7:56 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | | http_test.py:6:23:6:54 | ControlFlowNode for Attribute() | http_test.py:6:9:6:19 | ControlFlowNode for parsed_path | provenance | | -| http_test.py:6:45:6:53 | ControlFlowNode for Attribute | http_test.py:6:23:6:54 | ControlFlowNode for Attribute() | provenance | MaD:78 | +| http_test.py:6:45:6:53 | ControlFlowNode for Attribute | http_test.py:6:23:6:54 | ControlFlowNode for Attribute() | provenance | MaD:2 | | http_test.py:7:9:7:14 | ControlFlowNode for params | http_test.py:8:23:8:28 | ControlFlowNode for params | provenance | | | http_test.py:7:18:7:57 | ControlFlowNode for Attribute() | http_test.py:7:9:7:14 | ControlFlowNode for params | provenance | | -| http_test.py:7:40:7:56 | ControlFlowNode for Attribute | http_test.py:7:18:7:57 | ControlFlowNode for Attribute() | provenance | MaD:77 | +| http_test.py:7:40:7:56 | ControlFlowNode for Attribute | http_test.py:7:18:7:57 | ControlFlowNode for Attribute() | provenance | MaD:1 | | http_test.py:8:9:8:19 | ControlFlowNode for input_value | http_test.py:12:40:12:50 | ControlFlowNode for input_value | provenance | | | http_test.py:8:23:8:28 | ControlFlowNode for params | http_test.py:8:23:8:47 | ControlFlowNode for Attribute() | provenance | dict.get | | http_test.py:8:23:8:47 | ControlFlowNode for Attribute() | http_test.py:8:9:8:19 | ControlFlowNode for input_value | provenance | | @@ -25,6 +33,9 @@ edges | wsgiref_tests.py:4:14:4:20 | ControlFlowNode for environ | wsgiref_tests.py:7:5:7:9 | ControlFlowNode for h_val | provenance | | | wsgiref_tests.py:6:5:6:10 | ControlFlowNode for h_name | wsgiref_tests.py:8:17:8:22 | ControlFlowNode for h_name | provenance | | | wsgiref_tests.py:7:5:7:9 | ControlFlowNode for h_val | wsgiref_tests.py:8:42:8:46 | ControlFlowNode for h_val | provenance | | +models +| 1 | Summary: urllib; Member[parse].Member[parse_qs]; Argument[0,qs:]; ReturnValue; taint | +| 2 | Summary: urllib; Member[parse].Member[urlparse]; Argument[0,urlstring:]; ReturnValue; taint | nodes | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember | | flask_tests.py:1:29:1:35 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | @@ -55,11 +66,3 @@ nodes | wsgiref_tests.py:8:17:8:22 | ControlFlowNode for h_name | semmle.label | ControlFlowNode for h_name | | wsgiref_tests.py:8:42:8:46 | ControlFlowNode for h_val | semmle.label | ControlFlowNode for h_val | subpaths -#select -| flask_tests.py:13:17:13:26 | ControlFlowNode for rfs_header | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | flask_tests.py:13:17:13:26 | ControlFlowNode for rfs_header | This HTTP header is constructed from a $@. | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | user-provided value | -| flask_tests.py:20:36:20:61 | ControlFlowNode for Subscript | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | flask_tests.py:20:36:20:61 | ControlFlowNode for Subscript | This HTTP header is constructed from a $@. | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | user-provided value | -| flask_tests.py:33:11:33:20 | ControlFlowNode for rfs_header | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | flask_tests.py:33:11:33:20 | ControlFlowNode for rfs_header | This HTTP header is constructed from a $@. | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | user-provided value | -| flask_tests.py:35:12:35:21 | ControlFlowNode for rfs_header | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | flask_tests.py:35:12:35:21 | ControlFlowNode for rfs_header | This HTTP header is constructed from a $@. | flask_tests.py:1:29:1:35 | ControlFlowNode for ImportMember | user-provided value | -| http_test.py:12:40:12:50 | ControlFlowNode for input_value | http_test.py:5:16:5:19 | ControlFlowNode for self | http_test.py:12:40:12:50 | ControlFlowNode for input_value | This HTTP header is constructed from a $@. | http_test.py:5:16:5:19 | ControlFlowNode for self | user-provided value | -| wsgiref_tests.py:8:17:8:22 | ControlFlowNode for h_name | wsgiref_tests.py:4:14:4:20 | ControlFlowNode for environ | wsgiref_tests.py:8:17:8:22 | ControlFlowNode for h_name | This HTTP header is constructed from a $@. | wsgiref_tests.py:4:14:4:20 | ControlFlowNode for environ | user-provided value | -| wsgiref_tests.py:8:42:8:46 | ControlFlowNode for h_val | wsgiref_tests.py:4:14:4:20 | ControlFlowNode for environ | wsgiref_tests.py:8:42:8:46 | ControlFlowNode for h_val | This HTTP header is constructed from a $@. | wsgiref_tests.py:4:14:4:20 | ControlFlowNode for environ | user-provided value | diff --git a/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests1/HeaderInjection.qlref b/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests1/HeaderInjection.qlref index 7dbe28e4b591..bf103a555451 100644 --- a/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests1/HeaderInjection.qlref +++ b/python/ql/test/query-tests/Security/CWE-113-HeaderInjection/Tests1/HeaderInjection.qlref @@ -1 +1,2 @@ -Security/CWE-113/HeaderInjection.ql \ No newline at end of file +query: Security/CWE-113/HeaderInjection.ql +postprocess: utils/test/PrettyPrintModels.ql From ef6332c5814be8821cefce637c0b2981097d3331 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Thu, 29 Jan 2026 12:04:18 +0000 Subject: [PATCH 2/5] Allow MaD sanitizers for queries with MaD sinks --- .../security/dataflow/CodeInjectionCustomizations.qll | 7 +++++++ .../dataflow/CommandInjectionCustomizations.qll | 7 +++++++ .../security/dataflow/LogInjectionCustomizations.qll | 7 +++++++ .../security/dataflow/PathInjectionCustomizations.qll | 7 +++++++ .../security/dataflow/ReflectedXSSCustomizations.qll | 7 +++++++ .../security/dataflow/SqlInjectionCustomizations.qll | 7 +++++++ .../dataflow/UnsafeDeserializationCustomizations.qll | 7 +++++++ .../ql/src/Security/CWE-798/HardcodedCredentials.ql | 11 +++++++++++ 8 files changed, 60 insertions(+) diff --git a/python/ql/lib/semmle/python/security/dataflow/CodeInjectionCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/CodeInjectionCustomizations.qll index 5878245ed104..5a7e5882e449 100644 --- a/python/ql/lib/semmle/python/security/dataflow/CodeInjectionCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/CodeInjectionCustomizations.qll @@ -60,4 +60,11 @@ module CodeInjection { /** DEPRECATED: Use ConstCompareAsSanitizerGuard instead. */ deprecated class StringConstCompareAsSanitizerGuard = ConstCompareAsSanitizerGuard; + + /** + * A sanitizer defined via models-as-data with kind "code-injection". + */ + class SanitizerFromModel extends Sanitizer { + SanitizerFromModel() { ModelOutput::barrierNode(this, "code-injection") } + } } diff --git a/python/ql/lib/semmle/python/security/dataflow/CommandInjectionCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/CommandInjectionCustomizations.qll index 0bfd6494a1cd..facb422e7285 100644 --- a/python/ql/lib/semmle/python/security/dataflow/CommandInjectionCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/CommandInjectionCustomizations.qll @@ -95,4 +95,11 @@ module CommandInjection { /** DEPRECATED: Use ConstCompareAsSanitizerGuard instead. */ deprecated class StringConstCompareAsSanitizerGuard = ConstCompareAsSanitizerGuard; + + /** + * A sanitizer defined via models-as-data with kind "command-injection". + */ + class SanitizerFromModel extends Sanitizer { + SanitizerFromModel() { ModelOutput::barrierNode(this, "command-injection") } + } } diff --git a/python/ql/lib/semmle/python/security/dataflow/LogInjectionCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/LogInjectionCustomizations.qll index 59e52f0ab0a9..98c767df2894 100644 --- a/python/ql/lib/semmle/python/security/dataflow/LogInjectionCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/LogInjectionCustomizations.qll @@ -106,4 +106,11 @@ module LogInjection { this.getArg(0).asExpr().(StringLiteral).getText() in ["\r\n", "\n"] } } + + /** + * A sanitizer defined via models-as-data with kind "log-injection". + */ + class SanitizerFromModel extends Sanitizer { + SanitizerFromModel() { ModelOutput::barrierNode(this, "log-injection") } + } } diff --git a/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll index e2399d49c0b8..269026b591bc 100644 --- a/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll @@ -98,4 +98,11 @@ module PathInjection { /** DEPRECATED: Use ConstCompareAsSanitizerGuard instead. */ deprecated class StringConstCompareAsSanitizerGuard = ConstCompareAsSanitizerGuard; + + /** + * A sanitizer defined via models-as-data with kind "path-injection". + */ + class SanitizerFromModel extends Sanitizer { + SanitizerFromModel() { ModelOutput::barrierNode(this, "path-injection") } + } } diff --git a/python/ql/lib/semmle/python/security/dataflow/ReflectedXSSCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/ReflectedXSSCustomizations.qll index 58e5adc86605..0ef2234a5772 100644 --- a/python/ql/lib/semmle/python/security/dataflow/ReflectedXSSCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/ReflectedXSSCustomizations.qll @@ -84,4 +84,11 @@ module ReflectedXss { /** DEPRECATED: Use ConstCompareAsSanitizerGuard instead. */ deprecated class StringConstCompareAsSanitizerGuard = ConstCompareAsSanitizerGuard; + + /** + * A sanitizer defined via models-as-data with kind "html-injection" or "js-injection". + */ + class SanitizerFromModel extends Sanitizer { + SanitizerFromModel() { ModelOutput::barrierNode(this, ["html-injection", "js-injection"]) } + } } diff --git a/python/ql/lib/semmle/python/security/dataflow/SqlInjectionCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/SqlInjectionCustomizations.qll index 4118732e8dae..ee6f17b03d2c 100644 --- a/python/ql/lib/semmle/python/security/dataflow/SqlInjectionCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/SqlInjectionCustomizations.qll @@ -69,4 +69,11 @@ module SqlInjection { private class DataAsSqlSink extends Sink { DataAsSqlSink() { ModelOutput::sinkNode(this, "sql-injection") } } + + /** + * A sanitizer defined via models-as-data with kind "sql-injection". + */ + class SanitizerFromModel extends Sanitizer { + SanitizerFromModel() { ModelOutput::barrierNode(this, "sql-injection") } + } } diff --git a/python/ql/lib/semmle/python/security/dataflow/UnsafeDeserializationCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/UnsafeDeserializationCustomizations.qll index 074677ee1dc2..17450bb82d96 100644 --- a/python/ql/lib/semmle/python/security/dataflow/UnsafeDeserializationCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/UnsafeDeserializationCustomizations.qll @@ -65,4 +65,11 @@ module UnsafeDeserialization { /** DEPRECATED: Use ConstCompareAsSanitizerGuard instead. */ deprecated class StringConstCompareAsSanitizerGuard = ConstCompareAsSanitizerGuard; + + /** + * A sanitizer defined via models-as-data with kind "unsafe-deserialization". + */ + class SanitizerFromModel extends Sanitizer { + SanitizerFromModel() { ModelOutput::barrierNode(this, "unsafe-deserialization") } + } } diff --git a/python/ql/src/Security/CWE-798/HardcodedCredentials.ql b/python/ql/src/Security/CWE-798/HardcodedCredentials.ql index 97b1f2fba1c9..1e7b4452a9a6 100644 --- a/python/ql/src/Security/CWE-798/HardcodedCredentials.ql +++ b/python/ql/src/Security/CWE-798/HardcodedCredentials.ql @@ -105,6 +105,15 @@ class CredentialSink extends DataFlow::Node { } } +class CredentialSanitizer extends DataFlow::Node { + CredentialSanitizer() { + exists(string s | s.matches("credentials-%") | + // Whatever the string, this will sanitize flow to all credential sinks. + ModelOutput::barrierNode(this, s) + ) + } +} + /** * Gets a regular expression for matching names of locations (variables, parameters, keys) that * indicate the value being held is a credential. @@ -120,6 +129,8 @@ private module HardcodedCredentialsConfig implements DataFlow::ConfigSig { predicate isSink(DataFlow::Node sink) { sink instanceof CredentialSink } + predicate isBarrier(DataFlow::Node node) { node instanceof CredentialSanitizer } + predicate observeDiffInformedIncrementalMode() { any() } } From b4cb2c3f132c33d5adf2143bc885defb079eafce Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Thu, 29 Jan 2026 12:05:18 +0000 Subject: [PATCH 3/5] Make qldoc slightly more specific --- .../python/security/dataflow/UrlRedirectCustomizations.qll | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/ql/lib/semmle/python/security/dataflow/UrlRedirectCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/UrlRedirectCustomizations.qll index 28e18552bfcc..75a638fc3a42 100644 --- a/python/ql/lib/semmle/python/security/dataflow/UrlRedirectCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/UrlRedirectCustomizations.qll @@ -162,7 +162,8 @@ module UrlRedirect { deprecated class StringConstCompareAsSanitizerGuard = ConstCompareAsSanitizerGuard; /** - * A sanitizer defined via models-as-data with kind "url-redirection". + * A sanitizer which sanitizes all flow states, defined via models-as-data + * with kind "url-redirection". */ class SanitizerFromModel extends Sanitizer { SanitizerFromModel() { ModelOutput::barrierNode(this, "url-redirection") } From a3885cd8b281381ce07cc98dd9eeb22cecf2d909 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Fri, 30 Jan 2026 09:27:38 +0000 Subject: [PATCH 4/5] Replace sanitizer by exclusion from sink definition --- python/ql/lib/semmle/python/Concepts.qll | 12 ++++++++++ .../ql/lib/semmle/python/frameworks/Flask.qll | 22 +++++-------------- .../dataflow/PathInjectionCustomizations.qll | 1 + 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/python/ql/lib/semmle/python/Concepts.qll b/python/ql/lib/semmle/python/Concepts.qll index 0ca8a4dbef01..7c90ff98dc0e 100644 --- a/python/ql/lib/semmle/python/Concepts.qll +++ b/python/ql/lib/semmle/python/Concepts.qll @@ -116,6 +116,12 @@ module SystemCommandExecution { class FileSystemAccess extends DataFlow::Node instanceof FileSystemAccess::Range { /** Gets an argument to this file system access that is interpreted as a path. */ DataFlow::Node getAPathArgument() { result = super.getAPathArgument() } + + /** + * Gets an argument to this file system access that is interpreted as a path, + * but which is not vulnerable to path injection. + */ + DataFlow::Node getASafePathArgument() { result = super.getASafePathArgument() } } /** Provides a class for modeling new file system access APIs. */ @@ -130,6 +136,12 @@ module FileSystemAccess { abstract class Range extends DataFlow::Node { /** Gets an argument to this file system access that is interpreted as a path. */ abstract DataFlow::Node getAPathArgument(); + + /** + * Gets an argument to this file system access that is interpreted as a path, + * but which is not vulnerable to path injection. + */ + DataFlow::Node getASafePathArgument() { none() } } } diff --git a/python/ql/lib/semmle/python/frameworks/Flask.qll b/python/ql/lib/semmle/python/frameworks/Flask.qll index 8ac9f3deab31..eaafdf8ea23b 100644 --- a/python/ql/lib/semmle/python/frameworks/Flask.qll +++ b/python/ql/lib/semmle/python/frameworks/Flask.qll @@ -621,24 +621,14 @@ module Flask { } override DataFlow::Node getAPathArgument() { - result in [ - this.getArg(0), this.getArgByName("directory"), - // as described in the docs, the `filename` argument is restrained to be within - // the provided directory, so is not exposed to path-injection. (but is still a - // path-argument). - this.getArg(1), this.getArgByName("filename") - ] + result = this.getArg([0, 1]) or + result = this.getArgByName(["directory", "filename"]) } - } - /** - * To exclude `filename` argument to `flask.send_from_directory` as a path-injection sink. - */ - private class FlaskSendFromDirectoryCallFilenameSanitizer extends PathInjection::Sanitizer { - FlaskSendFromDirectoryCallFilenameSanitizer() { - this = any(FlaskSendFromDirectoryCall c).getArg(1) - or - this = any(FlaskSendFromDirectoryCall c).getArgByName("filename") + override DataFlow::Node getASafePathArgument() { + // as described in the docs, the `filename` argument is restrained to be within + // the provided directory, so is not exposed to path-injection. + result in [this.getArg(1), this.getArgByName("filename")] } } diff --git a/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll index 269026b591bc..10903fe0c513 100644 --- a/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll @@ -58,6 +58,7 @@ module PathInjection { class FileSystemAccessAsSink extends Sink { FileSystemAccessAsSink() { this = any(FileSystemAccess e).getAPathArgument() and + not this = any(FileSystemAccess e).getASafePathArgument() and // since implementation of Path.open in pathlib.py is like // ```py // def open(self, ...): From 0222159df5aaf9a43f257e9a16dc165a41b11a4d Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Fri, 30 Jan 2026 14:10:03 +0000 Subject: [PATCH 5/5] Specify vulnerable args instead of safe ones --- python/ql/lib/semmle/python/Concepts.qll | 20 +++++++++++++------ .../ql/lib/semmle/python/frameworks/Flask.qll | 7 ++++--- .../dataflow/PathInjectionCustomizations.qll | 3 +-- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/python/ql/lib/semmle/python/Concepts.qll b/python/ql/lib/semmle/python/Concepts.qll index 7c90ff98dc0e..1c018566cbf0 100644 --- a/python/ql/lib/semmle/python/Concepts.qll +++ b/python/ql/lib/semmle/python/Concepts.qll @@ -118,10 +118,14 @@ class FileSystemAccess extends DataFlow::Node instanceof FileSystemAccess::Range DataFlow::Node getAPathArgument() { result = super.getAPathArgument() } /** - * Gets an argument to this file system access that is interpreted as a path, - * but which is not vulnerable to path injection. + * Gets an argument to this file system access that is interpreted as a path + * which is vulnerable to path injection. + * + * By default all path arguments are considered vulnerable, but this can be overridden to + * exclude certain arguments that are known to be safe, for example because they are + * restricted to a specific directory. */ - DataFlow::Node getASafePathArgument() { result = super.getASafePathArgument() } + DataFlow::Node getAVulnerablePathArgument() { result = super.getAVulnerablePathArgument() } } /** Provides a class for modeling new file system access APIs. */ @@ -138,10 +142,14 @@ module FileSystemAccess { abstract DataFlow::Node getAPathArgument(); /** - * Gets an argument to this file system access that is interpreted as a path, - * but which is not vulnerable to path injection. + * Gets an argument to this file system access that is interpreted as a path + * which is vulnerable to path injection. + * + * By default all path arguments are considered vulnerable, but this can be overridden to + * exclude certain arguments that are known to be safe, for example because they are + * restricted to a specific directory. */ - DataFlow::Node getASafePathArgument() { none() } + DataFlow::Node getAVulnerablePathArgument() { result = this.getAPathArgument() } } } diff --git a/python/ql/lib/semmle/python/frameworks/Flask.qll b/python/ql/lib/semmle/python/frameworks/Flask.qll index eaafdf8ea23b..b9bba675ac05 100644 --- a/python/ql/lib/semmle/python/frameworks/Flask.qll +++ b/python/ql/lib/semmle/python/frameworks/Flask.qll @@ -625,10 +625,11 @@ module Flask { result = this.getArgByName(["directory", "filename"]) } - override DataFlow::Node getASafePathArgument() { - // as described in the docs, the `filename` argument is restrained to be within + override DataFlow::Node getAVulnerablePathArgument() { + result = this.getAPathArgument() and + // as described in the docs, the `filename` argument is restricted to be within // the provided directory, so is not exposed to path-injection. - result in [this.getArg(1), this.getArgByName("filename")] + not result in [this.getArg(1), this.getArgByName("filename")] } } diff --git a/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll index 10903fe0c513..7121faa19ffb 100644 --- a/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll +++ b/python/ql/lib/semmle/python/security/dataflow/PathInjectionCustomizations.qll @@ -57,8 +57,7 @@ module PathInjection { */ class FileSystemAccessAsSink extends Sink { FileSystemAccessAsSink() { - this = any(FileSystemAccess e).getAPathArgument() and - not this = any(FileSystemAccess e).getASafePathArgument() and + this = any(FileSystemAccess e).getAVulnerablePathArgument() and // since implementation of Path.open in pathlib.py is like // ```py // def open(self, ...):