Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ jsonschema = "0.26"
# WASM runtime
# SecLang/CRS engine. Pinned by revision while its API settles; moves to a
# crates.io version when it stabilises (ADR-0031).
parapet = { package = "barbacane-waf", version = "0.1.0", features = ["serde"] }
parapet = { package = "barbacane-waf", version = "0.1.1", features = ["serde"] }
wasmtime = { version = "47", features = ["cranelift", "pooling-allocator"] }
wasmtime-wasi = { version = "47" }

Expand Down
5 changes: 4 additions & 1 deletion crates/barbacane/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1359,7 +1359,7 @@ impl Gateway {
let mut request_matched: Vec<u32> = Vec::new();
if let Some(stage) = &self.waf {
let waf_start = std::time::Instant::now();
let (decision, inspection) = stage.inspect_request(
let (decision, mut inspection) = stage.inspect_request(
&method_str,
&uri_string,
waf_protocol,
Expand Down Expand Up @@ -1404,6 +1404,9 @@ impl Gateway {
&route_path,
"waf_blocked",
);
// Run phase 5 so a request-phase block is logged and
// correlated the same as one that reached the response.
inspection.run_logging();
self.emit_waf_audit(
&inspection,
&method_str,
Expand Down
21 changes: 14 additions & 7 deletions crates/barbacane/src/waf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,10 +274,9 @@ impl WafInspection<'_> {
/// rule could block. CRS phase-4 rules are mostly data-leakage detection:
/// stack traces, SQL errors, source code and credentials in a response body.
///
/// Phases run in order and the engine stops phase processing after a
/// disruptive verdict, so a phase-3 block skips phases 4 and 5 and a
/// phase-4 block skips phase 5. Phase 5 is logging and correlation on the
/// non-blocked path.
/// A block in phase 3 skips phase 4, but phase 5 (logging and correlation)
/// runs on every transaction, including a blocked one, so its correlation
/// rules contribute to the audit record.
pub fn inspect_response(
&mut self,
status: u16,
Expand All @@ -304,6 +303,14 @@ impl WafInspection<'_> {
self.tx.matched_ids()
}

/// Run phase 5 (logging and correlation) on a request that blocked before
/// reaching response inspection, so a request-phase block is logged the
/// same as one that reached the response. The response path runs phase 5
/// through `inspect_response`, so this is only for the request-block path.
pub fn run_logging(&mut self) {
self.tx.process_logging();
}

/// The rules that matched and would appear in the audit log, in match
/// order. Excludes `nolog` rules, which matched and scored but do not log.
pub fn audit_matches(&self) -> Vec<WafAuditMatch> {
Expand Down Expand Up @@ -650,7 +657,7 @@ SecRule ARGS "@rx attack" "id:11,phase:2,pass,nolog,msg:'silent'"
/// The engine stops phase processing after a disruptive verdict, so phases
/// after the blocking one do not run.
#[test]
fn a_phase_three_block_stops_before_later_phases() {
fn a_phase_three_block_skips_phase_four_but_runs_phase_five() {
let stage = stage(EngineMode::Blocking);
let (_, mut inspection) = inspect(&stage, "/?q=fine");
let decision = inspection.inspect_response(
Expand All @@ -665,8 +672,8 @@ SecRule ARGS "@rx attack" "id:11,phase:2,pass,nolog,msg:'silent'"
"phase 4 must not run after a phase-3 block"
);
assert!(
!matched.contains(&5),
"phase 5 must not run after a phase-3 block"
matched.contains(&5),
"phase 5 runs even after a phase-3 block"
);
}
}