From 9f71b29bda46a84c16dd419d6c64205ccf037f31 Mon Sep 17 00:00:00 2001 From: haileyesus2433 Date: Tue, 25 Aug 2026 02:13:01 -0400 Subject: [PATCH 1/7] feat(jans-cedarling): add MaxDepthExceeded error variant to PolicyStoreError Signed-off-by: haileyesus2433 --- jans-cedarling/cedarling/src/common/policy_store/errors.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/jans-cedarling/cedarling/src/common/policy_store/errors.rs b/jans-cedarling/cedarling/src/common/policy_store/errors.rs index 374a0a022a3..43392728617 100644 --- a/jans-cedarling/cedarling/src/common/policy_store/errors.rs +++ b/jans-cedarling/cedarling/src/common/policy_store/errors.rs @@ -146,6 +146,10 @@ pub(crate) enum PolicyStoreError { #[source] source: std::io::Error, }, + + /// Maximum directory recursion depth exceeded + #[error("Maximum directory recursion depth ({max_depth}) exceeded at '{path}'")] + MaxDepthExceeded { path: String, max_depth: usize }, } /// Details about Cedar parsing errors. From e6492f20b910f9b288d14a7cd707b276dff10597 Mon Sep 17 00:00:00 2001 From: haileyesus2433 Date: Tue, 25 Aug 2026 02:13:37 -0400 Subject: [PATCH 2/7] feat(jans-cedarling): Add DecompressedSizeExceeded error variant Signed-off-by: haileyesus2433 --- jans-cedarling/cedarling/src/jwt/status_list/error.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jans-cedarling/cedarling/src/jwt/status_list/error.rs b/jans-cedarling/cedarling/src/jwt/status_list/error.rs index b56753c6062..d3c4e44240e 100644 --- a/jans-cedarling/cedarling/src/jwt/status_list/error.rs +++ b/jans-cedarling/cedarling/src/jwt/status_list/error.rs @@ -26,6 +26,8 @@ pub enum ParseStatusListError { JwtInvalidBitsType(serde_json::Value), #[error("failed to convert bits value: {0}")] BitsConversion(#[from] std::num::TryFromIntError), + #[error("decompressed status list exceeds the maximum allowed size of {0} bytes")] + DecompressedSizeExceeded(u64), } #[derive(Debug, Error)] From c1b125d9efaf41c2bbff1e6e3e81bf25e9647d8b Mon Sep 17 00:00:00 2001 From: haileyesus2433 Date: Tue, 25 Aug 2026 02:14:25 -0400 Subject: [PATCH 3/7] feat(jans-cedarling): Limit directory recursion depth in policy loader Prevent stack overflows by restricting directory traversal depth to 64 levels. Signed-off-by: haileyesus2433 --- .../cedarling/src/common/policy_store/loader.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/jans-cedarling/cedarling/src/common/policy_store/loader.rs b/jans-cedarling/cedarling/src/common/policy_store/loader.rs index 5c6e7189718..98794cf31dd 100644 --- a/jans-cedarling/cedarling/src/common/policy_store/loader.rs +++ b/jans-cedarling/cedarling/src/common/policy_store/loader.rs @@ -513,6 +513,10 @@ impl DefaultPolicyStoreLoader { Ok(issuers) } + /// Maximum directory recursion depth to prevent stack overflow from deeply + /// nested `.cjar` archives or filesystem trees. + const MAX_RECURSION_DEPTH: usize = 64; + /// Helper: Load all .cedar files from a directory, recursively scanning subdirectories. fn load_cedar_files( &self, @@ -520,7 +524,7 @@ impl DefaultPolicyStoreLoader { _file_type: &str, ) -> Result, PolicyStoreError> { let mut files = Vec::new(); - self.load_cedar_files_recursive(dir, &mut files)?; + self.load_cedar_files_recursive(dir, &mut files, 0)?; Ok(files) } @@ -529,7 +533,15 @@ impl DefaultPolicyStoreLoader { &self, dir: &str, files: &mut Vec, + depth: usize, ) -> Result<(), PolicyStoreError> { + if depth > Self::MAX_RECURSION_DEPTH { + return Err(PolicyStoreError::MaxDepthExceeded { + path: dir.to_string(), + max_depth: Self::MAX_RECURSION_DEPTH, + }); + } + let entries = self.vfs .read_dir(dir) @@ -541,7 +553,7 @@ impl DefaultPolicyStoreLoader { for entry in entries { if entry.is_dir { // Recursively scan subdirectories - self.load_cedar_files_recursive(&entry.path, files)?; + self.load_cedar_files_recursive(&entry.path, files, depth + 1)?; } else { // Validate .cedar extension if !entry.name.to_lowercase().ends_with(".cedar") { From 6a4318976c4ddb9863637f3a05b3972c8e321520 Mon Sep 17 00:00:00 2001 From: haileyesus2433 Date: Tue, 25 Aug 2026 02:15:38 -0400 Subject: [PATCH 4/7] feat(jans-cedarling): Limit decompressed status list size to 10MB Add a size limit to the Zlib decoder to prevent zip-bomb attacks and potential out-of-memory errors. Signed-off-by: haileyesus2433 --- .../cedarling/src/jwt/status_list.rs | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/jans-cedarling/cedarling/src/jwt/status_list.rs b/jans-cedarling/cedarling/src/jwt/status_list.rs index df5a3404716..8e750fb0a0d 100644 --- a/jans-cedarling/cedarling/src/jwt/status_list.rs +++ b/jans-cedarling/cedarling/src/jwt/status_list.rs @@ -35,11 +35,27 @@ pub(super) struct StatusList { } impl StatusList { + /// Maximum allowed decompressed size for a status list to prevent zip-bombs/OOM. + /// Default is 10 MB. + const MAX_DECOMPRESSED_SIZE: u64 = 10 * 1024 * 1024; + pub(super) fn parse(encoded: &str, bits: u8) -> Result { let list = BASE64_URL_SAFE_NO_PAD.decode(encoded)?; - let mut decoder = ZlibDecoder::new(list.as_slice()); + let decoder = ZlibDecoder::new(list.as_slice()); + + let mut bounded_decoder = decoder.take(Self::MAX_DECOMPRESSED_SIZE); + let mut list = Vec::new(); - decoder.read_to_end(&mut list)?; + bounded_decoder.read_to_end(&mut list)?; + + // Check if there is still more data in the decoder stream + let mut extra = [0u8; 1]; + if bounded_decoder.into_inner().read(&mut extra)? != 0 { + return Err(ParseStatusListError::DecompressedSizeExceeded( + Self::MAX_DECOMPRESSED_SIZE, + )); + } + Ok(Self { bit_size: bits.try_into()?, list, @@ -351,4 +367,16 @@ mod test { } ); } + #[test] + fn prevent_zip_bomb() { + let size = 10 * 1024 * 1024 + 1; // 10 MB + 1 byte + let malicious_payload = vec![0u8; size]; + let encoded = compress_and_encode(&malicious_payload); + + let result = StatusList::parse(&encoded, 1); + assert!(matches!( + result, + Err(ParseStatusListError::DecompressedSizeExceeded(_)) + )); + } } From f925248680fab9cd8c8d83c394cc7aa136d7a11d Mon Sep 17 00:00:00 2001 From: haileyesus2433 Date: Tue, 25 Aug 2026 02:18:19 -0400 Subject: [PATCH 5/7] feat(jans-cedarling): add test for max directory recursion depth violation Signed-off-by: haileyesus2433 --- .../src/common/policy_store/loader_tests.rs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/jans-cedarling/cedarling/src/common/policy_store/loader_tests.rs b/jans-cedarling/cedarling/src/common/policy_store/loader_tests.rs index 3a0f89e6a7a..a704df86ede 100644 --- a/jans-cedarling/cedarling/src/common/policy_store/loader_tests.rs +++ b/jans-cedarling/cedarling/src/common/policy_store/loader_tests.rs @@ -2159,3 +2159,51 @@ fn test_archive_shared_namespace_full_pipeline() { "Archive schema should contain App::Admin; got: {type_names:?}" ); } + +#[test] +fn test_max_recursion_depth_exceeded() { + let vfs = MemoryVfs::new(); + + vfs.create_file( + "metadata.json", + br#"{ + "cedar_version": "4.4.0", + "policy_store": { + "id": "abcdef1234567890", + "name": "Deep Nesting Test", + "version": "1.0.0" + } + }"#, + ) + .unwrap(); + + vfs.create_file( + "schema.cedarschema", + b"namespace App { entity User; entity Resource; action \"read\" appliesTo { principal: [User], resource: [Resource] }; }", + ) + .unwrap(); + + // Build a directory tree deeper than MAX_RECURSION_DEPTH (64). + // Place a .cedar file at the bottom so the only failure path is the depth check. + let depth = 66; + let mut path = String::from("policies"); + for i in 0..depth { + path.push_str(&format!("/level{i}")); + } + let file_path = format!("{path}/deep.cedar"); + vfs.create_file( + &file_path, + b"permit(principal, action, resource);", + ) + .unwrap(); + + let loader = DefaultPolicyStoreLoader::new(vfs); + let result = loader.load_directory(".", true); + + let err = result.expect_err("Should fail with MaxDepthExceeded"); + let err_msg = err.to_string(); + assert!( + err_msg.contains("Maximum directory recursion depth"), + "Error should mention max depth, got: {err_msg}" + ); +} From 0368bee5b182a09a462ce9e07e95525dd7e0ac35 Mon Sep 17 00:00:00 2001 From: haileyesus2433 Date: Tue, 25 Aug 2026 02:39:10 -0400 Subject: [PATCH 6/7] chore(jans-cedarling): improve error message in status list test Signed-off-by: haileyesus2433 --- jans-cedarling/cedarling/src/jwt/status_list.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/jans-cedarling/cedarling/src/jwt/status_list.rs b/jans-cedarling/cedarling/src/jwt/status_list.rs index 8e750fb0a0d..c0984f48fe4 100644 --- a/jans-cedarling/cedarling/src/jwt/status_list.rs +++ b/jans-cedarling/cedarling/src/jwt/status_list.rs @@ -374,9 +374,12 @@ mod test { let encoded = compress_and_encode(&malicious_payload); let result = StatusList::parse(&encoded, 1); - assert!(matches!( - result, - Err(ParseStatusListError::DecompressedSizeExceeded(_)) - )); + assert!( + matches!( + result, + Err(ParseStatusListError::DecompressedSizeExceeded(_)) + ), + "Oversized payload must return DecompressedSizeExceeded, but got {:?}", result + ); } } From f9fd9f1e9d9870c488525559afa805363f6efda3 Mon Sep 17 00:00:00 2001 From: haileyesus2433 Date: Tue, 25 Aug 2026 03:06:42 -0400 Subject: [PATCH 7/7] chore(jans-cedarling): fix clipy issues Signed-off-by: haileyesus2433 --- .../cedarling/src/common/policy_store/loader_tests.rs | 3 ++- jans-cedarling/cedarling/src/jwt/status_list.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/jans-cedarling/cedarling/src/common/policy_store/loader_tests.rs b/jans-cedarling/cedarling/src/common/policy_store/loader_tests.rs index a704df86ede..462b0355760 100644 --- a/jans-cedarling/cedarling/src/common/policy_store/loader_tests.rs +++ b/jans-cedarling/cedarling/src/common/policy_store/loader_tests.rs @@ -2188,7 +2188,8 @@ fn test_max_recursion_depth_exceeded() { let depth = 66; let mut path = String::from("policies"); for i in 0..depth { - path.push_str(&format!("/level{i}")); + use std::fmt::Write; + let _ = write!(path, "/level{i}"); } let file_path = format!("{path}/deep.cedar"); vfs.create_file( diff --git a/jans-cedarling/cedarling/src/jwt/status_list.rs b/jans-cedarling/cedarling/src/jwt/status_list.rs index c0984f48fe4..fb8cd9f32c0 100644 --- a/jans-cedarling/cedarling/src/jwt/status_list.rs +++ b/jans-cedarling/cedarling/src/jwt/status_list.rs @@ -379,7 +379,7 @@ mod test { result, Err(ParseStatusListError::DecompressedSizeExceeded(_)) ), - "Oversized payload must return DecompressedSizeExceeded, but got {:?}", result + "Oversized payload must return DecompressedSizeExceeded, but got {result:?}" ); } }