// CWE-79: Template injection via string interpolation in HTML context.
// Detects route handlers that return HTML with string interpolation
// containing route parameters or user input without encoding.
///|
priv struct Cwe079TemplateVisitor {
findings : Array[Finding]
file : String
source : String
severity : Severity
}
///|
pub fn cwe079_template_info() -> RuleInfo {
{
id: { cwe: 79, variant: "template-injection" },
name: "XSS via String Interpolation in HTML Response",
description: "Route handler returns HTML with string interpolation containing user input. Without HTML encoding, this enables reflected XSS.",
default_severity: Error,
}
}
///|
pub fn run_cwe079_template(
impls : @list.List[@syntax.Impl],
file : String,
source : String,
findings : Array[Finding],
severity : Severity,
) -> Unit {
if !source.contains("text/html") && !source.contains("html(") {
return
}
let visitor : Cwe079TemplateVisitor = { findings, file, source, severity }
impls.each(fn(impl_) { @syntax.IterVisitor::visit_Impl(visitor, impl_) })
}
///|
fn is_html_response_func(func : @syntax.Expr) -> Bool {
match func {
Ident(id~, ..) =>
match id.name {
Ident(name="html") => true
Dot(id="html", ..) => true
_ => false
}
_ => false
}
}
///|
fn has_interpolation(expr : @syntax.Expr) -> Bool {
match expr {
Interp(..) => true
_ => false
}
}
///|
impl @syntax.IterVisitor for Cwe079TemplateVisitor with fn visit_Expr_Apply(
env,
func~,
args~,
loc~,
) {
if is_html_response_func(func) {
let has_dynamic = args.any(fn(arg) { has_interpolation(arg.value) })
if has_dynamic {
env.findings.push(
make_finding(
{ cwe: 79, variant: "template-injection" },
env.severity,
Medium,
"HTML response contains string interpolation with potentially user-controlled values. Apply HTML encoding to prevent reflected XSS.",
env.file,
env.source,
loc,
),
)
}
}
@syntax.IterVisitor::base(env).visit_Expr_Apply(func~, args~, loc~)
}
///|
impl @syntax.IterVisitor for Cwe079TemplateVisitor with fn visit_Expr_DotApply(
env,
self~,
method_name~,
args~,
return_self~,
loc~,
) {
if method_name.name == "html" {
let has_dynamic = args.any(fn(arg) { has_interpolation(arg.value) })
if has_dynamic {
env.findings.push(
make_finding(
{ cwe: 79, variant: "template-injection" },
env.severity,
Medium,
"HTML response contains string interpolation with potentially user-controlled values. Apply HTML encoding to prevent reflected XSS.",
env.file,
env.source,
loc,
),
)
}
}
@syntax.IterVisitor::base(env).visit_Expr_DotApply(
self~,
method_name~,
args~,
return_self~,
loc~,
)
}