// CWE-116: String::replace() used for HTML/security escaping
// MoonBit's replace() only replaces the FIRST occurrence.
// Must use replace_all() for security escaping.
///|
priv struct Cwe116Visitor {
findings : Array[Finding]
file : String
source : String
severity : Severity
}
///|
pub fn cwe116_info() -> RuleInfo {
{
id: { cwe: 116, variant: "replace-escaping" },
name: "Incomplete HTML Escaping via String::replace",
description: "String::replace() only replaces the first occurrence in MoonBit. When used with HTML special characters (<, >, &, etc.), subsequent occurrences remain unescaped, enabling XSS bypass.",
default_severity: Error,
}
}
///|
pub fn run_cwe116(
impls : @list.List[@syntax.Impl],
file : String,
source : String,
findings : Array[Finding],
severity : Severity,
) -> Unit {
let visitor : Cwe116Visitor = { findings, file, source, severity }
impls.each(fn(impl_) { @syntax.IterVisitor::visit_Impl(visitor, impl_) })
}
///|
impl @syntax.IterVisitor for Cwe116Visitor with fn visit_Expr_DotApply(
env,
self~,
method_name~,
args~,
return_self~,
loc~,
) {
if method_name.name == "replace" && is_security_relevant_replace(args) {
env.findings.push(
make_finding(
{ cwe: 116, variant: "replace-escaping" },
env.severity,
High,
"String::replace() only replaces the first occurrence. Use replace_all() for HTML escaping to prevent XSS bypass.",
env.file,
env.source,
loc,
),
)
}
@syntax.IterVisitor::base(env).visit_Expr_DotApply(
self~,
method_name~,
args~,
return_self~,
loc~,
)
}