Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/development/build-and-test/pull-requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,26 @@ Select the current native build using `MXC_FFI_DIR` for Node and the managed
prebuilt-native MSBuild properties for .NET. These are opt-in host tests, not
substitutes for the PR build matrix.

### Local Windows capture diagnostics benchmark

From `src`, run:

```text
cargo test --release -p mxc-sdk --lib benchmark_high_cardinality_network_capture -- --ignored --nocapture
```

This opt-in benchmark exercises the production network extractor, bounded
aggregation, denial deduplication, v5 document construction, JSON writing, and
version-specific validation. It compares repeated signatures with changing
filter IDs and ephemeral local ports for connections to the same destination,
at 4,096, 100,000, and 1,000,000 events. It reports retained groups, output bytes,
post-TDH processing time, document construction/writing time, and average
version-dispatching validation versus direct v5 deserialization time.
It also checks denial and overflow accounting. Timings are measurements, not
CI pass/fail thresholds. This synthetic decoded-event workload excludes TDH
decoding, ETL I/O, and native trace stop/close, so it is not an end-to-end OS
capture or teardown measurement.

## Azure Pipelines (optional on PRs, required on `main`)

The ADO pipeline (`MXC-PR-Build`) is the Azure version of the PR pipeline. The official
Expand Down
2 changes: 2 additions & 0 deletions docs/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ depends on the backend; see its guide. When `network`, `network.egress`, or
Both ingress controls also default to `deny` when omitted. `hostLoopback`
resolves independently of `ingress.default`, so host-loopback access must
be requested explicitly.
A runtime proxy may omit `network.egress`: the effective egress policy is
still `deny` with no direct allow or deny rules.
A ProcessContainer proxy requires `ingress.default: "allow"`. Identity-scoped
proxies set a non-blank `allowedProxyPeer` and keep `hostLoopback: "deny"`;
identity-less host proxies omit `allowedProxyPeer` and require
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::process_security_environment_spec::process_security_environment_layou
};

use crate::process_container_common::network_policy_helpers::{
add_default_network_capabilities, ensure_capability,
add_default_network_capabilities, ensure_capability, INTERNET_CLIENT_CAPABILITY,
};
use crate::process_container_common::secenv::SecurityEnvironmentVersion;

Expand All @@ -31,13 +31,14 @@ pub(super) fn build_psec_v1_security_environment_spec(
request: &ExecutionRequest,
version: SecurityEnvironmentVersion,
supports_network_ingress: bool,
capture_network_decisions: bool,
) -> Vec<u8> {
assert_eq!(
version.major, 1,
"build_psec_v1_security_environment_spec only supports PSEC major version 1"
);
let mut builder = flatbuffers::FlatBufferBuilder::with_capacity(1024);
let mut capabilities = effective_capabilities(&request.policy);
let mut capabilities = effective_capabilities(request, capture_network_decisions);
if request.policy.network_proxy.is_enabled()
&& unrestricted_host_loopback_allowed(&request.policy)
{
Expand Down Expand Up @@ -73,14 +74,28 @@ pub(super) fn build_psec_v1_security_environment_spec(
builder.finished_data().to_vec()
}

fn effective_capabilities(policy: &ContainerPolicy) -> Vec<String> {
fn effective_capabilities(
request: &ExecutionRequest,
capture_network_decisions: bool,
) -> Vec<String> {
let policy = &request.policy;
let mut capabilities: Vec<_> = policy
.capabilities
.iter()
.filter(|capability| !capability.is_empty())
.cloned()
.collect();
add_default_network_capabilities(policy, &mut capabilities);
if policy.capture_denials.is_some()
Comment thread
richiemsft marked this conversation as resolved.
&& capture_network_decisions
&& policy.network_egress.is_some()
Comment thread
richiemsft marked this conversation as resolved.
&& !policy.network_proxy.is_enabled()
{
// Let outbound attempts pass the capability gate so Tessera's egress
// policy makes and records the decision. Proxy mode must remain direct-
// egress denied and therefore deliberately omits internetClient.
ensure_capability(&mut capabilities, INTERNET_CLIENT_CAPABILITY);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium (performance) β€” Measure the newly reachable high-cardinality capture path.

Attribution: newly_exposed_by_change β€” this capture-only internetClient grant lets direct default-deny attempts reach Tessera; the native path now persists their v5 WFP diagnostic signatures. The unchanged extractor includes volatile fields such as local ports and filter IDs in signature properties, and the bounded aggregator sizes and inserts each distinct key into a sorted vector before saturation.

Network-heavy workloads may produce thousands of distinct groups for repeated connections to the same destination. This is an unmeasured CPU/teardown risk, not an unbounded-output claim or a merge condition. Fix: Benchmark a representative high-cardinality capture; if material, aggregate successfully decoded events by stable reason/endpoint fields or use a keyed structure while retaining detailed malformed-event diagnostics.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Measured in 8a693f0 with the new opt-in benchmark, documented in docs/development/build-and-test/pull-requests.md. Command from src: cargo test -q --release -p mxc-sdk --lib -- benchmark_high_cardinality_network_capture --ignored --nocapture passed.

The benchmark calls the production post-TDH extractor/accumulator, using valid direct-default-deny events to the same TCP destination, and compares identical signatures against changing filter IDs and ephemeral local ports. Descending filter IDs exercise front-of-vector insertions. Results on this Windows x64 host with the pinned Rust 1.93 toolchain:

Events Repeated-signature processing High-cardinality processing
4,096 68.79 ms 148.83 ms
100,000 1.702 s 1.835 s
1,000,000 16.769 s 18.344 s

High-cardinality retention saturated at 4,096 groups, with one actionable destination denial and exact overflow accounting; the verbose JSON was 9,539,872 bytes at one million events. Document merge + pretty JSON writing took about 65 ms. The high-cardinality timings include fixture-property mutation, so the difference is not purely aggregator cost.

I have kept the existing bounded diagnostic representation rather than adding an unproven keyed structure or dropping successfully decoded properties: the measured incremental cost is about 80 ms at the group bound and about 9% at one million events. This is a synthetic decoded-event measurement, not an end-to-end native capture benchmark: TDH decoding, ETL I/O, and native stop/close are explicitly excluded, so it does not establish OS teardown latency.

}
capabilities
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,12 @@ fn runtime_proxy_request(proxy: &TcpListener) -> ExecutionRequest {
fn runtime_proxy_uses_peer_and_capability_without_native_ingress() {
let proxy = TcpListener::bind(("127.0.0.1", 0)).unwrap();
let request = runtime_proxy_request(&proxy);
let bytes =
build_psec_v1_security_environment_spec(&request, SecurityEnvironmentVersion::V1_1, true);
let bytes = build_psec_v1_security_environment_spec(
&request,
SecurityEnvironmentVersion::V1_1,
true,
false,
);
let spec = psec_layout::root_as_process_security_environment(&bytes).unwrap();
let network = spec.network_policy().expect("network policy");
let proxy_url = format!("http://{}", proxy.local_addr().unwrap());
Expand Down Expand Up @@ -87,8 +91,12 @@ fn runtime_proxy_creates_native_security_environment() {
}
let proxy = TcpListener::bind(("127.0.0.1", 0)).unwrap();
let request = runtime_proxy_request(&proxy);
let bytes =
build_psec_v1_security_environment_spec(&request, SecurityEnvironmentVersion::V1_1, true);
let bytes = build_psec_v1_security_environment_spec(
&request,
SecurityEnvironmentVersion::V1_1,
true,
false,
);
let environment =
create(&bytes, PROCESS_SECURITY_ENVIRONMENT_FLAG_NONE).unwrap_or_else(|error| {
panic!("valid runtime proxy policy failed to create PSEC: {error}")
Expand All @@ -107,8 +115,12 @@ fn identity_scoped_proxy_does_not_grant_host_loopback() {
.unwrap()
.host_loopback = NetworkAction::Deny;
request.policy.allowed_proxy_peer = Some("Contoso.Proxy_12345".to_string());
let bytes =
build_psec_v1_security_environment_spec(&request, SecurityEnvironmentVersion::V1_0, false);
let bytes = build_psec_v1_security_environment_spec(
&request,
SecurityEnvironmentVersion::V1_0,
false,
false,
);
let spec = psec_layout::root_as_process_security_environment(&bytes).unwrap();
let network = spec.network_policy().unwrap();

Expand Down
Loading
Loading