Policy Components
Network policy in OpenShell sandbox environments determines which hosts your session can reach.
Rather than maintaining hardcoded endpoint lists in Go source code, cc-deck uses declarative YAML component files.
The build refresh command assembles these components into a deterministic openshell/policy.yaml that produces identical output for the same inputs, every time.
How Assembly Works
When you run cc-deck build refresh with an OpenShell target configured, the assembly pipeline follows these steps:
-
Load component files from all three tiers (embedded, cached catalog, user-local).
-
Resolve precedence by filename stem. Higher tiers replace lower ones entirely.
-
Evaluate each component’s match conditions against the manifest.
-
Sort matching components alphabetically by key.
-
Apply any explicit overrides from
targets.openshell.policyin the manifest. -
Write
openshell/policy.yaml.
The output is deterministic because every step uses stable ordering. No timestamps, random IDs, or runtime-dependent values enter the pipeline.
Component Tiers
Components are loaded from three locations.
When multiple tiers contain a file with the same stem (for example, rust.yaml), the highest-precedence tier wins.
The lower version is replaced entirely, not merged.
Embedded (lowest precedence)
Built into the cc-deck binary.
These provide sensible defaults for common tools and services like Claude Code, GitHub, and standard package registries.
They are updated by upgrading the binary.
Adding Custom Endpoints
Create a YAML file in .cc-deck/setup/openshell/policies/:
key: internal_api
name: Internal API
match:
always: true
endpoints:
- host: api.internal.corp
port: 8443
Then run cc-deck build refresh to include it in the generated policy.
The component file requires four fields: key (the output section name), name (a human-readable label), match (at least one condition), and endpoints (at least one entry).
For the full schema, see Configuration Reference.
Match Conditions
Each component declares when it should be included. Evaluation uses OR semantics: a single condition match is enough to include the component.
always: true-
The component is included regardless of the manifest. Used for universal services like Claude Code and GitHub.
tools: [name1, name2]-
Included if any listed tool name appears in the manifest’s
toolssection or insources[].detected_tools. Matching is case-insensitive substring. For example,rustmatches a manifest tool namedRust Analyzer. credentials: [type1, type2]-
Included if any listed credential type appears in the manifest’s
credentialssection. Uses exact match on the credentialtypefield. features: [flag1, flag2]-
Reserved for future use.
Binary Path Resolution
The OpenShell supervisor restricts network access per binary.
For each network policy entry, the binaries field lists which executables are allowed to reach the entry’s endpoints.
Binary paths come from three sources, applied in this order:
Explicit Binaries
Components can declare fixed binary paths directly in their YAML:
binaries:
- path: /usr/local/bin/claude
- path: /sandbox/.local/bin/claude
Explicit binaries are always preserved.
The probe step never overwrites them.
The embedded claude-code.yaml and git-hosting.yaml components use this approach because their binary locations are known and stable.
Probed Paths
For tool-matched components without explicit binaries, the build discovers paths by probing the built image.
The probe_binaries field lists the binary names to search for:
probe_binaries:
- pip
- pip3
- uv
- python3
During the build, which <name> runs inside the image for each listed binary.
If which fails, a find / -name <name> -type f -executable search runs as a fallback.
Found paths are added to the component’s binaries field in the generated policy.
If probe_binaries is omitted, the system falls back to probing each entry in match.tools.
Runtime Glob Patterns
Some tools create new binaries after the image is built.
Python virtual environments, Rust toolchain installs via rustup, and npx all produce executables that did not exist at build time.
The runtime_globs field covers these locations with filesystem glob patterns:
runtime_globs:
- /sandbox/**/bin/pip
- /sandbox/**/bin/pip3
- /sandbox/**/bin/python3
Runtime globs are merged into the binaries field alongside probed paths.
Duplicate paths are deduplicated automatically.
Glob patterns that match no files at runtime are ignored by the OpenShell supervisor.
Example: Component Input to Policy Output
A Python component YAML defines match conditions, probe targets, and globs:
key: pkg_python
name: python packages
match:
tools:
- python
- pip
- uv
probe_binaries:
- pip
- pip3
- uv
- python3
runtime_globs:
- /sandbox/**/bin/pip
- /sandbox/**/bin/pip3
- /sandbox/**/bin/python3
endpoints:
- host: pypi.org
port: 443
- host: files.pythonhosted.org
port: 443
After the two-pass build probes the image and finds pip at /usr/bin/pip and pip3 at /usr/bin/pip3, the generated policy entry looks like this:
pkg_python:
name: python packages
endpoints:
- host: pypi.org
port: 443
- host: files.pythonhosted.org
port: 443
binaries:
- path: /usr/bin/pip
- path: /usr/bin/pip3
- path: /sandbox/**/bin/pip
- path: /sandbox/**/bin/pip3
- path: /sandbox/**/bin/python3
The probed paths cover the binaries installed at build time. The glob patterns cover binaries created later when a developer sets up a virtual environment inside the sandbox.
For the full two-pass build process, see Build Command.
Overriding an Embedded Component
To replace an embedded component with your own version, create a file with the same filename stem in the user-local directory.
For example, to override the built-in rust.yaml with a custom set of Rust registry endpoints:
# .cc-deck/setup/openshell/policies/rust.yaml
key: pkg_rust
name: Custom Rust Registries
match:
tools:
- rust
- cargo
endpoints:
- host: my-registry.corp
port: 443
- host: crates.io
port: 443
The user-local version replaces the embedded one entirely.
Updating the Catalog
The capture command fetches updated components from the remote catalog repo:
cc-deck capture
This downloads component files to .cc-deck/setup/openshell/components/.
If the network is unavailable, the operation warns and continues with whatever is already cached.
After updating the catalog, run build refresh to regenerate the policy with the new components.