From b5fda88bd3fa0cb7df637866a391c99dbcfa4fa2 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Tue, 30 Sep 2025 12:04:39 +0100 Subject: [PATCH 1/6] Remove duplication of `UnsafeFieldReadSanitizer` --- .../go/security/OpenUrlRedirectCustomizations.qll | 15 --------------- .../go/security/RequestForgeryCustomizations.qll | 15 --------------- .../go/security/SafeUrlFlowCustomizations.qll | 15 +++++++++++++++ 3 files changed, 15 insertions(+), 30 deletions(-) diff --git a/go/ql/lib/semmle/go/security/OpenUrlRedirectCustomizations.qll b/go/ql/lib/semmle/go/security/OpenUrlRedirectCustomizations.qll index 870edeee9621..14c0b232d8ba 100644 --- a/go/ql/lib/semmle/go/security/OpenUrlRedirectCustomizations.qll +++ b/go/ql/lib/semmle/go/security/OpenUrlRedirectCustomizations.qll @@ -121,21 +121,6 @@ module OpenUrlRedirect { /** A sink for an open redirect, considered as a sink for safe URL flow. */ private class SafeUrlSink extends SafeUrlFlow::Sink instanceof OpenUrlRedirect::Sink { } -/** - * A read of a field considered unsafe to redirect to, considered as a sanitizer for a safe - * URL. - */ -private class UnsafeFieldReadSanitizer extends SafeUrlFlow::SanitizerEdge { - UnsafeFieldReadSanitizer() { - exists(DataFlow::FieldReadNode frn, string name | - name = ["User", "RawQuery", "Fragment"] and - frn.getField().hasQualifiedName("net/url", "URL") - | - this = frn.getBase() - ) - } -} - /** * Reinstate the usual field propagation rules for fields, which the OpenURLRedirect * query usually excludes, for fields of `Params` other than `Params.Fixed`. diff --git a/go/ql/lib/semmle/go/security/RequestForgeryCustomizations.qll b/go/ql/lib/semmle/go/security/RequestForgeryCustomizations.qll index 2449ffe488ca..a34a47dd7eda 100644 --- a/go/ql/lib/semmle/go/security/RequestForgeryCustomizations.qll +++ b/go/ql/lib/semmle/go/security/RequestForgeryCustomizations.qll @@ -118,18 +118,3 @@ module RequestForgery { /** A sink for request forgery, considered as a sink for safe URL flow. */ private class SafeUrlSink extends SafeUrlFlow::Sink instanceof RequestForgery::Sink { } - -/** - * A read of a field considered unsafe for request forgery, considered as a sanitizer for a safe - * URL. - */ -private class UnsafeFieldReadSanitizer extends SafeUrlFlow::SanitizerEdge { - UnsafeFieldReadSanitizer() { - exists(DataFlow::FieldReadNode frn, string name | - (name = "RawQuery" or name = "Fragment" or name = "User") and - frn.getField().hasQualifiedName("net/url", "URL") - | - this = frn.getBase() - ) - } -} diff --git a/go/ql/lib/semmle/go/security/SafeUrlFlowCustomizations.qll b/go/ql/lib/semmle/go/security/SafeUrlFlowCustomizations.qll index 5f0572db11ef..eefdea3a145a 100644 --- a/go/ql/lib/semmle/go/security/SafeUrlFlowCustomizations.qll +++ b/go/ql/lib/semmle/go/security/SafeUrlFlowCustomizations.qll @@ -40,4 +40,19 @@ module SafeUrlFlow { private class StringSlicingEdge extends SanitizerEdge { StringSlicingEdge() { this = any(DataFlow::SliceNode sn) } } + + /** + * A read of a field considered unsafe to redirect to, considered as a sanitizer for a safe + * URL. + */ + private class UnsafeFieldReadSanitizer extends SanitizerEdge { + UnsafeFieldReadSanitizer() { + exists(DataFlow::FieldReadNode frn, string name | + name = ["Fragment", "RawQuery", "User"] and + frn.getField().hasQualifiedName("net/url", "URL") + | + this = frn.getBase() + ) + } + } } From 5b07e8c9c49d8ae13d7540ad267d1da006d95dd2 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Tue, 30 Sep 2025 12:05:06 +0100 Subject: [PATCH 2/6] Fix bug in `UnsafeFieldReadSanitizer` --- go/ql/lib/semmle/go/security/SafeUrlFlowCustomizations.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go/ql/lib/semmle/go/security/SafeUrlFlowCustomizations.qll b/go/ql/lib/semmle/go/security/SafeUrlFlowCustomizations.qll index eefdea3a145a..8acd88fb26d8 100644 --- a/go/ql/lib/semmle/go/security/SafeUrlFlowCustomizations.qll +++ b/go/ql/lib/semmle/go/security/SafeUrlFlowCustomizations.qll @@ -49,7 +49,7 @@ module SafeUrlFlow { UnsafeFieldReadSanitizer() { exists(DataFlow::FieldReadNode frn, string name | name = ["Fragment", "RawQuery", "User"] and - frn.getField().hasQualifiedName("net/url", "URL") + frn.getField().hasQualifiedName("net/url", "URL", name) | this = frn.getBase() ) From a2a95755871b18de3c2d804ccd02db663dc6ced6 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Tue, 30 Sep 2025 11:23:52 +0100 Subject: [PATCH 3/6] Add tests for safe URL flow --- .../OpenUrlRedirectCustomizations.qll | 2 +- .../security/RequestForgeryCustomizations.qll | 2 +- go/ql/lib/semmle/go/security/SafeUrlFlow.qll | 6 +- .../security/SafeUrlFlow/SafeUrlFlow.expected | 112 ++++++++++++++ .../go/security/SafeUrlFlow/SafeUrlFlow.go | 139 ++++++++++++++++++ .../go/security/SafeUrlFlow/SafeUrlFlow.ql | 15 ++ .../go/security/SafeUrlFlow/SafeUrlFlow.qlref | 4 + 7 files changed, 276 insertions(+), 4 deletions(-) create mode 100644 go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.expected create mode 100644 go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.go create mode 100644 go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.ql create mode 100644 go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.qlref diff --git a/go/ql/lib/semmle/go/security/OpenUrlRedirectCustomizations.qll b/go/ql/lib/semmle/go/security/OpenUrlRedirectCustomizations.qll index 14c0b232d8ba..c278bdf58c5d 100644 --- a/go/ql/lib/semmle/go/security/OpenUrlRedirectCustomizations.qll +++ b/go/ql/lib/semmle/go/security/OpenUrlRedirectCustomizations.qll @@ -6,7 +6,7 @@ import go import UrlConcatenation -import SafeUrlFlowCustomizations +private import SafeUrlFlowCustomizations import semmle.go.dataflow.barrierguardutil.RedirectCheckBarrierGuard import semmle.go.dataflow.barrierguardutil.RegexpCheck import semmle.go.dataflow.barrierguardutil.UrlCheck diff --git a/go/ql/lib/semmle/go/security/RequestForgeryCustomizations.qll b/go/ql/lib/semmle/go/security/RequestForgeryCustomizations.qll index a34a47dd7eda..1298785b726c 100644 --- a/go/ql/lib/semmle/go/security/RequestForgeryCustomizations.qll +++ b/go/ql/lib/semmle/go/security/RequestForgeryCustomizations.qll @@ -4,7 +4,7 @@ import go import UrlConcatenation -import SafeUrlFlowCustomizations +private import SafeUrlFlowCustomizations import semmle.go.dataflow.barrierguardutil.RedirectCheckBarrierGuard import semmle.go.dataflow.barrierguardutil.RegexpCheck import semmle.go.dataflow.barrierguardutil.UrlCheck diff --git a/go/ql/lib/semmle/go/security/SafeUrlFlow.qll b/go/ql/lib/semmle/go/security/SafeUrlFlow.qll index 77b7aeda591b..1fc39072dfbf 100644 --- a/go/ql/lib/semmle/go/security/SafeUrlFlow.qll +++ b/go/ql/lib/semmle/go/security/SafeUrlFlow.qll @@ -30,8 +30,10 @@ module SafeUrlFlow { predicate isBarrierOut(DataFlow::Node node) { // block propagation of this safe value when its host is overwritten - exists(Write w, Field f | f.hasQualifiedName("net/url", "URL", "Host") | - w.writesField(node.getASuccessor(), f, _) + exists(Write w, DataFlow::Node b, Field f | + f.hasQualifiedName("net/url", "URL", "Host") and + b = node.getASuccessor() and + w.writesField(b, f, _) ) or node instanceof SanitizerEdge diff --git a/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.expected b/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.expected new file mode 100644 index 000000000000..36129fab07cb --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.expected @@ -0,0 +1,112 @@ +#select +| SafeUrlFlow.go:11:24:11:46 | ...+... | SafeUrlFlow.go:10:10:10:17 | selection of Host | SafeUrlFlow.go:11:24:11:46 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:10:10:10:17 | selection of Host | here | +| SafeUrlFlow.go:14:29:14:44 | call to String | SafeUrlFlow.go:13:13:13:19 | selection of URL | SafeUrlFlow.go:14:29:14:44 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:13:13:13:19 | selection of URL | here | +| SafeUrlFlow.go:18:11:18:28 | call to String | SafeUrlFlow.go:10:10:10:17 | selection of Host | SafeUrlFlow.go:18:11:18:28 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:10:10:10:17 | selection of Host | here | +| SafeUrlFlow.go:49:24:49:57 | ...+... | SafeUrlFlow.go:39:13:39:19 | selection of URL | SafeUrlFlow.go:49:24:49:57 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:39:13:39:19 | selection of URL | here | +| SafeUrlFlow.go:50:29:50:51 | ...+... | SafeUrlFlow.go:39:13:39:19 | selection of URL | SafeUrlFlow.go:50:29:50:51 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:39:13:39:19 | selection of URL | here | +| SafeUrlFlow.go:51:11:51:38 | ...+... | SafeUrlFlow.go:39:13:39:19 | selection of URL | SafeUrlFlow.go:51:11:51:38 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:39:13:39:19 | selection of URL | here | +| SafeUrlFlow.go:60:11:60:26 | call to String | SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:60:11:60:26 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:57:13:57:19 | selection of URL | here | +| SafeUrlFlow.go:61:12:61:27 | call to String | SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:61:12:61:27 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:57:13:57:19 | selection of URL | here | +| SafeUrlFlow.go:62:16:62:31 | call to String | SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:62:16:62:31 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:57:13:57:19 | selection of URL | here | +| SafeUrlFlow.go:63:12:63:27 | call to String | SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:63:12:63:27 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:57:13:57:19 | selection of URL | here | +| SafeUrlFlow.go:67:13:67:28 | call to String | SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:67:13:67:28 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:57:13:57:19 | selection of URL | here | +| SafeUrlFlow.go:68:14:68:29 | call to String | SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:68:14:68:29 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:57:13:57:19 | selection of URL | here | +| SafeUrlFlow.go:69:18:69:33 | call to String | SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:69:18:69:33 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:57:13:57:19 | selection of URL | here | +| SafeUrlFlow.go:70:14:70:29 | call to String | SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:70:14:70:29 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:57:13:57:19 | selection of URL | here | +| SafeUrlFlow.go:73:39:73:54 | call to String | SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:73:39:73:54 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:57:13:57:19 | selection of URL | here | +| SafeUrlFlow.go:77:70:77:85 | call to String | SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:77:70:77:85 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:57:13:57:19 | selection of URL | here | +| SafeUrlFlow.go:81:40:81:55 | call to String | SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:81:40:81:55 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:57:13:57:19 | selection of URL | here | +| SafeUrlFlow.go:94:24:94:41 | call to String | SafeUrlFlow.go:87:14:87:21 | selection of Host | SafeUrlFlow.go:94:24:94:41 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:87:14:87:21 | selection of Host | here | +| SafeUrlFlow.go:116:11:116:23 | reconstructed | SafeUrlFlow.go:106:13:106:19 | selection of URL | SafeUrlFlow.go:116:11:116:23 | reconstructed | A safe URL flows here from $@. | SafeUrlFlow.go:106:13:106:19 | selection of URL | here | +| SafeUrlFlow.go:119:24:119:46 | ...+... | SafeUrlFlow.go:106:13:106:19 | selection of URL | SafeUrlFlow.go:119:24:119:46 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:106:13:106:19 | selection of URL | here | +| SafeUrlFlow.go:120:29:120:54 | ...+... | SafeUrlFlow.go:106:13:106:19 | selection of URL | SafeUrlFlow.go:120:29:120:54 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:106:13:106:19 | selection of URL | here | +| SafeUrlFlow.go:121:12:121:38 | ...+... | SafeUrlFlow.go:106:13:106:19 | selection of URL | SafeUrlFlow.go:121:12:121:38 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:106:13:106:19 | selection of URL | here | +edges +| SafeUrlFlow.go:10:10:10:17 | selection of Host | SafeUrlFlow.go:11:24:11:46 | ...+... | provenance | Sink:MaD:1 | +| SafeUrlFlow.go:10:10:10:17 | selection of Host | SafeUrlFlow.go:17:19:17:22 | host | provenance | | +| SafeUrlFlow.go:13:13:13:19 | selection of URL | SafeUrlFlow.go:14:29:14:35 | baseURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:14:29:14:35 | baseURL | SafeUrlFlow.go:14:29:14:44 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:17:19:17:22 | host | SafeUrlFlow.go:18:11:18:19 | targetURL | provenance | Config | +| SafeUrlFlow.go:18:11:18:19 | targetURL | SafeUrlFlow.go:18:11:18:28 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:39:13:39:19 | selection of URL | SafeUrlFlow.go:49:24:49:57 | ...+... | provenance | Src:MaD:2 Sink:MaD:1 | +| SafeUrlFlow.go:39:13:39:19 | selection of URL | SafeUrlFlow.go:50:29:50:51 | ...+... | provenance | Src:MaD:2 | +| SafeUrlFlow.go:39:13:39:19 | selection of URL | SafeUrlFlow.go:51:11:51:38 | ...+... | provenance | Src:MaD:2 | +| SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:60:11:60:17 | baseURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:61:12:61:18 | baseURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:62:16:62:22 | baseURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:63:12:63:18 | baseURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:67:13:67:19 | baseURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:68:14:68:20 | baseURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:69:18:69:24 | baseURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:70:14:70:20 | baseURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:73:39:73:45 | baseURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:77:70:77:76 | baseURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:81:40:81:46 | baseURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:60:11:60:17 | baseURL | SafeUrlFlow.go:60:11:60:26 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:61:12:61:18 | baseURL | SafeUrlFlow.go:61:12:61:27 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:62:16:62:22 | baseURL | SafeUrlFlow.go:62:16:62:31 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:63:12:63:18 | baseURL | SafeUrlFlow.go:63:12:63:27 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:67:13:67:19 | baseURL | SafeUrlFlow.go:67:13:67:28 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:68:14:68:20 | baseURL | SafeUrlFlow.go:68:14:68:29 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:69:18:69:24 | baseURL | SafeUrlFlow.go:69:18:69:33 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:70:14:70:20 | baseURL | SafeUrlFlow.go:70:14:70:29 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:73:39:73:45 | baseURL | SafeUrlFlow.go:73:39:73:54 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:77:70:77:76 | baseURL | SafeUrlFlow.go:77:70:77:85 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:81:40:81:46 | baseURL | SafeUrlFlow.go:81:40:81:55 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:87:14:87:21 | selection of Host | SafeUrlFlow.go:91:19:91:26 | safeHost | provenance | | +| SafeUrlFlow.go:91:19:91:26 | safeHost | SafeUrlFlow.go:94:24:94:32 | targetURL | provenance | Config | +| SafeUrlFlow.go:94:24:94:32 | targetURL | SafeUrlFlow.go:94:24:94:41 | call to String | provenance | MaD:3 Sink:MaD:1 | +| SafeUrlFlow.go:106:13:106:19 | selection of URL | SafeUrlFlow.go:116:11:116:23 | reconstructed | provenance | Src:MaD:2 | +| SafeUrlFlow.go:106:13:106:19 | selection of URL | SafeUrlFlow.go:119:24:119:46 | ...+... | provenance | Src:MaD:2 Sink:MaD:1 | +| SafeUrlFlow.go:106:13:106:19 | selection of URL | SafeUrlFlow.go:120:29:120:54 | ...+... | provenance | Src:MaD:2 | +| SafeUrlFlow.go:106:13:106:19 | selection of URL | SafeUrlFlow.go:121:12:121:38 | ...+... | provenance | Src:MaD:2 | +models +| 1 | Sink: net/http; ; false; Redirect; ; ; Argument[2]; url-redirection[0]; manual | +| 2 | Source: net/http; Request; true; URL; ; ; ; remote; manual | +| 3 | Summary: fmt; Stringer; true; String; ; ; Argument[receiver]; ReturnValue; taint; manual | +nodes +| SafeUrlFlow.go:10:10:10:17 | selection of Host | semmle.label | selection of Host | +| SafeUrlFlow.go:11:24:11:46 | ...+... | semmle.label | ...+... | +| SafeUrlFlow.go:13:13:13:19 | selection of URL | semmle.label | selection of URL | +| SafeUrlFlow.go:14:29:14:35 | baseURL | semmle.label | baseURL | +| SafeUrlFlow.go:14:29:14:44 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:17:19:17:22 | host | semmle.label | host | +| SafeUrlFlow.go:18:11:18:19 | targetURL | semmle.label | targetURL | +| SafeUrlFlow.go:18:11:18:28 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:39:13:39:19 | selection of URL | semmle.label | selection of URL | +| SafeUrlFlow.go:49:24:49:57 | ...+... | semmle.label | ...+... | +| SafeUrlFlow.go:50:29:50:51 | ...+... | semmle.label | ...+... | +| SafeUrlFlow.go:51:11:51:38 | ...+... | semmle.label | ...+... | +| SafeUrlFlow.go:57:13:57:19 | selection of URL | semmle.label | selection of URL | +| SafeUrlFlow.go:60:11:60:17 | baseURL | semmle.label | baseURL | +| SafeUrlFlow.go:60:11:60:26 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:61:12:61:18 | baseURL | semmle.label | baseURL | +| SafeUrlFlow.go:61:12:61:27 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:62:16:62:22 | baseURL | semmle.label | baseURL | +| SafeUrlFlow.go:62:16:62:31 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:63:12:63:18 | baseURL | semmle.label | baseURL | +| SafeUrlFlow.go:63:12:63:27 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:67:13:67:19 | baseURL | semmle.label | baseURL | +| SafeUrlFlow.go:67:13:67:28 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:68:14:68:20 | baseURL | semmle.label | baseURL | +| SafeUrlFlow.go:68:14:68:29 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:69:18:69:24 | baseURL | semmle.label | baseURL | +| SafeUrlFlow.go:69:18:69:33 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:70:14:70:20 | baseURL | semmle.label | baseURL | +| SafeUrlFlow.go:70:14:70:29 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:73:39:73:45 | baseURL | semmle.label | baseURL | +| SafeUrlFlow.go:73:39:73:54 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:77:70:77:76 | baseURL | semmle.label | baseURL | +| SafeUrlFlow.go:77:70:77:85 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:81:40:81:46 | baseURL | semmle.label | baseURL | +| SafeUrlFlow.go:81:40:81:55 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:87:14:87:21 | selection of Host | semmle.label | selection of Host | +| SafeUrlFlow.go:91:19:91:26 | safeHost | semmle.label | safeHost | +| SafeUrlFlow.go:94:24:94:32 | targetURL | semmle.label | targetURL | +| SafeUrlFlow.go:94:24:94:41 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:106:13:106:19 | selection of URL | semmle.label | selection of URL | +| SafeUrlFlow.go:116:11:116:23 | reconstructed | semmle.label | reconstructed | +| SafeUrlFlow.go:119:24:119:46 | ...+... | semmle.label | ...+... | +| SafeUrlFlow.go:120:29:120:54 | ...+... | semmle.label | ...+... | +| SafeUrlFlow.go:121:12:121:38 | ...+... | semmle.label | ...+... | +subpaths diff --git a/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.go b/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.go new file mode 100644 index 000000000000..a1844deb6fc4 --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.go @@ -0,0 +1,139 @@ +package main + +import ( + "context" + "net/http" + "net/url" +) + +func testStdlibSources(w http.ResponseWriter, req *http.Request) { + host := req.Host // $ Source + http.Redirect(w, req, "https://"+host+"/safe", http.StatusFound) // $ Alert + + baseURL := req.URL // $ Source + w.Header().Set("Location", baseURL.String()) // $ Alert + + targetURL := url.URL{} + targetURL.Host = host // propagation to URL when Host is assigned + http.Get(targetURL.String()) // $ Alert +} + +func testSanitizerEdge1(w http.ResponseWriter, req *http.Request) { + baseURL := req.URL + + // SanitizerEdge: Query method call (unsafe URL method - breaks flow) + query := baseURL.Query() // sanitizer edge blocks flow here + http.Redirect(w, req, query.Get("redirect"), http.StatusFound) // no flow expected +} + +func testSanitizerEdge2(w http.ResponseWriter, req *http.Request) { + baseURL := req.URL + + // SanitizerEdge: String slicing (breaks flow) + urlString := baseURL.String() + sliced := urlString[0:10] // sanitizer edge blocks flow here + w.Header().Set("Location", sliced) // no flow expected +} + +func testFieldReads(w http.ResponseWriter, req *http.Request) { + baseURL := req.URL // $ Source + + // Test that other URL methods preserve flow + scheme := baseURL.Scheme // should preserve flow + host := baseURL.Host // should preserve flow + path := baseURL.Path // should preserve flow + fragment := baseURL.Fragment // should preserve flow + user := baseURL.User // should preserve flow (but unsafe field) + + // These should still have flow (not sanitized) + http.Redirect(w, req, "https://"+scheme+"://example.com", http.StatusFound) // $ Alert + w.Header().Set("Location", "https://"+host+"/safe") // $ Alert + http.Get("https://example.com" + path) // $ Alert + http.Get(fragment) + http.Get(user.String()) +} + +func testRequestForgerySinks(req *http.Request) { + baseURL := req.URL // $ Source + + // Standard library HTTP functions (request-forgery sinks) + http.Get(baseURL.String()) // $ Alert + http.Post(baseURL.String(), "application/json", nil) // $ Alert + http.PostForm(baseURL.String(), nil) // $ Alert + http.Head(baseURL.String()) // $ Alert + + // HTTP Client methods (request-forgery sinks) + client := &http.Client{} + client.Get(baseURL.String()) // $ Alert + client.Post(baseURL.String(), "application/json", nil) // $ Alert + client.PostForm(baseURL.String(), nil) // $ Alert + client.Head(baseURL.String()) // $ Alert + + // NewRequest + Client.Do (request-forgery sinks) + request, _ := http.NewRequest("GET", baseURL.String(), nil) // $ Alert + client.Do(request) + + // NewRequestWithContext + Client.Do (request-forgery sinks) + reqWithCtx, _ := http.NewRequestWithContext(context.TODO(), "POST", baseURL.String(), nil) // $ Alert + client.Do(reqWithCtx) + + // RoundTrip method (request-forgery sink) + request2, _ := http.NewRequest("GET", baseURL.String(), nil) // $ Alert + transport := &http.Transport{} + transport.RoundTrip(request2) +} + +func testHostFieldAssignmentFlow(w http.ResponseWriter, req *http.Request) { + safeHost := req.Host // $ Source + + // Test additional flow step: propagation when Host field is assigned + targetURL, _ := url.Parse("http://example.com/data") + targetURL.Host = safeHost // additional flow step from SafeUrlFlow config + + // Flow should propagate to the whole URL after Host assignment + http.Redirect(w, req, targetURL.String(), http.StatusFound) // $ Alert +} + +func testHostFieldOverwritten(w http.ResponseWriter, req *http.Request) { + baseURL := req.URL + + // Flow should be blocked when Host is overwritten + baseURL.Host = "something.else.com" + http.Get(baseURL.String()) +} + +func testFieldAccess(w http.ResponseWriter, req *http.Request) { + baseURL := req.URL // $ Source + + // Safe field accesses that should preserve flow + host := baseURL.Host + path := baseURL.Path + scheme := baseURL.Scheme + opaquePart := baseURL.Opaque + + // Reconstruct URL - flow should be preserved through field access + reconstructed := scheme + "://" + host + path + http.Get(reconstructed) // $ Alert + + // Test individual fields + http.Redirect(w, req, "https://"+host+"/path", http.StatusFound) // $ Alert + w.Header().Set("Location", "https://example.com"+path) // $ Alert + http.Post(scheme+"://example.com/api", "application/json", nil) // $ Alert + use(opaquePart) // avoid unused variable warning + + // Unsafe field accesses that should be sanitized by UnsafeFieldReadSanitizer + // These read unsafe URL fields and should NOT have flow + unsafeUser := baseURL.User // sanitizer edge (User field) + unsafeQuery := baseURL.RawQuery // sanitizer edge (RawQuery field) + unsafeFragment := baseURL.Fragment // sanitizer edge (Fragment field) + + // These should NOT have flow due to sanitizer edges + if unsafeUser != nil { + http.Redirect(w, req, unsafeUser.String(), http.StatusFound) // no flow expected + } + w.Header().Set("Location", "https://example.com/?"+unsafeQuery) // no flow expected + http.Get("https://example.com/#" + unsafeFragment) // no flow expected +} + +// Helper function to avoid unused variable warnings +func use(vars ...interface{}) {} diff --git a/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.ql b/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.ql new file mode 100644 index 000000000000..badc69f386cb --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.ql @@ -0,0 +1,15 @@ +/** + * @id go/test-safe-url-flow + * @kind path-problem + * @problem.severity recommendation + */ + +import go +import semmle.go.security.RequestForgeryCustomizations +import semmle.go.security.OpenUrlRedirectCustomizations +import semmle.go.security.SafeUrlFlow +import SafeUrlFlow::Flow::PathGraph + +from SafeUrlFlow::Flow::PathNode source, SafeUrlFlow::Flow::PathNode sink +where SafeUrlFlow::Flow::flowPath(source, sink) +select sink.getNode(), source, sink, "A safe URL flows here from $@.", source.getNode(), "here" diff --git a/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.qlref b/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.qlref new file mode 100644 index 000000000000..db1b80a6317c --- /dev/null +++ b/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.qlref @@ -0,0 +1,4 @@ +query: SafeUrlFlow.ql +postprocess: + - utils/test/PrettyPrintModels.ql + - utils/test/InlineExpectationsTestQuery.ql From dd3f754cb36957a4440509aa6b6e71708893a676 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Tue, 30 Sep 2025 12:35:03 +0100 Subject: [PATCH 4/6] Add change note. --- go/ql/lib/change-notes/2025-09-30-fewer-safe-urls.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 go/ql/lib/change-notes/2025-09-30-fewer-safe-urls.md diff --git a/go/ql/lib/change-notes/2025-09-30-fewer-safe-urls.md b/go/ql/lib/change-notes/2025-09-30-fewer-safe-urls.md new file mode 100644 index 000000000000..5eeee51c4a3c --- /dev/null +++ b/go/ql/lib/change-notes/2025-09-30-fewer-safe-urls.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* `go/unvalidated-url-redirection` and `go/request-forgery` have a shared notion of a safe URL, which is known to not be malicious. Some URLs which were incorrectly considered safe are now correctly considered unsafe. This may lead to more alerts for those two queries. From c93852d87a9e5e2f56a6df04c5b604268ae3a279 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Wed, 1 Oct 2025 11:00:09 +0100 Subject: [PATCH 5/6] Improve comments in test file --- .../security/SafeUrlFlow/SafeUrlFlow.expected | 165 +++++++++--------- .../go/security/SafeUrlFlow/SafeUrlFlow.go | 51 +++--- 2 files changed, 106 insertions(+), 110 deletions(-) diff --git a/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.expected b/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.expected index 36129fab07cb..4cbe81bf2be0 100644 --- a/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.expected +++ b/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.expected @@ -2,25 +2,26 @@ | SafeUrlFlow.go:11:24:11:46 | ...+... | SafeUrlFlow.go:10:10:10:17 | selection of Host | SafeUrlFlow.go:11:24:11:46 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:10:10:10:17 | selection of Host | here | | SafeUrlFlow.go:14:29:14:44 | call to String | SafeUrlFlow.go:13:13:13:19 | selection of URL | SafeUrlFlow.go:14:29:14:44 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:13:13:13:19 | selection of URL | here | | SafeUrlFlow.go:18:11:18:28 | call to String | SafeUrlFlow.go:10:10:10:17 | selection of Host | SafeUrlFlow.go:18:11:18:28 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:10:10:10:17 | selection of Host | here | -| SafeUrlFlow.go:49:24:49:57 | ...+... | SafeUrlFlow.go:39:13:39:19 | selection of URL | SafeUrlFlow.go:49:24:49:57 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:39:13:39:19 | selection of URL | here | -| SafeUrlFlow.go:50:29:50:51 | ...+... | SafeUrlFlow.go:39:13:39:19 | selection of URL | SafeUrlFlow.go:50:29:50:51 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:39:13:39:19 | selection of URL | here | -| SafeUrlFlow.go:51:11:51:38 | ...+... | SafeUrlFlow.go:39:13:39:19 | selection of URL | SafeUrlFlow.go:51:11:51:38 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:39:13:39:19 | selection of URL | here | -| SafeUrlFlow.go:60:11:60:26 | call to String | SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:60:11:60:26 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:57:13:57:19 | selection of URL | here | -| SafeUrlFlow.go:61:12:61:27 | call to String | SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:61:12:61:27 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:57:13:57:19 | selection of URL | here | -| SafeUrlFlow.go:62:16:62:31 | call to String | SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:62:16:62:31 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:57:13:57:19 | selection of URL | here | -| SafeUrlFlow.go:63:12:63:27 | call to String | SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:63:12:63:27 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:57:13:57:19 | selection of URL | here | -| SafeUrlFlow.go:67:13:67:28 | call to String | SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:67:13:67:28 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:57:13:57:19 | selection of URL | here | -| SafeUrlFlow.go:68:14:68:29 | call to String | SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:68:14:68:29 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:57:13:57:19 | selection of URL | here | -| SafeUrlFlow.go:69:18:69:33 | call to String | SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:69:18:69:33 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:57:13:57:19 | selection of URL | here | -| SafeUrlFlow.go:70:14:70:29 | call to String | SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:70:14:70:29 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:57:13:57:19 | selection of URL | here | -| SafeUrlFlow.go:73:39:73:54 | call to String | SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:73:39:73:54 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:57:13:57:19 | selection of URL | here | -| SafeUrlFlow.go:77:70:77:85 | call to String | SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:77:70:77:85 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:57:13:57:19 | selection of URL | here | -| SafeUrlFlow.go:81:40:81:55 | call to String | SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:81:40:81:55 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:57:13:57:19 | selection of URL | here | -| SafeUrlFlow.go:94:24:94:41 | call to String | SafeUrlFlow.go:87:14:87:21 | selection of Host | SafeUrlFlow.go:94:24:94:41 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:87:14:87:21 | selection of Host | here | -| SafeUrlFlow.go:116:11:116:23 | reconstructed | SafeUrlFlow.go:106:13:106:19 | selection of URL | SafeUrlFlow.go:116:11:116:23 | reconstructed | A safe URL flows here from $@. | SafeUrlFlow.go:106:13:106:19 | selection of URL | here | -| SafeUrlFlow.go:119:24:119:46 | ...+... | SafeUrlFlow.go:106:13:106:19 | selection of URL | SafeUrlFlow.go:119:24:119:46 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:106:13:106:19 | selection of URL | here | -| SafeUrlFlow.go:120:29:120:54 | ...+... | SafeUrlFlow.go:106:13:106:19 | selection of URL | SafeUrlFlow.go:120:29:120:54 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:106:13:106:19 | selection of URL | here | -| SafeUrlFlow.go:121:12:121:38 | ...+... | SafeUrlFlow.go:106:13:106:19 | selection of URL | SafeUrlFlow.go:121:12:121:38 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:106:13:106:19 | selection of URL | here | +| SafeUrlFlow.go:47:24:47:57 | ...+... | SafeUrlFlow.go:37:13:37:19 | selection of URL | SafeUrlFlow.go:47:24:47:57 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:37:13:37:19 | selection of URL | here | +| SafeUrlFlow.go:48:29:48:51 | ...+... | SafeUrlFlow.go:37:13:37:19 | selection of URL | SafeUrlFlow.go:48:29:48:51 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:37:13:37:19 | selection of URL | here | +| SafeUrlFlow.go:49:11:49:38 | ...+... | SafeUrlFlow.go:37:13:37:19 | selection of URL | SafeUrlFlow.go:49:11:49:38 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:37:13:37:19 | selection of URL | here | +| SafeUrlFlow.go:58:11:58:26 | call to String | SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:58:11:58:26 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:55:13:55:19 | selection of URL | here | +| SafeUrlFlow.go:59:12:59:27 | call to String | SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:59:12:59:27 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:55:13:55:19 | selection of URL | here | +| SafeUrlFlow.go:60:16:60:31 | call to String | SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:60:16:60:31 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:55:13:55:19 | selection of URL | here | +| SafeUrlFlow.go:61:12:61:27 | call to String | SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:61:12:61:27 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:55:13:55:19 | selection of URL | here | +| SafeUrlFlow.go:65:13:65:28 | call to String | SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:65:13:65:28 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:55:13:55:19 | selection of URL | here | +| SafeUrlFlow.go:66:14:66:29 | call to String | SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:66:14:66:29 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:55:13:55:19 | selection of URL | here | +| SafeUrlFlow.go:67:18:67:33 | call to String | SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:67:18:67:33 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:55:13:55:19 | selection of URL | here | +| SafeUrlFlow.go:68:14:68:29 | call to String | SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:68:14:68:29 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:55:13:55:19 | selection of URL | here | +| SafeUrlFlow.go:71:39:71:54 | call to String | SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:71:39:71:54 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:55:13:55:19 | selection of URL | here | +| SafeUrlFlow.go:75:70:75:85 | call to String | SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:75:70:75:85 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:55:13:55:19 | selection of URL | here | +| SafeUrlFlow.go:79:40:79:55 | call to String | SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:79:40:79:55 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:55:13:55:19 | selection of URL | here | +| SafeUrlFlow.go:90:24:90:41 | call to String | SafeUrlFlow.go:85:10:85:17 | selection of Host | SafeUrlFlow.go:90:24:90:41 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:85:10:85:17 | selection of Host | here | +| SafeUrlFlow.go:111:11:111:23 | reconstructed | SafeUrlFlow.go:101:13:101:19 | selection of URL | SafeUrlFlow.go:111:11:111:23 | reconstructed | A safe URL flows here from $@. | SafeUrlFlow.go:101:13:101:19 | selection of URL | here | +| SafeUrlFlow.go:114:24:114:46 | ...+... | SafeUrlFlow.go:101:13:101:19 | selection of URL | SafeUrlFlow.go:114:24:114:46 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:101:13:101:19 | selection of URL | here | +| SafeUrlFlow.go:115:29:115:54 | ...+... | SafeUrlFlow.go:101:13:101:19 | selection of URL | SafeUrlFlow.go:115:29:115:54 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:101:13:101:19 | selection of URL | here | +| SafeUrlFlow.go:116:12:116:38 | ...+... | SafeUrlFlow.go:101:13:101:19 | selection of URL | SafeUrlFlow.go:116:12:116:38 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:101:13:101:19 | selection of URL | here | +| SafeUrlFlow.go:117:12:117:21 | opaquePart | SafeUrlFlow.go:101:13:101:19 | selection of URL | SafeUrlFlow.go:117:12:117:21 | opaquePart | A safe URL flows here from $@. | SafeUrlFlow.go:101:13:101:19 | selection of URL | here | edges | SafeUrlFlow.go:10:10:10:17 | selection of Host | SafeUrlFlow.go:11:24:11:46 | ...+... | provenance | Sink:MaD:1 | | SafeUrlFlow.go:10:10:10:17 | selection of Host | SafeUrlFlow.go:17:19:17:22 | host | provenance | | @@ -28,38 +29,39 @@ edges | SafeUrlFlow.go:14:29:14:35 | baseURL | SafeUrlFlow.go:14:29:14:44 | call to String | provenance | MaD:3 | | SafeUrlFlow.go:17:19:17:22 | host | SafeUrlFlow.go:18:11:18:19 | targetURL | provenance | Config | | SafeUrlFlow.go:18:11:18:19 | targetURL | SafeUrlFlow.go:18:11:18:28 | call to String | provenance | MaD:3 | -| SafeUrlFlow.go:39:13:39:19 | selection of URL | SafeUrlFlow.go:49:24:49:57 | ...+... | provenance | Src:MaD:2 Sink:MaD:1 | -| SafeUrlFlow.go:39:13:39:19 | selection of URL | SafeUrlFlow.go:50:29:50:51 | ...+... | provenance | Src:MaD:2 | -| SafeUrlFlow.go:39:13:39:19 | selection of URL | SafeUrlFlow.go:51:11:51:38 | ...+... | provenance | Src:MaD:2 | -| SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:60:11:60:17 | baseURL | provenance | Src:MaD:2 | -| SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:61:12:61:18 | baseURL | provenance | Src:MaD:2 | -| SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:62:16:62:22 | baseURL | provenance | Src:MaD:2 | -| SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:63:12:63:18 | baseURL | provenance | Src:MaD:2 | -| SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:67:13:67:19 | baseURL | provenance | Src:MaD:2 | -| SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:68:14:68:20 | baseURL | provenance | Src:MaD:2 | -| SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:69:18:69:24 | baseURL | provenance | Src:MaD:2 | -| SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:70:14:70:20 | baseURL | provenance | Src:MaD:2 | -| SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:73:39:73:45 | baseURL | provenance | Src:MaD:2 | -| SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:77:70:77:76 | baseURL | provenance | Src:MaD:2 | -| SafeUrlFlow.go:57:13:57:19 | selection of URL | SafeUrlFlow.go:81:40:81:46 | baseURL | provenance | Src:MaD:2 | -| SafeUrlFlow.go:60:11:60:17 | baseURL | SafeUrlFlow.go:60:11:60:26 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:37:13:37:19 | selection of URL | SafeUrlFlow.go:47:24:47:57 | ...+... | provenance | Src:MaD:2 Sink:MaD:1 | +| SafeUrlFlow.go:37:13:37:19 | selection of URL | SafeUrlFlow.go:48:29:48:51 | ...+... | provenance | Src:MaD:2 | +| SafeUrlFlow.go:37:13:37:19 | selection of URL | SafeUrlFlow.go:49:11:49:38 | ...+... | provenance | Src:MaD:2 | +| SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:58:11:58:17 | baseURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:59:12:59:18 | baseURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:60:16:60:22 | baseURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:61:12:61:18 | baseURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:65:13:65:19 | baseURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:66:14:66:20 | baseURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:67:18:67:24 | baseURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:68:14:68:20 | baseURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:71:39:71:45 | baseURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:75:70:75:76 | baseURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:79:40:79:46 | baseURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:58:11:58:17 | baseURL | SafeUrlFlow.go:58:11:58:26 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:59:12:59:18 | baseURL | SafeUrlFlow.go:59:12:59:27 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:60:16:60:22 | baseURL | SafeUrlFlow.go:60:16:60:31 | call to String | provenance | MaD:3 | | SafeUrlFlow.go:61:12:61:18 | baseURL | SafeUrlFlow.go:61:12:61:27 | call to String | provenance | MaD:3 | -| SafeUrlFlow.go:62:16:62:22 | baseURL | SafeUrlFlow.go:62:16:62:31 | call to String | provenance | MaD:3 | -| SafeUrlFlow.go:63:12:63:18 | baseURL | SafeUrlFlow.go:63:12:63:27 | call to String | provenance | MaD:3 | -| SafeUrlFlow.go:67:13:67:19 | baseURL | SafeUrlFlow.go:67:13:67:28 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:65:13:65:19 | baseURL | SafeUrlFlow.go:65:13:65:28 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:66:14:66:20 | baseURL | SafeUrlFlow.go:66:14:66:29 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:67:18:67:24 | baseURL | SafeUrlFlow.go:67:18:67:33 | call to String | provenance | MaD:3 | | SafeUrlFlow.go:68:14:68:20 | baseURL | SafeUrlFlow.go:68:14:68:29 | call to String | provenance | MaD:3 | -| SafeUrlFlow.go:69:18:69:24 | baseURL | SafeUrlFlow.go:69:18:69:33 | call to String | provenance | MaD:3 | -| SafeUrlFlow.go:70:14:70:20 | baseURL | SafeUrlFlow.go:70:14:70:29 | call to String | provenance | MaD:3 | -| SafeUrlFlow.go:73:39:73:45 | baseURL | SafeUrlFlow.go:73:39:73:54 | call to String | provenance | MaD:3 | -| SafeUrlFlow.go:77:70:77:76 | baseURL | SafeUrlFlow.go:77:70:77:85 | call to String | provenance | MaD:3 | -| SafeUrlFlow.go:81:40:81:46 | baseURL | SafeUrlFlow.go:81:40:81:55 | call to String | provenance | MaD:3 | -| SafeUrlFlow.go:87:14:87:21 | selection of Host | SafeUrlFlow.go:91:19:91:26 | safeHost | provenance | | -| SafeUrlFlow.go:91:19:91:26 | safeHost | SafeUrlFlow.go:94:24:94:32 | targetURL | provenance | Config | -| SafeUrlFlow.go:94:24:94:32 | targetURL | SafeUrlFlow.go:94:24:94:41 | call to String | provenance | MaD:3 Sink:MaD:1 | -| SafeUrlFlow.go:106:13:106:19 | selection of URL | SafeUrlFlow.go:116:11:116:23 | reconstructed | provenance | Src:MaD:2 | -| SafeUrlFlow.go:106:13:106:19 | selection of URL | SafeUrlFlow.go:119:24:119:46 | ...+... | provenance | Src:MaD:2 Sink:MaD:1 | -| SafeUrlFlow.go:106:13:106:19 | selection of URL | SafeUrlFlow.go:120:29:120:54 | ...+... | provenance | Src:MaD:2 | -| SafeUrlFlow.go:106:13:106:19 | selection of URL | SafeUrlFlow.go:121:12:121:38 | ...+... | provenance | Src:MaD:2 | +| SafeUrlFlow.go:71:39:71:45 | baseURL | SafeUrlFlow.go:71:39:71:54 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:75:70:75:76 | baseURL | SafeUrlFlow.go:75:70:75:85 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:79:40:79:46 | baseURL | SafeUrlFlow.go:79:40:79:55 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:85:10:85:17 | selection of Host | SafeUrlFlow.go:88:19:88:22 | host | provenance | | +| SafeUrlFlow.go:88:19:88:22 | host | SafeUrlFlow.go:90:24:90:32 | targetURL | provenance | Config | +| SafeUrlFlow.go:90:24:90:32 | targetURL | SafeUrlFlow.go:90:24:90:41 | call to String | provenance | MaD:3 Sink:MaD:1 | +| SafeUrlFlow.go:101:13:101:19 | selection of URL | SafeUrlFlow.go:111:11:111:23 | reconstructed | provenance | Src:MaD:2 | +| SafeUrlFlow.go:101:13:101:19 | selection of URL | SafeUrlFlow.go:114:24:114:46 | ...+... | provenance | Src:MaD:2 Sink:MaD:1 | +| SafeUrlFlow.go:101:13:101:19 | selection of URL | SafeUrlFlow.go:115:29:115:54 | ...+... | provenance | Src:MaD:2 | +| SafeUrlFlow.go:101:13:101:19 | selection of URL | SafeUrlFlow.go:116:12:116:38 | ...+... | provenance | Src:MaD:2 | +| SafeUrlFlow.go:101:13:101:19 | selection of URL | SafeUrlFlow.go:117:12:117:21 | opaquePart | provenance | Src:MaD:2 | models | 1 | Sink: net/http; ; false; Redirect; ; ; Argument[2]; url-redirection[0]; manual | | 2 | Source: net/http; Request; true; URL; ; ; ; remote; manual | @@ -73,40 +75,41 @@ nodes | SafeUrlFlow.go:17:19:17:22 | host | semmle.label | host | | SafeUrlFlow.go:18:11:18:19 | targetURL | semmle.label | targetURL | | SafeUrlFlow.go:18:11:18:28 | call to String | semmle.label | call to String | -| SafeUrlFlow.go:39:13:39:19 | selection of URL | semmle.label | selection of URL | -| SafeUrlFlow.go:49:24:49:57 | ...+... | semmle.label | ...+... | -| SafeUrlFlow.go:50:29:50:51 | ...+... | semmle.label | ...+... | -| SafeUrlFlow.go:51:11:51:38 | ...+... | semmle.label | ...+... | -| SafeUrlFlow.go:57:13:57:19 | selection of URL | semmle.label | selection of URL | -| SafeUrlFlow.go:60:11:60:17 | baseURL | semmle.label | baseURL | -| SafeUrlFlow.go:60:11:60:26 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:37:13:37:19 | selection of URL | semmle.label | selection of URL | +| SafeUrlFlow.go:47:24:47:57 | ...+... | semmle.label | ...+... | +| SafeUrlFlow.go:48:29:48:51 | ...+... | semmle.label | ...+... | +| SafeUrlFlow.go:49:11:49:38 | ...+... | semmle.label | ...+... | +| SafeUrlFlow.go:55:13:55:19 | selection of URL | semmle.label | selection of URL | +| SafeUrlFlow.go:58:11:58:17 | baseURL | semmle.label | baseURL | +| SafeUrlFlow.go:58:11:58:26 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:59:12:59:18 | baseURL | semmle.label | baseURL | +| SafeUrlFlow.go:59:12:59:27 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:60:16:60:22 | baseURL | semmle.label | baseURL | +| SafeUrlFlow.go:60:16:60:31 | call to String | semmle.label | call to String | | SafeUrlFlow.go:61:12:61:18 | baseURL | semmle.label | baseURL | | SafeUrlFlow.go:61:12:61:27 | call to String | semmle.label | call to String | -| SafeUrlFlow.go:62:16:62:22 | baseURL | semmle.label | baseURL | -| SafeUrlFlow.go:62:16:62:31 | call to String | semmle.label | call to String | -| SafeUrlFlow.go:63:12:63:18 | baseURL | semmle.label | baseURL | -| SafeUrlFlow.go:63:12:63:27 | call to String | semmle.label | call to String | -| SafeUrlFlow.go:67:13:67:19 | baseURL | semmle.label | baseURL | -| SafeUrlFlow.go:67:13:67:28 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:65:13:65:19 | baseURL | semmle.label | baseURL | +| SafeUrlFlow.go:65:13:65:28 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:66:14:66:20 | baseURL | semmle.label | baseURL | +| SafeUrlFlow.go:66:14:66:29 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:67:18:67:24 | baseURL | semmle.label | baseURL | +| SafeUrlFlow.go:67:18:67:33 | call to String | semmle.label | call to String | | SafeUrlFlow.go:68:14:68:20 | baseURL | semmle.label | baseURL | | SafeUrlFlow.go:68:14:68:29 | call to String | semmle.label | call to String | -| SafeUrlFlow.go:69:18:69:24 | baseURL | semmle.label | baseURL | -| SafeUrlFlow.go:69:18:69:33 | call to String | semmle.label | call to String | -| SafeUrlFlow.go:70:14:70:20 | baseURL | semmle.label | baseURL | -| SafeUrlFlow.go:70:14:70:29 | call to String | semmle.label | call to String | -| SafeUrlFlow.go:73:39:73:45 | baseURL | semmle.label | baseURL | -| SafeUrlFlow.go:73:39:73:54 | call to String | semmle.label | call to String | -| SafeUrlFlow.go:77:70:77:76 | baseURL | semmle.label | baseURL | -| SafeUrlFlow.go:77:70:77:85 | call to String | semmle.label | call to String | -| SafeUrlFlow.go:81:40:81:46 | baseURL | semmle.label | baseURL | -| SafeUrlFlow.go:81:40:81:55 | call to String | semmle.label | call to String | -| SafeUrlFlow.go:87:14:87:21 | selection of Host | semmle.label | selection of Host | -| SafeUrlFlow.go:91:19:91:26 | safeHost | semmle.label | safeHost | -| SafeUrlFlow.go:94:24:94:32 | targetURL | semmle.label | targetURL | -| SafeUrlFlow.go:94:24:94:41 | call to String | semmle.label | call to String | -| SafeUrlFlow.go:106:13:106:19 | selection of URL | semmle.label | selection of URL | -| SafeUrlFlow.go:116:11:116:23 | reconstructed | semmle.label | reconstructed | -| SafeUrlFlow.go:119:24:119:46 | ...+... | semmle.label | ...+... | -| SafeUrlFlow.go:120:29:120:54 | ...+... | semmle.label | ...+... | -| SafeUrlFlow.go:121:12:121:38 | ...+... | semmle.label | ...+... | +| SafeUrlFlow.go:71:39:71:45 | baseURL | semmle.label | baseURL | +| SafeUrlFlow.go:71:39:71:54 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:75:70:75:76 | baseURL | semmle.label | baseURL | +| SafeUrlFlow.go:75:70:75:85 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:79:40:79:46 | baseURL | semmle.label | baseURL | +| SafeUrlFlow.go:79:40:79:55 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:85:10:85:17 | selection of Host | semmle.label | selection of Host | +| SafeUrlFlow.go:88:19:88:22 | host | semmle.label | host | +| SafeUrlFlow.go:90:24:90:32 | targetURL | semmle.label | targetURL | +| SafeUrlFlow.go:90:24:90:41 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:101:13:101:19 | selection of URL | semmle.label | selection of URL | +| SafeUrlFlow.go:111:11:111:23 | reconstructed | semmle.label | reconstructed | +| SafeUrlFlow.go:114:24:114:46 | ...+... | semmle.label | ...+... | +| SafeUrlFlow.go:115:29:115:54 | ...+... | semmle.label | ...+... | +| SafeUrlFlow.go:116:12:116:38 | ...+... | semmle.label | ...+... | +| SafeUrlFlow.go:117:12:117:21 | opaquePart | semmle.label | opaquePart | subpaths diff --git a/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.go b/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.go index a1844deb6fc4..45a2b20b8d4d 100644 --- a/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.go +++ b/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.go @@ -8,30 +8,28 @@ import ( func testStdlibSources(w http.ResponseWriter, req *http.Request) { host := req.Host // $ Source - http.Redirect(w, req, "https://"+host+"/safe", http.StatusFound) // $ Alert + http.Redirect(w, req, "https://"+host+"/path", http.StatusFound) // $ Alert baseURL := req.URL // $ Source w.Header().Set("Location", baseURL.String()) // $ Alert targetURL := url.URL{} - targetURL.Host = host // propagation to URL when Host is assigned + targetURL.Host = host // additional flow step from Host field to URL struct http.Get(targetURL.String()) // $ Alert } -func testSanitizerEdge1(w http.ResponseWriter, req *http.Request) { +func testBarrierEdge1(w http.ResponseWriter, req *http.Request) { baseURL := req.URL - // SanitizerEdge: Query method call (unsafe URL method - breaks flow) - query := baseURL.Query() // sanitizer edge blocks flow here + query := baseURL.Query() // barrier edge blocks flow here http.Redirect(w, req, query.Get("redirect"), http.StatusFound) // no flow expected } -func testSanitizerEdge2(w http.ResponseWriter, req *http.Request) { +func testBarrierEdge2(w http.ResponseWriter, req *http.Request) { baseURL := req.URL - // SanitizerEdge: String slicing (breaks flow) urlString := baseURL.String() - sliced := urlString[0:10] // sanitizer edge blocks flow here + sliced := urlString[0:10] // barrier edge (string slicing) blocks flow here w.Header().Set("Location", sliced) // no flow expected } @@ -42,12 +40,12 @@ func testFieldReads(w http.ResponseWriter, req *http.Request) { scheme := baseURL.Scheme // should preserve flow host := baseURL.Host // should preserve flow path := baseURL.Path // should preserve flow - fragment := baseURL.Fragment // should preserve flow - user := baseURL.User // should preserve flow (but unsafe field) + fragment := baseURL.Fragment // should not preserve flow + user := baseURL.User // should not preserve flow // These should still have flow (not sanitized) http.Redirect(w, req, "https://"+scheme+"://example.com", http.StatusFound) // $ Alert - w.Header().Set("Location", "https://"+host+"/safe") // $ Alert + w.Header().Set("Location", "https://"+host+"/path") // $ Alert http.Get("https://example.com" + path) // $ Alert http.Get(fragment) http.Get(user.String()) @@ -84,28 +82,25 @@ func testRequestForgerySinks(req *http.Request) { } func testHostFieldAssignmentFlow(w http.ResponseWriter, req *http.Request) { - safeHost := req.Host // $ Source + host := req.Host // $ Source - // Test additional flow step: propagation when Host field is assigned targetURL, _ := url.Parse("http://example.com/data") - targetURL.Host = safeHost // additional flow step from SafeUrlFlow config + targetURL.Host = host // additional flow step from Host field to URL struct - // Flow should propagate to the whole URL after Host assignment http.Redirect(w, req, targetURL.String(), http.StatusFound) // $ Alert } func testHostFieldOverwritten(w http.ResponseWriter, req *http.Request) { baseURL := req.URL - // Flow should be blocked when Host is overwritten - baseURL.Host = "something.else.com" + baseURL.Host = "something.else.com" // barrier edge (Host field overwritten) blocks flow here http.Get(baseURL.String()) } func testFieldAccess(w http.ResponseWriter, req *http.Request) { baseURL := req.URL // $ Source - // Safe field accesses that should preserve flow + // These field accesses should preserve flow host := baseURL.Host path := baseURL.Path scheme := baseURL.Scheme @@ -119,20 +114,18 @@ func testFieldAccess(w http.ResponseWriter, req *http.Request) { http.Redirect(w, req, "https://"+host+"/path", http.StatusFound) // $ Alert w.Header().Set("Location", "https://example.com"+path) // $ Alert http.Post(scheme+"://example.com/api", "application/json", nil) // $ Alert - use(opaquePart) // avoid unused variable warning + http.Post(opaquePart, "application/json", nil) // $ Alert - // Unsafe field accesses that should be sanitized by UnsafeFieldReadSanitizer - // These read unsafe URL fields and should NOT have flow - unsafeUser := baseURL.User // sanitizer edge (User field) - unsafeQuery := baseURL.RawQuery // sanitizer edge (RawQuery field) - unsafeFragment := baseURL.Fragment // sanitizer edge (Fragment field) + // These field accesses should block flow + user := baseURL.User // barrier edge (User field) + query := baseURL.RawQuery // barrier edge (RawQuery field) + fragment := baseURL.Fragment // barrier edge (Fragment field) - // These should NOT have flow due to sanitizer edges - if unsafeUser != nil { - http.Redirect(w, req, unsafeUser.String(), http.StatusFound) // no flow expected + if user != nil { + http.Redirect(w, req, user.String(), http.StatusFound) // no flow expected } - w.Header().Set("Location", "https://example.com/?"+unsafeQuery) // no flow expected - http.Get("https://example.com/#" + unsafeFragment) // no flow expected + w.Header().Set("Location", "https://example.com/?"+query) // no flow expected + http.Get("https://example.com/#" + fragment) // no flow expected } // Helper function to avoid unused variable warnings From 8983ac92128a69bef80f59ca8376fead1de7db42 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Wed, 1 Oct 2025 14:13:15 +0100 Subject: [PATCH 6/6] Phrase test in terms of safe URLs --- .../security/SafeUrlFlow/SafeUrlFlow.expected | 200 +++++++++--------- .../go/security/SafeUrlFlow/SafeUrlFlow.go | 126 ++++++----- 2 files changed, 160 insertions(+), 166 deletions(-) diff --git a/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.expected b/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.expected index 4cbe81bf2be0..79e0c9145fe9 100644 --- a/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.expected +++ b/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.expected @@ -1,115 +1,115 @@ #select -| SafeUrlFlow.go:11:24:11:46 | ...+... | SafeUrlFlow.go:10:10:10:17 | selection of Host | SafeUrlFlow.go:11:24:11:46 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:10:10:10:17 | selection of Host | here | +| SafeUrlFlow.go:11:24:11:50 | ...+... | SafeUrlFlow.go:10:14:10:21 | selection of Host | SafeUrlFlow.go:11:24:11:50 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:10:14:10:21 | selection of Host | here | | SafeUrlFlow.go:14:29:14:44 | call to String | SafeUrlFlow.go:13:13:13:19 | selection of URL | SafeUrlFlow.go:14:29:14:44 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:13:13:13:19 | selection of URL | here | -| SafeUrlFlow.go:18:11:18:28 | call to String | SafeUrlFlow.go:10:10:10:17 | selection of Host | SafeUrlFlow.go:18:11:18:28 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:10:10:10:17 | selection of Host | here | -| SafeUrlFlow.go:47:24:47:57 | ...+... | SafeUrlFlow.go:37:13:37:19 | selection of URL | SafeUrlFlow.go:47:24:47:57 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:37:13:37:19 | selection of URL | here | -| SafeUrlFlow.go:48:29:48:51 | ...+... | SafeUrlFlow.go:37:13:37:19 | selection of URL | SafeUrlFlow.go:48:29:48:51 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:37:13:37:19 | selection of URL | here | -| SafeUrlFlow.go:49:11:49:38 | ...+... | SafeUrlFlow.go:37:13:37:19 | selection of URL | SafeUrlFlow.go:49:11:49:38 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:37:13:37:19 | selection of URL | here | -| SafeUrlFlow.go:58:11:58:26 | call to String | SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:58:11:58:26 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:55:13:55:19 | selection of URL | here | -| SafeUrlFlow.go:59:12:59:27 | call to String | SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:59:12:59:27 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:55:13:55:19 | selection of URL | here | -| SafeUrlFlow.go:60:16:60:31 | call to String | SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:60:16:60:31 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:55:13:55:19 | selection of URL | here | -| SafeUrlFlow.go:61:12:61:27 | call to String | SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:61:12:61:27 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:55:13:55:19 | selection of URL | here | -| SafeUrlFlow.go:65:13:65:28 | call to String | SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:65:13:65:28 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:55:13:55:19 | selection of URL | here | -| SafeUrlFlow.go:66:14:66:29 | call to String | SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:66:14:66:29 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:55:13:55:19 | selection of URL | here | -| SafeUrlFlow.go:67:18:67:33 | call to String | SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:67:18:67:33 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:55:13:55:19 | selection of URL | here | -| SafeUrlFlow.go:68:14:68:29 | call to String | SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:68:14:68:29 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:55:13:55:19 | selection of URL | here | -| SafeUrlFlow.go:71:39:71:54 | call to String | SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:71:39:71:54 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:55:13:55:19 | selection of URL | here | -| SafeUrlFlow.go:75:70:75:85 | call to String | SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:75:70:75:85 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:55:13:55:19 | selection of URL | here | -| SafeUrlFlow.go:79:40:79:55 | call to String | SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:79:40:79:55 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:55:13:55:19 | selection of URL | here | -| SafeUrlFlow.go:90:24:90:41 | call to String | SafeUrlFlow.go:85:10:85:17 | selection of Host | SafeUrlFlow.go:90:24:90:41 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:85:10:85:17 | selection of Host | here | -| SafeUrlFlow.go:111:11:111:23 | reconstructed | SafeUrlFlow.go:101:13:101:19 | selection of URL | SafeUrlFlow.go:111:11:111:23 | reconstructed | A safe URL flows here from $@. | SafeUrlFlow.go:101:13:101:19 | selection of URL | here | -| SafeUrlFlow.go:114:24:114:46 | ...+... | SafeUrlFlow.go:101:13:101:19 | selection of URL | SafeUrlFlow.go:114:24:114:46 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:101:13:101:19 | selection of URL | here | -| SafeUrlFlow.go:115:29:115:54 | ...+... | SafeUrlFlow.go:101:13:101:19 | selection of URL | SafeUrlFlow.go:115:29:115:54 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:101:13:101:19 | selection of URL | here | -| SafeUrlFlow.go:116:12:116:38 | ...+... | SafeUrlFlow.go:101:13:101:19 | selection of URL | SafeUrlFlow.go:116:12:116:38 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:101:13:101:19 | selection of URL | here | -| SafeUrlFlow.go:117:12:117:21 | opaquePart | SafeUrlFlow.go:101:13:101:19 | selection of URL | SafeUrlFlow.go:117:12:117:21 | opaquePart | A safe URL flows here from $@. | SafeUrlFlow.go:101:13:101:19 | selection of URL | here | +| SafeUrlFlow.go:18:11:18:28 | call to String | SafeUrlFlow.go:10:14:10:21 | selection of Host | SafeUrlFlow.go:18:11:18:28 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:10:14:10:21 | selection of Host | here | +| SafeUrlFlow.go:45:24:45:61 | ...+... | SafeUrlFlow.go:37:13:37:19 | selection of URL | SafeUrlFlow.go:45:24:45:61 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:37:13:37:19 | selection of URL | here | +| SafeUrlFlow.go:46:29:46:55 | ...+... | SafeUrlFlow.go:37:13:37:19 | selection of URL | SafeUrlFlow.go:46:29:46:55 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:37:13:37:19 | selection of URL | here | +| SafeUrlFlow.go:47:11:47:42 | ...+... | SafeUrlFlow.go:37:13:37:19 | selection of URL | SafeUrlFlow.go:47:11:47:42 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:37:13:37:19 | selection of URL | here | +| SafeUrlFlow.go:57:11:57:26 | call to String | SafeUrlFlow.go:54:13:54:19 | selection of URL | SafeUrlFlow.go:57:11:57:26 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:54:13:54:19 | selection of URL | here | +| SafeUrlFlow.go:58:12:58:27 | call to String | SafeUrlFlow.go:54:13:54:19 | selection of URL | SafeUrlFlow.go:58:12:58:27 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:54:13:54:19 | selection of URL | here | +| SafeUrlFlow.go:59:16:59:31 | call to String | SafeUrlFlow.go:54:13:54:19 | selection of URL | SafeUrlFlow.go:59:16:59:31 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:54:13:54:19 | selection of URL | here | +| SafeUrlFlow.go:60:12:60:27 | call to String | SafeUrlFlow.go:54:13:54:19 | selection of URL | SafeUrlFlow.go:60:12:60:27 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:54:13:54:19 | selection of URL | here | +| SafeUrlFlow.go:64:13:64:28 | call to String | SafeUrlFlow.go:54:13:54:19 | selection of URL | SafeUrlFlow.go:64:13:64:28 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:54:13:54:19 | selection of URL | here | +| SafeUrlFlow.go:65:14:65:29 | call to String | SafeUrlFlow.go:54:13:54:19 | selection of URL | SafeUrlFlow.go:65:14:65:29 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:54:13:54:19 | selection of URL | here | +| SafeUrlFlow.go:66:18:66:33 | call to String | SafeUrlFlow.go:54:13:54:19 | selection of URL | SafeUrlFlow.go:66:18:66:33 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:54:13:54:19 | selection of URL | here | +| SafeUrlFlow.go:67:14:67:29 | call to String | SafeUrlFlow.go:54:13:54:19 | selection of URL | SafeUrlFlow.go:67:14:67:29 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:54:13:54:19 | selection of URL | here | +| SafeUrlFlow.go:70:39:70:54 | call to String | SafeUrlFlow.go:54:13:54:19 | selection of URL | SafeUrlFlow.go:70:39:70:54 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:54:13:54:19 | selection of URL | here | +| SafeUrlFlow.go:74:70:74:85 | call to String | SafeUrlFlow.go:54:13:54:19 | selection of URL | SafeUrlFlow.go:74:70:74:85 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:54:13:54:19 | selection of URL | here | +| SafeUrlFlow.go:78:40:78:55 | call to String | SafeUrlFlow.go:54:13:54:19 | selection of URL | SafeUrlFlow.go:78:40:78:55 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:54:13:54:19 | selection of URL | here | +| SafeUrlFlow.go:89:24:89:41 | call to String | SafeUrlFlow.go:84:14:84:21 | selection of Host | SafeUrlFlow.go:89:24:89:41 | call to String | A safe URL flows here from $@. | SafeUrlFlow.go:84:14:84:21 | selection of Host | here | +| SafeUrlFlow.go:109:11:109:23 | reconstructed | SafeUrlFlow.go:100:13:100:19 | selection of URL | SafeUrlFlow.go:109:11:109:23 | reconstructed | A safe URL flows here from $@. | SafeUrlFlow.go:100:13:100:19 | selection of URL | here | +| SafeUrlFlow.go:112:24:112:50 | ...+... | SafeUrlFlow.go:100:13:100:19 | selection of URL | SafeUrlFlow.go:112:24:112:50 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:100:13:100:19 | selection of URL | here | +| SafeUrlFlow.go:113:29:113:58 | ...+... | SafeUrlFlow.go:100:13:100:19 | selection of URL | SafeUrlFlow.go:113:29:113:58 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:100:13:100:19 | selection of URL | here | +| SafeUrlFlow.go:114:12:114:42 | ...+... | SafeUrlFlow.go:100:13:100:19 | selection of URL | SafeUrlFlow.go:114:12:114:42 | ...+... | A safe URL flows here from $@. | SafeUrlFlow.go:100:13:100:19 | selection of URL | here | +| SafeUrlFlow.go:115:12:115:25 | safeOpaquePart | SafeUrlFlow.go:100:13:100:19 | selection of URL | SafeUrlFlow.go:115:12:115:25 | safeOpaquePart | A safe URL flows here from $@. | SafeUrlFlow.go:100:13:100:19 | selection of URL | here | edges -| SafeUrlFlow.go:10:10:10:17 | selection of Host | SafeUrlFlow.go:11:24:11:46 | ...+... | provenance | Sink:MaD:1 | -| SafeUrlFlow.go:10:10:10:17 | selection of Host | SafeUrlFlow.go:17:19:17:22 | host | provenance | | -| SafeUrlFlow.go:13:13:13:19 | selection of URL | SafeUrlFlow.go:14:29:14:35 | baseURL | provenance | Src:MaD:2 | -| SafeUrlFlow.go:14:29:14:35 | baseURL | SafeUrlFlow.go:14:29:14:44 | call to String | provenance | MaD:3 | -| SafeUrlFlow.go:17:19:17:22 | host | SafeUrlFlow.go:18:11:18:19 | targetURL | provenance | Config | +| SafeUrlFlow.go:10:14:10:21 | selection of Host | SafeUrlFlow.go:11:24:11:50 | ...+... | provenance | Sink:MaD:1 | +| SafeUrlFlow.go:10:14:10:21 | selection of Host | SafeUrlFlow.go:17:19:17:26 | safeHost | provenance | | +| SafeUrlFlow.go:13:13:13:19 | selection of URL | SafeUrlFlow.go:14:29:14:35 | safeURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:14:29:14:35 | safeURL | SafeUrlFlow.go:14:29:14:44 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:17:19:17:26 | safeHost | SafeUrlFlow.go:18:11:18:19 | targetURL | provenance | Config | | SafeUrlFlow.go:18:11:18:19 | targetURL | SafeUrlFlow.go:18:11:18:28 | call to String | provenance | MaD:3 | -| SafeUrlFlow.go:37:13:37:19 | selection of URL | SafeUrlFlow.go:47:24:47:57 | ...+... | provenance | Src:MaD:2 Sink:MaD:1 | -| SafeUrlFlow.go:37:13:37:19 | selection of URL | SafeUrlFlow.go:48:29:48:51 | ...+... | provenance | Src:MaD:2 | -| SafeUrlFlow.go:37:13:37:19 | selection of URL | SafeUrlFlow.go:49:11:49:38 | ...+... | provenance | Src:MaD:2 | -| SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:58:11:58:17 | baseURL | provenance | Src:MaD:2 | -| SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:59:12:59:18 | baseURL | provenance | Src:MaD:2 | -| SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:60:16:60:22 | baseURL | provenance | Src:MaD:2 | -| SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:61:12:61:18 | baseURL | provenance | Src:MaD:2 | -| SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:65:13:65:19 | baseURL | provenance | Src:MaD:2 | -| SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:66:14:66:20 | baseURL | provenance | Src:MaD:2 | -| SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:67:18:67:24 | baseURL | provenance | Src:MaD:2 | -| SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:68:14:68:20 | baseURL | provenance | Src:MaD:2 | -| SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:71:39:71:45 | baseURL | provenance | Src:MaD:2 | -| SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:75:70:75:76 | baseURL | provenance | Src:MaD:2 | -| SafeUrlFlow.go:55:13:55:19 | selection of URL | SafeUrlFlow.go:79:40:79:46 | baseURL | provenance | Src:MaD:2 | -| SafeUrlFlow.go:58:11:58:17 | baseURL | SafeUrlFlow.go:58:11:58:26 | call to String | provenance | MaD:3 | -| SafeUrlFlow.go:59:12:59:18 | baseURL | SafeUrlFlow.go:59:12:59:27 | call to String | provenance | MaD:3 | -| SafeUrlFlow.go:60:16:60:22 | baseURL | SafeUrlFlow.go:60:16:60:31 | call to String | provenance | MaD:3 | -| SafeUrlFlow.go:61:12:61:18 | baseURL | SafeUrlFlow.go:61:12:61:27 | call to String | provenance | MaD:3 | -| SafeUrlFlow.go:65:13:65:19 | baseURL | SafeUrlFlow.go:65:13:65:28 | call to String | provenance | MaD:3 | -| SafeUrlFlow.go:66:14:66:20 | baseURL | SafeUrlFlow.go:66:14:66:29 | call to String | provenance | MaD:3 | -| SafeUrlFlow.go:67:18:67:24 | baseURL | SafeUrlFlow.go:67:18:67:33 | call to String | provenance | MaD:3 | -| SafeUrlFlow.go:68:14:68:20 | baseURL | SafeUrlFlow.go:68:14:68:29 | call to String | provenance | MaD:3 | -| SafeUrlFlow.go:71:39:71:45 | baseURL | SafeUrlFlow.go:71:39:71:54 | call to String | provenance | MaD:3 | -| SafeUrlFlow.go:75:70:75:76 | baseURL | SafeUrlFlow.go:75:70:75:85 | call to String | provenance | MaD:3 | -| SafeUrlFlow.go:79:40:79:46 | baseURL | SafeUrlFlow.go:79:40:79:55 | call to String | provenance | MaD:3 | -| SafeUrlFlow.go:85:10:85:17 | selection of Host | SafeUrlFlow.go:88:19:88:22 | host | provenance | | -| SafeUrlFlow.go:88:19:88:22 | host | SafeUrlFlow.go:90:24:90:32 | targetURL | provenance | Config | -| SafeUrlFlow.go:90:24:90:32 | targetURL | SafeUrlFlow.go:90:24:90:41 | call to String | provenance | MaD:3 Sink:MaD:1 | -| SafeUrlFlow.go:101:13:101:19 | selection of URL | SafeUrlFlow.go:111:11:111:23 | reconstructed | provenance | Src:MaD:2 | -| SafeUrlFlow.go:101:13:101:19 | selection of URL | SafeUrlFlow.go:114:24:114:46 | ...+... | provenance | Src:MaD:2 Sink:MaD:1 | -| SafeUrlFlow.go:101:13:101:19 | selection of URL | SafeUrlFlow.go:115:29:115:54 | ...+... | provenance | Src:MaD:2 | -| SafeUrlFlow.go:101:13:101:19 | selection of URL | SafeUrlFlow.go:116:12:116:38 | ...+... | provenance | Src:MaD:2 | -| SafeUrlFlow.go:101:13:101:19 | selection of URL | SafeUrlFlow.go:117:12:117:21 | opaquePart | provenance | Src:MaD:2 | +| SafeUrlFlow.go:37:13:37:19 | selection of URL | SafeUrlFlow.go:45:24:45:61 | ...+... | provenance | Src:MaD:2 Sink:MaD:1 | +| SafeUrlFlow.go:37:13:37:19 | selection of URL | SafeUrlFlow.go:46:29:46:55 | ...+... | provenance | Src:MaD:2 | +| SafeUrlFlow.go:37:13:37:19 | selection of URL | SafeUrlFlow.go:47:11:47:42 | ...+... | provenance | Src:MaD:2 | +| SafeUrlFlow.go:54:13:54:19 | selection of URL | SafeUrlFlow.go:57:11:57:17 | safeURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:54:13:54:19 | selection of URL | SafeUrlFlow.go:58:12:58:18 | safeURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:54:13:54:19 | selection of URL | SafeUrlFlow.go:59:16:59:22 | safeURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:54:13:54:19 | selection of URL | SafeUrlFlow.go:60:12:60:18 | safeURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:54:13:54:19 | selection of URL | SafeUrlFlow.go:64:13:64:19 | safeURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:54:13:54:19 | selection of URL | SafeUrlFlow.go:65:14:65:20 | safeURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:54:13:54:19 | selection of URL | SafeUrlFlow.go:66:18:66:24 | safeURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:54:13:54:19 | selection of URL | SafeUrlFlow.go:67:14:67:20 | safeURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:54:13:54:19 | selection of URL | SafeUrlFlow.go:70:39:70:45 | safeURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:54:13:54:19 | selection of URL | SafeUrlFlow.go:74:70:74:76 | safeURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:54:13:54:19 | selection of URL | SafeUrlFlow.go:78:40:78:46 | safeURL | provenance | Src:MaD:2 | +| SafeUrlFlow.go:57:11:57:17 | safeURL | SafeUrlFlow.go:57:11:57:26 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:58:12:58:18 | safeURL | SafeUrlFlow.go:58:12:58:27 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:59:16:59:22 | safeURL | SafeUrlFlow.go:59:16:59:31 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:60:12:60:18 | safeURL | SafeUrlFlow.go:60:12:60:27 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:64:13:64:19 | safeURL | SafeUrlFlow.go:64:13:64:28 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:65:14:65:20 | safeURL | SafeUrlFlow.go:65:14:65:29 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:66:18:66:24 | safeURL | SafeUrlFlow.go:66:18:66:33 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:67:14:67:20 | safeURL | SafeUrlFlow.go:67:14:67:29 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:70:39:70:45 | safeURL | SafeUrlFlow.go:70:39:70:54 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:74:70:74:76 | safeURL | SafeUrlFlow.go:74:70:74:85 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:78:40:78:46 | safeURL | SafeUrlFlow.go:78:40:78:55 | call to String | provenance | MaD:3 | +| SafeUrlFlow.go:84:14:84:21 | selection of Host | SafeUrlFlow.go:87:19:87:26 | safeHost | provenance | | +| SafeUrlFlow.go:87:19:87:26 | safeHost | SafeUrlFlow.go:89:24:89:32 | targetURL | provenance | Config | +| SafeUrlFlow.go:89:24:89:32 | targetURL | SafeUrlFlow.go:89:24:89:41 | call to String | provenance | MaD:3 Sink:MaD:1 | +| SafeUrlFlow.go:100:13:100:19 | selection of URL | SafeUrlFlow.go:109:11:109:23 | reconstructed | provenance | Src:MaD:2 | +| SafeUrlFlow.go:100:13:100:19 | selection of URL | SafeUrlFlow.go:112:24:112:50 | ...+... | provenance | Src:MaD:2 Sink:MaD:1 | +| SafeUrlFlow.go:100:13:100:19 | selection of URL | SafeUrlFlow.go:113:29:113:58 | ...+... | provenance | Src:MaD:2 | +| SafeUrlFlow.go:100:13:100:19 | selection of URL | SafeUrlFlow.go:114:12:114:42 | ...+... | provenance | Src:MaD:2 | +| SafeUrlFlow.go:100:13:100:19 | selection of URL | SafeUrlFlow.go:115:12:115:25 | safeOpaquePart | provenance | Src:MaD:2 | models | 1 | Sink: net/http; ; false; Redirect; ; ; Argument[2]; url-redirection[0]; manual | | 2 | Source: net/http; Request; true; URL; ; ; ; remote; manual | | 3 | Summary: fmt; Stringer; true; String; ; ; Argument[receiver]; ReturnValue; taint; manual | nodes -| SafeUrlFlow.go:10:10:10:17 | selection of Host | semmle.label | selection of Host | -| SafeUrlFlow.go:11:24:11:46 | ...+... | semmle.label | ...+... | +| SafeUrlFlow.go:10:14:10:21 | selection of Host | semmle.label | selection of Host | +| SafeUrlFlow.go:11:24:11:50 | ...+... | semmle.label | ...+... | | SafeUrlFlow.go:13:13:13:19 | selection of URL | semmle.label | selection of URL | -| SafeUrlFlow.go:14:29:14:35 | baseURL | semmle.label | baseURL | +| SafeUrlFlow.go:14:29:14:35 | safeURL | semmle.label | safeURL | | SafeUrlFlow.go:14:29:14:44 | call to String | semmle.label | call to String | -| SafeUrlFlow.go:17:19:17:22 | host | semmle.label | host | +| SafeUrlFlow.go:17:19:17:26 | safeHost | semmle.label | safeHost | | SafeUrlFlow.go:18:11:18:19 | targetURL | semmle.label | targetURL | | SafeUrlFlow.go:18:11:18:28 | call to String | semmle.label | call to String | | SafeUrlFlow.go:37:13:37:19 | selection of URL | semmle.label | selection of URL | -| SafeUrlFlow.go:47:24:47:57 | ...+... | semmle.label | ...+... | -| SafeUrlFlow.go:48:29:48:51 | ...+... | semmle.label | ...+... | -| SafeUrlFlow.go:49:11:49:38 | ...+... | semmle.label | ...+... | -| SafeUrlFlow.go:55:13:55:19 | selection of URL | semmle.label | selection of URL | -| SafeUrlFlow.go:58:11:58:17 | baseURL | semmle.label | baseURL | -| SafeUrlFlow.go:58:11:58:26 | call to String | semmle.label | call to String | -| SafeUrlFlow.go:59:12:59:18 | baseURL | semmle.label | baseURL | -| SafeUrlFlow.go:59:12:59:27 | call to String | semmle.label | call to String | -| SafeUrlFlow.go:60:16:60:22 | baseURL | semmle.label | baseURL | -| SafeUrlFlow.go:60:16:60:31 | call to String | semmle.label | call to String | -| SafeUrlFlow.go:61:12:61:18 | baseURL | semmle.label | baseURL | -| SafeUrlFlow.go:61:12:61:27 | call to String | semmle.label | call to String | -| SafeUrlFlow.go:65:13:65:19 | baseURL | semmle.label | baseURL | -| SafeUrlFlow.go:65:13:65:28 | call to String | semmle.label | call to String | -| SafeUrlFlow.go:66:14:66:20 | baseURL | semmle.label | baseURL | -| SafeUrlFlow.go:66:14:66:29 | call to String | semmle.label | call to String | -| SafeUrlFlow.go:67:18:67:24 | baseURL | semmle.label | baseURL | -| SafeUrlFlow.go:67:18:67:33 | call to String | semmle.label | call to String | -| SafeUrlFlow.go:68:14:68:20 | baseURL | semmle.label | baseURL | -| SafeUrlFlow.go:68:14:68:29 | call to String | semmle.label | call to String | -| SafeUrlFlow.go:71:39:71:45 | baseURL | semmle.label | baseURL | -| SafeUrlFlow.go:71:39:71:54 | call to String | semmle.label | call to String | -| SafeUrlFlow.go:75:70:75:76 | baseURL | semmle.label | baseURL | -| SafeUrlFlow.go:75:70:75:85 | call to String | semmle.label | call to String | -| SafeUrlFlow.go:79:40:79:46 | baseURL | semmle.label | baseURL | -| SafeUrlFlow.go:79:40:79:55 | call to String | semmle.label | call to String | -| SafeUrlFlow.go:85:10:85:17 | selection of Host | semmle.label | selection of Host | -| SafeUrlFlow.go:88:19:88:22 | host | semmle.label | host | -| SafeUrlFlow.go:90:24:90:32 | targetURL | semmle.label | targetURL | -| SafeUrlFlow.go:90:24:90:41 | call to String | semmle.label | call to String | -| SafeUrlFlow.go:101:13:101:19 | selection of URL | semmle.label | selection of URL | -| SafeUrlFlow.go:111:11:111:23 | reconstructed | semmle.label | reconstructed | -| SafeUrlFlow.go:114:24:114:46 | ...+... | semmle.label | ...+... | -| SafeUrlFlow.go:115:29:115:54 | ...+... | semmle.label | ...+... | -| SafeUrlFlow.go:116:12:116:38 | ...+... | semmle.label | ...+... | -| SafeUrlFlow.go:117:12:117:21 | opaquePart | semmle.label | opaquePart | +| SafeUrlFlow.go:45:24:45:61 | ...+... | semmle.label | ...+... | +| SafeUrlFlow.go:46:29:46:55 | ...+... | semmle.label | ...+... | +| SafeUrlFlow.go:47:11:47:42 | ...+... | semmle.label | ...+... | +| SafeUrlFlow.go:54:13:54:19 | selection of URL | semmle.label | selection of URL | +| SafeUrlFlow.go:57:11:57:17 | safeURL | semmle.label | safeURL | +| SafeUrlFlow.go:57:11:57:26 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:58:12:58:18 | safeURL | semmle.label | safeURL | +| SafeUrlFlow.go:58:12:58:27 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:59:16:59:22 | safeURL | semmle.label | safeURL | +| SafeUrlFlow.go:59:16:59:31 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:60:12:60:18 | safeURL | semmle.label | safeURL | +| SafeUrlFlow.go:60:12:60:27 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:64:13:64:19 | safeURL | semmle.label | safeURL | +| SafeUrlFlow.go:64:13:64:28 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:65:14:65:20 | safeURL | semmle.label | safeURL | +| SafeUrlFlow.go:65:14:65:29 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:66:18:66:24 | safeURL | semmle.label | safeURL | +| SafeUrlFlow.go:66:18:66:33 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:67:14:67:20 | safeURL | semmle.label | safeURL | +| SafeUrlFlow.go:67:14:67:29 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:70:39:70:45 | safeURL | semmle.label | safeURL | +| SafeUrlFlow.go:70:39:70:54 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:74:70:74:76 | safeURL | semmle.label | safeURL | +| SafeUrlFlow.go:74:70:74:85 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:78:40:78:46 | safeURL | semmle.label | safeURL | +| SafeUrlFlow.go:78:40:78:55 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:84:14:84:21 | selection of Host | semmle.label | selection of Host | +| SafeUrlFlow.go:87:19:87:26 | safeHost | semmle.label | safeHost | +| SafeUrlFlow.go:89:24:89:32 | targetURL | semmle.label | targetURL | +| SafeUrlFlow.go:89:24:89:41 | call to String | semmle.label | call to String | +| SafeUrlFlow.go:100:13:100:19 | selection of URL | semmle.label | selection of URL | +| SafeUrlFlow.go:109:11:109:23 | reconstructed | semmle.label | reconstructed | +| SafeUrlFlow.go:112:24:112:50 | ...+... | semmle.label | ...+... | +| SafeUrlFlow.go:113:29:113:58 | ...+... | semmle.label | ...+... | +| SafeUrlFlow.go:114:12:114:42 | ...+... | semmle.label | ...+... | +| SafeUrlFlow.go:115:12:115:25 | safeOpaquePart | semmle.label | safeOpaquePart | subpaths diff --git a/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.go b/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.go index 45a2b20b8d4d..9a1b2e5677ef 100644 --- a/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.go +++ b/go/ql/test/library-tests/semmle/go/security/SafeUrlFlow/SafeUrlFlow.go @@ -7,126 +7,120 @@ import ( ) func testStdlibSources(w http.ResponseWriter, req *http.Request) { - host := req.Host // $ Source - http.Redirect(w, req, "https://"+host+"/path", http.StatusFound) // $ Alert + safeHost := req.Host // $ Source + http.Redirect(w, req, "https://"+safeHost+"/path", http.StatusFound) // $ Alert - baseURL := req.URL // $ Source - w.Header().Set("Location", baseURL.String()) // $ Alert + safeURL := req.URL // $ Source + w.Header().Set("Location", safeURL.String()) // $ Alert targetURL := url.URL{} - targetURL.Host = host // additional flow step from Host field to URL struct + targetURL.Host = safeHost // URL is safe if Host is safe http.Get(targetURL.String()) // $ Alert } func testBarrierEdge1(w http.ResponseWriter, req *http.Request) { - baseURL := req.URL + safeURL := req.URL - query := baseURL.Query() // barrier edge blocks flow here - http.Redirect(w, req, query.Get("redirect"), http.StatusFound) // no flow expected + query := safeURL.Query() // query is not guaranteed to be safe + http.Redirect(w, req, query.Get("redirect"), http.StatusFound) // not guaranteed to be safe } func testBarrierEdge2(w http.ResponseWriter, req *http.Request) { - baseURL := req.URL + safeURL := req.URL - urlString := baseURL.String() - sliced := urlString[0:10] // barrier edge (string slicing) blocks flow here - w.Header().Set("Location", sliced) // no flow expected + urlString := safeURL.String() + sliced := urlString[0:10] // a substring of a safe URL is not guaranteed to be safe + w.Header().Set("Location", sliced) // not guaranteed to be safe } func testFieldReads(w http.ResponseWriter, req *http.Request) { - baseURL := req.URL // $ Source - - // Test that other URL methods preserve flow - scheme := baseURL.Scheme // should preserve flow - host := baseURL.Host // should preserve flow - path := baseURL.Path // should preserve flow - fragment := baseURL.Fragment // should not preserve flow - user := baseURL.User // should not preserve flow - - // These should still have flow (not sanitized) - http.Redirect(w, req, "https://"+scheme+"://example.com", http.StatusFound) // $ Alert - w.Header().Set("Location", "https://"+host+"/path") // $ Alert - http.Get("https://example.com" + path) // $ Alert - http.Get(fragment) - http.Get(user.String()) + safeURL := req.URL // $ Source + + safeScheme := safeURL.Scheme // the scheme of a safe URL is safe + safeHost := safeURL.Host // the host of a safe URL is safe + safePath := safeURL.Path // the path of a safe URL is safe + fragment := safeURL.Fragment // the fragment of a safe URL is not guaranteed to be safe + user := safeURL.User // the user of a safe URL is not guaranteed to be safe + + http.Redirect(w, req, "https://"+safeScheme+"://example.com", http.StatusFound) // $ Alert + w.Header().Set("Location", "https://"+safeHost+"/path") // $ Alert + http.Get("https://example.com" + safePath) // $ Alert + + http.Get(fragment) // not guaranteed to be safe + http.Get(user.String()) // not guaranteed to be safe } func testRequestForgerySinks(req *http.Request) { - baseURL := req.URL // $ Source + safeURL := req.URL // $ Source // Standard library HTTP functions (request-forgery sinks) - http.Get(baseURL.String()) // $ Alert - http.Post(baseURL.String(), "application/json", nil) // $ Alert - http.PostForm(baseURL.String(), nil) // $ Alert - http.Head(baseURL.String()) // $ Alert + http.Get(safeURL.String()) // $ Alert + http.Post(safeURL.String(), "application/json", nil) // $ Alert + http.PostForm(safeURL.String(), nil) // $ Alert + http.Head(safeURL.String()) // $ Alert // HTTP Client methods (request-forgery sinks) client := &http.Client{} - client.Get(baseURL.String()) // $ Alert - client.Post(baseURL.String(), "application/json", nil) // $ Alert - client.PostForm(baseURL.String(), nil) // $ Alert - client.Head(baseURL.String()) // $ Alert + client.Get(safeURL.String()) // $ Alert + client.Post(safeURL.String(), "application/json", nil) // $ Alert + client.PostForm(safeURL.String(), nil) // $ Alert + client.Head(safeURL.String()) // $ Alert // NewRequest + Client.Do (request-forgery sinks) - request, _ := http.NewRequest("GET", baseURL.String(), nil) // $ Alert + request, _ := http.NewRequest("GET", safeURL.String(), nil) // $ Alert client.Do(request) // NewRequestWithContext + Client.Do (request-forgery sinks) - reqWithCtx, _ := http.NewRequestWithContext(context.TODO(), "POST", baseURL.String(), nil) // $ Alert + reqWithCtx, _ := http.NewRequestWithContext(context.TODO(), "POST", safeURL.String(), nil) // $ Alert client.Do(reqWithCtx) // RoundTrip method (request-forgery sink) - request2, _ := http.NewRequest("GET", baseURL.String(), nil) // $ Alert + request2, _ := http.NewRequest("GET", safeURL.String(), nil) // $ Alert transport := &http.Transport{} transport.RoundTrip(request2) } func testHostFieldAssignmentFlow(w http.ResponseWriter, req *http.Request) { - host := req.Host // $ Source + safeHost := req.Host // $ Source targetURL, _ := url.Parse("http://example.com/data") - targetURL.Host = host // additional flow step from Host field to URL struct + targetURL.Host = safeHost // URL is safe if Host is safe http.Redirect(w, req, targetURL.String(), http.StatusFound) // $ Alert } func testHostFieldOverwritten(w http.ResponseWriter, req *http.Request) { - baseURL := req.URL + safeURL := req.URL - baseURL.Host = "something.else.com" // barrier edge (Host field overwritten) blocks flow here - http.Get(baseURL.String()) + safeURL.Host = "something.else.com" // safeURL is not guaranteed to be safe now that Host is overwritten + http.Get(safeURL.String()) // not guaranteed to be safe } func testFieldAccess(w http.ResponseWriter, req *http.Request) { - baseURL := req.URL // $ Source + safeURL := req.URL // $ Source - // These field accesses should preserve flow - host := baseURL.Host - path := baseURL.Path - scheme := baseURL.Scheme - opaquePart := baseURL.Opaque + safeHost := safeURL.Host // the host of a safe URL is safe + safePath := safeURL.Path // the path of a safe URL is safe + safeScheme := safeURL.Scheme // the scheme of a safe URL is safe + safeOpaquePart := safeURL.Opaque // the opaque part of a safe URL is safe - // Reconstruct URL - flow should be preserved through field access - reconstructed := scheme + "://" + host + path + // Reconstruct URL - still guaranteed to be safe + reconstructed := safeScheme + "://" + safeHost + safePath http.Get(reconstructed) // $ Alert // Test individual fields - http.Redirect(w, req, "https://"+host+"/path", http.StatusFound) // $ Alert - w.Header().Set("Location", "https://example.com"+path) // $ Alert - http.Post(scheme+"://example.com/api", "application/json", nil) // $ Alert - http.Post(opaquePart, "application/json", nil) // $ Alert + http.Redirect(w, req, "https://"+safeHost+"/path", http.StatusFound) // $ Alert + w.Header().Set("Location", "https://example.com"+safePath) // $ Alert + http.Post(safeScheme+"://example.com/api", "application/json", nil) // $ Alert + http.Post(safeOpaquePart, "application/json", nil) // $ Alert - // These field accesses should block flow - user := baseURL.User // barrier edge (User field) - query := baseURL.RawQuery // barrier edge (RawQuery field) - fragment := baseURL.Fragment // barrier edge (Fragment field) + user := safeURL.User // the user of a safe URL is not guaranteed to be safe + query := safeURL.RawQuery // the query of a safe URL is not guaranteed to be safe + fragment := safeURL.Fragment // the fragment of a safe URL is not guaranteed to be safe if user != nil { - http.Redirect(w, req, user.String(), http.StatusFound) // no flow expected + http.Redirect(w, req, user.String(), http.StatusFound) // not guaranteed to be safe } - w.Header().Set("Location", "https://example.com/?"+query) // no flow expected - http.Get("https://example.com/#" + fragment) // no flow expected + w.Header().Set("Location", "https://example.com/?"+query) // not guaranteed to be safe + http.Get("https://example.com/#" + fragment) // not guaranteed to be safe } - -// Helper function to avoid unused variable warnings -func use(vars ...interface{}) {}