Skip to content

Commit 344a2eb

Browse files
committed
fix(policy): preserve path-specific endpoint overrides
Signed-off-by: John Myers <[email protected]>
1 parent 92efd90 commit 344a2eb

3 files changed

Lines changed: 33 additions & 2 deletions

File tree

‎architecture/security-policy.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,9 @@ deny rules only when their connection and request-processing metadata agree;
104104
conflicting TLS, destination, credential, parser, or enforcement metadata
105105
rejects the complete generation. Plain L4 endpoints do not contribute
106106
request-processing metadata, so they may overlap an L7 endpoint when their
107-
connection metadata agrees.
107+
connection metadata agrees. When request paths overlap, a path endpoint with a
108+
higher specificity rank deterministically overrides broader request-processing
109+
metadata. Equally specific overlapping endpoints must agree.
108110

109111
The `[openshell.gateway] policy_validation_failure_mode` configuration controls
110112
rejected generations. It defaults to `fail_closed`, which publishes a quarantine

‎crates/openshell-policy/src/ambiguity.rs‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ pub fn find_endpoint_ambiguities(policy: &SandboxPolicy) -> Vec<EndpointAmbiguit
9292
if endpoint_contributes_request_pipeline_metadata(left.endpoint)
9393
&& endpoint_contributes_request_pipeline_metadata(right.endpoint)
9494
&& path_patterns_overlap(&left.endpoint.path, &right.endpoint.path)
95+
&& path_selector_specificity(&left.endpoint.path)
96+
== path_selector_specificity(&right.endpoint.path)
9597
{
9698
conflicts.extend(request_pipeline_conflicts(left.endpoint, right.endpoint));
9799
}
@@ -347,6 +349,20 @@ fn path_patterns_overlap(left: &str, right: &str) -> bool {
347349
glob_patterns_overlap(left, right, '/')
348350
}
349351

352+
/// Match the runtime route-selection rank used by `L7EndpointConfig`.
353+
///
354+
/// Overlapping endpoints with different ranks do not compete for request
355+
/// metadata: the endpoint with the more-specific path wins. Equal-rank
356+
/// overlaps must agree because iteration order would otherwise decide which
357+
/// parser, credential handling, or enforcement behavior applies.
358+
fn path_selector_specificity(path: &str) -> usize {
359+
if path.is_empty() {
360+
0
361+
} else {
362+
path.chars().filter(|character| *character != '*').count()
363+
}
364+
}
365+
350366
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
351367
enum GlobToken {
352368
Literal(char),
@@ -541,6 +557,19 @@ mod tests {
541557
assert!(find_endpoint_ambiguities(&policy_with(left, right)).is_empty());
542558
}
543559

560+
#[test]
561+
fn more_specific_path_may_override_request_pipeline_metadata() {
562+
let mut left = endpoint("api.example.com", 443);
563+
left.protocol = "rest".to_string();
564+
left.enforcement = "enforce".to_string();
565+
let mut right = endpoint("api.example.com", 443);
566+
right.path = "/graphql".to_string();
567+
right.protocol = "graphql".to_string();
568+
right.enforcement = "enforce".to_string();
569+
570+
assert!(find_endpoint_ambiguities(&policy_with(left, right)).is_empty());
571+
}
572+
544573
#[test]
545574
fn exact_wildcard_tls_conflict_is_rejected() {
546575
let mut left = endpoint("*.example.com", 443);

‎docs/sandboxes/policies.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ The following steps outline the hot-reload policy update workflow.
203203

204204
### Validation failures
205205

206-
OpenShell validates a complete candidate policy before activating any part of it. Endpoints may overlap when their connection and request-processing metadata agree. For example, two `api.example.com:443` REST entries can contribute different allow and deny rules when they use the same TLS, destination, credential, parser, and enforcement settings. A plain L4 endpoint may overlap an L7 endpoint because it authorizes the destination without contributing request-processing metadata. OpenShell rejects the candidate when overlapping exact or wildcard host selectors can both contribute endpoint configuration and disagree on those fields. Path-scoped endpoints may use different protocols only when their path selectors cannot match the same request.
206+
OpenShell validates a complete candidate policy before activating any part of it. Endpoints may overlap when their connection and request-processing metadata agree. For example, two `api.example.com:443` REST entries can contribute different allow and deny rules when they use the same TLS, destination, credential, parser, and enforcement settings. A plain L4 endpoint may overlap an L7 endpoint because it authorizes the destination without contributing request-processing metadata. A more-specific path endpoint may override request-processing metadata from a broader endpoint, such as a `/graphql` GraphQL endpoint alongside a general REST endpoint for the same host. OpenShell rejects the candidate when overlapping exact or wildcard host selectors can both contribute equally specific endpoint configuration and disagree on those fields.
207207

208208
The gateway's `policy_validation_failure_mode` configuration determines what happens after rejection. Set it under `[openshell.gateway]` in `gateway.toml`. Its default is `fail_closed`:
209209

0 commit comments

Comments
 (0)