From f8fa9030bef3f75a3a5547e66d1d79d748c2bb33 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Thu, 22 Oct 2026 14:27:06 +0100 Subject: [PATCH] Fix URL redirection validation sanitizers --- .../2026-10-22-url-redirect-validation.md | 4 + .../python/frameworks/Stdlib/Urllib.qll | 277 ++++++- .../ql/src/Security/CWE-601/UrlRedirect.qhelp | 41 +- .../CWE-601/examples/redirect_good2.py | 14 - .../2026-10-22-url-redirect-validation.md | 4 + .../CWE-601-UrlRedirect/UrlRedirect.expected | 640 +++++++++++++++ .../Security/CWE-601-UrlRedirect/test.py | 751 +++++++++++++++++- 7 files changed, 1662 insertions(+), 69 deletions(-) create mode 100644 python/ql/lib/change-notes/2026-10-22-url-redirect-validation.md delete mode 100644 python/ql/src/Security/CWE-601/examples/redirect_good2.py create mode 100644 python/ql/src/change-notes/2026-10-22-url-redirect-validation.md 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..f9d992f07853 --- /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. Membership allowlists are trusted only when immutable or used directly as literals, and hostname attributes are treated as non-empty only when all possible receivers originate from `urlparse` or `urlsplit`. diff --git a/python/ql/lib/semmle/python/frameworks/Stdlib/Urllib.qll b/python/ql/lib/semmle/python/frameworks/Stdlib/Urllib.qll index 6b5764e55925..4fccf1338157 100644 --- a/python/ql/lib/semmle/python/frameworks/Stdlib/Urllib.qll +++ b/python/ql/lib/semmle/python/frameworks/Stdlib/Urllib.qll @@ -81,47 +81,278 @@ 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() + } + + /** 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, whose elements or keys all exclude + * `""`. */ - private predicate netlocCheck(DataFlow::GuardNode g, ControlFlowNode node, boolean branch) { - exists(DataFlow::CallCfgNode urlParseCall, DataFlow::AttrRead netlocRead | + private predicate membershipExcludesEmptyString(Expr e) { + 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 + collection instanceof Dict and + collection = e and + not collection.(Dict).getAnItem() instanceof DictUnpacking and + forall(Expr key | key = collection.(Dict).getAKey() | excludesEmptyString(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 membershipExcludesEmptyString(e) + 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 - // 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 - or - op in ["!=", "is not", "not in"] and branch = false + 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 + 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 that allows `""` (e.g. `not parsed.netloc` or `parsed.netloc in ["", host]`). */ + 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 + not comparisonExcludesEmptyString(op, other) + ) + } + + /** 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..23907337cd19 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,56 @@ | 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 | 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 +71,56 @@ 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: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 +168,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 +190,240 @@ 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 | | 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 +484,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 +510,284 @@ 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 | 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..ce0d62503fb7 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,745 @@ 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 ["", request.host] + ): + 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 ["", request.host] + ): + 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_named_host_collections') +def checked_named_host_collections(): + untrusted = request.args.get('target', '').replace("\\", "/").strip() + kind = request.args.get('kind') + if kind == "list": + allowed_hosts = ["", request.host] + elif kind == "tuple": + allowed_hosts = ("", request.host) + elif kind == "set": + allowed_hosts = {"", request.host} + else: + allowed_hosts = {"": True, request.host: True} + 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) # 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)