Policy and OrgPolicy
Constrain what a workstation manifest may name: which git repositories it may clone, where it may connect out to, and which LLM models an Integration may name.
Anyone who can write a manifest can point a workstation at any repository and let it reach anything the network routes. A Policy (namespaced) or an OrgPolicy (organization-wide) puts a bound on that, answering one question: what may a workstation manifest name?
This page documents three governed items, the kinds of value a policy can limit:
- Git source locations (
gitSources): the repositories a workstation clones into itself, viaspec.sources[].git.urlandspec.dotfilesRepo.url. - Egress destinations (
egressDestinations): the places a workstation may connect out to, viaspec.egress.destinations[], and the hosts an Integration connects its workstations to. - LLM models (
llmModels): the model anllmIntegration names inspec.llm.endpoint.model.
apiVersion: core.ringleader.dev/v1
kind: OrgPolicy # cluster-scoped, written by an org admin
kind: Policy # namespaced, written by a namespace adminAn apply that names something the resolved policy denies is refused at apply, with a message naming the field and the value that was rejected. Nothing is silently dropped.
remote: forbidden: spec.egress.destinations[1].cidr names "8.8.8.8/32",
which your organization's or namespace's policy does not permit (HTTP 403)The message stops there on purpose, and never names the policy object that
decided: an OrgPolicy is cluster-scoped, an ordinary namespace member cannot
read one, and naming it would disclose an object they have no access to. Which
policy decided, and at what priority, goes to the control plane’s audit log
rather than onto the wire. The field and the value are enough to fix most
manifests. For a refusal you did not expect, list your namespace’s own Policy
objects (see CLI below); if none of them explains it, an OrgPolicy you
cannot read decided, and an org admin can tell you which. Reading the audit record
itself needs whoever operates your control plane.
No policy objects means no constraint
Example
An organization that allows its own GitHub org and nothing else:
apiVersion: core.ringleader.dev/v1
kind: OrgPolicy
metadata:
name: acme-baseline
spec:
org: acme
priority: 200
rules:
gitSources:
default: deny
allow:
- github.com/acme
- "*.internal.acme.example"A single team loosening it for one vendor repository, which is possible only because the organization left room for it (see Priority below):
apiVersion: core.ringleader.dev/v1
kind: Policy
metadata:
name: vendor-exception
namespace: payments
spec:
priority: 50
rules:
gitSources:
allow:
- github.com/vendor/sdkFields
| Field | Type | Description |
|---|---|---|
org | string | OrgPolicy only, required. The organization this policy belongs to. |
priority | int | Which policy wins when several apply. Higher outranks lower. On a namespaced Policy it is capped at 100. |
rules | object | One block per governed item kind: gitSources, egressDestinations and llmModels. An unknown key is refused at apply, so a typo cannot produce a policy that constrains nothing. |
A rule block
| Field | Type | Description |
|---|---|---|
default | string | allow or deny, the verdict for anything neither list matches. Omit it to defer to a lower-priority policy’s default. If no policy declares one, the default is allow. |
allow | []string | Entries that permit a value. |
deny | []string | Entries that refuse a value. |
Declaring default and declaring nothing are genuinely different: an omitted default
defers, a declared one outranks.
Git source entries
A gitSources entry is a host with an optional path prefix, never a full URL:
allow:
- github.com # every repository on the host
- github.com/acme # every repository under the acme owner
- github.com/acme/platform # exactly this repository, and anything below it
- "*.internal.example" # any sub-domain (never the bare apex)
- git.example:2222 # a non-default port- Drop the scheme.
https://github.com/acmeis refused; writegithub.com/acme. - No user part. Write
github.com/acme, notgit@github.com/acme. - No whitespace.
- Paths match on segment boundaries.
github.com/acmecoversgithub.com/acme/platformbut nevergithub.com/acme-labs/x. *.matches one or more leading labels and never the apex.*.example.comcoversgit.example.com, notexample.com.- Matching is case-insensitive on both host and path.
The same entry means the same thing wherever it is written, so a rule you test in one policy behaves identically in another.
Egress destination entries
An egressDestinations entry is written exactly as a destination is written in a
workstation’s own egress
block: a network, a bare address, or a name with an optional *. wildcard.
The same rules also judge each Integration when it is applied. They judge these hosts:
- The host an
llmendpoint connects to, whether you wrote itsurlor the vendor supplies it. Forvendor: openrouterthat host isopenrouter.ai, and a refusal namesspec.llm.endpoint.vendor. - Every host a credential injection rule names.
- The host of an
mcpserver’surl.
An Integration that names a host the policy does not permit is refused. Hosts listed under
httpProxy.exclude are not judged.
rules:
egressDestinations:
default: deny
allow:
- 10.0.0.0/8
- 203.0.113.7
- github.com
- "*.githubusercontent.com"Entries are matched by containment, not equality, and the asymmetry is the point: a
rule permitting *.example.com covers a workstation asking for api.example.com, while
a rule permitting api.example.com does not cover one asking for *.example.com,
because that workstation is asking for more than the rule grants.
A name and an address never cover one another, in either direction. Deciding that
would mean resolving the name, and a policy verdict must not depend on what DNS
answered at the moment it was evaluated. The consequence is worth knowing rather than
discovering: under default: deny, an allow list holding only names denies every
cidr: destination, and the reverse. An organization that means to permit both writes
both.
Why this needs a policy at all
Configuration layers merge by unioning egress destinations, which only widens. A member can always author a higher-priority layer over their own workstation and add whatever they like to that union, so a restriction expressed as a layer is not a restriction. A floor has to live somewhere a member cannot write, which is what a policy is.
LLM model entries
An llmModels entry limits the model an llm
Integration names in spec.llm.endpoint.model.
An entry is an exact model name, or a prefix followed by one trailing *:
rules:
llmModels:
default: deny
allow:
- anthropic/claude-sonnet-4.5 # exactly this model
- "openai/gpt-5*" # every model whose name starts with openai/gpt-5- Matching is case-insensitive.
- A
*anywhere but at the end is refused, and so is a bare*. Usedefaultto allow or deny every model. - Only the model written in the Integration is judged. A program on the workstation can still ask the gateway for a different model when it calls it.
Priority, and who wins
Several policies can apply to one namespace: its own Policy objects, and every
OrgPolicy of the organization that owns it. They resolve in this order.
- The default is the one declared by the highest-priority block that declares any. If two blocks tie at that priority with different defaults, the answer is deny: a tie is an unresolved disagreement, and the restrictive answer is the safe one.
- The verdict comes from the highest priority at which any block matches the value, considering only blocks at or above the priority the winning default was declared at. No match there leaves the default standing.
- A conflict at that priority, where the value matched both an
allowand adeny, is resolved by tier first: anOrgPolicybeats aPolicy. Within one tier, the verdict that departs from the resolved default wins, on the reasoning that an explicit entry exists to be an exception. Withdefault: allowthat means the deny wins; withdefault: deny, the allow does.
The cap is the whole mechanism
An OrgPolicy’s priority is unbounded; a namespaced Policy’s is capped at
100. That single asymmetry is how an organization chooses its posture:
- Set an
OrgPolicyabove 100 and it supersedes every namespace policy. Teams can neither widen nor narrow it. - Set one below 100 and it is a default that teams may refine with their own
Policyobjects.
A namespace admin cannot outrank their own organization by typing a bigger number.
Who may write one
| Kind | Written by | Read by |
|---|---|---|
OrgPolicy | an org admin, for their own organization | org admins only. It is cluster-scoped, so a namespace member cannot read one even though it governs their applies. |
Policy | a namespace admin | members of that namespace |
A policy is a governance fact rather than a personal resource: there is no “my own policy” that an ordinary member can author for themselves.
An operator ceiling sits above both
A deployment may be configured with its own allowlist of permitted git hosts. That is a ceiling: a policy can only narrow within it, never widen past it. If a repository is refused and no policy of yours mentions it, the deployment itself does not permit that host, so ask whoever operates your control plane.
CLI
rl apply -f orgpolicy.yaml
rl orgpolicy get # alias: opol
rl policy get -n payments # alias: pol
rl policy describe vendor-exception -n paymentsSee also
- WorkstationConfig:
sources[]anddotfilesRepo, the fields agitSourcespolicy governs. - Organizations: who counts as an org admin.
- Access control: namespace roles.