Files
Piotr Mlocek 2b7f04fe0a feat(examples): add supervisor middleware content guard (#2169)
* feat(examples): add supervisor middleware content guard

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* docs(examples): refine middleware preview warning

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(examples): add middleware policy version

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* refactor(supervisor-middleware): simplify service endpoints

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(examples): adapt content guard to middleware enums

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(examples): align content guard with merged middleware

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* feat(examples): add content guard smoke flow

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* chore(examples): remove smoke launcher test

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(examples): align content guard smoke with main

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(examples): address content guard review feedback

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* refactor(examples): parse cargo metadata with jq

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(examples): prioritize longest content matches

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* docs(examples): use GitHub warning alert

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* fix(examples): merge overlapping content matches

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

* docs(examples): render preview warning on GitHub

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>

---------

Signed-off-by: Piotr Mlocek <pmlocek@nvidia.com>
2026-07-28 21:54:56 +00:00
..

Supervisor Middleware Content Guard

Warning

Supervisor middleware is a research preview. Its policy and service contracts may change without compatibility guarantees. Use it only to prototype and evaluate middleware integrations.

This example implements an operator-run supervisor middleware service. It scans UTF-8 HTTP request bodies for configured literal strings, then either replaces every match or denies the request. Findings report only aggregate counts and never include configured terms or request content.

Warning

This intentionally simple implementation demonstrates the supervisor middleware service contract. It is not a complete or reliable content guard and must not be used as a security control. It handles only UTF-8 request bodies and case-sensitive literal terms, merges overlapping literal match ranges before redaction, and does not address the encodings, transformations, normalization, streaming, or adversarial inputs that a production content guard must handle.

Prerequisites

Install cargo, curl, jq, and openssl on the host before running the smoke script.

Run the smoke example

Run the end-to-end smoke suite to build and start a local gateway, start the content-guard service, create a sandbox, and send the same request body to two destinations:

./examples/supervisor-middleware-content-guard/smoke.sh --test-suite

The first request goes to httpbin.org, which matches the middleware endpoint selector. The response contains [FILTERED] instead of prototype-secret. The second request goes to httpbingo.org, which is allowed by network policy but does not match the middleware selector. Its response contains the original prototype-secret value. The smoke suite asserts both results and cleans up the sandbox, gateway, and middleware processes.

Run the script without flags to leave the local stack running for interactive use:

./examples/supervisor-middleware-content-guard/smoke.sh

The script creates the sandbox and prints the guarded and unguarded request commands. Press Ctrl-C to clean up. The middleware service must be reachable from both the host gateway and sandbox containers. The script detects a non-loopback host address automatically; override it when necessary:

CONTENT_GUARD_SMOKE_HOST=192.168.1.10 ./examples/supervisor-middleware-content-guard/smoke.sh --test-suite

Run manually

Start the service before starting the gateway. Bind to all host interfaces so a local containerized gateway and sandbox supervisor can reach it:

cd examples/supervisor-middleware-content-guard
cargo run -- --bind 0.0.0.0:50051

Add the service registration to your local gateway TOML:

[[openshell.supervisor.middleware]]
name = "content-guard-example"
grpc_endpoint = "http://host.openshell.internal:50051"
max_body_bytes = 262144
timeout = "500ms"

The gateway calls Describe during startup and fails to start if the service is unavailable. Both the gateway and sandbox supervisors must resolve and reach the configured endpoint. Change the hostname when host.openshell.internal is not the shared host address for your local driver.

The http:// gRPC endpoint uses plaintext without peer authentication.

The service manifest describes its supported operation and phase. The policy attaches the complete service by the operator-owned content-guard-example registration name, not by the diagnostic manifest name.

The network_middlewares map key prototype-content-guard is the stable policy-local identity. The optional name field is a human-readable label, and order must be unique across every middleware config in the policy.

Apply the example policy

The included policy allows curl to POST to https://httpbin.org/anything and https://httpbingo.org/anything. Only httpbin.org matches the middleware selector, where the content guard replaces prototype-secret or internal-only in the request body:

openshell sandbox create --policy examples/supervisor-middleware-content-guard/policy.yaml

From the sandbox, send a matching request:

curl -sS https://httpbin.org/anything \
  --header 'content-type: application/json' \
  --data '{"note":"prototype-secret"}'

The echoed JSON body contains [FILTERED] instead of the configured term.

Configuration

Field Required Description
mode No redact (default) replaces matches; deny rejects the request.
terms Yes Non-empty list of non-empty, case-sensitive literal strings. Overlapping match ranges are merged before redaction.
replacement No Replacement text for redact; defaults to [REDACTED] and is invalid with deny.

To exercise denial, change the policy config to:

config:
  mode: deny
  terms:
    - prototype-secret

The implementation supports only HttpRequest/pre_credentials, advertises a 256 KiB body limit, and inherits the service-wide RPC timeout. The gateway registration may set a smaller body limit. A binding can advertise a shorter timeout, but it cannot extend the operator-configured timeout.