mirror of
https://github.com/openai/codex.git
synced 2026-04-26 07:35:29 +00:00
feat(network-proxy): add structured policy decision to blocked errors (#10420)
## Summary
Add explicit, model-visible network policy decision metadata to blocked
proxy responses/errors.
Introduces a standardized prefix line: `CODEX_NETWORK_POLICY_DECISION
{json}`
and wires it through blocked paths for:
- HTTP requests
- HTTPS CONNECT
- SOCKS5 TCP/UDP denials
## Why
The model should see *why* a request was blocked
(reason/source/protocol/host/port) so it can choose the correct next
action.
## Notes
- This PR is intentionally independent of config-layering/network-rule
runtime integration.
- Focus is blocked decision surface only.
This commit is contained in:
@@ -15,6 +15,51 @@ pub enum NetworkProtocol {
|
||||
Socks5Udp,
|
||||
}
|
||||
|
||||
impl NetworkProtocol {
|
||||
pub const fn as_policy_protocol(self) -> &'static str {
|
||||
match self {
|
||||
Self::Http => "http",
|
||||
Self::HttpsConnect => "https_connect",
|
||||
Self::Socks5Tcp => "socks5_tcp",
|
||||
Self::Socks5Udp => "socks5_udp",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum NetworkPolicyDecision {
|
||||
Deny,
|
||||
Ask,
|
||||
}
|
||||
|
||||
impl NetworkPolicyDecision {
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Deny => "deny",
|
||||
Self::Ask => "ask",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum NetworkDecisionSource {
|
||||
BaselinePolicy,
|
||||
ModeGuard,
|
||||
ProxyState,
|
||||
Decider,
|
||||
}
|
||||
|
||||
impl NetworkDecisionSource {
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::BaselinePolicy => "baseline_policy",
|
||||
Self::ModeGuard => "mode_guard",
|
||||
Self::ProxyState => "proxy_state",
|
||||
Self::Decider => "decider",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NetworkPolicyRequest {
|
||||
pub protocol: NetworkProtocol,
|
||||
@@ -62,18 +107,44 @@ impl NetworkPolicyRequest {
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum NetworkDecision {
|
||||
Allow,
|
||||
Deny { reason: String },
|
||||
Deny {
|
||||
reason: String,
|
||||
source: NetworkDecisionSource,
|
||||
decision: NetworkPolicyDecision,
|
||||
},
|
||||
}
|
||||
|
||||
impl NetworkDecision {
|
||||
pub fn deny(reason: impl Into<String>) -> Self {
|
||||
Self::deny_with_source(reason, NetworkDecisionSource::Decider)
|
||||
}
|
||||
|
||||
pub fn deny_with_source(reason: impl Into<String>, source: NetworkDecisionSource) -> Self {
|
||||
let reason = reason.into();
|
||||
let reason = if reason.is_empty() {
|
||||
REASON_POLICY_DENIED.to_string()
|
||||
} else {
|
||||
reason
|
||||
};
|
||||
Self::Deny { reason }
|
||||
Self::Deny {
|
||||
reason,
|
||||
source,
|
||||
decision: NetworkPolicyDecision::Deny,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ask_with_source(reason: impl Into<String>, source: NetworkDecisionSource) -> Self {
|
||||
let reason = reason.into();
|
||||
let reason = if reason.is_empty() {
|
||||
REASON_POLICY_DENIED.to_string()
|
||||
} else {
|
||||
reason
|
||||
};
|
||||
Self::Deny {
|
||||
reason,
|
||||
source,
|
||||
decision: NetworkPolicyDecision::Ask,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,12 +185,31 @@ pub(crate) async fn evaluate_host_policy(
|
||||
HostBlockDecision::Allowed => Ok(NetworkDecision::Allow),
|
||||
HostBlockDecision::Blocked(HostBlockReason::NotAllowed) => {
|
||||
if let Some(decider) = decider {
|
||||
Ok(decider.decide(request.clone()).await)
|
||||
Ok(map_decider_decision(decider.decide(request.clone()).await))
|
||||
} else {
|
||||
Ok(NetworkDecision::deny(HostBlockReason::NotAllowed.as_str()))
|
||||
Ok(NetworkDecision::deny_with_source(
|
||||
HostBlockReason::NotAllowed.as_str(),
|
||||
NetworkDecisionSource::BaselinePolicy,
|
||||
))
|
||||
}
|
||||
}
|
||||
HostBlockDecision::Blocked(reason) => Ok(NetworkDecision::deny(reason.as_str())),
|
||||
HostBlockDecision::Blocked(reason) => Ok(NetworkDecision::deny_with_source(
|
||||
reason.as_str(),
|
||||
NetworkDecisionSource::BaselinePolicy,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_decider_decision(decision: NetworkDecision) -> NetworkDecision {
|
||||
match decision {
|
||||
NetworkDecision::Allow => NetworkDecision::Allow,
|
||||
NetworkDecision::Deny {
|
||||
reason, decision, ..
|
||||
} => NetworkDecision::Deny {
|
||||
reason,
|
||||
source: NetworkDecisionSource::Decider,
|
||||
decision,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +289,9 @@ mod tests {
|
||||
assert_eq!(
|
||||
decision,
|
||||
NetworkDecision::Deny {
|
||||
reason: REASON_DENIED.to_string()
|
||||
reason: REASON_DENIED.to_string(),
|
||||
source: NetworkDecisionSource::BaselinePolicy,
|
||||
decision: NetworkPolicyDecision::Deny,
|
||||
}
|
||||
);
|
||||
assert_eq!(calls.load(Ordering::SeqCst), 0);
|
||||
@@ -237,7 +329,9 @@ mod tests {
|
||||
assert_eq!(
|
||||
decision,
|
||||
NetworkDecision::Deny {
|
||||
reason: REASON_NOT_ALLOWED_LOCAL.to_string()
|
||||
reason: REASON_NOT_ALLOWED_LOCAL.to_string(),
|
||||
source: NetworkDecisionSource::BaselinePolicy,
|
||||
decision: NetworkPolicyDecision::Deny,
|
||||
}
|
||||
);
|
||||
assert_eq!(calls.load(Ordering::SeqCst), 0);
|
||||
|
||||
Reference in New Issue
Block a user