Skip to content
Draft
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
1,123 changes: 1,123 additions & 0 deletions crates/openshell-supervisor-network/src/cedar_parity_tests/examples.rs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
//! tally printed gives the totals (cases, same, known differences) for the
//! whole run.

mod examples;
mod ported_opa_a;
mod ported_opa_b;
mod ported_phase1;
Expand Down
2 changes: 2 additions & 0 deletions docs/how-it-works/policies/cedar.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,8 @@ when { context.method == "GET" };

## Next Steps

- The example policies in the [OpenShell repository](https://github.com/NVIDIA/OpenShell/tree/main/examples) have a Cedar version beside each YAML policy, and their scripts accept `POLICY_FORMAT=cedar`.

- [Policies Overview](/how-it-works/policies/overview) covers the YAML policy format.
- [Policy Schema Reference](/how-it-works/policies/schema) lists every YAML field.
- [Supervisor Middleware Configuration](/extensibility/supervisor-middleware/configure) describes middleware fields and failure behavior.
Expand Down
6 changes: 6 additions & 0 deletions examples/agent-driven-policy-management/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ rule for `api.github.com` plus the binary set Codex needs. The agent has to
ask for the additional `PUT /repos/.../contents/...` write itself — that's the
proposal you approve.

`policy.template.cedar` is the same starting policy in Cedar. The demo itself
needs YAML: approved proposals are merged into the sandbox policy, and the
gateway rejects proposals for a Cedar sandbox.
Cedar needs a development build of the gateway and supervisor: Release Dev,
or `OPENSHELL_BUILD_CEDAR=1` for local e2e builds.

## What gets approved (trust model)

Every proposal lands in the gateway as a `PolicyChunk` — a structured object
Expand Down
22 changes: 20 additions & 2 deletions examples/agent-driven-policy-management/demo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,20 @@ set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
POLICY_TEMPLATE="${SCRIPT_DIR}/policy.template.yaml"
# POLICY_FORMAT selects the starting policy: policy.template.yaml (default)
# or policy.template.cedar. The approval loop merges the agent's proposed
# YAML rules into the sandbox policy, and the gateway rejects proposals for a
# Cedar sandbox, so the demo runs only with YAML; POLICY_FORMAT=cedar stops
# with an explanation.
POLICY_FORMAT="${POLICY_FORMAT:-yaml}"
case "$POLICY_FORMAT" in
yaml | cedar) ;;
*)
printf 'POLICY_FORMAT must be yaml or cedar\n' >&2
exit 2
;;
esac
POLICY_TEMPLATE="${SCRIPT_DIR}/policy.template.${POLICY_FORMAT}"
TASK_TEMPLATE="${SCRIPT_DIR}/agent-task.md"
SANDBOX_AGENT="${SCRIPT_DIR}/sandbox-agent.sh"

Expand Down Expand Up @@ -52,7 +65,7 @@ DEMO_KEEP_SANDBOX="${DEMO_KEEP_SANDBOX:-0}"

TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/openshell-policy-demo.XXXXXX")"
PAYLOAD_DIR="${TMP_DIR}/payload"
POLICY_FILE="${TMP_DIR}/policy.yaml"
POLICY_FILE="${TMP_DIR}/policy.${POLICY_FORMAT}"
AGENT_LOG="${TMP_DIR}/agent.log"
mkdir -p "$PAYLOAD_DIR"

Expand Down Expand Up @@ -216,6 +229,11 @@ resolve_codex_auth() {
}

validate_env() {
if [[ "$POLICY_FORMAT" == "cedar" ]]; then
fail "this demo approves agent proposals, which merge YAML rules into the sandbox policy; \
the gateway rejects proposals for a Cedar sandbox. Run it with the default POLICY_FORMAT=yaml. \
policy.template.cedar is the Cedar form of the starting policy: openshell sandbox create --policy ${POLICY_TEMPLATE}"
fi
require_command curl
require_command jq
require_command "$OPENSHELL_BIN"
Expand Down
160 changes: 160 additions & 0 deletions examples/agent-driven-policy-management/policy.template.cedar
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

// Cedar version of policy.template.yaml, the initial policy of the
// agent-driven policy demo. It grants the same access, but the demo itself
// needs the YAML policy: approved proposals are YAML rules merged into the
// sandbox policy, and Cedar policies do not accept merges.
//
// Landlock compatibility (best_effort) and process identity use their
// defaults, as in policy.template.yaml. A YAML binary path also matches an
// ancestor process and a symlink to the binary, so each binary condition
// tests all three. The YAML binary glob `/usr/lib/node_modules/@openai/**`
// also matches an ancestor process; `context.binary_globs` lists the patterns
// that match the calling binary or an ancestor, so the condition tests it
// there.

// filesystem_policy.read_write. Cedar has no include_workdir; /sandbox is the
// default workdir and is listed explicitly.
@id("filesystem/read_write")
permit (
principal is Sandbox::Process,
action in [Sandbox::Action::"ReadFile", Sandbox::Action::"WriteFile"],
resource is Sandbox::FilesystemPath
)
when {
resource in Sandbox::FilesystemPath::"/sandbox"
|| resource in Sandbox::FilesystemPath::"/tmp"
|| resource in Sandbox::FilesystemPath::"/dev/null"
};

// filesystem_policy.read_only.
@id("filesystem/read_only")
permit (
principal is Sandbox::Process,
action == Sandbox::Action::"ReadFile",
resource is Sandbox::FilesystemPath
)
when {
resource in Sandbox::FilesystemPath::"/usr"
|| resource in Sandbox::FilesystemPath::"/lib"
|| resource in Sandbox::FilesystemPath::"/proc"
|| resource in Sandbox::FilesystemPath::"/dev/urandom"
|| resource in Sandbox::FilesystemPath::"/app"
|| resource in Sandbox::FilesystemPath::"/etc"
|| resource in Sandbox::FilesystemPath::"/var/log"
};

// network_policies.codex: Codex and its Node runtime reach the OpenAI and
// ChatGPT endpoints. Each endpoint is `protocol: rest`, `enforcement:
// enforce`, `access: full`: one policy per endpoint allows the connection and
// every request on it, and naming the endpoint for HttpRequest makes the
// proxy inspect each request.
@id("codex/api.openai.com")
permit (
principal is Sandbox::Process,
action in [Sandbox::Action::"NetworkConnect", Sandbox::Action::"HttpRequest"],
resource == Sandbox::NetworkEndpoint::"api.openai.com:443"
)
when {
["/usr/bin/codex", "/sandbox/payload/codex", "/usr/bin/node"].contains(context.binary_path)
|| context.ancestors.containsAny(["/usr/bin/codex", "/sandbox/payload/codex", "/usr/bin/node"])
|| context.binary_aliases.containsAny(["/usr/bin/codex", "/sandbox/payload/codex", "/usr/bin/node"])
|| context.binary_globs.contains("/usr/lib/node_modules/@openai/**")
};

@id("codex/auth.openai.com")
permit (
principal is Sandbox::Process,
action in [Sandbox::Action::"NetworkConnect", Sandbox::Action::"HttpRequest"],
resource == Sandbox::NetworkEndpoint::"auth.openai.com:443"
)
when {
["/usr/bin/codex", "/sandbox/payload/codex", "/usr/bin/node"].contains(context.binary_path)
|| context.ancestors.containsAny(["/usr/bin/codex", "/sandbox/payload/codex", "/usr/bin/node"])
|| context.binary_aliases.containsAny(["/usr/bin/codex", "/sandbox/payload/codex", "/usr/bin/node"])
|| context.binary_globs.contains("/usr/lib/node_modules/@openai/**")
};

@id("codex/chatgpt.com")
permit (
principal is Sandbox::Process,
action in [Sandbox::Action::"NetworkConnect", Sandbox::Action::"HttpRequest"],
resource == Sandbox::NetworkEndpoint::"chatgpt.com:443"
)
when {
["/usr/bin/codex", "/sandbox/payload/codex", "/usr/bin/node"].contains(context.binary_path)
|| context.ancestors.containsAny(["/usr/bin/codex", "/sandbox/payload/codex", "/usr/bin/node"])
|| context.binary_aliases.containsAny(["/usr/bin/codex", "/sandbox/payload/codex", "/usr/bin/node"])
|| context.binary_globs.contains("/usr/lib/node_modules/@openai/**")
};

@id("codex/ab.chatgpt.com")
permit (
principal is Sandbox::Process,
action in [Sandbox::Action::"NetworkConnect", Sandbox::Action::"HttpRequest"],
resource == Sandbox::NetworkEndpoint::"ab.chatgpt.com:443"
)
when {
["/usr/bin/codex", "/sandbox/payload/codex", "/usr/bin/node"].contains(context.binary_path)
|| context.ancestors.containsAny(["/usr/bin/codex", "/sandbox/payload/codex", "/usr/bin/node"])
|| context.binary_aliases.containsAny(["/usr/bin/codex", "/sandbox/payload/codex", "/usr/bin/node"])
|| context.binary_globs.contains("/usr/lib/node_modules/@openai/**")
};

// network_policies.github_api_readonly: curl reads api.github.com. The
// `access: read-only` preset allows GET, HEAD, and OPTIONS on every path, so
// writes are denied.
@id("github_api_readonly/connect")
permit (
principal is Sandbox::Process,
action == Sandbox::Action::"NetworkConnect",
resource == Sandbox::NetworkEndpoint::"api.github.com:443"
)
when {
context.binary_path == "/usr/bin/curl"
|| context.ancestors.contains("/usr/bin/curl")
|| context.binary_aliases.contains("/usr/bin/curl")
};

@id("github_api_readonly/read-only")
permit (
principal is Sandbox::Process,
action == Sandbox::Action::"HttpRequest",
resource == Sandbox::NetworkEndpoint::"api.github.com:443"
)
when {
(context.binary_path == "/usr/bin/curl"
|| context.ancestors.contains("/usr/bin/curl")
|| context.binary_aliases.contains("/usr/bin/curl"))
&& ["GET", "HEAD", "OPTIONS"].contains(context.method)
};

// network_policies.github_raw_scoped: curl may GET one bootstrap path on
// raw.githubusercontent.com. A YAML GET rule also allows HEAD; Cedar lists
// HEAD explicitly.
@id("github_raw_scoped/connect")
permit (
principal is Sandbox::Process,
action == Sandbox::Action::"NetworkConnect",
resource == Sandbox::NetworkEndpoint::"raw.githubusercontent.com:443"
)
when {
context.binary_path == "/usr/bin/curl"
|| context.ancestors.contains("/usr/bin/curl")
|| context.binary_aliases.contains("/usr/bin/curl")
};

@id("github_raw_scoped/rules")
permit (
principal is Sandbox::Process,
action == Sandbox::Action::"HttpRequest",
resource == Sandbox::NetworkEndpoint::"raw.githubusercontent.com:443"
)
when {
(context.binary_path == "/usr/bin/curl"
|| context.ancestors.contains("/usr/bin/curl")
|| context.binary_aliases.contains("/usr/bin/curl"))
&& ["GET", "HEAD"].contains(context.method)
&& context.path == "/github/rest-api-description/main/README.md"
};
7 changes: 7 additions & 0 deletions examples/governance-interceptor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ preserves repeated-field order. Policy and profile hashes use the
hash is a separate operational revision identifier and is not expected to
match the signed governance hash.

Pass `--policy policy.cedar` to govern sandboxes with the same policy written in
Cedar; the `.cedar` extension selects the format, as it does for the CLI.
Cedar needs a development build of the gateway and supervisor: Release Dev,
or `OPENSHELL_BUILD_CEDAR=1` for local e2e builds.

The interceptor polls the policy file every second by default. When `policy.yaml`
changes and parses successfully, the interceptor re-signs it immediately. New
sandboxes receive the updated signed policy through `CreateSandbox`. If
Expand Down Expand Up @@ -150,6 +155,8 @@ To run the governance smoke test suite and stop the gateway when it completes:
./smoke.sh --test-suite
```

Set `POLICY_FORMAT=cedar` to run the suite with `policy.cedar`.

The suite uses a gateway-signed JWT for the created sandbox identity to attempt
an unsigned policy widening and a policy proposal. It verifies that both are
denied, telemetry is accepted, and the active policy version and hash remain
Expand Down
72 changes: 72 additions & 0 deletions examples/governance-interceptor/policy.cedar
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

// Cedar version of policy.yaml: curl has full REST access to
// api-1.example.com. The interceptor injects policy.yaml, so this file is a
// reference for creating a Cedar sandbox directly:
//
// openshell sandbox create --policy policy.cedar
//
// Landlock compatibility (best_effort) and process identity use their
// defaults, as in policy.yaml.

// filesystem_policy.read_write. Cedar has no include_workdir; /sandbox is the
// default workdir and is listed explicitly.
@id("filesystem/read_write")
permit (
principal is Sandbox::Process,
action in [Sandbox::Action::"ReadFile", Sandbox::Action::"WriteFile"],
resource is Sandbox::FilesystemPath
)
when {
resource in Sandbox::FilesystemPath::"/sandbox"
|| resource in Sandbox::FilesystemPath::"/tmp"
|| resource in Sandbox::FilesystemPath::"/dev/null"
};

// filesystem_policy.read_only.
@id("filesystem/read_only")
permit (
principal is Sandbox::Process,
action == Sandbox::Action::"ReadFile",
resource is Sandbox::FilesystemPath
)
when {
resource in Sandbox::FilesystemPath::"/usr"
|| resource in Sandbox::FilesystemPath::"/lib"
|| resource in Sandbox::FilesystemPath::"/proc"
|| resource in Sandbox::FilesystemPath::"/dev/urandom"
|| resource in Sandbox::FilesystemPath::"/app"
|| resource in Sandbox::FilesystemPath::"/etc"
|| resource in Sandbox::FilesystemPath::"/var/log"
};

// network_policies.my_api: curl connects to api-1.example.com:443. A YAML
// binary path also matches an ancestor process and a symlink to the binary,
// so the condition tests all three.
@id("my_api/connect")
permit (
principal is Sandbox::Process,
action == Sandbox::Action::"NetworkConnect",
resource == Sandbox::NetworkEndpoint::"api-1.example.com:443"
)
when {
context.binary_path == "/usr/bin/curl"
|| context.ancestors.contains("/usr/bin/curl")
|| context.binary_aliases.contains("/usr/bin/curl")
};

// `protocol: rest`, `enforcement: enforce`, `access: full`: every method on
// every path. Naming the endpoint in an HttpRequest policy makes the proxy
// inspect each request.
@id("my_api/full")
permit (
principal is Sandbox::Process,
action == Sandbox::Action::"HttpRequest",
resource == Sandbox::NetworkEndpoint::"api-1.example.com:443"
)
when {
context.binary_path == "/usr/bin/curl"
|| context.ancestors.contains("/usr/bin/curl")
|| context.binary_aliases.contains("/usr/bin/curl")
};
Loading
Loading