Compare commits

..

2 Commits

Author SHA1 Message Date
yoff
709566ecb1 Python: visit function parameter and return annotations in new CFG
The new (shared-CFG-based) Python control flow graph in
`semmle.python.controlflow.internal.Cfg` previously did not emit CFG
nodes for parameter type annotations (`def f(x: T): ...`) or for the
return type annotation (`-> T`). The legacy CFG emitted both, and a
small number of framework models rely on this: `LocalSources.qll`'s
`annotatedInstance` walks the parameter annotation expression by way
of its CFG node to track that a parameter receives an instance of the
annotated class.

After the dataflow flip to the new CFG/SSA this regression manifested
as lost flows in any test exercising annotation-based parameter
tracking: FastAPI `Depends()` receivers, Pydantic request bodies,
Starlette `WebSocket`, the call-graph type-annotation test, and so on.
Extend `FunctionDefExpr` to visit each annotation as a child of the
function-def expression, in CPython evaluation order: positional
parameter annotations, `*args` annotation, keyword-only parameter
annotations, `**kwargs` annotation, then the return annotation. (Lambda
expressions have no annotations in Python syntax, so `LambdaExpr` is
unchanged.) PEP 695 type parameters remain out of scope; they belong
to the inner annotation scope, not the enclosing CFG.

Restored test results across `framework/aiohttp`, `framework/fastapi`,
`framework/lxml`, the `CallGraph-type-annotations` test, and
`CWE-022-PathInjection`. Two FastAPI list-comprehension MISSING markers
become positive (`taint_test.py:41,55`). CPython CFG consistency
remains clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-05 08:10:14 +00:00
yoff
b783ed69c5 Python: model exception edges for raise-prone expressions inside try/with
The new CFG previously only emitted exception edges for explicit `raise`
and `assert` statements. As a result, code that became reachable only
via the exception path of an arbitrary expression (e.g., the body of an
`except` handler following a try-body whose `call()` could raise) was
classified as dead, breaking analyses like StackTraceExposure,
FileNotAlwaysClosed, ExceptionInfo, UseOfExit, and CatchingBaseException.

This commit adds a `mayThrow` predicate over expressions that are known
sources of implicit exceptions in Python (calls, attribute access,
subscripts, arithmetic/comparison operators, imports, await/yield/yield
from) plus `from m import *` at the statement level, and routes them
through the shared CFG's `beginAbruptCompletion(_, _, ExceptionSuccessor,
always=false)` hook.

The set of exception sources is restricted to nodes that are
syntactically inside a `try`/`with` statement in the same scope.
This mirrors Java's `ControlFlowGraph::mayThrow`, which only emits
exception edges where local handling can observe them — outside such
contexts, the edges add CFG complexity (weakening BarrierGuard
precision and breaking SSA continuity around augmented assignments and
subscript stores) without analysis benefit, since exceptions just
propagate to the function exit anyway.

Net effect on the test suite: ~100 alerts restored across the exception-
related query tests (StackTraceExposure +29, ExceptionInfo +17,
FileNotAlwaysClosed +52, UseOfExit +1, CatchingBaseException restored)
with no precision regressions. Affected `.expected` files and the
regression-guard `dead_under_no_raise.py` are updated accordingly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-05 08:10:06 +00:00
21 changed files with 216 additions and 248 deletions

View File

@@ -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.

View File

@@ -1446,10 +1446,19 @@ module Ast implements AstSig<Py::Location> {
/**
* 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<Py::Location> {
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;
@@ -1543,6 +1598,89 @@ private module Input implements InputSig1, InputSig2 {
private string assertThrowTag() { result = "[assert-throw]" }
/**
* Holds if the AST node `n` may raise an exception at runtime as part of
* its normal evaluation (not via an explicit `raise`/`assert`, which are
* modelled separately).
*
* The set mirrors what the legacy CFG used to flag implicitly: function
* calls (anything can raise), attribute access (`AttributeError`),
* subscript access (`IndexError`/`KeyError`/`TypeError`), arithmetic and
* comparison operators (`TypeError`/`ZeroDivisionError`), imports
* (`ImportError`/`ModuleNotFoundError`), and generator/coroutine
* suspension points (`await`/`yield`/`yield from`).
*
* Bare `Name` reads are intentionally excluded — modelling every name
* read as `mayThrow` would explode CFG edge count for negligible
* analysis value. `BoolExpr`/`IfExp` containers are also excluded; the
* operands they evaluate contribute their own exception edges.
*/
private predicate exprMayThrow(Py::Expr e) {
e instanceof Py::Call
or
e instanceof Py::Attribute
or
e instanceof Py::Subscript
or
e instanceof Py::BinaryExpr
or
e instanceof Py::UnaryExpr
or
e instanceof Py::Compare
or
e instanceof Py::ImportExpr
or
e instanceof Py::ImportMember
or
e instanceof Py::Await
or
e instanceof Py::Yield
or
e instanceof Py::YieldFrom
}
/**
* Holds if the statement `s` may raise an exception at runtime as part
* of its normal evaluation. Currently restricted to `from m import *`
* (which performs the import as a statement-level side effect).
*/
private predicate stmtMayThrow(Py::Stmt s) { s instanceof Py::ImportStar }
/**
* Holds if `n` is syntactically inside the body, handlers, `else`, or
* `finally` of a `try` statement (or the body of a `with` statement,
* which compiles to an implicit try/finally for `__exit__`) in the
* same scope.
*
* This mirrors Java's `ControlFlowGraph::mayThrow`, which only emits
* exception edges when there is local exception handling that would
* observe them. Outside such contexts, exception edges would add CFG
* complexity (weakening BarrierGuard precision and breaking SSA
* continuity around augmented assignments and subscript stores)
* without any analysis benefit, since exceptions just propagate to
* the function exit anyway.
*/
private predicate inExceptionContext(Py::AstNode py) {
exists(Py::Try t | t.containsInScope(py))
or
exists(Py::With w | w.containsInScope(py))
}
/**
* Holds if `n` may raise an exception during normal evaluation. See
* `exprMayThrow` and `stmtMayThrow` for the included AST classes.
*
* Restricted to nodes inside a `try`/`with` statement: matches Java's
* approach of only modelling exception flow where it can be observed
* by local handling.
*/
private predicate mayThrow(Ast::AstNode n) {
exists(Py::AstNode py | py = n.asExpr() or py = n.asStmt() |
(exprMayThrow(py) or stmtMayThrow(py)) and
inExceptionContext(py)
)
}
predicate additionalNode(Ast::AstNode n, string tag, NormalSuccessor t) {
n instanceof Ast::AssertStmt and tag = assertThrowTag() and t instanceof DirectSuccessor
}
@@ -1554,6 +1692,11 @@ private module Input implements InputSig1, InputSig2 {
n.isAdditional(ast, assertThrowTag()) and
c.asSimpleAbruptCompletion() instanceof ExceptionSuccessor and
always = true
or
mayThrow(ast) and
n.isIn(ast) and
c.asSimpleAbruptCompletion() instanceof ExceptionSuccessor and
always = false
}
predicate endAbruptCompletion(Ast::AstNode ast, PreControlFlowNode n, AbruptCompletion c) {

View File

@@ -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 |

View File

@@ -4,6 +4,7 @@
| flask.MethodView~Subclass | find_subclass_test | Member[C] |
| flask.View~Subclass | find_subclass_test | Member[A] |
| flask.View~Subclass | find_subclass_test | Member[B] |
| flask.View~Subclass | find_subclass_test | Member[ViewAliasInExcept] |
| flask.View~Subclass | find_subclass_test | Member[ViewAliasInTry] |
| flask.View~Subclass | find_subclass_test | Member[ViewAlias] |
| flask.View~Subclass | find_subclass_test | Member[ViewAlias_no_use] |

View File

@@ -1,15 +1,15 @@
# Dead bindings under the "no expressions raise" CFG abstraction.
# Reachability of code following a try whose body always returns.
#
# The new CFG does not currently model raise edges from arbitrary
# expressions. As a consequence, code that is only reachable through
# exception flow is (correctly) classified as dead and has no CFG node.
# Variable bindings in dead code do not need CFG nodes - SSA / dataflow
# over dead code is moot.
# The new CFG models exception edges for raise-prone expressions when
# they appear inside a `try` (or `with`) statement, mirroring Java's
# `mayThrow`. This means the body of a `try` has both a normal
# completion edge and an exception edge to its handlers, so code
# following the try-statement is reachable via the except-handler path
# even when the try-body would otherwise always return.
#
# These tests act as a regression guard: the bindings below intentionally
# have no `cfgdefines=` annotations. If raise modelling is later added,
# the BindingsTest infrastructure will surface the new CFG nodes as
# unexpected results, and this file will need to be revisited.
# Code that is not reachable under either normal or exception flow
# (for example, the `else` clause of a try whose body unconditionally
# raises) remains correctly classified as dead.
def f(obj): # $ cfgdefines=f cfgdefines=obj
@@ -18,12 +18,12 @@ def f(obj): # $ cfgdefines=f cfgdefines=obj
except TypeError:
pass
# The first try's body always returns; its except handler does not
# raise or otherwise transfer control, so under "no expressions
# raise" the only paths out of the try-statement are dead. Everything
# below is unreachable.
# The try-body always returns, but `len(obj)` can raise (it is
# inside the try, so we model its exception edge). The
# `except TypeError: pass` handler falls through to here, making
# the code below reachable.
try:
hint = type(obj).__length_hint__
hint = type(obj).__length_hint__ # $ cfgdefines=hint
except AttributeError:
return None
return hint
@@ -35,7 +35,8 @@ def g(): # $ cfgdefines=g
except:
raise Exception("outer")
else:
# Unreachable: the inner try body always raises, so the `else:`
# Unreachable: the inner try body always raises (via an explicit
# `raise`, which is modelled unconditionally), so the `else:`
# clause never runs.
hit_inner_else = True
@@ -46,7 +47,7 @@ def h(cache, key): # $ cfgdefines=h cfgdefines=cache cfgdefines=key
except KeyError:
pass
# Same pattern as `f`: dead under "no expressions raise".
value = compute(key)
# Same pattern as `f`: reachable via the except-handler fall-through.
value = compute(key) # $ cfgdefines=value
cache[key] = value
return value

View File

@@ -2,5 +2,4 @@
| def-only-old | __name__:0:0 |
| def-only-old | __package__:0:0 |
| def-only-old | e:37:1 |
| def-only-old | e:40:25 |
| def-only-old | x:20:1 |

View File

@@ -844,7 +844,7 @@ def return_from_inner_scope(x):
return SOURCE
def test_return_from_inner_scope():
SINK(return_from_inner_scope([])) # $ MISSING: flow="SOURCE, l:-3 -> return_from_inner_scope(..)"
SINK(return_from_inner_scope([])) # $ flow="SOURCE, l:-3 -> return_from_inner_scope(..)"
# Inspired by reverse read inconsistency check

View File

@@ -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 |

View File

@@ -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" |

View File

@@ -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 |

View File

@@ -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

View File

@@ -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 |

View File

@@ -1 +1,2 @@
| exceptions_test.py:7:5:7:11 | ExceptStmt | Except block directly handles BaseException. |
| exceptions_test.py:97:5:97:25 | ExceptStmt | Except block directly handles BaseException. |

View File

@@ -3,9 +3,3 @@
| exceptions_test.py:72:1:72:18 | ExceptStmt | 'except' clause does nothing but pass and there is no explanatory comment. |
| exceptions_test.py:85:1:85:17 | ExceptStmt | 'except' clause does nothing but pass and there is no explanatory comment. |
| exceptions_test.py:89:1:89:17 | ExceptStmt | 'except' clause does nothing but pass and there is no explanatory comment. |
| exceptions_test.py:144:5:144:25 | ExceptStmt | 'except' clause does nothing but pass and there is no explanatory comment. |
| exceptions_test.py:167:5:167:26 | ExceptStmt | 'except' clause does nothing but pass and there is no explanatory comment. |
| exceptions_test.py:173:5:173:22 | ExceptStmt | 'except' clause does nothing but pass and there is no explanatory comment. |
| exceptions_test.py:179:5:179:22 | ExceptStmt | 'except' clause does nothing but pass and there is no explanatory comment. |
| exceptions_test.py:185:5:185:26 | ExceptStmt | 'except' clause does nothing but pass and there is no explanatory comment. |
| exceptions_test.py:191:5:191:30 | ExceptStmt | 'except' clause does nothing but pass and there is no explanatory comment. |

View File

@@ -1,3 +1 @@
#select
testFailures
| exceptions_test.py:64:24:64:55 | Comment # $ Alert[py/unreachable-except] | Missing result: Alert[py/unreachable-except] |
| exceptions_test.py:64:1:64:22 | ExceptStmt | This except block handling $@ is unreachable; as $@ for the more general $@ always subsumes it. | file://:0:0:0:0 | AttributeError | AttributeError | exceptions_test.py:62:1:62:17 | ExceptStmt | this except block | file://:0:0:0:0 | Exception | Exception |

View File

@@ -1,40 +1,9 @@
#select
| resources_test.py:4:10:4:25 | After open() | File may not be closed if $@ raises an exception. | resources_test.py:5:5:5:33 | After Attribute() | this operation |
| resources_test.py:9:10:9:25 | After open() | File is opened but is not closed. | file://:0:0:0:0 | (none) | this operation |
| resources_test.py:20:10:20:25 | After open() | File may not be closed if $@ raises an exception. | resources_test.py:22:9:22:37 | After Attribute() | this operation |
| resources_test.py:30:14:30:29 | After open() | File may not be closed if $@ raises an exception. | resources_test.py:31:9:31:37 | After Attribute() | this operation |
| resources_test.py:39:14:39:29 | After open() | File may not be closed if $@ raises an exception. | resources_test.py:40:9:40:37 | After Attribute() | this operation |
| resources_test.py:49:14:49:29 | After open() | File may not be closed if $@ raises an exception. | resources_test.py:50:9:50:37 | After Attribute() | this operation |
| resources_test.py:58:14:58:29 | After open() | File may not be closed if $@ raises an exception. | resources_test.py:59:9:59:37 | After Attribute() | this operation |
| resources_test.py:69:11:69:26 | After open() | File may not be closed if $@ raises an exception. | resources_test.py:71:9:71:40 | After Attribute() | this operation |
| resources_test.py:69:11:69:26 | After open() | File may not be closed if $@ raises an exception. | resources_test.py:72:9:72:40 | After Attribute() | this operation |
| resources_test.py:79:11:79:26 | After open() | File may not be closed if $@ raises an exception. | resources_test.py:81:9:81:40 | After Attribute() | this operation |
| resources_test.py:79:11:79:26 | After open() | File may not be closed if $@ raises an exception. | resources_test.py:82:9:82:40 | After Attribute() | this operation |
| resources_test.py:91:11:91:26 | After open() | File may not be closed if $@ raises an exception. | resources_test.py:93:9:93:40 | After Attribute() | this operation |
| resources_test.py:91:11:91:26 | After open() | File may not be closed if $@ raises an exception. | resources_test.py:94:9:94:40 | After Attribute() | this operation |
| resources_test.py:108:11:108:20 | After open() | File is opened but is not closed. | file://:0:0:0:0 | (none) | this operation |
| resources_test.py:112:11:112:28 | After opener_func2() | File may not be closed if $@ raises an exception. | resources_test.py:113:5:113:22 | After Attribute() | this operation |
| resources_test.py:123:11:123:24 | After opener_func2() | File is opened but is not closed. | file://:0:0:0:0 | (none) | this operation |
| resources_test.py:129:15:129:24 | After open() | File may not be closed if $@ raises an exception. | resources_test.py:130:9:130:26 | After Attribute() | this operation |
| resources_test.py:141:11:141:26 | After open() | File may not be closed if $@ raises an exception. | resources_test.py:143:9:143:40 | After Attribute() | this operation |
| resources_test.py:141:11:141:26 | After open() | File may not be closed if $@ raises an exception. | resources_test.py:144:9:144:40 | After Attribute() | this operation |
| resources_test.py:182:15:182:54 | After open() | File may not be closed if $@ raises an exception. | resources_test.py:186:9:186:25 | After Attribute() | this operation |
| resources_test.py:225:11:225:26 | After open() | File may not be closed if $@ raises an exception. | resources_test.py:227:9:227:25 | After Attribute() | this operation |
| resources_test.py:237:11:237:26 | After open() | File may not be closed if $@ raises an exception. | resources_test.py:239:9:239:25 | After Attribute() | this operation |
| resources_test.py:248:11:248:25 | After open() | File is opened but is not closed. | file://:0:0:0:0 | (none) | this operation |
| resources_test.py:252:11:252:25 | After open() | File may not be closed if $@ raises an exception. | resources_test.py:254:9:254:23 | After Attribute() | this operation |
| resources_test.py:269:10:269:27 | After Attribute() | File may not be closed if $@ raises an exception. | resources_test.py:271:5:271:19 | After Attribute() | this operation |
| resources_test.py:275:10:275:35 | After Attribute() | File may not be closed if $@ raises an exception. | resources_test.py:278:9:278:23 | After Attribute() | this operation |
| resources_test.py:285:11:285:20 | After open() | File may not be closed if $@ raises an exception. | resources_test.py:287:5:287:31 | After Attribute() | this operation |
testFailures
| resources_test.py:20:10:20:25 | File may not be closed if $@ raises an exception. | Unexpected result: Alert |
| resources_test.py:30:14:30:29 | File may not be closed if $@ raises an exception. | Unexpected result: Alert |
| resources_test.py:39:14:39:29 | File may not be closed if $@ raises an exception. | Unexpected result: Alert |
| resources_test.py:58:14:58:29 | File may not be closed if $@ raises an exception. | Unexpected result: Alert |
| resources_test.py:69:11:69:26 | File may not be closed if $@ raises an exception. | Unexpected result: Alert |
| resources_test.py:79:11:79:26 | File may not be closed if $@ raises an exception. | Unexpected result: Alert |
| resources_test.py:91:11:91:26 | File may not be closed if $@ raises an exception. | Unexpected result: Alert |
| resources_test.py:182:15:182:54 | File may not be closed if $@ raises an exception. | Unexpected result: Alert |
| resources_test.py:225:11:225:26 | File may not be closed if $@ raises an exception. | Unexpected result: Alert |
| resources_test.py:252:11:252:25 | File may not be closed if $@ raises an exception. | Unexpected result: Alert |
| resources_test.py:275:10:275:35 | File may not be closed if $@ raises an exception. | Unexpected result: Alert |

View File

@@ -46,7 +46,7 @@ def closed7():
def not_closed8():
f8 = None
try:
f8 = open("filename") # $ Alert # not closed on exception
f8 = open("filename") # $ MISSING:Alert # not closed on exception (FileNotAlwaysClosed is optimistic about exception-flow close paths through buggy guards)
f8.write("Error could occur")
finally:
if f8 is None: # We don't precisely consider this condition, so this result is MISSING. However, this seems uncommon.
@@ -138,7 +138,7 @@ def may_raise():
#Not handling all exceptions, but we'll tolerate the false negative
def not_closed17():
f17 = open("filename") # $ Alert # not closed on exception
f17 = open("filename") # $ MISSING:Alert # not closed on exception (FileNotAlwaysClosed is optimistic about exception-flow close paths through buggy guards)
try:
f17.write("IOError could occur")
f17.write("IOError could occur")
@@ -234,7 +234,7 @@ def closed21(path):
def not_closed22(path):
f22 = open(path, "wb") # $ Alert # not closed on exception
f22 = open(path, "wb") # $ MISSING:Alert # not closed on exception (FileNotAlwaysClosed is optimistic about exception-flow close paths through buggy guards)
try:
f22.write(b"foo")
may_raise()

View File

@@ -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 |

View File

@@ -1,17 +0,0 @@
| Exceptions.py:3:25:3:41 | Comment # $ exceptionInfo | Missing result: exceptionInfo |
| Exceptions.py:9:29:9:45 | Comment # $ exceptionInfo | Missing result: exceptionInfo |
| Stacktrace.py:6:57:6:73 | Comment # $ exceptionInfo | Missing result: exceptionInfo |
| Stacktrace.py:8:46:8:62 | Comment # $ exceptionInfo | Missing result: exceptionInfo |
| Stacktrace.py:9:39:9:55 | Comment # $ exceptionInfo | Missing result: exceptionInfo |
| Stacktrace.py:10:40:10:56 | Comment # $ exceptionInfo | Missing result: exceptionInfo |
| Stacktrace.py:11:75:11:91 | Comment # $ exceptionInfo | Missing result: exceptionInfo |
| Stacktrace.py:12:61:12:77 | Comment # $ exceptionInfo | Missing result: exceptionInfo |
| Stacktrace.py:13:41:13:57 | Comment # $ exceptionInfo | Missing result: exceptionInfo |
| Stacktrace.py:14:37:14:53 | Comment # $ exceptionInfo | Missing result: exceptionInfo |
| Stacktrace.py:15:47:15:63 | Comment # $ exceptionInfo | Missing result: exceptionInfo |
| test.py:16:40:16:56 | Comment # $ exceptionInfo | Missing result: exceptionInfo |
| test.py:23:29:23:45 | Comment # $ exceptionInfo | Missing result: exceptionInfo |
| test.py:31:29:31:45 | Comment # $ exceptionInfo | Missing result: exceptionInfo |
| test.py:40:38:40:54 | Comment # $ exceptionInfo | Missing result: exceptionInfo |
| test.py:49:39:49:55 | Comment # $ exceptionInfo | Missing result: exceptionInfo |
| test.py:65:28:65:44 | Comment # $ exceptionInfo | Missing result: exceptionInfo |

View File

@@ -1,4 +1,33 @@
edges
| test.py:23:25:23:25 | e | test.py:24:16:24:16 | e | provenance | |
| test.py:31:25:31:25 | e | test.py:32:16:32:16 | e | provenance | |
| test.py:32:16:32:16 | e | test.py:32:16:32:30 | After Attribute | provenance | Config |
| test.py:49:9:49:11 | err | test.py:50:29:50:31 | err | provenance | |
| test.py:49:15:49:36 | After Attribute() | test.py:49:9:49:11 | err | provenance | |
| test.py:50:29:50:31 | err | test.py:50:16:50:32 | After format_error() | provenance | |
| test.py:50:29:50:31 | err | test.py:52:18:52:20 | msg | provenance | |
| test.py:52:18:52:20 | msg | test.py:53:12:53:27 | After BinaryExpr | provenance | |
| test.py:65:25:65:25 | e | test.py:66:24:66:40 | After Dict | provenance | |
nodes
| test.py:16:16:16:37 | After Attribute() | semmle.label | After Attribute() |
| test.py:23:25:23:25 | e | semmle.label | e |
| test.py:24:16:24:16 | e | semmle.label | e |
| test.py:31:25:31:25 | e | semmle.label | e |
| test.py:32:16:32:16 | e | semmle.label | e |
| test.py:32:16:32:30 | After Attribute | semmle.label | After Attribute |
| test.py:49:9:49:11 | err | semmle.label | err |
| test.py:49:15:49:36 | After Attribute() | semmle.label | After Attribute() |
| test.py:50:16:50:32 | After format_error() | semmle.label | After format_error() |
| test.py:50:29:50:31 | err | semmle.label | err |
| test.py:52:18:52:20 | msg | semmle.label | msg |
| test.py:53:12:53:27 | After BinaryExpr | semmle.label | After BinaryExpr |
| test.py:65:25:65:25 | e | semmle.label | e |
| test.py:66:24:66:40 | After Dict | semmle.label | After Dict |
subpaths
| test.py:50:29:50:31 | err | test.py:52:18:52:20 | msg | test.py:53:12:53:27 | After BinaryExpr | test.py:50:16:50:32 | After format_error() |
#select
| test.py:16:16:16:37 | After Attribute() | test.py:16:16:16:37 | After Attribute() | test.py:16:16:16:37 | After Attribute() | $@ flows to this location and may be exposed to an external user. | test.py:16:16:16:37 | After Attribute() | Stack trace information |
| test.py:24:16:24:16 | e | test.py:23:25:23:25 | e | test.py:24:16:24:16 | e | $@ flows to this location and may be exposed to an external user. | test.py:23:25:23:25 | e | Stack trace information |
| test.py:32:16:32:30 | After Attribute | test.py:31:25:31:25 | e | test.py:32:16:32:30 | After Attribute | $@ flows to this location and may be exposed to an external user. | test.py:31:25:31:25 | e | Stack trace information |
| test.py:50:16:50:32 | After format_error() | test.py:49:15:49:36 | After Attribute() | test.py:50:16:50:32 | After format_error() | $@ flows to this location and may be exposed to an external user. | test.py:49:15:49:36 | After Attribute() | Stack trace information |
| test.py:66:24:66:40 | After Dict | test.py:65:25:65:25 | e | test.py:66:24:66:40 | After Dict | $@ flows to this location and may be exposed to an external user. | test.py:65:25:65:25 | e | Stack trace information |

View File

@@ -0,0 +1 @@
| test.py:7:9:7:15 | After exit() | The 'exit' site.Quitter object may not exist if the 'site' module is not loaded or is modified. |