diff --git a/python/ql/lib/change-notes/2026-06-04-cfg-parameter-annotations.md b/python/ql/lib/change-notes/2026-06-04-cfg-parameter-annotations.md new file mode 100644 index 000000000000..96ba81e1610e --- /dev/null +++ b/python/ql/lib/change-notes/2026-06-04-cfg-parameter-annotations.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* The new (shared-CFG-based) Python control flow graph now visits parameter and return type annotations as CFG nodes for function definitions, matching the legacy CFG. This restores annotation-based type tracking through framework models such as FastAPI's `Depends()`, Pydantic request models, Starlette `WebSocket` handlers, and any other models that flow a class reference through `Parameter.getAnnotation()` to identify instances of the annotated class. diff --git a/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll b/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll index be5cc707b021..cae096f32e8a 100644 --- a/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll +++ b/python/ql/lib/semmle/python/controlflow/internal/AstNodeImpl.qll @@ -1446,10 +1446,19 @@ module Ast implements AstSig { /** * A function definition expression (visits positional and keyword - * defaults, but NOT PEP 695 type parameters — those bind in an - * annotation scope that nests the function body, so they belong to - * the inner scope's CFG, not the enclosing scope's; the legacy CFG - * also omitted them). + * defaults followed by parameter and return type annotations, but NOT + * PEP 695 type parameters — those bind in an annotation scope that + * nests the function body, so they belong to the inner scope's CFG, + * not the enclosing scope's; the legacy CFG also omitted them). + * + * Evaluation order follows CPython: defaults are pushed first, then + * keyword-only defaults, then annotations (the `__annotations__` dict + * is built last, before `MAKE_FUNCTION`). Annotations are emitted as + * CFG nodes so that flows from a class reference into a parameter's + * type annotation are visible to dataflow (e.g. so that framework + * models like FastAPI's `Depends()` can use a parameter's type hint + * to track that the parameter receives an instance of the annotated + * class — see `LocalSources::annotatedInstance`). */ additional class FunctionDefExpr extends Expr { private Py::FunctionExpr funcExpr; @@ -1473,15 +1482,61 @@ module Ast implements AstSig { rank[n + 1](Py::Expr d, int i | d = funcExpr.getArgs().getKwDefault(i) | d order by i) } + /** + * Gets the `n`th annotation expression, in CPython evaluation + * order: positional parameter annotations (by argument position), + * `*args` annotation, keyword-only parameter annotations (by + * argument position), `**kwargs` annotation, then the return + * annotation. Each annotation appears at most once. + */ + Expr getAnnotation(int n) { + result.asExpr() = + rank[n + 1](Py::Expr a, int subOrder, int subIndex | + functionAnnotation(funcExpr, a, subOrder, subIndex) + | + a order by subOrder, subIndex + ) + } + int getNumberOfDefaults() { result = count(funcExpr.getArgs().getADefault()) } + int getNumberOfKwDefaults() { result = count(funcExpr.getArgs().getAKwDefault()) } + + int getNumberOfAnnotations() { + result = count(Py::Expr a | functionAnnotation(funcExpr, a, _, _)) + } + override AstNode getChild(int index) { result = this.getDefault(index) or result = this.getKwDefault(index - this.getNumberOfDefaults()) + or + result = this.getAnnotation(index - this.getNumberOfDefaults() - this.getNumberOfKwDefaults()) } } + /** + * Holds if `a` is an annotation of `funcExpr` in slot + * `(subOrder, subIndex)`. Slots are CPython evaluation order: + * positional param annotations (subOrder 0, subIndex = argument + * position), `*args` annotation (1, 0), keyword-only annotations + * (2, position), `**kwargs` annotation (3, 0), return annotation + * (4, 0). + */ + private predicate functionAnnotation( + Py::FunctionExpr funcExpr, Py::Expr a, int subOrder, int subIndex + ) { + a = funcExpr.getArgs().getAnnotation(subIndex) and subOrder = 0 + or + a = funcExpr.getArgs().getVarargannotation() and subOrder = 1 and subIndex = 0 + or + a = funcExpr.getArgs().getKwAnnotation(subIndex) and subOrder = 2 + or + a = funcExpr.getArgs().getKwargannotation() and subOrder = 3 and subIndex = 0 + or + a = funcExpr.getReturns() and subOrder = 4 and subIndex = 0 + } + /** A lambda expression (has default args evaluated at definition time). */ additional class LambdaExpr extends Expr { private Py::Lambda lambda; diff --git a/python/ql/test/experimental/library-tests/CallGraph-type-annotations/InlineCallGraphTest.expected b/python/ql/test/experimental/library-tests/CallGraph-type-annotations/InlineCallGraphTest.expected index 064abf1b1c49..cb69b8368c50 100644 --- a/python/ql/test/experimental/library-tests/CallGraph-type-annotations/InlineCallGraphTest.expected +++ b/python/ql/test/experimental/library-tests/CallGraph-type-annotations/InlineCallGraphTest.expected @@ -1,7 +1,7 @@ testFailures -| type_annotations.py:6:16:6:32 | Comment # $ tt=Foo.method | Missing result: tt=Foo.method | -| type_annotations.py:16:16:16:32 | Comment # $ tt=Foo.method | Missing result: tt=Foo.method | debug_callableNotUnique pointsTo_found_typeTracker_notFound typeTracker_found_pointsTo_notFound +| type_annotations.py:6:5:6:14 | Attribute() | Foo.method | +| type_annotations.py:16:5:16:14 | Attribute() | Foo.method | | type_annotations.py:29:5:29:14 | Attribute() | Foo.method | diff --git a/python/ql/test/library-tests/frameworks/aiohttp/InlineTaintTest.expected b/python/ql/test/library-tests/frameworks/aiohttp/InlineTaintTest.expected index 8f8d5d302fb6..020c338fd192 100644 --- a/python/ql/test/library-tests/frameworks/aiohttp/InlineTaintTest.expected +++ b/python/ql/test/library-tests/frameworks/aiohttp/InlineTaintTest.expected @@ -1,10 +1,3 @@ argumentToEnsureNotTaintedNotMarkedAsSpurious untaintedArgumentToEnsureTaintedNotMarkedAsMissing -| taint_test.py:151:9:151:15 | taint_test.py:151 | ERROR, you should add `# $ MISSING: tainted` annotation | request | -| taint_test.py:152:9:152:19 | taint_test.py:152 | ERROR, you should add `# $ MISSING: tainted` annotation | request.url | -| taint_test.py:153:9:153:36 | taint_test.py:153 | ERROR, you should add `# $ MISSING: tainted` annotation | Await | testFailures -| taint_test.py:151:18:151:28 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:152:22:152:32 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:153:39:153:49 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:168:76:168:96 | Comment # $ SPURIOUS: tainted | Fixed spurious result: tainted | diff --git a/python/ql/test/library-tests/frameworks/fastapi/ConceptsTest.expected b/python/ql/test/library-tests/frameworks/fastapi/ConceptsTest.expected index 1c7200610ea4..e69de29bb2d1 100644 --- a/python/ql/test/library-tests/frameworks/fastapi/ConceptsTest.expected +++ b/python/ql/test/library-tests/frameworks/fastapi/ConceptsTest.expected @@ -1,45 +0,0 @@ -| response_test.py:11:30:11:37 | Parameter | Unexpected result: routedParameter=response | -| response_test.py:12:41:12:151 | Comment # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieHttpOnly=false | -| response_test.py:12:41:12:151 | Comment # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieName="key" | -| response_test.py:12:41:12:151 | Comment # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieSameSite=Lax | -| response_test.py:12:41:12:151 | Comment # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieSecure=false | -| response_test.py:12:41:12:151 | Comment # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieValue="value" | -| response_test.py:12:41:12:151 | Comment # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieWrite | -| response_test.py:13:51:13:161 | Comment # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieHttpOnly=false | -| response_test.py:13:51:13:161 | Comment # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieName="key" | -| response_test.py:13:51:13:161 | Comment # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieSameSite=Lax | -| response_test.py:13:51:13:161 | Comment # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieSecure=false | -| response_test.py:13:51:13:161 | Comment # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieValue="value" | -| response_test.py:13:51:13:161 | Comment # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieWrite | -| response_test.py:14:96:14:205 | Comment # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=true CookieSameSite=Lax | Missing result: CookieHttpOnly=true | -| response_test.py:14:96:14:205 | Comment # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=true CookieSameSite=Lax | Missing result: CookieName="key" | -| response_test.py:14:96:14:205 | Comment # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=true CookieSameSite=Lax | Missing result: CookieSameSite=Lax | -| response_test.py:14:96:14:205 | Comment # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=true CookieSameSite=Lax | Missing result: CookieSecure=false | -| response_test.py:14:96:14:205 | Comment # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=true CookieSameSite=Lax | Missing result: CookieValue="value" | -| response_test.py:14:96:14:205 | Comment # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=true CookieSameSite=Lax | Missing result: CookieWrite | -| response_test.py:15:58:15:221 | Comment # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieHttpOnly=false | -| response_test.py:15:58:15:221 | Comment # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieRawHeader="key2=value2" | -| response_test.py:15:58:15:221 | Comment # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieSameSite=Lax | -| response_test.py:15:58:15:221 | Comment # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieSecure=false | -| response_test.py:15:58:15:221 | Comment # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieWrite | -| response_test.py:15:58:15:221 | Comment # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: headerWriteName="Set-Cookie" | -| response_test.py:15:58:15:221 | Comment # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: headerWriteValue="key2=value2" | -| response_test.py:16:68:16:231 | Comment # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieHttpOnly=false | -| response_test.py:16:68:16:231 | Comment # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieRawHeader="key2=value2" | -| response_test.py:16:68:16:231 | Comment # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieSameSite=Lax | -| response_test.py:16:68:16:231 | Comment # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieSecure=false | -| response_test.py:16:68:16:231 | Comment # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieWrite | -| response_test.py:16:68:16:231 | Comment # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: headerWriteName="Set-Cookie" | -| response_test.py:16:68:16:231 | Comment # $ headerWriteName="Set-Cookie" headerWriteValue="key2=value2" CookieWrite CookieRawHeader="key2=value2" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: headerWriteValue="key2=value2" | -| response_test.py:17:53:17:116 | Comment # $ headerWriteName="X-MyHeader" headerWriteValue="header-value" | Missing result: headerWriteName="X-MyHeader" | -| response_test.py:17:53:17:116 | Comment # $ headerWriteName="X-MyHeader" headerWriteValue="header-value" | Missing result: headerWriteValue="header-value" | -| response_test.py:23:26:23:29 | Parameter | Unexpected result: routedParameter=resp | -| response_test.py:41:42:41:49 | Parameter | Unexpected result: routedParameter=response | -| response_test.py:48:41:48:151 | Comment # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieHttpOnly=false | -| response_test.py:48:41:48:151 | Comment # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieName="key" | -| response_test.py:48:41:48:151 | Comment # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieSameSite=Lax | -| response_test.py:48:41:48:151 | Comment # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieSecure=false | -| response_test.py:48:41:48:151 | Comment # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieValue="value" | -| response_test.py:48:41:48:151 | Comment # $ CookieWrite CookieName="key" CookieValue="value" CookieSecure=false CookieHttpOnly=false CookieSameSite=Lax | Missing result: CookieWrite | -| response_test.py:49:87:49:184 | Comment # $ headerWriteName="Custom-Response-Type" headerWriteValue="yes, but only after function has run" | Missing result: headerWriteName="Custom-Response-Type" | -| response_test.py:49:87:49:184 | Comment # $ headerWriteName="Custom-Response-Type" headerWriteValue="yes, but only after function has run" | Missing result: headerWriteValue="yes, but only after function has run" | diff --git a/python/ql/test/library-tests/frameworks/fastapi/InlineTaintTest.expected b/python/ql/test/library-tests/frameworks/fastapi/InlineTaintTest.expected index 4b34068dccfb..020c338fd192 100644 --- a/python/ql/test/library-tests/frameworks/fastapi/InlineTaintTest.expected +++ b/python/ql/test/library-tests/frameworks/fastapi/InlineTaintTest.expected @@ -1,107 +1,3 @@ argumentToEnsureNotTaintedNotMarkedAsSpurious untaintedArgumentToEnsureTaintedNotMarkedAsMissing -| taint_test.py:33:9:33:24 | taint_test.py:33 | ERROR, you should add `# $ MISSING: tainted` annotation | also_input.field | -| taint_test.py:35:9:35:27 | taint_test.py:35 | ERROR, you should add `# $ MISSING: tainted` annotation | also_input.main_foo | -| taint_test.py:36:9:36:31 | taint_test.py:36 | ERROR, you should add `# $ MISSING: tainted` annotation | also_input.main_foo.foo | -| taint_test.py:38:9:38:29 | taint_test.py:38 | ERROR, you should add `# $ MISSING: tainted` annotation | also_input.other_foos | -| taint_test.py:39:9:39:32 | taint_test.py:39 | ERROR, you should add `# $ MISSING: tainted` annotation | also_input.other_foos[0] | -| taint_test.py:40:9:40:36 | taint_test.py:40 | ERROR, you should add `# $ MISSING: tainted` annotation | also_input.other_foos[0].foo | -| taint_test.py:43:9:43:30 | taint_test.py:43 | ERROR, you should add `# $ MISSING: tainted` annotation | also_input.nested_foos | -| taint_test.py:44:9:44:33 | taint_test.py:44 | ERROR, you should add `# $ MISSING: tainted` annotation | also_input.nested_foos[0] | -| taint_test.py:45:9:45:36 | taint_test.py:45 | ERROR, you should add `# $ MISSING: tainted` annotation | also_input.nested_foos[0][0] | -| taint_test.py:46:9:46:40 | taint_test.py:46 | ERROR, you should add `# $ MISSING: tainted` annotation | also_input.nested_foos[0][0].foo | -| taint_test.py:52:9:52:18 | taint_test.py:52 | ERROR, you should add `# $ MISSING: tainted` annotation | other_foos | -| taint_test.py:53:9:53:21 | taint_test.py:53 | ERROR, you should add `# $ MISSING: tainted` annotation | other_foos[0] | -| taint_test.py:54:9:54:25 | taint_test.py:54 | ERROR, you should add `# $ MISSING: tainted` annotation | other_foos[0].foo | -| taint_test.py:140:9:140:21 | taint_test.py:140 | ERROR, you should add `# $ MISSING: tainted` annotation | websocket.url | -| taint_test.py:142:9:142:28 | taint_test.py:142 | ERROR, you should add `# $ MISSING: tainted` annotation | websocket.url.netloc | -| taint_test.py:143:9:143:26 | taint_test.py:143 | ERROR, you should add `# $ MISSING: tainted` annotation | websocket.url.path | -| taint_test.py:144:9:144:27 | taint_test.py:144 | ERROR, you should add `# $ MISSING: tainted` annotation | websocket.url.query | -| taint_test.py:145:9:145:30 | taint_test.py:145 | ERROR, you should add `# $ MISSING: tainted` annotation | websocket.url.fragment | -| taint_test.py:146:9:146:30 | taint_test.py:146 | ERROR, you should add `# $ MISSING: tainted` annotation | websocket.url.username | -| taint_test.py:147:9:147:30 | taint_test.py:147 | ERROR, you should add `# $ MISSING: tainted` annotation | websocket.url.password | -| taint_test.py:148:9:148:30 | taint_test.py:148 | ERROR, you should add `# $ MISSING: tainted` annotation | websocket.url.hostname | -| taint_test.py:149:9:149:26 | taint_test.py:149 | ERROR, you should add `# $ MISSING: tainted` annotation | websocket.url.port | -| taint_test.py:151:9:151:32 | taint_test.py:151 | ERROR, you should add `# $ MISSING: tainted` annotation | websocket.url.components | -| taint_test.py:152:9:152:39 | taint_test.py:152 | ERROR, you should add `# $ MISSING: tainted` annotation | websocket.url.components.netloc | -| taint_test.py:153:9:153:37 | taint_test.py:153 | ERROR, you should add `# $ MISSING: tainted` annotation | websocket.url.components.path | -| taint_test.py:154:9:154:38 | taint_test.py:154 | ERROR, you should add `# $ MISSING: tainted` annotation | websocket.url.components.query | -| taint_test.py:155:9:155:41 | taint_test.py:155 | ERROR, you should add `# $ MISSING: tainted` annotation | websocket.url.components.fragment | -| taint_test.py:156:9:156:41 | taint_test.py:156 | ERROR, you should add `# $ MISSING: tainted` annotation | websocket.url.components.username | -| taint_test.py:157:9:157:41 | taint_test.py:157 | ERROR, you should add `# $ MISSING: tainted` annotation | websocket.url.components.password | -| taint_test.py:158:9:158:41 | taint_test.py:158 | ERROR, you should add `# $ MISSING: tainted` annotation | websocket.url.components.hostname | -| taint_test.py:159:9:159:37 | taint_test.py:159 | ERROR, you should add `# $ MISSING: tainted` annotation | websocket.url.components.port | -| taint_test.py:161:9:161:25 | taint_test.py:161 | ERROR, you should add `# $ MISSING: tainted` annotation | websocket.headers | -| taint_test.py:162:9:162:32 | taint_test.py:162 | ERROR, you should add `# $ MISSING: tainted` annotation | websocket.headers["key"] | -| taint_test.py:164:9:164:30 | taint_test.py:164 | ERROR, you should add `# $ MISSING: tainted` annotation | websocket.query_params | -| taint_test.py:165:9:165:37 | taint_test.py:165 | ERROR, you should add `# $ MISSING: tainted` annotation | websocket.query_params["key"] | -| taint_test.py:167:9:167:25 | taint_test.py:167 | ERROR, you should add `# $ MISSING: tainted` annotation | websocket.cookies | -| taint_test.py:168:9:168:32 | taint_test.py:168 | ERROR, you should add `# $ MISSING: tainted` annotation | websocket.cookies["key"] | -| taint_test.py:170:9:170:33 | taint_test.py:170 | ERROR, you should add `# $ MISSING: tainted` annotation | Await | -| taint_test.py:171:9:171:39 | taint_test.py:171 | ERROR, you should add `# $ MISSING: tainted` annotation | Await | -| taint_test.py:172:9:172:38 | taint_test.py:172 | ERROR, you should add `# $ MISSING: tainted` annotation | Await | -| taint_test.py:173:9:173:38 | taint_test.py:173 | ERROR, you should add `# $ MISSING: tainted` annotation | Await | -| taint_test.py:183:24:183:27 | taint_test.py:183 | ERROR, you should add `# $ MISSING: tainted` annotation | data | -| taint_test.py:186:24:186:27 | taint_test.py:186 | ERROR, you should add `# $ MISSING: tainted` annotation | data | -| taint_test.py:189:24:189:27 | taint_test.py:189 | ERROR, you should add `# $ MISSING: tainted` annotation | data | -| taint_test.py:205:9:205:28 | taint_test.py:205 | ERROR, you should add `# $ MISSING: tainted` annotation | Await | -| taint_test.py:207:9:207:28 | taint_test.py:207 | ERROR, you should add `# $ MISSING: tainted` annotation | Await | -| taint_test.py:208:9:208:35 | taint_test.py:208 | ERROR, you should add `# $ MISSING: tainted` annotation | Await | -| taint_test.py:211:9:211:28 | taint_test.py:211 | ERROR, you should add `# $ MISSING: tainted` annotation | Await | -| taint_test.py:212:9:212:35 | taint_test.py:212 | ERROR, you should add `# $ MISSING: tainted` annotation | Await | -| taint_test.py:219:9:219:23 | taint_test.py:219 | ERROR, you should add `# $ MISSING: tainted` annotation | request.cookies | -| taint_test.py:220:9:220:30 | taint_test.py:220 | ERROR, you should add `# $ MISSING: tainted` annotation | request.cookies["key"] | -| taint_test.py:224:24:224:28 | taint_test.py:224 | ERROR, you should add `# $ MISSING: tainted` annotation | chunk | testFailures -| taint_test.py:33:27:33:37 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:35:30:35:40 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:36:34:36:44 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:38:32:38:42 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:39:35:39:45 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:40:39:40:49 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:43:33:43:43 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:44:36:44:46 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:45:39:45:49 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:46:43:46:53 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:52:21:52:31 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:53:24:53:34 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:54:28:54:38 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:140:24:140:34 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:142:31:142:41 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:143:29:143:39 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:144:30:144:40 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:145:33:145:43 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:146:33:146:43 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:147:33:147:43 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:148:33:148:43 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:149:29:149:39 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:151:35:151:45 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:152:42:152:52 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:153:40:153:50 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:154:41:154:51 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:155:44:155:54 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:156:44:156:54 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:157:44:157:54 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:158:44:158:54 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:159:40:159:50 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:161:28:161:38 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:162:35:162:45 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:164:33:164:43 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:165:40:165:50 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:167:28:167:38 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:168:35:168:45 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:170:36:170:46 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:171:42:171:52 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:172:41:172:51 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:173:41:173:51 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:183:30:183:40 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:186:30:186:40 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:189:30:189:40 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:205:31:205:41 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:207:31:207:41 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:208:38:208:48 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:211:31:211:41 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:212:38:212:48 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:219:26:219:36 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:220:33:220:43 | Comment # $ tainted | Missing result: tainted | -| taint_test.py:224:31:224:41 | Comment # $ tainted | Missing result: tainted | diff --git a/python/ql/test/library-tests/frameworks/fastapi/taint_test.py b/python/ql/test/library-tests/frameworks/fastapi/taint_test.py index 2ceca3b9dd0a..c14f6d84c907 100644 --- a/python/ql/test/library-tests/frameworks/fastapi/taint_test.py +++ b/python/ql/test/library-tests/frameworks/fastapi/taint_test.py @@ -38,7 +38,7 @@ async def test_taint(name : str, number : int, also_input: MyComplexModel): # $ also_input.other_foos, # $ tainted also_input.other_foos[0], # $ tainted also_input.other_foos[0].foo, # $ tainted - [f.foo for f in also_input.other_foos], # $ MISSING: tainted + [f.foo for f in also_input.other_foos], # $ tainted also_input.nested_foos, # $ tainted also_input.nested_foos[0], # $ tainted @@ -52,7 +52,7 @@ async def test_taint(name : str, number : int, also_input: MyComplexModel): # $ other_foos, # $ tainted other_foos[0], # $ tainted other_foos[0].foo, # $ tainted - [f.foo for f in other_foos], # $ MISSING: tainted + [f.foo for f in other_foos], # $ tainted ) return "ok" # $ HttpResponse diff --git a/python/ql/test/library-tests/frameworks/lxml/InlineTaintTest.expected b/python/ql/test/library-tests/frameworks/lxml/InlineTaintTest.expected index 36d192b057e8..020c338fd192 100644 --- a/python/ql/test/library-tests/frameworks/lxml/InlineTaintTest.expected +++ b/python/ql/test/library-tests/frameworks/lxml/InlineTaintTest.expected @@ -1,5 +1,3 @@ argumentToEnsureNotTaintedNotMarkedAsSpurious untaintedArgumentToEnsureTaintedNotMarkedAsMissing -| taint_test.py:133:13:133:35 | taint_test.py:133 | ERROR, you should add `# $ MISSING: tainted` annotation | tree_arg.getroot().text | testFailures -| taint_test.py:133:37:133:82 | Comment # $ tainted # Type tracking from the type hint | Missing result: tainted | diff --git a/python/ql/test/query-tests/Security/CWE-022-PathInjection/PathInjection.expected b/python/ql/test/query-tests/Security/CWE-022-PathInjection/PathInjection.expected index e31cc5af9ba4..05b636ce3b9d 100644 --- a/python/ql/test/query-tests/Security/CWE-022-PathInjection/PathInjection.expected +++ b/python/ql/test/query-tests/Security/CWE-022-PathInjection/PathInjection.expected @@ -1,5 +1,6 @@ #select | fastapi_path_injection.py:7:19:7:26 | filepath | fastapi_path_injection.py:17:21:17:24 | path | fastapi_path_injection.py:7:19:7:26 | filepath | This path depends on a $@. | fastapi_path_injection.py:17:21:17:24 | path | user-provided value | +| fastapi_path_injection.py:7:19:7:26 | filepath | fastapi_path_injection.py:26:21:26:24 | path | fastapi_path_injection.py:7:19:7:26 | filepath | This path depends on a $@. | fastapi_path_injection.py:26:21:26:24 | path | user-provided value | | fastapi_path_injection.py:7:19:7:26 | filepath | fastapi_path_injection.py:31:21:31:24 | path | fastapi_path_injection.py:7:19:7:26 | filepath | This path depends on a $@. | fastapi_path_injection.py:31:21:31:24 | path | user-provided value | | fastapi_path_injection.py:7:19:7:26 | filepath | fastapi_path_injection.py:48:21:48:24 | path | fastapi_path_injection.py:7:19:7:26 | filepath | This path depends on a $@. | fastapi_path_injection.py:48:21:48:24 | path | user-provided value | | flask_path_injection.py:21:32:21:38 | dirname | flask_path_injection.py:1:26:1:32 | After ImportMember | flask_path_injection.py:21:32:21:38 | dirname | This path depends on a $@. | flask_path_injection.py:1:26:1:32 | After ImportMember | user-provided value | @@ -26,6 +27,8 @@ edges | fastapi_path_injection.py:6:24:6:31 | filepath | fastapi_path_injection.py:7:19:7:26 | filepath | provenance | | | fastapi_path_injection.py:17:21:17:24 | path | fastapi_path_injection.py:20:34:20:37 | path | provenance | | | fastapi_path_injection.py:20:34:20:37 | path | fastapi_path_injection.py:6:24:6:31 | filepath | provenance | | +| fastapi_path_injection.py:26:21:26:24 | path | fastapi_path_injection.py:27:34:27:37 | path | provenance | | +| fastapi_path_injection.py:27:34:27:37 | path | fastapi_path_injection.py:6:24:6:31 | filepath | provenance | | | fastapi_path_injection.py:31:21:31:24 | path | fastapi_path_injection.py:32:34:32:37 | path | provenance | | | fastapi_path_injection.py:32:34:32:37 | path | fastapi_path_injection.py:6:24:6:31 | filepath | provenance | | | fastapi_path_injection.py:48:21:48:24 | path | fastapi_path_injection.py:49:45:49:48 | path | provenance | | @@ -157,6 +160,8 @@ nodes | fastapi_path_injection.py:7:19:7:26 | filepath | semmle.label | filepath | | fastapi_path_injection.py:17:21:17:24 | path | semmle.label | path | | fastapi_path_injection.py:20:34:20:37 | path | semmle.label | path | +| fastapi_path_injection.py:26:21:26:24 | path | semmle.label | path | +| fastapi_path_injection.py:27:34:27:37 | path | semmle.label | path | | fastapi_path_injection.py:31:21:31:24 | path | semmle.label | path | | fastapi_path_injection.py:32:34:32:37 | path | semmle.label | path | | fastapi_path_injection.py:48:21:48:24 | path | semmle.label | path | @@ -291,5 +296,3 @@ nodes subpaths | test.py:25:19:25:19 | x | test.py:12:15:12:15 | x | test.py:13:12:13:30 | After Attribute() | test.py:25:9:25:20 | After normalize() | | test.py:48:23:48:23 | x | test.py:12:15:12:15 | x | test.py:13:12:13:30 | After Attribute() | test.py:48:13:48:24 | After normalize() | -testFailures -| fastapi_path_injection.py:26:72:26:81 | Comment # $ Source | Missing result: Source |