Description
Concatenating user input into JavaScript code can let an attacker run scripts in the page. template.JS marks a value as trusted JavaScript; it does not validate or escape the input.
Potential impact
- An attacker may read sensitive data accessible to the page or send requests with the user's privileges.
- They may alter the page or collect information through a fake form.
Remediation
- Do not convert user input to
template.JSor concatenate it into JavaScript code. - Pass ordinary strings to
html/templatefor context-aware escaping. - As in the example below, pass values through data attributes and read them in JavaScript after creating the DOM element.
Examples
Before
go
package main
import (
"html/template"
"net/http"
)
// This example demonstrates the vulnerable pattern.
func UnsafeJS(w http.ResponseWriter, r *http.Request) {
// User input from the id query parameter
userID := r.URL.Query().Get("id")
// Vulnerable: concatenate user input directly into HTML/JS
// Converting to template.JS does not validate or escape the input
js := "var msg = '<h1>" + userID + "</h1>'; document.write(msg);"
// ruleid: unescaped-data-in-js
safeLike := template.JS(js) // The value is still unsafe
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_, _ = w.Write([]byte("<script>" + string(safeLike) + "</script>"))
}
After
go
package main
import (
"html/template"
"net/http"
)
// The template escapes data inserted at {{.UserID}}
var pageTmpl = template.Must(template.New("page").Parse(`
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>Profile</title></head>
<body>
<h1>User: {{.UserID}}</h1>
<div id="user" data-userid="{{.UserID}}"></div>
<script>
// Pass data through data-* attributes or appropriate JSON encoding
const userId = document.querySelector('#user').dataset.userid;
console.log('User:', userId);
</script>
</body>
</html>
`))
type PageData struct {
UserID string
}
func SafeJS(w http.ResponseWriter, r *http.Request) {
userID := r.URL.Query().Get("id")
// Validate length and allowed characters on the server as needed
// if !validUserID(userID) { ... }
data := PageData{UserID: userID}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
// html/template escapes values for their output context
_ = pageTmpl.Execute(w, data)
}
Explanation: Before, the response directly contains a script built from user input. After, UserID is rendered as template text and an attribute value, and JavaScript reads it from an existing element. Do not then interpret the value as HTML or code.