Files
huskies/server/src/validation/sanitize.rs
T

68 lines
2.1 KiB
Rust
Raw Normal View History

2026-05-14 12:53:14 +00:00
//! HTML sanitisation for user-supplied text fields.
//!
//! Uses ammonia to strip dangerous HTML tags and attributes while preserving
//! the visible text content. Sanitisation that actually fires is logged at
//! WARN so operators can spot abuse patterns.
use sha2::Digest;
use std::collections::HashSet;
/// Sanitise `value` for the named `field`.
///
/// Strips all HTML tags (keeping their text content) and removes dangerous
/// attributes. Returns `(sanitised_value, was_modified)`. When `was_modified`
/// is `true` the caller should log at WARN.
pub(super) fn sanitize_html(field: &str, value: &str) -> (String, bool) {
// Build an ammonia cleaner that allows NO tags but keeps text content.
// clear_content_tags is also set to empty so that <script>...</script>
// content is preserved as literal text rather than silently discarded.
let clean = ammonia::Builder::new()
.tags(HashSet::new())
.clean_content_tags(HashSet::new())
.clean(value)
.to_string();
let modified = clean != value;
if modified {
crate::slog_warn!(
"[validation] HTML sanitised in field '{}': fingerprint={}",
field,
fingerprint(value)
);
}
(clean, modified)
}
/// Return an 8-hex-char SHA-256 fingerprint of the input string.
fn fingerprint(input: &str) -> String {
let hash = sha2::Sha256::digest(input.as_bytes());
hash[..4].iter().map(|b| format!("{b:02x}")).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn script_tags_stripped_content_preserved() {
let (out, modified) = sanitize_html("name", "<script>alert('xss')</script>");
assert!(modified);
assert!(!out.contains("<script>"));
assert!(out.contains("alert("));
}
#[test]
fn plain_text_unchanged() {
let (out, modified) = sanitize_html("name", "Hello World");
assert!(!modified);
assert_eq!(out, "Hello World");
}
#[test]
fn on_event_stripped() {
let (out, modified) = sanitize_html("name", r#"<img onload="evil()">"#);
assert!(modified);
assert!(!out.contains("onload"));
}
}