Skip to content

Commit 5d2f955

Browse files
committed
Cover Wasm cache-store header rejection paths
1 parent 38ab3b6 commit 5d2f955

4 files changed

Lines changed: 74 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ behavior when the change improves security or project direction.
2727
longer-lived origin response.
2828
- Add live listener tests proving fixed stored response-header metadata appears
2929
on cache HIT and forbidden stored-header IDs fail closed.
30+
- Add live listener coverage proving duplicate stored-header mutations fail
31+
closed, plus unit coverage for stored-header mutation-count caps.
3032
- Add a checked-in Wasm cache-policy example plus a config template for the
3133
bounded `1.7.5` cache ABI.
3234
- Add live listener coverage that compiles the checked-in example sources and

crates/fluxheim-server/src/native_http1_route_proxy_tests/wasm.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1396,6 +1396,16 @@ async fn native_wasm_cache_store_forbidden_tag_fails_closed() {
13961396
.assert_service_unavailable();
13971397
}
13981398

1399+
#[tokio::test]
1400+
async fn native_wasm_cache_store_duplicate_header_fails_closed() {
1401+
wasm_cache_store_failure_response(
1402+
WasmPluginBody::CacheStoreDuplicateHeader,
1403+
"header-duplicate",
1404+
)
1405+
.await
1406+
.assert_service_unavailable();
1407+
}
1408+
13991409
#[tokio::test]
14001410
async fn native_wasm_cache_store_deny_wins_over_earlier_skip() {
14011411
let fixture = WasmRouteFixture::new(&[
@@ -1526,6 +1536,7 @@ async fn wasm_cache_store_failure_response(
15261536
"ttl" => &[("/static/store-ttl.png", "hidden")],
15271537
"ttl-duplicate" => &[("/static/store-ttl-duplicate.png", "hidden")],
15281538
"tag" => &[("/static/store-tag.png", "hidden")],
1539+
"header-duplicate" => &[("/static/store-header-duplicate.png", "hidden")],
15291540
_ => &[("/static/store-failure.png", "hidden")],
15301541
};
15311542
let path = responses[0].0;
@@ -1618,6 +1629,7 @@ enum WasmPluginBody {
16181629
CacheStoreForbiddenTag,
16191630
CacheStoreHeader,
16201631
CacheStoreForbiddenHeader,
1632+
CacheStoreDuplicateHeader,
16211633
CacheLookupPolicyExample,
16221634
CacheStorePolicyExample,
16231635
Trap,
@@ -1882,6 +1894,23 @@ impl WasmPluginBody {
18821894
"#
18831895
.to_owned()
18841896
}
1897+
Self::CacheStoreDuplicateHeader => {
1898+
r#"
1899+
(module
1900+
(import "fluxheim_policy_v1" "set_cache_store_header" (func $set_cache_store_header (param i32 i32) (result i32)))
1901+
(func (export "fluxheim_cache_store") (result i32)
1902+
i32.const 1
1903+
i32.const 1
1904+
call $set_cache_store_header
1905+
drop
1906+
i32.const 1
1907+
i32.const 2
1908+
call $set_cache_store_header
1909+
drop
1910+
i32.const 0))
1911+
"#
1912+
.to_owned()
1913+
}
18851914
Self::CacheLookupPolicyExample => {
18861915
include_str!("../../../../examples/wasm/cache-lookup-policy.wat").to_owned()
18871916
}
@@ -1958,7 +1987,8 @@ fn wasm_plugin_phases(body: WasmPluginBody) -> Vec<fluxheim_config::WasmPluginPh
19581987
| WasmPluginBody::CacheStoreDuplicateTtl
19591988
| WasmPluginBody::CacheStoreForbiddenTag
19601989
| WasmPluginBody::CacheStoreHeader
1961-
| WasmPluginBody::CacheStoreForbiddenHeader => {
1990+
| WasmPluginBody::CacheStoreForbiddenHeader
1991+
| WasmPluginBody::CacheStoreDuplicateHeader => {
19621992
vec![fluxheim_config::WasmPluginPhase::CacheStore]
19631993
}
19641994
WasmPluginBody::CacheLookupPolicyExample => {

crates/fluxheim-server/src/native_http1_route_wasm.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1650,3 +1650,42 @@ impl NativeWasmAdmissionScope {
16501650
}
16511651
}
16521652
}
1653+
1654+
#[cfg(test)]
1655+
mod tests {
1656+
use super::*;
1657+
1658+
#[test]
1659+
fn cache_store_metadata_merge_caps_stored_headers_without_dropping_tags() {
1660+
let mut target = NativeProxyCacheStoreMetadata {
1661+
ttl_override: None,
1662+
cache_tags: Vec::new(),
1663+
response_headers: vec![
1664+
("x-one", "1"),
1665+
("x-two", "2"),
1666+
("x-three", "3"),
1667+
("x-four", "4"),
1668+
],
1669+
};
1670+
let source = NativeProxyCacheStoreMetadata {
1671+
ttl_override: Some(Duration::from_secs(1)),
1672+
cache_tags: vec!["wasm-policy"],
1673+
response_headers: vec![("x-five", "5")],
1674+
};
1675+
1676+
merge_wasm_cache_store_metadata(&mut target, source);
1677+
1678+
assert_eq!(target.ttl_override, Some(Duration::from_secs(1)));
1679+
assert_eq!(target.cache_tags, vec!["wasm-policy"]);
1680+
assert_eq!(
1681+
target.response_headers.len(),
1682+
MAX_WASM_CACHE_STORE_HEADER_MUTATIONS
1683+
);
1684+
assert!(
1685+
!target
1686+
.response_headers
1687+
.iter()
1688+
.any(|(name, _)| *name == "x-five")
1689+
);
1690+
}
1691+
}

release-notes/RELEASE_NOTES_1.7.5.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ mutate arbitrary cached response headers.
3939
- Add live native HTTP/1 listener coverage proving a plugin can set the fixed
4040
stored response header on cache HIT and that forbidden header IDs fail
4141
closed.
42+
- Add negative coverage proving duplicate stored-header mutations fail closed
43+
and stored-header mutation caps are enforced.
4244
- Add `examples/wasm/cache-lookup-policy.wat`,
4345
`examples/wasm/cache-store-policy.wat`, and a matching config template for
4446
the bounded cache-policy ABI.

0 commit comments

Comments
 (0)