diff --git a/python/ql/lib/change-notes/2026-10-22-url-redirect-validation.md b/python/ql/lib/change-notes/2026-10-22-url-redirect-validation.md new file mode 100644 index 000000000000..dab96c0c809a --- /dev/null +++ b/python/ql/lib/change-notes/2026-10-22-url-redirect-validation.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* URL redirection sanitizers based on `urllib.parse.urlparse` and `urllib.parse.urlsplit` now require checks of the scheme, leading slashes, and browser-ignored characters when the network location may be empty. Host allowlists must resolve locally to known values, and collection allowlists are trusted only when immutable or used directly as literals. Hostname attributes are trusted only when all possible receivers originate from `urlparse` or `urlsplit` calls on constant URLs. diff --git a/python/ql/lib/semmle/python/frameworks/Stdlib/Urllib.qll b/python/ql/lib/semmle/python/frameworks/Stdlib/Urllib.qll index 6b5764e55925..a08c5bf99dfd 100644 --- a/python/ql/lib/semmle/python/frameworks/Stdlib/Urllib.qll +++ b/python/ql/lib/semmle/python/frameworks/Stdlib/Urllib.qll @@ -81,47 +81,296 @@ private module Urllib { */ module Parse { /** - * A call to `urllib.parse.urlparse`. + * A call to `urllib.parse.urlparse` or `urllib.parse.urlsplit`. */ private DataFlow::CallCfgNode getUrlParseCall() { - result = API::moduleImport("urllib").getMember("parse").getMember("urlparse").getACall() + result = + API::moduleImport("urllib") + .getMember("parse") + .getMember(["urlparse", "urlsplit"]) + .getACall() and + forex(DataFlow::LocalSourceNode source | source = result.getFunction().getALocalSource() | + source = + API::moduleImport("urllib") + .getMember("parse") + .getMember(["urlparse", "urlsplit"]) + .getAValueReachableFromSource() + ) + } + + /** A URL parser call whose URL argument resolves entirely to string literals. */ + private DataFlow::CallCfgNode getConstantUrlParseCall() { + result = getUrlParseCall() and + forex(DataFlow::LocalSourceNode source | + source = [result.getArg(0), result.getArgByName(["url", "urlstring"])].getALocalSource() + | + source.asExpr() instanceof StringLiteral + ) + } + + /** Holds if `e` is a parsed URL hostname, which is either non-empty or `None`. */ + private predicate isParsedUrlHostname(Expr e) { + exists(DataFlow::AttrRead read | + read = DataFlow::exprNode(e) and + read.getAttributeName() = "hostname" and + forex(DataFlow::LocalSourceNode source | source = read.getObject().getALocalSource() | + source = getConstantUrlParseCall() + ) + ) + } + + /** Holds if `e` itself is known not to be `""`. */ + private predicate sourceExcludesEmptyString(Expr e) { + e.(StringLiteral).getText() != "" + or + isParsedUrlHostname(e) + } + + /** Holds if every local source of `e` excludes `""`. */ + private predicate excludesEmptyString(Expr e) { + forex(DataFlow::LocalSourceNode source | source = DataFlow::exprNode(e).getALocalSource() | + sourceExcludesEmptyString(source.asExpr()) + ) + } + + /** Holds if every local source of `e` is a collection literal, rather than a string. */ + private predicate membershipChecksCollection(Expr e) { + forex(DataFlow::LocalSourceNode source | source = DataFlow::exprNode(e).getALocalSource() | + source.asCfgNode() instanceof IterableNode + or + source.asCfgNode() instanceof DictNode + ) } /** - * A read of the `netloc` attribute of a parsed URL as returned by `urllib.parse.urlparse`, - * which is being checked in a way that is relevant for URL redirection vulnerabilities. + * Holds if every local source of `e` is an immutable collection literal, or a mutable + * collection literal used directly in the membership test, with known host elements or keys. + * Empty strings are accepted only when `allowEmpty` is true. */ - private predicate netlocCheck(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) { - exists(DataFlow::CallCfgNode urlParseCall, DataFlow::AttrRead netlocRead | + private predicate membershipChecksKnownHosts(Expr e, boolean allowEmpty) { + allowEmpty in [true, false] and + forex(DataFlow::LocalSourceNode source | source = DataFlow::exprNode(e).getALocalSource() | + exists(Expr collection | collection = source.asExpr() | + (collection instanceof List or collection instanceof Tuple or collection instanceof Set) and + (collection instanceof Tuple or collection = e) and + forall(Expr elt | + elt = + [ + collection.(List).getAnElt(), collection.(Tuple).getAnElt(), + collection.(Set).getAnElt() + ] + | + excludesEmptyString(elt) + or + allowEmpty = true and isEmptyString(elt) + ) + or + collection instanceof Dict and + collection = e and + not collection.(Dict).getAnItem() instanceof DictUnpacking and + forall(Expr key | key = collection.(Dict).getAKey() | + excludesEmptyString(key) + or + allowEmpty = true and isEmptyString(key) + ) + ) + ) + } + + /** Holds if every local source of `e` is `""`. */ + private predicate isEmptyString(Expr e) { + forex(DataFlow::LocalSourceNode source | source = DataFlow::exprNode(e).getALocalSource() | + source.asExpr().(StringLiteral).getText() = "" + ) + } + + /** Holds if comparing against `e` using `op` cannot accept `""`. */ + private predicate comparisonExcludesEmptyString(Cmpop op, Expr e) { + op.getSymbol() in ["in", "not in"] and membershipChecksKnownHosts(e, false) + or + op.getSymbol() in ["==", "is", "!=", "is not"] and excludesEmptyString(e) + } + + /** Holds if `read` reads `attribute` from the result of `urlParseCall`. */ + private predicate parsedUrlAttributeRead( + DataFlow::CallCfgNode urlParseCall, DataFlow::AttrRead read, string attribute + ) { + read.getAttributeName() = attribute and + forex(DataFlow::LocalSourceNode source | source = read.getObject().getALocalSource() | + source = urlParseCall + ) + } + + /** + * Holds if `g` is a falsiness check `not parsed.` on a parsed URL `node`. + */ + private predicate parsedUrlAttributeFalsiness( + DataFlow::GuardNode g, ControlFlowNode node, boolean branch, string attribute + ) { + exists(DataFlow::CallCfgNode urlParseCall, DataFlow::AttrRead attributeRead | urlParseCall = getUrlParseCall() and - netlocRead = urlParseCall.getAnAttributeRead("netloc") and - node = urlParseCall.getArg(0).asCfgNode() - | - // either a simple check of the netloc attribute - g = netlocRead.asCfgNode() and + parsedUrlAttributeRead(urlParseCall, attributeRead, attribute) and + g = attributeRead.asCfgNode() and + node = [urlParseCall.getArg(0), urlParseCall.getArgByName(["url", "urlstring"])].asCfgNode() and branch = false + ) + } + + /** + * Holds if `g` compares `attribute` (`"netloc"` or `"scheme"`) of a parsed URL `node` + * against `other` using `op`, and the equality or collection membership holds on `branch`. + */ + private predicate parsedUrlAttributeComparison( + DataFlow::GuardNode g, ControlFlowNode node, boolean branch, string attribute, Cmpop op, + Expr other + ) { + exists( + DataFlow::CallCfgNode urlParseCall, DataFlow::AttrRead attributeRead, CompareNode cmp, + ControlFlowNode otherNode + | + urlParseCall = getUrlParseCall() and + parsedUrlAttributeRead(urlParseCall, attributeRead, attribute) and + node = [urlParseCall.getArg(0), urlParseCall.getArgByName(["url", "urlstring"])].asCfgNode() and + cmp = g and + op = unique(Cmpop opp | opp = cmp.getNode().getAnOp()) and + other = otherNode.getNode() + | + op.getSymbol() in ["==", "is"] and + branch = true and + ( + cmp.operands(attributeRead.asCfgNode(), op, otherNode) or + cmp.operands(otherNode, op, attributeRead.asCfgNode()) + ) + or + op.getSymbol() in ["!=", "is not"] and + branch = false and + ( + cmp.operands(attributeRead.asCfgNode(), op, otherNode) or + cmp.operands(otherNode, op, attributeRead.asCfgNode()) + ) + or + op.getSymbol() = "in" and + membershipChecksCollection(other) and + branch = true and + cmp.operands(attributeRead.asCfgNode(), op, otherNode) or - // or a comparison (we don't care against what) - exists(Compare cmp, string op | - cmp = g.getNode() and - op = unique(Cmpop opp | opp = cmp.getAnOp()).getSymbol() and - cmp.getASubExpression() = netlocRead.asExpr() - | - op in ["==", "is", "in"] and branch = true + op.getSymbol() = "not in" and + membershipChecksCollection(other) and + branch = false and + cmp.operands(attributeRead.asCfgNode(), op, otherNode) + ) + } + + /** A `netloc` check that does not allow `""` (e.g. `parsed.netloc == request.host`). */ + private predicate nonEmptyNetlocCheck( + DataFlow::GuardNode g, ControlFlowNode node, boolean branch + ) { + exists(Cmpop op, Expr other | + parsedUrlAttributeComparison(g, node, branch, "netloc", op, other) and + comparisonExcludesEmptyString(op, other) + ) + } + + /** A `netloc` check allowing `""` and otherwise only known hosts. */ + private predicate possiblyEmptyNetlocCheck( + DataFlow::GuardNode g, ControlFlowNode node, boolean branch + ) { + parsedUrlAttributeFalsiness(g, node, branch, "netloc") + or + exists(Cmpop op, Expr other | + parsedUrlAttributeComparison(g, node, branch, "netloc", op, other) and + ( + op.getSymbol() in ["==", "is", "!=", "is not"] and isEmptyString(other) or - op in ["!=", "is not", "not in"] and branch = false + op.getSymbol() in ["in", "not in"] and membershipChecksKnownHosts(other, true) ) ) } + /** A `scheme` check that ensures `scheme` is `""`. */ + private predicate emptySchemeCheck(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) { + parsedUrlAttributeFalsiness(g, node, branch, "scheme") + or + exists(Cmpop op, Expr other | + parsedUrlAttributeComparison(g, node, branch, "scheme", op, other) and + op.getSymbol() in ["==", "is", "!=", "is not"] and + isEmptyString(other) + ) + } + + /** Holds if every possible callee is `methodName` bound to `object`. */ + private predicate callsOnlyMethod( + DataFlow::MethodCallNode call, DataFlow::Node object, string methodName + ) { + forex(DataFlow::LocalSourceNode source | source = call.getFunction().getALocalSource() | + source.(DataFlow::AttrRead).getAttributeName() = methodName and + source.(DataFlow::AttrRead).getObject() = object + ) + } + + private predicate leadingSlashesCheck( + DataFlow::GuardNode g, ControlFlowNode node, boolean branch + ) { + exists(DataFlow::MethodCallNode call, DataFlow::Node url, Expr prefix | + callsOnlyMethod(call, url, "startswith") and + ( + prefix = call.getArg(0).asExpr() or + prefix = call.getArg(0).asExpr().(Tuple).getAnElt() + ) and + prefix.(StringLiteral).getText() in ["/", "//", "///"] and + not exists(call.getArg(1)) and + not exists(call.getStarArg()) and + g = call.asCfgNode() and + node = url.asCfgNode() and + branch = false + ) + } + + /** Holds if every local source of `node` is a no-argument `strip` or `lstrip` call. */ + private predicate isStripped(DataFlow::Node node) { + forex(DataFlow::LocalSourceNode source | source = node.getALocalSource() | + callsOnlyMethod(source, _, ["strip", "lstrip"]) and + not exists(source.asExpr().(Call).getAPositionalArg()) and + not exists(source.asExpr().(Call).getANamedArg()) + ) + } + + /** An `isprintable` check on a stripped URL. */ + private predicate normalizedPrintableCheck( + DataFlow::GuardNode g, ControlFlowNode node, boolean branch + ) { + exists(DataFlow::MethodCallNode call, DataFlow::Node url | + callsOnlyMethod(call, url, "isprintable") and + not exists(call.asExpr().(Call).getAPositionalArg()) and + not exists(call.asExpr().(Call).getANamedArg()) and + isStripped(url) and + g = call.asCfgNode() and + node = url.asCfgNode() and + branch = true + ) + } + /** - * A check of `urllib.parse.urlparse().netloc`, considered as a sanitizer-guard for URL redirection. + * A check of `urllib.parse.urlparse` or `urllib.parse.urlsplit` considered as a + * sanitizer-guard for URL redirection. + * + * Either `netloc` is restricted to a non-empty host, or `netloc` may be empty + * and `scheme`, leading slashes (`///`), and browser-ignored characters are also checked. */ private class NetlocCheck extends UrlRedirect::Sanitizer { - NetlocCheck() { this = DataFlow::BarrierGuard::getABarrierNode() } + NetlocCheck() { + this = DataFlow::BarrierGuard::getABarrierNode() + or + this = DataFlow::BarrierGuard::getABarrierNode() and + this = DataFlow::BarrierGuard::getABarrierNode() and + // Browsers interpret three or more leading slashes as an authority, unlike `urlparse`. + this = DataFlow::BarrierGuard::getABarrierNode() and + // Browsers ignore leading whitespace and control characters within URLs. + this = DataFlow::BarrierGuard::getABarrierNode() + } override predicate sanitizes(UrlRedirect::FlowState state) { - // `urlparse` does not handle backslashes + // Neither parser handles backslashes as browsers do. state instanceof UrlRedirect::NoBackslashes } } diff --git a/python/ql/src/Security/CWE-601/UrlRedirect.qhelp b/python/ql/src/Security/CWE-601/UrlRedirect.qhelp index c321ff23684d..6b3859293909 100644 --- a/python/ql/src/Security/CWE-601/UrlRedirect.qhelp +++ b/python/ql/src/Security/CWE-601/UrlRedirect.qhelp @@ -18,7 +18,7 @@ redirects on the server; then choose from that list based on the user input prov

If this is not possible, then the user input should be validated in some other way, -for example, by verifying that the target URL does not include an explicit host name. +using a maintained framework helper that accounts for how browsers interpret URLs.

@@ -38,40 +38,41 @@ and check that the user input is in that list:

-Often this is not possible, so an alternative is to check that the target URL does not -specify an explicit host name. For example, you can use the urlparse function -from the Python standard library to parse the URL and check that the netloc -attribute is empty. +If an allowlist is not practical, use a maintained framework helper to validate the +redirect target. For Django applications, url_has_allowed_host_and_scheme +checks whether a URL is safe to redirect to:

+ +

-Note, however, that some cases are not handled as we desire out-of-the-box by urlparse, so we need to adjust two things, as shown in the example below: +Note that url_has_allowed_host_and_scheme handles backslashes correctly, so no +additional processing is required. +

+ +

+Do not rely on urlparse or urlsplit alone to validate redirect +targets. These functions do not interpret all URLs in the same way as browsers:

  • Many browsers accept backslash characters (\) as equivalent - to forward slash characters (/) in URLs, but the urlparse function - does not. + to forward slash characters (/) in URLs, but these parsers do not.
  • Mistyped URLs such as https:/example.com or https:///example.com are parsed as having an empty netloc attribute, while browsers will still redirect to the correct site.
  • +
  • + URLs such as ///example.com and ////example.com are parsed as + having both an empty netloc and an empty scheme, but browsers + interpret them as URLs with a host name. +
- - -

-For Django application, you can use the function url_has_allowed_host_and_scheme -to check that a URL is safe to redirect to, as shown in the following example: -

- - - -

-Note that url_has_allowed_host_and_scheme handles backslashes correctly, so no -additional processing is required. +Hand-written validation must also account for whitespace and control characters that +browsers may ignore. Validate the same normalized value that is used in the redirect.

diff --git a/python/ql/src/Security/CWE-601/examples/redirect_good2.py b/python/ql/src/Security/CWE-601/examples/redirect_good2.py deleted file mode 100644 index f9cc0313b969..000000000000 --- a/python/ql/src/Security/CWE-601/examples/redirect_good2.py +++ /dev/null @@ -1,14 +0,0 @@ -from flask import Flask, request, redirect -from urllib.parse import urlparse - -app = Flask(__name__) - -@app.route('/') -def hello(): - target = request.args.get('target', '') - target = target.replace('\\', '') - if not urlparse(target).netloc and not urlparse(target).scheme: - # relative path, safe to redirect - return redirect(target, code=302) - # ignore the target and redirect to the home page - return redirect('/', code=302) diff --git a/python/ql/src/change-notes/2026-10-22-url-redirect-validation.md b/python/ql/src/change-notes/2026-10-22-url-redirect-validation.md new file mode 100644 index 000000000000..afe82d5f6057 --- /dev/null +++ b/python/ql/src/change-notes/2026-10-22-url-redirect-validation.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* The `py/url-redirection` query now reports redirects guarded only by incomplete `urllib.parse` checks, including checks that accept URLs beginning with three or more slashes. The query help no longer recommends an empty network location and scheme as sufficient validation. diff --git a/python/ql/test/query-tests/Security/CWE-601-UrlRedirect/UrlRedirect.expected b/python/ql/test/query-tests/Security/CWE-601-UrlRedirect/UrlRedirect.expected index 3e43c112e2a3..efc85453ad40 100644 --- a/python/ql/test/query-tests/Security/CWE-601-UrlRedirect/UrlRedirect.expected +++ b/python/ql/test/query-tests/Security/CWE-601-UrlRedirect/UrlRedirect.expected @@ -9,8 +9,62 @@ | test.py:83:21:83:26 | ControlFlowNode for unsafe | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:83:21:83:26 | ControlFlowNode for unsafe | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | | test.py:93:18:93:26 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:93:18:93:26 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | | test.py:114:25:114:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:114:25:114:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:124:25:124:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:124:25:124:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:132:25:132:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:132:25:132:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | | test.py:140:25:140:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:140:25:140:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | | test.py:148:25:148:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:148:25:148:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:156:25:156:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:156:25:156:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:165:21:165:29 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:165:21:165:29 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:300:25:300:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:300:25:300:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:314:25:314:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:314:25:314:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:328:25:328:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:328:25:328:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:342:25:342:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:342:25:342:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:355:25:355:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:355:25:355:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:370:25:370:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:370:25:370:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:382:25:382:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:382:25:382:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:391:25:391:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:391:25:391:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:404:25:404:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:404:25:404:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:417:25:417:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:417:25:417:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:431:25:431:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:431:25:431:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:444:25:444:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:444:25:444:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:458:25:458:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:458:25:458:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:470:25:470:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:470:25:470:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:483:25:483:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:483:25:483:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:497:25:497:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:497:25:497:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:514:25:514:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:514:25:514:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:527:25:527:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:527:25:527:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:539:25:539:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:539:25:539:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:553:21:553:29 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:553:21:553:29 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:566:25:566:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:566:25:566:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:582:25:582:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:582:25:582:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:597:21:597:29 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:597:21:597:29 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:612:21:612:29 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:612:21:612:29 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:629:25:629:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:629:25:629:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:644:25:644:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:644:25:644:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:702:25:702:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:702:25:702:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:720:25:720:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:720:25:720:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:729:25:729:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:729:25:729:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:739:25:739:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:739:25:739:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:751:25:751:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:751:25:751:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:760:25:760:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:760:25:760:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:768:25:768:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:768:25:768:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:776:25:776:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:776:25:776:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:784:25:784:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:784:25:784:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:792:25:792:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:792:25:792:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:800:25:800:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:800:25:800:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:810:25:810:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:810:25:810:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:827:25:827:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:827:25:827:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:835:25:835:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:835:25:835:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:849:25:849:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:849:25:849:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:863:25:863:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:863:25:863:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:877:25:877:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:877:25:877:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:891:25:891:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:891:25:891:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:914:25:914:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:914:25:914:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:924:25:924:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:924:25:924:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:953:25:953:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:953:25:953:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:967:25:967:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:967:25:967:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:981:25:981:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:981:25:981:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | +| test.py:997:25:997:33 | ControlFlowNode for untrusted | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:997:25:997:33 | ControlFlowNode for untrusted | Untrusted URL redirection depends on a $@. | test.py:1:26:1:32 | ControlFlowNode for ImportMember | user-provided value | edges | test.py:1:26:1:32 | ControlFlowNode for ImportMember | test.py:1:26:1:32 | ControlFlowNode for request | provenance | | | test.py:1:26:1:32 | ControlFlowNode for request | test.py:7:14:7:20 | ControlFlowNode for request | provenance | | @@ -23,8 +77,62 @@ edges | test.py:1:26:1:32 | ControlFlowNode for request | test.py:81:17:81:23 | ControlFlowNode for request | provenance | | | test.py:1:26:1:32 | ControlFlowNode for request | test.py:90:17:90:23 | ControlFlowNode for request | provenance | | | test.py:1:26:1:32 | ControlFlowNode for request | test.py:111:17:111:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:121:17:121:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:129:17:129:23 | ControlFlowNode for request | provenance | | | test.py:1:26:1:32 | ControlFlowNode for request | test.py:137:17:137:23 | ControlFlowNode for request | provenance | | | test.py:1:26:1:32 | ControlFlowNode for request | test.py:145:17:145:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:153:17:153:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:161:17:161:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:293:17:293:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:306:17:306:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:320:17:320:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:334:17:334:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:348:17:348:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:361:17:361:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:376:17:376:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:388:17:388:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:397:17:397:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:410:17:410:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:423:17:423:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:437:17:437:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:450:17:450:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:464:17:464:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:476:17:476:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:489:17:489:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:503:17:503:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:520:17:520:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:533:17:533:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:545:17:545:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:558:17:558:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:572:17:572:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:588:17:588:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:602:17:602:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:617:17:617:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:635:17:635:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:695:17:695:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:717:17:717:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:726:17:726:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:735:17:735:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:745:17:745:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:757:17:757:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:766:17:766:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:774:17:774:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:782:17:782:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:790:17:790:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:798:17:798:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:806:17:806:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:824:17:824:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:833:17:833:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:841:17:841:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:855:17:855:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:869:17:869:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:883:17:883:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:905:17:905:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:920:17:920:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:945:17:945:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:959:17:959:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:973:17:973:23 | ControlFlowNode for request | provenance | | +| test.py:1:26:1:32 | ControlFlowNode for request | test.py:987:17:987:23 | ControlFlowNode for request | provenance | | | test.py:7:5:7:10 | ControlFlowNode for target | test.py:8:21:8:26 | ControlFlowNode for target | provenance | | | test.py:7:14:7:20 | ControlFlowNode for request | test.py:7:14:7:25 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | | test.py:7:14:7:25 | ControlFlowNode for Attribute | test.py:7:14:7:43 | ControlFlowNode for Attribute() | provenance | dict.get | @@ -72,6 +180,20 @@ edges | test.py:111:17:111:23 | ControlFlowNode for request | test.py:111:17:111:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | | test.py:111:17:111:28 | ControlFlowNode for Attribute | test.py:111:17:111:46 | ControlFlowNode for Attribute() | provenance | dict.get | | test.py:111:17:111:46 | ControlFlowNode for Attribute() | test.py:111:5:111:13 | ControlFlowNode for untrusted | provenance | | +| test.py:121:5:121:13 | ControlFlowNode for untrusted | test.py:122:17:122:25 | ControlFlowNode for untrusted | provenance | | +| test.py:121:17:121:23 | ControlFlowNode for request | test.py:121:17:121:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:121:17:121:28 | ControlFlowNode for Attribute | test.py:121:17:121:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:121:17:121:46 | ControlFlowNode for Attribute() | test.py:121:5:121:13 | ControlFlowNode for untrusted | provenance | | +| test.py:122:5:122:13 | ControlFlowNode for untrusted | test.py:124:25:124:33 | ControlFlowNode for untrusted | provenance | | +| test.py:122:17:122:25 | ControlFlowNode for untrusted | test.py:122:17:122:44 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:122:17:122:44 | ControlFlowNode for Attribute() | test.py:122:5:122:13 | ControlFlowNode for untrusted | provenance | | +| test.py:129:5:129:13 | ControlFlowNode for untrusted | test.py:130:17:130:25 | ControlFlowNode for untrusted | provenance | | +| test.py:129:17:129:23 | ControlFlowNode for request | test.py:129:17:129:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:129:17:129:28 | ControlFlowNode for Attribute | test.py:129:17:129:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:129:17:129:46 | ControlFlowNode for Attribute() | test.py:129:5:129:13 | ControlFlowNode for untrusted | provenance | | +| test.py:130:5:130:13 | ControlFlowNode for untrusted | test.py:132:25:132:33 | ControlFlowNode for untrusted | provenance | | +| test.py:130:17:130:25 | ControlFlowNode for untrusted | test.py:130:17:130:44 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:130:17:130:44 | ControlFlowNode for Attribute() | test.py:130:5:130:13 | ControlFlowNode for untrusted | provenance | | | test.py:137:5:137:13 | ControlFlowNode for untrusted | test.py:140:25:140:33 | ControlFlowNode for untrusted | provenance | | | test.py:137:17:137:23 | ControlFlowNode for request | test.py:137:17:137:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | | test.py:137:17:137:28 | ControlFlowNode for Attribute | test.py:137:17:137:46 | ControlFlowNode for Attribute() | provenance | dict.get | @@ -80,6 +202,270 @@ edges | test.py:145:17:145:23 | ControlFlowNode for request | test.py:145:17:145:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | | test.py:145:17:145:28 | ControlFlowNode for Attribute | test.py:145:17:145:46 | ControlFlowNode for Attribute() | provenance | dict.get | | test.py:145:17:145:46 | ControlFlowNode for Attribute() | test.py:145:5:145:13 | ControlFlowNode for untrusted | provenance | | +| test.py:153:5:153:13 | ControlFlowNode for untrusted | test.py:154:17:154:25 | ControlFlowNode for untrusted | provenance | | +| test.py:153:17:153:23 | ControlFlowNode for request | test.py:153:17:153:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:153:17:153:28 | ControlFlowNode for Attribute | test.py:153:17:153:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:153:17:153:46 | ControlFlowNode for Attribute() | test.py:153:5:153:13 | ControlFlowNode for untrusted | provenance | | +| test.py:154:5:154:13 | ControlFlowNode for untrusted | test.py:156:25:156:33 | ControlFlowNode for untrusted | provenance | | +| test.py:154:17:154:25 | ControlFlowNode for untrusted | test.py:154:17:154:44 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:154:17:154:44 | ControlFlowNode for Attribute() | test.py:154:5:154:13 | ControlFlowNode for untrusted | provenance | | +| test.py:161:5:161:13 | ControlFlowNode for untrusted | test.py:162:17:162:25 | ControlFlowNode for untrusted | provenance | | +| test.py:161:17:161:23 | ControlFlowNode for request | test.py:161:17:161:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:161:17:161:28 | ControlFlowNode for Attribute | test.py:161:17:161:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:161:17:161:46 | ControlFlowNode for Attribute() | test.py:161:5:161:13 | ControlFlowNode for untrusted | provenance | | +| test.py:162:5:162:13 | ControlFlowNode for untrusted | test.py:165:21:165:29 | ControlFlowNode for untrusted | provenance | | +| test.py:162:17:162:25 | ControlFlowNode for untrusted | test.py:162:17:162:44 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:162:17:162:44 | ControlFlowNode for Attribute() | test.py:162:5:162:13 | ControlFlowNode for untrusted | provenance | | +| test.py:293:5:293:13 | ControlFlowNode for untrusted | test.py:300:25:300:33 | ControlFlowNode for untrusted | provenance | | +| test.py:293:17:293:23 | ControlFlowNode for request | test.py:293:17:293:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:293:17:293:28 | ControlFlowNode for Attribute | test.py:293:17:293:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:293:17:293:46 | ControlFlowNode for Attribute() | test.py:293:5:293:13 | ControlFlowNode for untrusted | provenance | | +| test.py:306:5:306:13 | ControlFlowNode for untrusted | test.py:314:25:314:33 | ControlFlowNode for untrusted | provenance | | +| test.py:306:17:306:23 | ControlFlowNode for request | test.py:306:17:306:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:306:17:306:28 | ControlFlowNode for Attribute | test.py:306:17:306:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:306:17:306:46 | ControlFlowNode for Attribute() | test.py:306:17:306:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:306:17:306:65 | ControlFlowNode for Attribute() | test.py:306:5:306:13 | ControlFlowNode for untrusted | provenance | | +| test.py:320:5:320:13 | ControlFlowNode for untrusted | test.py:328:25:328:33 | ControlFlowNode for untrusted | provenance | | +| test.py:320:17:320:23 | ControlFlowNode for request | test.py:320:17:320:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:320:17:320:28 | ControlFlowNode for Attribute | test.py:320:17:320:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:320:17:320:46 | ControlFlowNode for Attribute() | test.py:320:17:320:54 | ControlFlowNode for Attribute() | provenance | | +| test.py:320:17:320:54 | ControlFlowNode for Attribute() | test.py:320:17:320:72 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:320:17:320:72 | ControlFlowNode for Attribute() | test.py:320:5:320:13 | ControlFlowNode for untrusted | provenance | | +| test.py:334:5:334:13 | ControlFlowNode for untrusted | test.py:342:25:342:33 | ControlFlowNode for untrusted | provenance | | +| test.py:334:17:334:23 | ControlFlowNode for request | test.py:334:17:334:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:334:17:334:28 | ControlFlowNode for Attribute | test.py:334:17:334:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:334:17:334:46 | ControlFlowNode for Attribute() | test.py:334:17:334:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:334:17:334:65 | ControlFlowNode for Attribute() | test.py:334:5:334:13 | ControlFlowNode for untrusted | provenance | | +| test.py:348:5:348:13 | ControlFlowNode for untrusted | test.py:355:25:355:33 | ControlFlowNode for untrusted | provenance | | +| test.py:348:17:348:23 | ControlFlowNode for request | test.py:348:17:348:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:348:17:348:28 | ControlFlowNode for Attribute | test.py:348:17:348:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:348:17:348:46 | ControlFlowNode for Attribute() | test.py:348:17:348:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:348:17:348:65 | ControlFlowNode for Attribute() | test.py:348:5:348:13 | ControlFlowNode for untrusted | provenance | | +| test.py:361:5:361:13 | ControlFlowNode for untrusted | test.py:370:25:370:33 | ControlFlowNode for untrusted | provenance | | +| test.py:361:17:361:23 | ControlFlowNode for request | test.py:361:17:361:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:361:17:361:28 | ControlFlowNode for Attribute | test.py:361:17:361:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:361:17:361:46 | ControlFlowNode for Attribute() | test.py:361:17:361:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:361:17:361:65 | ControlFlowNode for Attribute() | test.py:361:5:361:13 | ControlFlowNode for untrusted | provenance | | +| test.py:376:5:376:13 | ControlFlowNode for untrusted | test.py:382:25:382:33 | ControlFlowNode for untrusted | provenance | | +| test.py:376:17:376:23 | ControlFlowNode for request | test.py:376:17:376:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:376:17:376:28 | ControlFlowNode for Attribute | test.py:376:17:376:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:376:17:376:46 | ControlFlowNode for Attribute() | test.py:376:17:376:64 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:376:17:376:64 | ControlFlowNode for Attribute() | test.py:376:5:376:13 | ControlFlowNode for untrusted | provenance | | +| test.py:388:5:388:13 | ControlFlowNode for untrusted | test.py:391:25:391:33 | ControlFlowNode for untrusted | provenance | | +| test.py:388:17:388:23 | ControlFlowNode for request | test.py:388:17:388:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:388:17:388:28 | ControlFlowNode for Attribute | test.py:388:17:388:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:388:17:388:46 | ControlFlowNode for Attribute() | test.py:388:17:388:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:388:17:388:65 | ControlFlowNode for Attribute() | test.py:388:5:388:13 | ControlFlowNode for untrusted | provenance | | +| test.py:397:5:397:13 | ControlFlowNode for untrusted | test.py:404:25:404:33 | ControlFlowNode for untrusted | provenance | | +| test.py:397:17:397:23 | ControlFlowNode for request | test.py:397:17:397:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:397:17:397:28 | ControlFlowNode for Attribute | test.py:397:17:397:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:397:17:397:46 | ControlFlowNode for Attribute() | test.py:397:17:397:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:397:17:397:65 | ControlFlowNode for Attribute() | test.py:397:5:397:13 | ControlFlowNode for untrusted | provenance | | +| test.py:410:5:410:13 | ControlFlowNode for untrusted | test.py:417:25:417:33 | ControlFlowNode for untrusted | provenance | | +| test.py:410:17:410:23 | ControlFlowNode for request | test.py:410:17:410:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:410:17:410:28 | ControlFlowNode for Attribute | test.py:410:17:410:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:410:17:410:46 | ControlFlowNode for Attribute() | test.py:410:17:410:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:410:17:410:65 | ControlFlowNode for Attribute() | test.py:410:5:410:13 | ControlFlowNode for untrusted | provenance | | +| test.py:423:5:423:13 | ControlFlowNode for untrusted | test.py:431:25:431:33 | ControlFlowNode for untrusted | provenance | | +| test.py:423:17:423:23 | ControlFlowNode for request | test.py:423:17:423:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:423:17:423:28 | ControlFlowNode for Attribute | test.py:423:17:423:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:423:17:423:46 | ControlFlowNode for Attribute() | test.py:423:17:423:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:423:17:423:65 | ControlFlowNode for Attribute() | test.py:423:5:423:13 | ControlFlowNode for untrusted | provenance | | +| test.py:437:5:437:13 | ControlFlowNode for untrusted | test.py:444:25:444:33 | ControlFlowNode for untrusted | provenance | | +| test.py:437:17:437:23 | ControlFlowNode for request | test.py:437:17:437:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:437:17:437:28 | ControlFlowNode for Attribute | test.py:437:17:437:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:437:17:437:46 | ControlFlowNode for Attribute() | test.py:437:17:437:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:437:17:437:65 | ControlFlowNode for Attribute() | test.py:437:5:437:13 | ControlFlowNode for untrusted | provenance | | +| test.py:450:5:450:13 | ControlFlowNode for untrusted | test.py:458:25:458:33 | ControlFlowNode for untrusted | provenance | | +| test.py:450:17:450:23 | ControlFlowNode for request | test.py:450:17:450:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:450:17:450:28 | ControlFlowNode for Attribute | test.py:450:17:450:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:450:17:450:46 | ControlFlowNode for Attribute() | test.py:450:17:450:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:450:17:450:65 | ControlFlowNode for Attribute() | test.py:450:5:450:13 | ControlFlowNode for untrusted | provenance | | +| test.py:464:5:464:13 | ControlFlowNode for untrusted | test.py:470:25:470:33 | ControlFlowNode for untrusted | provenance | | +| test.py:464:17:464:23 | ControlFlowNode for request | test.py:464:17:464:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:464:17:464:28 | ControlFlowNode for Attribute | test.py:464:17:464:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:464:17:464:46 | ControlFlowNode for Attribute() | test.py:464:17:464:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:464:17:464:65 | ControlFlowNode for Attribute() | test.py:464:5:464:13 | ControlFlowNode for untrusted | provenance | | +| test.py:476:5:476:13 | ControlFlowNode for untrusted | test.py:483:25:483:33 | ControlFlowNode for untrusted | provenance | | +| test.py:476:17:476:23 | ControlFlowNode for request | test.py:476:17:476:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:476:17:476:28 | ControlFlowNode for Attribute | test.py:476:17:476:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:476:17:476:46 | ControlFlowNode for Attribute() | test.py:476:17:476:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:476:17:476:65 | ControlFlowNode for Attribute() | test.py:476:5:476:13 | ControlFlowNode for untrusted | provenance | | +| test.py:489:5:489:13 | ControlFlowNode for untrusted | test.py:497:25:497:33 | ControlFlowNode for untrusted | provenance | | +| test.py:489:17:489:23 | ControlFlowNode for request | test.py:489:17:489:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:489:17:489:28 | ControlFlowNode for Attribute | test.py:489:17:489:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:489:17:489:46 | ControlFlowNode for Attribute() | test.py:489:17:489:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:489:17:489:65 | ControlFlowNode for Attribute() | test.py:489:5:489:13 | ControlFlowNode for untrusted | provenance | | +| test.py:503:5:503:13 | ControlFlowNode for untrusted | test.py:514:25:514:33 | ControlFlowNode for untrusted | provenance | | +| test.py:503:17:503:23 | ControlFlowNode for request | test.py:503:17:503:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:503:17:503:28 | ControlFlowNode for Attribute | test.py:503:17:503:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:503:17:503:46 | ControlFlowNode for Attribute() | test.py:503:17:503:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:503:17:503:65 | ControlFlowNode for Attribute() | test.py:503:5:503:13 | ControlFlowNode for untrusted | provenance | | +| test.py:520:5:520:13 | ControlFlowNode for untrusted | test.py:527:25:527:33 | ControlFlowNode for untrusted | provenance | | +| test.py:520:17:520:23 | ControlFlowNode for request | test.py:520:17:520:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:520:17:520:28 | ControlFlowNode for Attribute | test.py:520:17:520:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:520:17:520:46 | ControlFlowNode for Attribute() | test.py:520:17:520:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:520:17:520:65 | ControlFlowNode for Attribute() | test.py:520:5:520:13 | ControlFlowNode for untrusted | provenance | | +| test.py:533:5:533:13 | ControlFlowNode for untrusted | test.py:539:25:539:33 | ControlFlowNode for untrusted | provenance | | +| test.py:533:17:533:23 | ControlFlowNode for request | test.py:533:17:533:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:533:17:533:28 | ControlFlowNode for Attribute | test.py:533:17:533:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:533:17:533:46 | ControlFlowNode for Attribute() | test.py:533:17:533:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:533:17:533:65 | ControlFlowNode for Attribute() | test.py:533:5:533:13 | ControlFlowNode for untrusted | provenance | | +| test.py:545:5:545:13 | ControlFlowNode for untrusted | test.py:553:21:553:29 | ControlFlowNode for untrusted | provenance | | +| test.py:545:17:545:23 | ControlFlowNode for request | test.py:545:17:545:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:545:17:545:28 | ControlFlowNode for Attribute | test.py:545:17:545:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:545:17:545:46 | ControlFlowNode for Attribute() | test.py:545:17:545:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:545:17:545:65 | ControlFlowNode for Attribute() | test.py:545:5:545:13 | ControlFlowNode for untrusted | provenance | | +| test.py:558:5:558:13 | ControlFlowNode for untrusted | test.py:566:25:566:33 | ControlFlowNode for untrusted | provenance | | +| test.py:558:17:558:23 | ControlFlowNode for request | test.py:558:17:558:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:558:17:558:28 | ControlFlowNode for Attribute | test.py:558:17:558:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:558:17:558:46 | ControlFlowNode for Attribute() | test.py:558:17:558:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:558:17:558:65 | ControlFlowNode for Attribute() | test.py:558:5:558:13 | ControlFlowNode for untrusted | provenance | | +| test.py:572:5:572:13 | ControlFlowNode for untrusted | test.py:582:25:582:33 | ControlFlowNode for untrusted | provenance | | +| test.py:572:17:572:23 | ControlFlowNode for request | test.py:572:17:572:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:572:17:572:28 | ControlFlowNode for Attribute | test.py:572:17:572:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:572:17:572:46 | ControlFlowNode for Attribute() | test.py:572:17:572:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:572:17:572:65 | ControlFlowNode for Attribute() | test.py:572:5:572:13 | ControlFlowNode for untrusted | provenance | | +| test.py:588:5:588:13 | ControlFlowNode for untrusted | test.py:597:21:597:29 | ControlFlowNode for untrusted | provenance | | +| test.py:588:17:588:23 | ControlFlowNode for request | test.py:588:17:588:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:588:17:588:28 | ControlFlowNode for Attribute | test.py:588:17:588:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:588:17:588:46 | ControlFlowNode for Attribute() | test.py:588:17:588:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:588:17:588:65 | ControlFlowNode for Attribute() | test.py:588:5:588:13 | ControlFlowNode for untrusted | provenance | | +| test.py:602:5:602:13 | ControlFlowNode for untrusted | test.py:612:21:612:29 | ControlFlowNode for untrusted | provenance | | +| test.py:602:17:602:23 | ControlFlowNode for request | test.py:602:17:602:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:602:17:602:28 | ControlFlowNode for Attribute | test.py:602:17:602:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:602:17:602:46 | ControlFlowNode for Attribute() | test.py:602:17:602:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:602:17:602:65 | ControlFlowNode for Attribute() | test.py:602:5:602:13 | ControlFlowNode for untrusted | provenance | | +| test.py:617:5:617:13 | ControlFlowNode for untrusted | test.py:629:25:629:33 | ControlFlowNode for untrusted | provenance | | +| test.py:617:17:617:23 | ControlFlowNode for request | test.py:617:17:617:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:617:17:617:28 | ControlFlowNode for Attribute | test.py:617:17:617:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:617:17:617:46 | ControlFlowNode for Attribute() | test.py:617:17:617:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:617:17:617:65 | ControlFlowNode for Attribute() | test.py:617:5:617:13 | ControlFlowNode for untrusted | provenance | | +| test.py:635:5:635:13 | ControlFlowNode for untrusted | test.py:644:25:644:33 | ControlFlowNode for untrusted | provenance | | +| test.py:635:17:635:23 | ControlFlowNode for request | test.py:635:17:635:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:635:17:635:28 | ControlFlowNode for Attribute | test.py:635:17:635:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:635:17:635:46 | ControlFlowNode for Attribute() | test.py:635:17:635:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:635:17:635:65 | ControlFlowNode for Attribute() | test.py:635:5:635:13 | ControlFlowNode for untrusted | provenance | | +| test.py:695:5:695:13 | ControlFlowNode for untrusted | test.py:702:25:702:33 | ControlFlowNode for untrusted | provenance | | +| test.py:695:17:695:23 | ControlFlowNode for request | test.py:695:17:695:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:695:17:695:28 | ControlFlowNode for Attribute | test.py:695:17:695:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:695:17:695:46 | ControlFlowNode for Attribute() | test.py:695:17:695:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:695:17:695:65 | ControlFlowNode for Attribute() | test.py:695:5:695:13 | ControlFlowNode for untrusted | provenance | | +| test.py:717:5:717:13 | ControlFlowNode for untrusted | test.py:720:25:720:33 | ControlFlowNode for untrusted | provenance | | +| test.py:717:17:717:23 | ControlFlowNode for request | test.py:717:17:717:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:717:17:717:28 | ControlFlowNode for Attribute | test.py:717:17:717:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:717:17:717:46 | ControlFlowNode for Attribute() | test.py:717:17:717:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:717:17:717:65 | ControlFlowNode for Attribute() | test.py:717:5:717:13 | ControlFlowNode for untrusted | provenance | | +| test.py:726:5:726:13 | ControlFlowNode for untrusted | test.py:729:25:729:33 | ControlFlowNode for untrusted | provenance | | +| test.py:726:17:726:23 | ControlFlowNode for request | test.py:726:17:726:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:726:17:726:28 | ControlFlowNode for Attribute | test.py:726:17:726:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:726:17:726:46 | ControlFlowNode for Attribute() | test.py:726:17:726:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:726:17:726:65 | ControlFlowNode for Attribute() | test.py:726:5:726:13 | ControlFlowNode for untrusted | provenance | | +| test.py:735:5:735:13 | ControlFlowNode for untrusted | test.py:739:25:739:33 | ControlFlowNode for untrusted | provenance | | +| test.py:735:17:735:23 | ControlFlowNode for request | test.py:735:17:735:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:735:17:735:28 | ControlFlowNode for Attribute | test.py:735:17:735:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:735:17:735:46 | ControlFlowNode for Attribute() | test.py:735:17:735:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:735:17:735:65 | ControlFlowNode for Attribute() | test.py:735:5:735:13 | ControlFlowNode for untrusted | provenance | | +| test.py:745:5:745:13 | ControlFlowNode for untrusted | test.py:751:25:751:33 | ControlFlowNode for untrusted | provenance | | +| test.py:745:17:745:23 | ControlFlowNode for request | test.py:745:17:745:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:745:17:745:28 | ControlFlowNode for Attribute | test.py:745:17:745:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:745:17:745:46 | ControlFlowNode for Attribute() | test.py:745:17:745:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:745:17:745:65 | ControlFlowNode for Attribute() | test.py:745:5:745:13 | ControlFlowNode for untrusted | provenance | | +| test.py:757:5:757:13 | ControlFlowNode for untrusted | test.py:760:25:760:33 | ControlFlowNode for untrusted | provenance | | +| test.py:757:17:757:23 | ControlFlowNode for request | test.py:757:17:757:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:757:17:757:28 | ControlFlowNode for Attribute | test.py:757:17:757:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:757:17:757:46 | ControlFlowNode for Attribute() | test.py:757:17:757:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:757:17:757:65 | ControlFlowNode for Attribute() | test.py:757:5:757:13 | ControlFlowNode for untrusted | provenance | | +| test.py:766:5:766:13 | ControlFlowNode for untrusted | test.py:768:25:768:33 | ControlFlowNode for untrusted | provenance | | +| test.py:766:17:766:23 | ControlFlowNode for request | test.py:766:17:766:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:766:17:766:28 | ControlFlowNode for Attribute | test.py:766:17:766:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:766:17:766:46 | ControlFlowNode for Attribute() | test.py:766:17:766:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:766:17:766:65 | ControlFlowNode for Attribute() | test.py:766:5:766:13 | ControlFlowNode for untrusted | provenance | | +| test.py:774:5:774:13 | ControlFlowNode for untrusted | test.py:776:25:776:33 | ControlFlowNode for untrusted | provenance | | +| test.py:774:17:774:23 | ControlFlowNode for request | test.py:774:17:774:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:774:17:774:28 | ControlFlowNode for Attribute | test.py:774:17:774:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:774:17:774:46 | ControlFlowNode for Attribute() | test.py:774:17:774:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:774:17:774:65 | ControlFlowNode for Attribute() | test.py:774:5:774:13 | ControlFlowNode for untrusted | provenance | | +| test.py:782:5:782:13 | ControlFlowNode for untrusted | test.py:784:25:784:33 | ControlFlowNode for untrusted | provenance | | +| test.py:782:17:782:23 | ControlFlowNode for request | test.py:782:17:782:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:782:17:782:28 | ControlFlowNode for Attribute | test.py:782:17:782:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:782:17:782:46 | ControlFlowNode for Attribute() | test.py:782:17:782:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:782:17:782:65 | ControlFlowNode for Attribute() | test.py:782:5:782:13 | ControlFlowNode for untrusted | provenance | | +| test.py:790:5:790:13 | ControlFlowNode for untrusted | test.py:792:25:792:33 | ControlFlowNode for untrusted | provenance | | +| test.py:790:17:790:23 | ControlFlowNode for request | test.py:790:17:790:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:790:17:790:28 | ControlFlowNode for Attribute | test.py:790:17:790:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:790:17:790:46 | ControlFlowNode for Attribute() | test.py:790:17:790:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:790:17:790:65 | ControlFlowNode for Attribute() | test.py:790:5:790:13 | ControlFlowNode for untrusted | provenance | | +| test.py:798:5:798:13 | ControlFlowNode for untrusted | test.py:800:25:800:33 | ControlFlowNode for untrusted | provenance | | +| test.py:798:17:798:23 | ControlFlowNode for request | test.py:798:17:798:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:798:17:798:28 | ControlFlowNode for Attribute | test.py:798:17:798:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:798:17:798:46 | ControlFlowNode for Attribute() | test.py:798:17:798:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:798:17:798:65 | ControlFlowNode for Attribute() | test.py:798:5:798:13 | ControlFlowNode for untrusted | provenance | | +| test.py:806:5:806:13 | ControlFlowNode for untrusted | test.py:810:25:810:33 | ControlFlowNode for untrusted | provenance | | +| test.py:806:17:806:23 | ControlFlowNode for request | test.py:806:17:806:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:806:17:806:28 | ControlFlowNode for Attribute | test.py:806:17:806:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:806:17:806:46 | ControlFlowNode for Attribute() | test.py:806:17:806:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:806:17:806:65 | ControlFlowNode for Attribute() | test.py:806:5:806:13 | ControlFlowNode for untrusted | provenance | | +| test.py:824:5:824:13 | ControlFlowNode for untrusted | test.py:827:25:827:33 | ControlFlowNode for untrusted | provenance | | +| test.py:824:17:824:23 | ControlFlowNode for request | test.py:824:17:824:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:824:17:824:28 | ControlFlowNode for Attribute | test.py:824:17:824:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:824:17:824:46 | ControlFlowNode for Attribute() | test.py:824:17:824:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:824:17:824:65 | ControlFlowNode for Attribute() | test.py:824:5:824:13 | ControlFlowNode for untrusted | provenance | | +| test.py:833:5:833:13 | ControlFlowNode for untrusted | test.py:835:25:835:33 | ControlFlowNode for untrusted | provenance | | +| test.py:833:17:833:23 | ControlFlowNode for request | test.py:833:17:833:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:833:17:833:28 | ControlFlowNode for Attribute | test.py:833:17:833:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:833:17:833:46 | ControlFlowNode for Attribute() | test.py:833:17:833:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:833:17:833:65 | ControlFlowNode for Attribute() | test.py:833:5:833:13 | ControlFlowNode for untrusted | provenance | | +| test.py:841:5:841:13 | ControlFlowNode for untrusted | test.py:849:25:849:33 | ControlFlowNode for untrusted | provenance | | +| test.py:841:17:841:23 | ControlFlowNode for request | test.py:841:17:841:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:841:17:841:28 | ControlFlowNode for Attribute | test.py:841:17:841:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:841:17:841:46 | ControlFlowNode for Attribute() | test.py:841:17:841:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:841:17:841:65 | ControlFlowNode for Attribute() | test.py:841:5:841:13 | ControlFlowNode for untrusted | provenance | | +| test.py:855:5:855:13 | ControlFlowNode for untrusted | test.py:863:25:863:33 | ControlFlowNode for untrusted | provenance | | +| test.py:855:17:855:23 | ControlFlowNode for request | test.py:855:17:855:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:855:17:855:28 | ControlFlowNode for Attribute | test.py:855:17:855:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:855:17:855:46 | ControlFlowNode for Attribute() | test.py:855:17:855:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:855:17:855:65 | ControlFlowNode for Attribute() | test.py:855:5:855:13 | ControlFlowNode for untrusted | provenance | | +| test.py:869:5:869:13 | ControlFlowNode for untrusted | test.py:877:25:877:33 | ControlFlowNode for untrusted | provenance | | +| test.py:869:17:869:23 | ControlFlowNode for request | test.py:869:17:869:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:869:17:869:28 | ControlFlowNode for Attribute | test.py:869:17:869:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:869:17:869:46 | ControlFlowNode for Attribute() | test.py:869:17:869:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:869:17:869:65 | ControlFlowNode for Attribute() | test.py:869:5:869:13 | ControlFlowNode for untrusted | provenance | | +| test.py:883:5:883:13 | ControlFlowNode for untrusted | test.py:891:25:891:33 | ControlFlowNode for untrusted | provenance | | +| test.py:883:17:883:23 | ControlFlowNode for request | test.py:883:17:883:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:883:17:883:28 | ControlFlowNode for Attribute | test.py:883:17:883:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:883:17:883:46 | ControlFlowNode for Attribute() | test.py:883:17:883:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:883:17:883:65 | ControlFlowNode for Attribute() | test.py:883:5:883:13 | ControlFlowNode for untrusted | provenance | | +| test.py:905:5:905:13 | ControlFlowNode for untrusted | test.py:914:25:914:33 | ControlFlowNode for untrusted | provenance | | +| test.py:905:17:905:23 | ControlFlowNode for request | test.py:905:17:905:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:905:17:905:28 | ControlFlowNode for Attribute | test.py:905:17:905:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:905:17:905:46 | ControlFlowNode for Attribute() | test.py:905:17:905:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:905:17:905:65 | ControlFlowNode for Attribute() | test.py:905:5:905:13 | ControlFlowNode for untrusted | provenance | | +| test.py:920:5:920:13 | ControlFlowNode for untrusted | test.py:924:25:924:33 | ControlFlowNode for untrusted | provenance | | +| test.py:920:17:920:23 | ControlFlowNode for request | test.py:920:17:920:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:920:17:920:28 | ControlFlowNode for Attribute | test.py:920:17:920:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:920:17:920:46 | ControlFlowNode for Attribute() | test.py:920:17:920:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:920:17:920:65 | ControlFlowNode for Attribute() | test.py:920:5:920:13 | ControlFlowNode for untrusted | provenance | | +| test.py:945:5:945:13 | ControlFlowNode for untrusted | test.py:953:25:953:33 | ControlFlowNode for untrusted | provenance | | +| test.py:945:17:945:23 | ControlFlowNode for request | test.py:945:17:945:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:945:17:945:28 | ControlFlowNode for Attribute | test.py:945:17:945:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:945:17:945:46 | ControlFlowNode for Attribute() | test.py:945:17:945:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:945:17:945:65 | ControlFlowNode for Attribute() | test.py:945:5:945:13 | ControlFlowNode for untrusted | provenance | | +| test.py:959:5:959:13 | ControlFlowNode for untrusted | test.py:967:25:967:33 | ControlFlowNode for untrusted | provenance | | +| test.py:959:17:959:23 | ControlFlowNode for request | test.py:959:17:959:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:959:17:959:28 | ControlFlowNode for Attribute | test.py:959:17:959:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:959:17:959:46 | ControlFlowNode for Attribute() | test.py:959:17:959:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:959:17:959:65 | ControlFlowNode for Attribute() | test.py:959:5:959:13 | ControlFlowNode for untrusted | provenance | | +| test.py:973:5:973:13 | ControlFlowNode for untrusted | test.py:981:25:981:33 | ControlFlowNode for untrusted | provenance | | +| test.py:973:17:973:23 | ControlFlowNode for request | test.py:973:17:973:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:973:17:973:28 | ControlFlowNode for Attribute | test.py:973:17:973:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:973:17:973:46 | ControlFlowNode for Attribute() | test.py:973:17:973:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:973:17:973:65 | ControlFlowNode for Attribute() | test.py:973:5:973:13 | ControlFlowNode for untrusted | provenance | | +| test.py:987:5:987:13 | ControlFlowNode for untrusted | test.py:997:25:997:33 | ControlFlowNode for untrusted | provenance | | +| test.py:987:17:987:23 | ControlFlowNode for request | test.py:987:17:987:28 | ControlFlowNode for Attribute | provenance | AdditionalTaintStep | +| test.py:987:17:987:28 | ControlFlowNode for Attribute | test.py:987:17:987:46 | ControlFlowNode for Attribute() | provenance | dict.get | +| test.py:987:17:987:46 | ControlFlowNode for Attribute() | test.py:987:17:987:65 | ControlFlowNode for Attribute() | provenance | Config | +| test.py:987:17:987:65 | ControlFlowNode for Attribute() | test.py:987:5:987:13 | ControlFlowNode for untrusted | provenance | | nodes | test.py:1:26:1:32 | ControlFlowNode for ImportMember | semmle.label | ControlFlowNode for ImportMember | | test.py:1:26:1:32 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | @@ -140,6 +526,22 @@ nodes | test.py:111:17:111:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | | test.py:111:17:111:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | | test.py:114:25:114:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:121:5:121:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:121:17:121:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:121:17:121:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:121:17:121:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:122:5:122:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:122:17:122:25 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:122:17:122:44 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:124:25:124:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:129:5:129:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:129:17:129:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:129:17:129:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:129:17:129:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:130:5:130:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:130:17:130:25 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:130:17:130:44 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:132:25:132:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | | test.py:137:5:137:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | | test.py:137:17:137:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | | test.py:137:17:137:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | @@ -150,4 +552,320 @@ nodes | test.py:145:17:145:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | | test.py:145:17:145:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | | test.py:148:25:148:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:153:5:153:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:153:17:153:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:153:17:153:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:153:17:153:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:154:5:154:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:154:17:154:25 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:154:17:154:44 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:156:25:156:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:161:5:161:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:161:17:161:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:161:17:161:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:161:17:161:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:162:5:162:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:162:17:162:25 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:162:17:162:44 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:165:21:165:29 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:293:5:293:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:293:17:293:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:293:17:293:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:293:17:293:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:300:25:300:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:306:5:306:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:306:17:306:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:306:17:306:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:306:17:306:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:306:17:306:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:314:25:314:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:320:5:320:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:320:17:320:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:320:17:320:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:320:17:320:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:320:17:320:54 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:320:17:320:72 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:328:25:328:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:334:5:334:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:334:17:334:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:334:17:334:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:334:17:334:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:334:17:334:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:342:25:342:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:348:5:348:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:348:17:348:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:348:17:348:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:348:17:348:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:348:17:348:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:355:25:355:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:361:5:361:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:361:17:361:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:361:17:361:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:361:17:361:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:361:17:361:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:370:25:370:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:376:5:376:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:376:17:376:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:376:17:376:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:376:17:376:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:376:17:376:64 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:382:25:382:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:388:5:388:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:388:17:388:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:388:17:388:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:388:17:388:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:388:17:388:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:391:25:391:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:397:5:397:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:397:17:397:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:397:17:397:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:397:17:397:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:397:17:397:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:404:25:404:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:410:5:410:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:410:17:410:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:410:17:410:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:410:17:410:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:410:17:410:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:417:25:417:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:423:5:423:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:423:17:423:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:423:17:423:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:423:17:423:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:423:17:423:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:431:25:431:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:437:5:437:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:437:17:437:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:437:17:437:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:437:17:437:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:437:17:437:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:444:25:444:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:450:5:450:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:450:17:450:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:450:17:450:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:450:17:450:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:450:17:450:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:458:25:458:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:464:5:464:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:464:17:464:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:464:17:464:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:464:17:464:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:464:17:464:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:470:25:470:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:476:5:476:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:476:17:476:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:476:17:476:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:476:17:476:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:476:17:476:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:483:25:483:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:489:5:489:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:489:17:489:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:489:17:489:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:489:17:489:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:489:17:489:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:497:25:497:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:503:5:503:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:503:17:503:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:503:17:503:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:503:17:503:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:503:17:503:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:514:25:514:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:520:5:520:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:520:17:520:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:520:17:520:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:520:17:520:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:520:17:520:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:527:25:527:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:533:5:533:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:533:17:533:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:533:17:533:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:533:17:533:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:533:17:533:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:539:25:539:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:545:5:545:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:545:17:545:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:545:17:545:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:545:17:545:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:545:17:545:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:553:21:553:29 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:558:5:558:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:558:17:558:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:558:17:558:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:558:17:558:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:558:17:558:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:566:25:566:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:572:5:572:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:572:17:572:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:572:17:572:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:572:17:572:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:572:17:572:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:582:25:582:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:588:5:588:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:588:17:588:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:588:17:588:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:588:17:588:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:588:17:588:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:597:21:597:29 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:602:5:602:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:602:17:602:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:602:17:602:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:602:17:602:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:602:17:602:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:612:21:612:29 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:617:5:617:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:617:17:617:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:617:17:617:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:617:17:617:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:617:17:617:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:629:25:629:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:635:5:635:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:635:17:635:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:635:17:635:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:635:17:635:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:635:17:635:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:644:25:644:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:695:5:695:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:695:17:695:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:695:17:695:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:695:17:695:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:695:17:695:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:702:25:702:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:717:5:717:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:717:17:717:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:717:17:717:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:717:17:717:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:717:17:717:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:720:25:720:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:726:5:726:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:726:17:726:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:726:17:726:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:726:17:726:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:726:17:726:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:729:25:729:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:735:5:735:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:735:17:735:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:735:17:735:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:735:17:735:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:735:17:735:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:739:25:739:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:745:5:745:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:745:17:745:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:745:17:745:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:745:17:745:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:745:17:745:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:751:25:751:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:757:5:757:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:757:17:757:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:757:17:757:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:757:17:757:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:757:17:757:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:760:25:760:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:766:5:766:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:766:17:766:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:766:17:766:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:766:17:766:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:766:17:766:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:768:25:768:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:774:5:774:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:774:17:774:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:774:17:774:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:774:17:774:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:774:17:774:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:776:25:776:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:782:5:782:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:782:17:782:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:782:17:782:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:782:17:782:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:782:17:782:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:784:25:784:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:790:5:790:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:790:17:790:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:790:17:790:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:790:17:790:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:790:17:790:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:792:25:792:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:798:5:798:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:798:17:798:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:798:17:798:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:798:17:798:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:798:17:798:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:800:25:800:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:806:5:806:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:806:17:806:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:806:17:806:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:806:17:806:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:806:17:806:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:810:25:810:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:824:5:824:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:824:17:824:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:824:17:824:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:824:17:824:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:824:17:824:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:827:25:827:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:833:5:833:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:833:17:833:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:833:17:833:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:833:17:833:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:833:17:833:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:835:25:835:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:841:5:841:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:841:17:841:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:841:17:841:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:841:17:841:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:841:17:841:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:849:25:849:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:855:5:855:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:855:17:855:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:855:17:855:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:855:17:855:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:855:17:855:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:863:25:863:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:869:5:869:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:869:17:869:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:869:17:869:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:869:17:869:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:869:17:869:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:877:25:877:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:883:5:883:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:883:17:883:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:883:17:883:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:883:17:883:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:883:17:883:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:891:25:891:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:905:5:905:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:905:17:905:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:905:17:905:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:905:17:905:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:905:17:905:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:914:25:914:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:920:5:920:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:920:17:920:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:920:17:920:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:920:17:920:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:920:17:920:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:924:25:924:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:945:5:945:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:945:17:945:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:945:17:945:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:945:17:945:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:945:17:945:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:953:25:953:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:959:5:959:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:959:17:959:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:959:17:959:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:959:17:959:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:959:17:959:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:967:25:967:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:973:5:973:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:973:17:973:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:973:17:973:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:973:17:973:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:973:17:973:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:981:25:981:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:987:5:987:13 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | +| test.py:987:17:987:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request | +| test.py:987:17:987:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute | +| test.py:987:17:987:46 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:987:17:987:65 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() | +| test.py:997:25:997:33 | ControlFlowNode for untrusted | semmle.label | ControlFlowNode for untrusted | subpaths diff --git a/python/ql/test/query-tests/Security/CWE-601-UrlRedirect/test.py b/python/ql/test/query-tests/Security/CWE-601-UrlRedirect/test.py index dd374618a9db..0a27038042b6 100644 --- a/python/ql/test/query-tests/Security/CWE-601-UrlRedirect/test.py +++ b/python/ql/test/query-tests/Security/CWE-601-UrlRedirect/test.py @@ -116,20 +116,20 @@ def not_ok5(): from urllib.parse import urlparse -@app.route('/ok8') -def ok8(): +@app.route('/not_ok8') +def not_ok8(): untrusted = request.args.get('target', '') untrusted = untrusted.replace("\\", "/") if not urlparse(untrusted).netloc: - return redirect(untrusted, code=302) # OK + return redirect(untrusted, code=302) # $ Alert return redirect("/", code=302) -@app.route('/ok9') -def ok9(): +@app.route('/not_ok9') +def not_ok9(): untrusted = request.args.get('target', '') untrusted = untrusted.replace("\\", "/") if urlparse(untrusted).netloc == "": - return redirect(untrusted, code=302) # OK + return redirect(untrusted, code=302) # $ Alert return redirect("/", code=302) @app.route('/not_ok6') @@ -148,18 +148,851 @@ def not_ok7(): return redirect(untrusted, code=302) # $ Alert # NOT OK return redirect("/", code=302) -@app.route('/ok10') -def ok10(): +@app.route('/not_ok10') +def not_ok10(): untrusted = request.args.get('target', '') untrusted = untrusted.replace("\\", "/") if urlparse(untrusted).netloc in ["", request.host]: - return redirect(untrusted, code=302) # OK + return redirect(untrusted, code=302) # $ Alert return redirect("/", code=302) -@app.route('/ok11') -def ok11(): +@app.route('/not_ok11') +def not_ok11(): untrusted = request.args.get('target', '') untrusted = untrusted.replace("\\", "/") if urlparse(untrusted).netloc not in ["", request.host]: return redirect("/", code=302) # OK - return redirect(untrusted, code=302) + return redirect(untrusted, code=302) # $ Alert + + +@app.route('/checked_relative_url') +def checked_relative_url(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and not urlparse(untrusted).scheme + and not urlparse(untrusted).netloc + ): + return redirect(untrusted, code=302) # OK + return redirect("/", code=302) + + +from urllib.parse import urlsplit + +@app.route('/checked_lstripped_relative_url') +def checked_lstripped_relative_url(): + untrusted = request.args.get('target', '').replace("\\", "/").lstrip() + parsed = urlsplit(untrusted) + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and not parsed.scheme + and not parsed.netloc + ): + return redirect(untrusted, code=302) # OK + return redirect("/", code=302) + + +@app.route('/checked_relative_url_comparisons') +def checked_relative_url_comparisons(): + untrusted = request.args.get('target', '').replace("\\", "").strip() + parsed = urlparse(untrusted) + if ( + untrusted.isprintable() + and not untrusted.startswith("//") + and parsed.scheme == "" + and parsed.netloc == "" + ): + return redirect(untrusted, code=302) # OK + return redirect("/", code=302) + + +@app.route('/checked_relative_url_early_return') +def checked_relative_url_early_return(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + parsed = urlparse(untrusted) + if ( + not untrusted.isprintable() + or untrusted.startswith("///") + or parsed.scheme != "" + or parsed.netloc not in ["", "example.com"] + ): + return redirect("/", code=302) # OK + return redirect(untrusted, code=302) # OK + + +@app.route('/checked_split_url') +def checked_split_url(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + parsed = urlsplit(url=untrusted) + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and not parsed.scheme + and parsed.netloc in ["", "example.com"] + ): + return redirect(untrusted, code=302) # OK + return redirect("/", code=302) + + +@app.route('/checked_tuple_startswith') +def checked_tuple_startswith(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + parsed = urlparse(untrusted) + if ( + untrusted.isprintable() + and not untrusted.startswith(("///", "\\")) + and not parsed.scheme + and not parsed.netloc + ): + return redirect(untrusted, code=302) # OK + return redirect("/", code=302) + + +@app.route('/named_empty_scheme') +def named_empty_scheme(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + empty_scheme = "" + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and urlparse(untrusted).scheme == empty_scheme + and not urlparse(untrusted).netloc + ): + return redirect(untrusted, code=302) # OK + return redirect("/", code=302) + + +@app.route('/checked_host_collections') +def checked_host_collections(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + kind = request.args.get('kind') + parsed = urlsplit(untrusted) + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and not parsed.scheme + ): + if kind == "list" and parsed.netloc in ["", "example.com"]: + return redirect(untrusted, code=302) # OK + elif kind == "tuple": + allowed_hosts = ("", "example.com") + hosts = allowed_hosts + if parsed.netloc in hosts: + return redirect(untrusted, code=302) # OK + elif kind == "set" and parsed.netloc in {"", "example.com"}: + return redirect(untrusted, code=302) # OK + elif kind == "dict" and parsed.netloc in {"": True, "example.com": True}: + return redirect(untrusted, code=302) # OK + return redirect("/", code=302) + + +@app.route('/missing_backslash_check') +def missing_backslash_check(): + untrusted = request.args.get('target', '').strip() + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and not urlparse(untrusted).scheme + and not urlparse(untrusted).netloc + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/printable_without_strip') +def printable_without_strip(): + untrusted = request.args.get('target', '').replace("\\", "/") + parsed = urlparse(untrusted) + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and not parsed.scheme + and not parsed.netloc + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/strip_before_backslash_replace') +def strip_before_backslash_replace(): + untrusted = request.args.get('target', '').strip().replace("\\", "") + parsed = urlparse(untrusted) + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and not parsed.scheme + and not parsed.netloc + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/custom_strip_characters') +def custom_strip_characters(): + untrusted = request.args.get('target', '').replace("\\", "/").strip(" ") + parsed = urlsplit(untrusted) + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and not parsed.scheme + and not parsed.netloc + ): + return redirect(untrusted, code=302) # $ SPURIOUS: Alert + return redirect("/", code=302) + + +@app.route('/stripped_without_printable_check') +def stripped_without_printable_check(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + parsed = urlparse(untrusted) + if ( + not untrusted.startswith("///") + and not parsed.scheme + and not parsed.netloc + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/printable_check_on_other_url') +def printable_check_on_other_url(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + other = request.args.get('other', '').replace("\\", "/").strip() + parsed = urlparse(untrusted) + if ( + other.isprintable() + and not untrusted.startswith("///") + and not parsed.scheme + and not parsed.netloc + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/missing_slash_check') +def missing_slash_check(): + untrusted = request.args.get('target', '').replace("\\", "").strip() + if ( + untrusted.isprintable() + and not urlparse(untrusted).netloc + and not urlparse(untrusted).scheme + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/unchecked_split_url_slashes') +def unchecked_split_url_slashes(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + parsed = urlsplit(untrusted) + if untrusted.isprintable() and not parsed.scheme and not parsed.netloc: + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/insufficient_slash_check') +def insufficient_slash_check(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + if ( + untrusted.isprintable() + and not untrusted.startswith("////") + and not urlparse(untrusted).scheme + and not urlparse(untrusted).netloc + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/slash_check_with_offset') +def slash_check_with_offset(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + if ( + untrusted.isprintable() + and not untrusted.startswith("///", 1) + and not urlparse(untrusted).scheme + and not urlparse(untrusted).netloc + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/slash_check_with_starred_offset') +def slash_check_with_starred_offset(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + bounds = (1,) + if ( + untrusted.isprintable() + and not untrusted.startswith("///", *bounds) + and not urlparse(untrusted).scheme + and not urlparse(untrusted).netloc + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/wrong_slash_branch') +def wrong_slash_branch(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + if ( + untrusted.isprintable() + and untrusted.startswith("///") + and not urlparse(untrusted).scheme + and not urlparse(untrusted).netloc + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/checks_on_different_urls') +def checks_on_different_urls(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + other = request.args.get('other', '').replace("\\", "/").strip() + if ( + untrusted.isprintable() + and not other.startswith("///") + and not urlparse(untrusted).scheme + and not urlparse(untrusted).netloc + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/missing_scheme_check') +def missing_scheme_check(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and not urlparse(untrusted).netloc + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/nonempty_scheme') +def nonempty_scheme(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and urlparse(untrusted).scheme == "https" + and not urlparse(untrusted).netloc + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/unknown_scheme') +def unknown_scheme(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + allowed_scheme = get_allowed_scheme() + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and urlparse(untrusted).scheme == allowed_scheme + and not urlparse(untrusted).netloc + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/conditionally_nonempty_scheme') +def conditionally_nonempty_scheme(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + if request.args.get('allow-https'): + allowed_scheme = "https" + else: + allowed_scheme = "" + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and urlparse(untrusted).scheme == allowed_scheme + and not urlparse(untrusted).netloc + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/reversed_scheme_membership') +def reversed_scheme_membership(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and "" in urlparse(untrusted).scheme + and not urlparse(untrusted).netloc + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/missing_netloc_check') +def missing_netloc_check(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and not urlparse(untrusted).scheme + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/unchecked_branch') +def unchecked_branch(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and not urlparse(untrusted).scheme + and not urlparse(untrusted).netloc + ): + return redirect(untrusted, code=302) # OK + return redirect(untrusted, code=302) # $ Alert + + +@app.route('/host_substring_membership') +def host_substring_membership(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + parsed = urlparse(untrusted) + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and not parsed.scheme + and parsed.netloc in "example.com" + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/named_host_substring_membership') +def named_host_substring_membership(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + allowed_hosts = "example.com" + hosts = allowed_hosts + parsed = urlsplit(untrusted) + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and not parsed.scheme + and parsed.netloc in hosts + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/split_host_substring_membership_early_return') +def split_host_substring_membership_early_return(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + parsed = urlsplit(untrusted) + if ( + not untrusted.isprintable() + or untrusted.startswith("///") + or parsed.scheme + or parsed.netloc not in "example.com" + ): + return redirect("/", code=302) + return redirect(untrusted, code=302) # $ Alert + + +@app.route('/named_host_substring_membership_early_return') +def named_host_substring_membership_early_return(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + allowed_hosts = "example.com" + parsed = urlparse(untrusted) + if ( + not untrusted.isprintable() + or untrusted.startswith("///") + or parsed.scheme + or parsed.netloc not in allowed_hosts + ): + return redirect("/", code=302) + return redirect(untrusted, code=302) # $ Alert + + +@app.route('/conditionally_string_host_list') +def conditionally_string_host_list(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + if request.args.get('use-list'): + allowed_hosts = ["", request.host] + else: + allowed_hosts = "example.com" + parsed = urlsplit(untrusted) + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and not parsed.scheme + and parsed.netloc in allowed_hosts + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/unknown_host_list') +def unknown_host_list(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + allowed_hosts = get_allowed_hosts() + parsed = urlsplit(untrusted) + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and not parsed.scheme + and parsed.netloc in allowed_hosts + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/checked_explicit_host') +def checked_explicit_host(): + untrusted = request.args.get('target', '').replace("\\", "/") + if urlparse(untrusted).netloc == "example.com": + return redirect(untrusted, code=302) # OK + return redirect("/", code=302) + + +@app.route('/checked_explicit_host_list') +def checked_explicit_host_list(): + untrusted = request.args.get('target', '').replace("\\", "/") + if urlparse(untrusted).netloc in ["example.com", "sub.example.com"]: + return redirect(untrusted, code=302) # OK + return redirect("/", code=302) + + +@app.route('/named_nonempty_host_tuple') +def named_nonempty_host_tuple(): + untrusted = request.args.get('target', '').replace("\\", "/") + allowed_hosts = ("example.com", "sub.example.com") + if urlparse(untrusted).netloc in allowed_hosts: + return redirect(untrusted, code=302) # OK + return redirect("/", code=302) + + +@app.route('/host_list_with_named_nonempty_host') +def host_list_with_named_nonempty_host(): + untrusted = request.args.get('target', '').replace("\\", "/") + allowed_host = "sub.example.com" + if urlparse(untrusted).netloc in [allowed_host, "example.com"]: + return redirect(untrusted, code=302) # OK + return redirect("/", code=302) + + +@app.route('/parsed_hostname') +def parsed_hostname(): + untrusted = request.args.get('target', '').replace("\\", "/") + allowed_host = urlparse("https://example.com").hostname + if urlparse(untrusted).netloc == allowed_host: + return redirect(untrusted, code=302) # OK + return redirect("/", code=302) + + +from types import SimpleNamespace + +@app.route('/mixed_source_parsed_hostname') +def mixed_source_parsed_hostname(): + untrusted = request.args.get('target', '').replace("\\", "/") + parts = ( + urlsplit("https://example.com") + if request.args.get('use-parser') + else SimpleNamespace(hostname="") + ) + if urlparse(untrusted).netloc == parts.hostname: + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/split_hostname_list') +def split_hostname_list(): + untrusted = request.args.get('target', '').replace("\\", "/") + allowed_host = urlsplit("https://example.com").hostname + if urlparse(untrusted).netloc in [allowed_host, "sub.example.com"]: + return redirect(untrusted, code=302) # OK + return redirect("/", code=302) + + +@app.route('/named_empty_host') +def named_empty_host(): + untrusted = request.args.get('target', '').replace("\\", "/") + empty_host = "" + if urlparse(untrusted).netloc == empty_host: + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/named_possibly_empty_host_list') +def named_possibly_empty_host_list(): + untrusted = request.args.get('target', '').replace("\\", "/") + allowed_hosts = ["", "example.com"] + if urlparse(untrusted).netloc in allowed_hosts: + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/aliased_possibly_empty_host_list') +def aliased_possibly_empty_host_list(): + untrusted = request.args.get('target', '').replace("\\", "/") + allowed_hosts = ["", "example.com"] + hosts = allowed_hosts + if urlparse(untrusted).netloc in hosts: + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/conditionally_empty_host_list') +def conditionally_empty_host_list(): + untrusted = request.args.get('target', '').replace("\\", "/") + if request.args.get('allow-relative'): + allowed_hosts = ["", "example.com"] + else: + allowed_hosts = ["example.com"] + if urlparse(untrusted).netloc in allowed_hosts: + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/host_list_with_named_empty_host') +def host_list_with_named_empty_host(): + untrusted = request.args.get('target', '').replace("\\", "/") + empty_host = "" + if urlparse(untrusted).netloc in [empty_host, "example.com"]: + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/host_list_with_unknown_element') +def host_list_with_unknown_element(): + untrusted = request.args.get('target', '').replace("\\", "/") + if urlparse(untrusted).netloc in [get_allowed_host(), "example.com"]: + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/request_host_attribute') +def request_host_attribute(): + untrusted = request.args.get('target', '').replace("\\", "/") + if urlparse(untrusted).netloc == request.host: + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/request_host_list') +def request_host_list(): + untrusted = request.args.get('target', '').replace("\\", "/") + if urlparse(untrusted).netloc in [request.host, "sub.example.com"]: + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/arbitrary_attribute_host') +def arbitrary_attribute_host(): + untrusted = request.args.get('target', '').replace("\\", "/") + if urlparse(untrusted).netloc == app.redirect_host: + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/arbitrary_attribute_host_list') +def arbitrary_attribute_host_list(): + untrusted = request.args.get('target', '').replace("\\", "/") + if urlparse(untrusted).netloc in [app.redirect_host, "sub.example.com"]: + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/mutated_host_list') +def mutated_host_list(): + untrusted = request.args.get('target', '').replace("\\", "/") + allowed_hosts = ["example.com"] + allowed_hosts.append("") + if urlparse(untrusted).netloc in allowed_hosts: + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/explicit_host_dict') +def explicit_host_dict(): + untrusted = request.args.get('target', '').replace("\\", "/") + if urlparse(untrusted).netloc in {"example.com": True}: + return redirect(untrusted, code=302) # OK + return redirect("/", code=302) + + +@app.route('/unpacked_host_dict') +def unpacked_host_dict(): + untrusted = request.args.get('target', '').replace("\\", "/") + allowed_hosts = get_allowed_hosts() + if urlparse(untrusted).netloc in {**allowed_hosts, "example.com": True}: + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/request_derived_parsed_hostname') +def request_derived_parsed_hostname(): + untrusted = request.args.get('target', '').replace("\\", "/") + if urlsplit(untrusted).netloc == urlsplit(untrusted).hostname: + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/mixed_source_parse_falsiness') +def mixed_source_parse_falsiness(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + parts = urlsplit(untrusted) if request.args.get('parse-target') else urlsplit("/") + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and not parts.scheme + and not parts.netloc + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/mixed_source_parse_comparison') +def mixed_source_parse_comparison(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + parts = urlsplit(untrusted) if request.args.get('parse-target') else urlsplit("/") + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and parts.scheme == "" + and parts.netloc == "" + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/mixed_prefix_method') +def mixed_prefix_method(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + check = untrusted.startswith if request.args.get('safe-check') else untrusted.endswith + if ( + untrusted.isprintable() + and not check("///") + and not urlparse(untrusted).scheme + and not urlparse(untrusted).netloc + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/mixed_printable_method') +def mixed_printable_method(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + check = untrusted.isprintable if request.args.get('safe-check') else untrusted.isascii + if ( + check() + and not untrusted.startswith("///") + and not urlparse(untrusted).scheme + and not urlparse(untrusted).netloc + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +def ignores_target(target): + return urlsplit("/") + + +def parses_request_target(target): + return urlsplit(request.args.get('target', '')) + + +@app.route('/mixed_parser_callee') +def mixed_parser_callee(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + parse = urlsplit if request.args.get('use-parser') else ignores_target + parts = parse(untrusted) + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and not parts.scheme + and not parts.netloc + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/mixed_constant_hostname_callee') +def mixed_constant_hostname_callee(): + untrusted = request.args.get('target', '').replace("\\", "/") + parse = urlsplit if request.args.get('use-parser') else parses_request_target + allowed_host = parse("https://example.com").hostname + if urlsplit(untrusted).netloc == allowed_host: + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/aliased_parser') +def aliased_parser(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + parse = urlsplit if request.args.get('use-split') else urlparse + parts = parse(untrusted) + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and not parts.scheme + and not parts.netloc + ): + return redirect(untrusted, code=302) # OK + return redirect("/", code=302) + + +@app.route('/guarded_request_derived_hostname') +def guarded_request_derived_hostname(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + parts = urlsplit(untrusted) + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and not parts.scheme + and parts.netloc == parts.hostname + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/guarded_request_derived_host_list') +def guarded_request_derived_host_list(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + parts = urlsplit(untrusted) + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and not parts.scheme + and parts.netloc in ["", parts.hostname] + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302) + + +@app.route('/guarded_request_host') +def guarded_request_host(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + parts = urlsplit(untrusted) + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and not parts.scheme + and parts.netloc in ["", request.host] + ): + return redirect(untrusted, code=302) # $ SPURIOUS: Alert + return redirect("/", code=302) + + +@app.route('/guarded_mutated_host_list') +def guarded_mutated_host_list(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + allowed_hosts = [""] + allowed_hosts.append(request.args.get('host', '')) + parts = urlsplit(untrusted) + if ( + untrusted.isprintable() + and not untrusted.startswith("///") + and not parts.scheme + and parts.netloc in allowed_hosts + ): + return redirect(untrusted, code=302) # $ Alert + return redirect("/", code=302)