use crate::ProxyState;
use wafrift_strategy::HostState;
#[must_use]
pub fn render_live_findings(state: &ProxyState) -> String {
let mut out = String::new();
out.push_str("# wafrift live findings\n\n");
out.push_str(&format!(
"Total proxied: {} · Total WAF blocks observed: {} · Hosts seen: {}\n\n",
state.total_scanned(),
state.total_blocks(),
state.hosts.len(),
));
if state.total_scanned() == 0 {
out.push_str("No requests have been proxied yet. Send traffic through the proxy to begin evasion discovery.\n");
return out;
}
let mut hosts_with_winners: Vec<(&String, &HostState)> = state
.hosts
.iter()
.filter(|(_, hs)| !hs.proven_winners.is_empty())
.collect();
hosts_with_winners.sort_by(|a, b| a.0.cmp(b.0));
if hosts_with_winners.is_empty() {
out.push_str("_No bypasses discovered yet — keep traffic flowing through the proxy. Blocks are being recorded and will inform technique selection._\n");
return out;
}
out.push_str("## Hosts with proven bypasses\n\n");
for (host, hs) in hosts_with_winners {
let host_md = sanitize_for_markdown(host);
let waf_md = hs.waf_name.as_deref().map(sanitize_for_markdown);
out.push_str(&format!("### `{host_md}`\n\n"));
if let Some(waf) = &waf_md {
out.push_str(&format!("**Identified WAF:** {waf}\n\n"));
}
out.push_str("**Working techniques:**\n\n");
for t in &hs.proven_winners {
out.push_str(&format!("- `{}`\n", sanitize_for_markdown(t)));
}
out.push('\n');
out.push_str(&format!(
"**Reproduce:** `wafrift replay --target 'https://{host_md}/<PATH>' --param q --payload '<PAYLOAD>' --from-host '{host_md}'`\n\n",
));
}
out
}
#[must_use]
pub fn sanitize_for_markdown(s: &str) -> String {
s.chars()
.map(|c| {
if c.is_ascii_alphanumeric() || matches!(c, '.' | '-' | '_' | ':' | '/' | '+' | '@') {
c
} else {
'_'
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sanitize_strips_markdown_breakers() {
assert_eq!(sanitize_for_markdown("hello"), "hello");
assert_eq!(sanitize_for_markdown("example.com"), "example.com");
assert_eq!(sanitize_for_markdown("a:b@c"), "a:b@c");
assert_eq!(sanitize_for_markdown("a|b"), "a_b");
assert_eq!(sanitize_for_markdown("a`b"), "a_b");
assert_eq!(sanitize_for_markdown("**bold**"), "__bold__");
assert_eq!(sanitize_for_markdown("[x](y)"), "_x__y_");
}
#[test]
fn sanitize_preserves_canonical_host_chars() {
assert_eq!(sanitize_for_markdown("[::1]:8080"), "_::1_:8080");
}
#[test]
fn sanitize_unicode_replaced_with_underscore() {
let result = sanitize_for_markdown("paypaⅼ.com"); assert!(result.contains('_'), "non-ASCII letter must be sanitised");
}
#[test]
fn render_with_zero_requests_explains_no_traffic_yet() {
let state = ProxyState::default();
let md = render_live_findings(&state);
assert!(md.contains("No requests have been proxied yet"));
}
#[test]
fn sanitize_empty_string_returns_empty() {
assert_eq!(sanitize_for_markdown(""), "");
}
#[test]
fn sanitize_all_allowed_chars_unchanged() {
let allowed = "abcXYZ09.-_:/@+";
assert_eq!(sanitize_for_markdown(allowed), allowed);
}
#[test]
fn sanitize_injection_control_chars_replaced() {
let with_null = "host\x00name";
assert_eq!(sanitize_for_markdown(with_null), "host_name");
let with_newline = "host\nname";
assert_eq!(sanitize_for_markdown(with_newline), "host_name");
let with_tab = "host\tname";
assert_eq!(sanitize_for_markdown(with_tab), "host_name");
}
#[test]
fn sanitize_angle_brackets_replaced() {
assert_eq!(sanitize_for_markdown("<script>"), "_script_");
}
#[test]
fn render_no_bypasses_yet_branch_reached() {
let empty_state = ProxyState::default();
let md_empty = render_live_findings(&empty_state);
assert!(
md_empty.contains("No requests have been proxied yet"),
"empty state should produce the no-traffic message"
);
assert!(
md_empty.contains("# wafrift live findings"),
"findings header must always be present"
);
}
#[test]
fn render_output_always_starts_with_heading() {
let state = ProxyState::default();
let md = render_live_findings(&state);
assert!(
md.starts_with("# wafrift live findings"),
"render must always start with the level-1 heading"
);
}
#[test]
fn render_output_never_empty() {
let state = ProxyState::default();
let md = render_live_findings(&state);
assert!(!md.is_empty(), "render must never return an empty string");
}
}