feat(network-proxy): structured policy signaling and attempt correlation to core (#11662)

## Summary
When network requests were blocked, downstream code often had to infer
ask vs deny from free-form response text. That was brittle and led to
incorrect approval behavior.
This PR fixes the proxy side so blocked decisions are structured and
request metadata survives reliably.

## Description
- Blocked proxy responses now carry consistent structured policy
decision data.
- Request attempt metadata is preserved across proxy env paths
(including ALL_PROXY flows).
- Header stripping was tightened so we still remove unsafe forwarding
headers, but keep metadata needed for policy handling.
- Block messages were clarified (for example, allowlist miss vs explicit
deny).
- Added unified violation log entries so policy failures can be
inspected in one place.
- Added/updated tests for these behaviors.

---------

Co-authored-by: Codex <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
This commit is contained in:
viyatb-oai
2026-02-13 01:01:11 -08:00
committed by GitHub
parent fca5629e34
commit 2bced810da
14 changed files with 581 additions and 83 deletions

View File

@@ -69,6 +69,7 @@ pub struct NetworkPolicyRequest {
pub method: Option<String>,
pub command: Option<String>,
pub exec_policy_hint: Option<String>,
pub attempt_id: Option<String>,
}
pub struct NetworkPolicyRequestArgs {
@@ -79,6 +80,7 @@ pub struct NetworkPolicyRequestArgs {
pub method: Option<String>,
pub command: Option<String>,
pub exec_policy_hint: Option<String>,
pub attempt_id: Option<String>,
}
impl NetworkPolicyRequest {
@@ -91,6 +93,7 @@ impl NetworkPolicyRequest {
method,
command,
exec_policy_hint,
attempt_id,
} = args;
Self {
protocol,
@@ -100,6 +103,7 @@ impl NetworkPolicyRequest {
method,
command,
exec_policy_hint,
attempt_id,
}
}
}
@@ -119,6 +123,10 @@ impl NetworkDecision {
Self::deny_with_source(reason, NetworkDecisionSource::Decider)
}
pub fn ask(reason: impl Into<String>) -> Self {
Self::ask_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() {
@@ -216,9 +224,9 @@ fn map_decider_decision(decision: NetworkDecision) -> NetworkDecision {
#[cfg(test)]
mod tests {
use super::*;
use crate::config::NetworkProxySettings;
use crate::reasons::REASON_DENIED;
use crate::reasons::REASON_NOT_ALLOWED;
use crate::reasons::REASON_NOT_ALLOWED_LOCAL;
use crate::state::network_proxy_state_for_policy;
use pretty_assertions::assert_eq;
@@ -248,6 +256,7 @@ mod tests {
method: Some("GET".to_string()),
command: None,
exec_policy_hint: None,
attempt_id: None,
});
let decision = evaluate_host_policy(&state, Some(&decider), &request)
@@ -281,6 +290,7 @@ mod tests {
method: Some("GET".to_string()),
command: None,
exec_policy_hint: None,
attempt_id: None,
});
let decision = evaluate_host_policy(&state, Some(&decider), &request)
@@ -321,6 +331,7 @@ mod tests {
method: Some("GET".to_string()),
command: None,
exec_policy_hint: None,
attempt_id: None,
});
let decision = evaluate_host_policy(&state, Some(&decider), &request)
@@ -336,4 +347,16 @@ mod tests {
);
assert_eq!(calls.load(Ordering::SeqCst), 0);
}
#[test]
fn ask_uses_decider_source_and_ask_decision() {
assert_eq!(
NetworkDecision::ask(REASON_NOT_ALLOWED),
NetworkDecision::Deny {
reason: REASON_NOT_ALLOWED.to_string(),
source: NetworkDecisionSource::Decider,
decision: NetworkPolicyDecision::Ask,
}
);
}
}