sbx: restructure governance docs and add API reference (#25162)

## Summary

Restructures the Docker AI Governance documentation under
\`/ai/sandboxes/governance/\` and adds the supporting API reference.

Preview links:

-
https://deploy-preview-25162--docsdocker.netlify.app/ai/sandboxes/governance/
-
https://deploy-preview-25162--docsdocker.netlify.app/reference/api/ai-governance/

### Information architecture

The existing \`security/governance\` and \`security/policy\` pages are
merged into a new top-level \`governance\` section so local-policy and
org-policy sit side by side instead of being split across unrelated
parents:

- \`/ai/sandboxes/governance/\` — section landing; explains local + org
as layered enforcement
- \`/ai/sandboxes/governance/concepts/\` — resource model, rule syntax,
evaluation, precedence
- \`/ai/sandboxes/governance/local/\` — \`sbx policy\` CLI for
individual machines
- \`/ai/sandboxes/governance/org/\` — Admin Console flow (was
\`security/governance.md\`)
- \`/ai/sandboxes/governance/monitoring/\` — \`sbx policy ls\` / \`sbx
policy log\`

### API reference

\`/reference/api/ai-governance/\` renders the Governance OpenAPI spec
vendored at \`content/reference/api/ai-governance/api.yaml\` from
\`docker/governor-services\`. Operations, schemas, examples, and status
codes are fully driven by the spec — future updates land via re-vendor,
not in-repo edits. Anything wrong in the rendered reference should be
fixed upstream and re-vendored here.

The spec has been re-vendored to the latest upstream version, which
updated the server URL to \`hub.docker.com/v2\` and added the
\`/governance/\` prefix to all API paths.

### Review focus

1. The \`/ai/sandboxes/governance/\` landing — does the local + org
framing match how the product is positioned?
2. \`/reference/api/ai-governance/\` — does the rendered spec match the
source of truth, and is anything important missing?

Generated by Claude Code

---------

Co-authored-by: Louis-Arnaud <la.catoire@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
David Karlsson
2026-06-02 10:05:05 +02:00
committed by GitHub
parent a464561139
commit 553c69e1b7
24 changed files with 2431 additions and 442 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ build containers, install packages, and modify files without touching your host
system.
Organization admins can
[centrally manage sandbox network and filesystem policies](security/governance.md)
[centrally manage sandbox network and filesystem policies](governance/org.md)
from the Docker Admin Console, so the same rules apply uniformly across every
developer's machine. Available on a separate paid subscription.
+1 -1
View File
@@ -42,7 +42,7 @@ layers, and volumes, and this grows as you build images and install packages.
All outbound traffic from the sandbox routes through an HTTP/HTTPS proxy on
your host. Agents are configured to use the proxy automatically. The proxy
enforces [network access policies](security/policy.md) and handles
enforces [network access policies](governance/) and handles
[credential injection](security/credentials.md). See
[Network isolation](security/isolation.md#network-isolation) for how this
works and [Default security posture](security/defaults.md) for what is
+5 -6
View File
@@ -14,7 +14,7 @@ Signing in gives each sandbox a verified identity, which lets Docker:
containers, install packages, and push code. Your Docker identity is the
anchor.
- **Enable team features.** Team-scale features like
[organization governance](security/governance.md), shared environments, and
[organization governance](governance/org.md), shared environments, and
audit logs need a concept of "who," and adding that later would be worse for
everyone.
- **Authenticate against Docker infrastructure.** Sandboxes pull images, run
@@ -26,11 +26,10 @@ Your Docker account email is only used for authentication, not marketing.
Yes. Admins can centrally manage network and filesystem policies from the
Docker Admin Console. Rules defined there apply to every sandbox in the
organization and take precedence over local rules set with `sbx policy`.
Admins can optionally delegate specific rule types back to local control so
developers can add additional allow rules.
organization. When organization governance is active, it replaces local rules
set with `sbx policy` — local rules are no longer evaluated.
See [Organization governance](security/governance.md). This feature requires
See [Organization governance](governance/org.md). This feature requires
a separate paid subscription —
[contact Docker Sales](https://www.docker.com/products/ai-governance/#contact-sales)
to get started.
@@ -99,7 +98,7 @@ $ echo $BRAVE_API_KEY
## Why do agents run without approval prompts?
The sandbox itself is the safety boundary. Because agents run inside an
isolated microVM with [network policies](security/policy.md),
isolated microVM with [network policies](governance/),
[credential isolation](security/credentials.md), and no access to your host
system outside the workspace, the usual reasons for approval prompts (preventing
destructive commands, network access, file modifications) are handled by the
+3 -3
View File
@@ -114,7 +114,7 @@ Use ↑/↓ to navigate, Enter to select, or press 13.
**Balanced** is a good starting point — it permits traffic to common
development services while blocking everything else. You can adjust individual
rules later. See [Policies](security/policy.md) for a full description of each
rules later. See [Policies](governance/local.md) for a full description of each
option.
> [!NOTE]
@@ -233,7 +233,7 @@ $ sbx policy allow network -g registry.npmjs.org
With **Locked Down**, even your model provider API is blocked unless you
explicitly allow it. With **Balanced**, common development services are
permitted by default. See [Policies](security/policy.md) for the full rule
permitted by default. See [Policies](governance/local.md) for the full rule
set and how to customize it.
## Clean up
@@ -270,4 +270,4 @@ working tree are unaffected.
- [Credentials](security/credentials.md) — credential storage and management
- [Workspace isolation](security/isolation.md#workspace-isolation) — what
the agent can affect on your host, and how to review changes
- [Policies](security/policy.md) — control outbound access
- [Governance](governance/) — control outbound access
@@ -0,0 +1,38 @@
---
title: Governance
weight: 55
description: Control what sandboxes can access, from local developer rules to org-wide enforcement.
keywords: docker sandboxes, governance, policy, network access, filesystem access, organization policy
---
Sandbox governance covers the policy system that controls what sandboxes can
access over the network and on the filesystem. It operates at two layers, and
only one applies at a time:
**Local policy** is configured per machine using the `sbx policy` CLI. It
lets individual developers customize which domains their sandboxes can reach.
See [Local policy](local.md).
**Organization policy** is configured centrally in the Docker Admin Console or
via the [Governance API](/reference/api/ai-governance/). Rules defined at the org level apply
uniformly across every sandbox in the organization. When organization
governance is active, it replaces local policy entirely: local `sbx policy`
rules are no longer evaluated. See [Organization policy](org.md).
> [!NOTE]
> Organization governance is available on a separate paid subscription.
> [Contact Docker Sales](https://www.docker.com/products/ai-governance/#contact-sales)
> to request access.
## Learn more
- [Policy concepts](concepts.md): resource model, rule syntax, evaluation,
and precedence
- [Local policy](local.md): configure network and filesystem rules on your
machine with the `sbx policy` CLI
- [Organization policy](org.md): centrally manage sandbox policies across
your organization from the Admin Console
- [Monitoring](monitoring.md): inspect active rules and monitor sandbox
network traffic with `sbx policy ls` and `sbx policy log`
- [API reference](/reference/api/ai-governance/): manage org policies
programmatically via the Governance API
@@ -0,0 +1,10 @@
---
title: AI Governance API
linkTitle: API reference
description: Programmatic management of Docker AI Governance policies and rules via HTTP+JSON.
weight: 30
build:
render: never
sidebar:
goto: /reference/api/ai-governance/
---
@@ -0,0 +1,104 @@
---
title: Policy concepts
weight: 5
description: The resource model, rule syntax, and evaluation logic behind Docker sandbox governance.
keywords: docker sandboxes, policy concepts, rule syntax, network rules, filesystem rules, precedence, rule evaluation
---
## Resource model
Docker sandbox governance is built around two resource types: **policies** and
**rules**.
A **policy** is a named collection of rules that controls sandbox access.
Policies exist at different levels:
- **Local**: configured per machine using the `sbx policy` CLI. Applies to
sandboxes on that machine only.
- **Organization**: configured in the Docker Admin Console or via the
[Governance API](/reference/api/ai-governance/). Applies uniformly across every sandbox in the
organization.
- **Team**: applies to sandboxes used by a specific team within an
organization. Coming soon.
When multiple levels are active, organization policies take precedence over
local policies. See [Precedence](#precedence).
A **rule** is the unit of access control within a policy. Each rule has:
- **Name**: a human-readable label
- **Actions**: the type of access the rule controls
- **Resources**: the targets the rule matches against
- **Decision**: `allow` or `deny`
Rules are grouped by domain: all rules in a policy must share the same domain,
either `network` or `filesystem`.
## Rule syntax
### Network rules
Network rules use the actions `connect:tcp` and `connect:udp`. Resources are
hostnames, CIDR ranges, or ports.
**Hostname patterns**
| Pattern | Example | Matches |
| ------- | ------- | ------- |
| Exact hostname | `example.com` | `example.com` only, not subdomains |
| Single-level wildcard | `*.example.com` | One subdomain level: `api.example.com` |
| Multi-level wildcard | `**.example.com` | Any depth: `api.example.com`, `v2.api.example.com` |
| Hostname with port | `example.com:443` | `example.com` on port 443 only |
`example.com` and `*.example.com` don't cover each other. Specify both if you
need to match the root domain and its subdomains.
**CIDR ranges**
Both IPv4 and IPv6 notation are supported: `10.0.0.0/8`, `192.168.1.0/24`,
`2001:db8::/32`.
### Filesystem rules
Filesystem rules use the actions `read` and `write`. Resources are host paths
that sandboxes can mount as workspaces.
| Pattern | Example | Matches |
| ------- | ------- | ------- |
| Exact path | `/data` | `/data` only |
| Segment wildcard | `/data/*` | `/data/project`, one path segment only, not subdirectories |
| Recursive wildcard | `/data/**` | `/data/project`, `/data/project/src`, any depth |
Use `**` when you intend to match a directory tree recursively. A single `*`
only matches within one path segment and won't cross directory boundaries.
For example, `~/**` matches all paths under the home directory, while `~/*`
matches only direct children of `~`.
## Rule evaluation
All rules in a policy are evaluated against every request. The outcome follows
two principles:
**Deny wins.** If any rule matches with `decision: deny`, the request is
denied regardless of any matching allow rules.
**Default deny.** Outbound traffic is blocked unless an explicit allow rule
matches.
These principles apply within whichever policy is active. When organization
governance is active, only organization rules are evaluated; local rules have
no effect.
## Precedence
Local and organization policies don't combine. Which one applies depends on
whether your organization has governance enabled:
- **No organization governance**: local rules determine what sandboxes can
access.
- **Organization governance active**: organization rules apply across all
developer machines, and local rules are not evaluated. Local rules still
appear in `sbx policy ls`, but with an `inactive` status.
Within the active policy, deny rules beat allow rules regardless of specificity
or order.
@@ -0,0 +1,155 @@
---
title: Local policy
weight: 10
description: Configure network access rules for sandboxes on your local machine.
keywords: docker sandboxes, local policy, network access, allow rules, deny rules, sbx policy
aliases:
- /ai/sandboxes/security/policy/
---
The `sbx policy` command manages network access rules on your local machine.
Rules apply to all sandboxes on the machine when you use the global scope, or
to a single sandbox when scoped by name.
Local rules apply only when your organization doesn't enforce governance:
- **No org governance**: local rules fully control what sandboxes can access.
- **Org governance active**: the organization policy replaces local policy.
Local rules are inactive, and `sbx policy allow` and `sbx policy deny` have
no effect. Local rules still appear in `sbx policy ls` with an `inactive`
status.
See [Organization policy](org.md) for how organization governance works.
For domain patterns, wildcards, CIDR ranges, and filesystem path syntax, see
[Policy concepts](concepts.md#rule-syntax).
## Default preset
The only way traffic can leave a sandbox is through an HTTP/HTTPS proxy on
your host, which enforces access rules on every outbound request. Non-HTTP TCP
traffic, including SSH, can be allowed by adding a policy rule for the
destination IP and port (for example, `sbx policy allow network -g
"10.1.2.3:22"`). UDP and ICMP are blocked at the network layer and can't be
unblocked with policy rules.
On first start, and after running `sbx policy reset`, the daemon prompts you
to choose a network preset:
```plaintext
Choose a default network policy:
1. Open — All network traffic allowed, no restrictions.
2. Balanced — Default deny, with common dev sites allowed.
3. Locked Down — All network traffic blocked unless you allow it.
Use ↑/↓ to navigate, Enter to select, or press 13.
```
| Preset | Description |
| ------ | ----------- |
| Open | All outbound traffic is allowed. Equivalent to adding a wildcard allow rule with `sbx policy allow network -g "**"`. |
| Balanced | Default deny, with a baseline allowlist covering AI provider APIs, package managers, code hosts, container registries, and common cloud services. |
| Locked Down | All outbound traffic is blocked, including model provider APIs (for example, `api.anthropic.com`). You must explicitly allow everything you need. |
The **Balanced** preset's baseline allowlist is a good starting point for most
workflows. Run `sbx policy ls` to see exactly which rules it includes.
> [!NOTE]
> If your organization manages sandbox policies centrally, organization rules
> take precedence over the preset you select here. See
> [Organization policy](org.md).
### Non-interactive environments
In non-interactive environments such as CI pipelines or headless servers, the
interactive prompt can't be displayed. Use `sbx policy set-default` to set the
preset before running any other `sbx` commands:
```console
$ sbx policy set-default balanced
```
Available values are `allow-all`, `balanced`, and `deny-all`.
## Managing rules
Use [`sbx policy allow`](/reference/cli/sbx/policy/allow/) and
[`sbx policy deny`](/reference/cli/sbx/policy/deny/) to add or restrict access
on top of the active preset. Changes take effect immediately. Pass `-g` to
apply a rule globally to all sandboxes:
```console
$ sbx policy allow network -g api.anthropic.com
$ sbx policy deny network -g ads.example.com
```
Pass a sandbox name to scope a rule to one sandbox:
```console
$ sbx policy allow network my-sandbox api.example.com
$ sbx policy deny network my-sandbox ads.example.com
```
Specify multiple hosts in one command with a comma-separated list:
```console
$ sbx policy allow network -g "api.anthropic.com,*.npmjs.org,*.pypi.org"
```
Remove a rule by resource or by rule ID:
```console
$ sbx policy rm network -g --resource ads.example.com
$ sbx policy rm network -g --id 2d3c1f0e-4a73-4e05-bc9d-f2f9a4b50d67
```
To remove a sandbox-scoped rule, include the sandbox name:
```console
$ sbx policy rm network my-sandbox --resource api.example.com
```
To inspect which rules are active and where they come from, use
`sbx policy ls`. See [Monitoring](monitoring.md).
### Resetting
To remove all custom rules and start fresh with a new preset, use
`sbx policy reset`:
```console
$ sbx policy reset
```
This deletes the local policy store and stops the daemon. When the daemon
restarts on the next command, you are prompted to choose a new preset. Running
sandboxes stop when the daemon shuts down. Pass `--force` to skip the
confirmation prompt:
```console
$ sbx policy reset --force
```
## Troubleshooting
### Local rules have no effect
If rules you add with `sbx policy allow` or `sbx policy deny` don't change
sandbox behavior, your organization likely has governance enabled. Run `sbx
policy ls` to check: if the output starts with a `Governance: managed by <org>`
header, org governance is active. When it's active, the organization policy
replaces local policy, so your rules appear with `inactive` status and have no
effect.
Organization policy can't be supplemented from your machine. To change what
your sandboxes can access, ask your admin to update the organization policy in
the Admin Console.
### A domain is still blocked after adding an allow rule
If a domain remains blocked after you add a local allow rule, your organization
likely enforces governance, which makes local rules inactive. Run `sbx policy
ls` to check whether org governance is active and whether your rule shows an
`inactive` status. If so, the block can only be lifted by updating the org
policy in the Admin Console or via the [API](/reference/api/ai-governance/).
@@ -0,0 +1,106 @@
---
title: Monitoring policies
weight: 25
description: Inspect active policy rules and monitor sandbox network traffic with sbx policy ls and sbx policy log.
keywords: docker sandboxes, policy monitoring, sbx policy ls, sbx policy log, network traffic, policy debugging
---
`sbx policy ls` and `sbx policy log` give you a combined view of all active
policy rules and sandbox network activity, regardless of whether those rules
come from local configuration or organization governance. They're useful both
for verifying rules you've written and for debugging why a request is being
blocked or allowed.
## Listing rules
Use `sbx policy ls` to see all active rules and their current status:
```console
$ sbx policy ls
NAME TYPE ORIGIN DECISION STATUS RESOURCES
balanced-dev network local allow active api.anthropic.com
ads-block network local deny active ads.example.com
kit:my-sandbox network sandbox:my-sandbox allow active api.example.com
kit:my-sandbox:deny network sandbox:my-sandbox deny active telemetry.example.com
```
The columns are:
- `NAME`: the rule name.
- `TYPE`: the rule domain, such as `network`.
- `ORIGIN`: where the rule was configured. `local` means the rule is global
and applies to all sandboxes. `sandbox:<name>` means the rule is scoped to
the named sandbox. `remote` means the rule was set by your organization.
- `DECISION`: whether the rule allows or denies the resource.
- `STATUS`: whether the rule is in effect. A rule may be `inactive` if it's
overridden or suppressed (for example, when organization governance is
active, local rules are not evaluated and show as `inactive`).
- `RESOURCES`: the hosts or patterns the rule applies to.
When organization governance is active, the output starts with a governance
header showing which organization manages the policy and when it last synced:
```console
$ sbx policy ls
Governance: managed by my-org
[OK] last synced 13:54:21
NAME TYPE ORIGIN DECISION STATUS RESOURCES
balanced-dev network local allow inactive api.anthropic.com
allow AI services network remote allow active api.anthropic.com
api.openai.com
allow Docker services network remote allow active *.docker.com
*.docker.io
```
The governance header shows which organization is managing the policy and
confirms the daemon has successfully pulled the latest rules. If the sync
status shows an error or a stale timestamp, the daemon may not have the most
recent org policy. Run `sbx policy reset` to force a fresh pull.
Use `--type network` to show only network rules. Without a sandbox argument,
`sbx policy ls` shows every rule across all sandboxes. Pass a sandbox name to
filter to global rules and rules scoped to that sandbox:
```console
$ sbx policy ls my-sandbox
```
## Monitoring traffic
Use `sbx policy log` to see which hosts your sandboxes have contacted and
which rules matched:
```console
$ sbx policy log
Blocked requests:
SANDBOX TYPE HOST PROXY RULE REASON LAST SEEN COUNT
my-sandbox network blocked.example.com transparent domain-blocked default-deny 10:15:25 29-Jan 1
Allowed requests:
SANDBOX TYPE HOST PROXY RULE REASON LAST SEEN COUNT
my-sandbox network api.anthropic.com forward domain-allowed 10:15:23 29-Jan 42
my-sandbox network registry.npmjs.org forward-bypass domain-allowed 10:15:20 29-Jan 18
my-sandbox network app.example.com browser-open 10:15:10 29-Jan 1
```
The `PROXY` column shows how the request left the sandbox:
| Value | Description |
| ----- | ----------- |
| `forward` | Routed through the forward proxy. Supports [credential injection](../security/credentials.md). |
| `forward-bypass` | Routed through the forward proxy without credential injection. |
| `transparent` | Intercepted by the transparent proxy. Policy is enforced but credential injection is not available. |
| `network` | Non-HTTP traffic (raw TCP, UDP, ICMP). TCP can be allowed with a policy rule; UDP and ICMP are always blocked. |
| `browser-open` | A sandbox process requested opening a URL in the host browser. Policy is enforced before opening the URL. |
The `RULE` column identifies the policy rule that matched the request. The
`REASON` column includes extra context when the daemon records one.
Filter by sandbox name by passing it as an argument:
```console
$ sbx policy log my-sandbox
```
Use `--limit N` to show only the last `N` entries, `--json` for
machine-readable output, or `--type network` to filter by policy type.
@@ -0,0 +1,91 @@
---
title: Organization policy
linkTitle: Org policy
weight: 20
description: Centrally manage sandbox network and filesystem policies for your organization.
keywords: docker sandboxes, governance, organization policy, AI governance, admin console, network access, filesystem access
aliases:
- /ai/sandboxes/security/governance/
---
[Local policies](local.md) give individual developers control over what their
sandboxes can access. Organization policy moves that control to the admin level:
rules defined in the [Docker Admin Console](https://app.docker.com/admin) apply
uniformly to every sandbox in the organization. When organization governance is
active, it replaces local `sbx policy` rules entirely — local rules are no
longer evaluated and can't be used to supplement or override the organization
policy.
Admins can manage organization policies through the Admin Console UI or
programmatically using the [Governance API](/reference/api/ai-governance/).
> [!NOTE]
> Sandbox organization governance is available on a separate paid
> subscription.
> [Contact Docker Sales](https://www.docker.com/products/ai-governance/#contact-sales)
> to request access.
## Network policies
### Configuring org-level network rules
Define network allow and deny rules in the Admin Console under
**AI governance > Network access**. Each rule takes a network target and an
action (allow or deny). You can add multiple entries at once, one per line.
For the full syntax reference (exact hostnames, wildcard subdomains, port
suffixes, and CIDR ranges), see [Policy concepts](concepts.md#network-rules).
When organization governance is active, local network rules are not evaluated.
The organization policy is the only policy in effect. Local rules still appear
in `sbx policy ls` but with an `inactive` status. See [Monitoring](monitoring.md)
for how to read the rule view.
## Filesystem policies
Filesystem policies control which host paths a sandbox can mount as
workspaces. By default, sandboxes can mount any directory the user has
access to.
Admins can restrict which paths are mountable by defining filesystem allow
and deny rules in the Admin Console under **AI governance > Filesystem
access**. Each rule takes a path pattern and an action (allow or deny).
For path pattern syntax including the difference between `*` and `**`, see
[Policy concepts](concepts.md#filesystem-rules).
## Precedence
Within the active policy, deny rules beat allow rules. If a domain matches both,
it's blocked regardless of specificity. Outbound traffic is blocked unless a
rule allows it.
When organization governance is active, local rules are not evaluated. Only
organization rules set in the Admin Console determine what is allowed or
denied, and they can't be supplemented or overridden from a developer's machine.
The same model applies to filesystem policies: organization rules replace local
behavior entirely.
To unblock a domain when organization governance is active, update the rule in
the Admin Console or via the [API](/reference/api/ai-governance/). Without
organization governance, remove the local rule with `sbx policy rm`.
## Troubleshooting
### Policy changes not taking effect
After updating organization policies in the Admin Console, changes take up
to 5 minutes to propagate to developer machines. To apply changes
immediately, users can run `sbx policy reset`, which stops the daemon and
forces it to pull the latest organization policies on the next `sbx`
command.
> [!WARNING]
> `sbx policy reset` deletes all locally configured policy rules. The command
> prompts for confirmation before proceeding.
### Sandbox cannot mount workspace
If a sandbox fails to mount with a `mount policy denied` error, verify that
the filesystem allow rule in the Admin Console uses `**` rather than `*`. A
single `*` doesn't match across directory separators.
@@ -92,11 +92,9 @@ On a single developer's machine, network and filesystem policies are
configured locally with `sbx policy`. Admins can also centrally define those
policies in the Docker Admin Console. When organization governance is active,
the centrally defined rules apply uniformly across every sandbox in the
organization and take precedence over local rules. Admins can optionally
delegate specific rule types back to local control so developers can add
additional allow rules.
organization and replace local rules, which are no longer evaluated.
See [Organization governance](governance/) for details.
See [Organization policy](../governance/org.md) for details.
## Learn more
@@ -105,6 +103,5 @@ See [Organization governance](governance/) for details.
- [Default security posture](defaults/): what a fresh sandbox permits and
blocks
- [Credentials](credentials/): how to provide and manage API keys
- [Policies](policy/): how to customize network access rules
- [Organization governance](governance/): centrally manage policies across
an organization
- [Governance](../governance/): configure network and filesystem access rules,
locally or across your organization
@@ -16,10 +16,10 @@ it (deny-by-default). All non-HTTP protocols (raw TCP, UDP including DNS, and
ICMP) are blocked at the network layer. Traffic to private IP ranges, loopback
addresses, and link-local addresses is also blocked.
Run `sbx policy ls` to see the active network rules for your installation. To
customize network access, see [Policies](policy.md). If your organization
manages sandbox policies centrally, those rules apply on top of the defaults
described here. See [Organization governance](governance.md).
Run `sbx policy ls` to see the active network rules for your installation.
Rules can be customized per machine with the `sbx policy` CLI, or managed
centrally across your organization from the Admin Console. Org-level rules
take precedence over local rules. See [Governance](../governance/).
## Workspace defaults
@@ -1,152 +0,0 @@
---
title: Organization governance
linkTitle: Org governance
weight: 35
description: Centrally manage sandbox network and filesystem policies for your organization.
keywords: docker sandboxes, governance, organization policy, AI governance, admin console, network access, filesystem access
---
This page covers how to configure organization policies in the Docker Admin
Console under AI governance settings. For local sandbox policies that
individual users configure on their own machine, see [Policies](policy.md).
Sandbox network and filesystem policies defined in the
[Docker Admin Console](https://app.docker.com/admin) apply uniformly to every
sandbox in the organization. Rules are enforced across all developers'
machines, take precedence over local `sbx policy` rules, and can't be
overridden by individual users. Admins can optionally
[delegate](#delegate-rules-to-local-policy) specific rule types back to local
control so developers can add additional allow rules.
> [!NOTE]
> Sandbox organization governance is available on a separate paid
> subscription.
> [Contact Docker Sales](https://www.docker.com/products/ai-governance/#contact-sales)
> to request access.
## Network policies
### Configuring org-level network rules
Define network allow and deny rules in the Admin Console under
**AI governance > Network access**. Each rule takes a network target (domain,
wildcard, or CIDR range) and an action (allow or deny). You can add multiple
entries at once, one per line.
Rules support exact domains (`example.com`), wildcard subdomains
(`*.example.com`), and optional port suffixes (`example.com:443`).
`example.com` doesn't match subdomains, and `*.example.com` doesn't match
the root domain. Specify both to cover both.
### Delegate rules to local policy
When organization governance is active, local rules are ignored by default —
only the organization policy is in effect. Admins can delegate a rule type
back to local policy by turning on the **User defined** setting for that
rule type in AI governance settings. Turning the setting on delegates the
rule type: local `sbx policy` rules of that type are evaluated alongside
organization rules, letting users add hosts to the allowlist from their own
machine.
If a rule type isn't delegated, local rules of that type still appear in
`sbx policy ls` but with an `inactive` status and a note that the
organization hasn't delegated the rule type to local policy:
```console
$ sbx policy ls
NAME TYPE ORIGIN DECISION STATUS RESOURCES
balanced-dev network local allow inactive — corporate policy takes precedence and does api.anthropic.com
not delegate this rule type to local policy.
allow AI services network remote allow active api.anthropic.com
api.openai.com
allow Docker services network remote allow active *.docker.com
*.docker.io
```
Organization rules show up with `remote` in the `ORIGIN` column.
Delegated local rules can expand access for domains the organization hasn't
explicitly denied, but can't override organization-level deny rules. This
applies to exact matches and wildcard matches alike; if the organization denies
`*.example.com`, a local allow for `api.example.com` has no effect because the
org-level wildcard deny covers it.
For example, given an organization policy that allows `api.anthropic.com`
and denies `*.corp.internal`:
- `sbx policy allow network -g api.example.com` — works, because the
organization hasn't denied `api.example.com`
- `sbx policy allow network -g build.corp.internal` — no effect, because the
organization denies `*.corp.internal`
#### Blocked values in delegated rules
To prevent overly broad rules from undermining the organization's policy,
certain catch-all values are blocked in delegated local rules:
- Domain patterns: `*`, `**`, `*.com`, `**.com`, `*.*`, `**.**`
- CIDR ranges: `0.0.0.0/0`, `::/0`
Scoped wildcards like `*.example.com` are still allowed. If a user attempts
to use a blocked value, `sbx policy` returns an error immediately.
## Filesystem policies
Filesystem policies control which host paths a sandbox can mount as
workspaces. By default, sandboxes can mount any directory the user has
access to.
Admins can restrict which paths are mountable by defining filesystem allow
and deny rules in the Admin Console under **AI governance > Filesystem
access**. Each rule takes a path pattern and an action (allow or deny).
> [!CAUTION]
> Use `**` (double wildcard) rather than `*` (single wildcard) when writing
> path patterns to match path segments recursively. A single `*` only matches
> within a single path segment. For example, `~/**` matches all paths under
> the user's home directory, whereas `~/*` matches only paths directly
> under `~`.
## Precedence
Within any layer, deny rules beat allow rules. If a domain matches both, it's
blocked regardless of specificity. Outbound traffic is blocked unless a rule
allows it.
When organization governance is active, local rules are not evaluated. Only
organization rules set in the Admin Console determine what is allowed or
denied. Organization-level denials can't be overridden locally.
If the admin [delegates](#delegate-rules-to-local-policy) a rule type to
local policy by turning on the **User defined** setting, local rules of
that type are also evaluated alongside organization rules. Delegated local
rules can expand access for domains the organization hasn't explicitly
denied, but can't override organization-level denials.
The same model applies to filesystem policies: organization-level rules take
precedence over local behavior.
To unblock a domain, identify where the deny rule comes from. For local
rules, remove it with `sbx policy rm`. For organization-level rules, update
the rule in the Admin Console.
## Troubleshooting
### Policy changes not taking effect
After updating organization policies in the Admin Console, changes take up
to 5 minutes to propagate to developer machines. To apply changes
immediately, users can run `sbx policy reset`, which stops the daemon and
forces it to pull the latest organization policies on the next `sbx`
command.
> [!WARNING]
> `sbx policy reset` deletes all locally configured policy rules. The command
> prompts for confirmation before proceeding.
### Sandbox cannot mount workspace
If a sandbox fails to mount with a `mount policy denied` error, verify that
the filesystem allow rule in the Admin Console uses `**` rather than `*`. A
single `*` doesn't match across directory separators.
@@ -37,7 +37,7 @@ each other and cannot reach your host's localhost. There is no shared network
between sandboxes or between a sandbox and your host.
All HTTP and HTTPS traffic leaving a sandbox passes through a proxy on your
host that enforces the [network policy](policy.md). The sandbox routes
host that enforces the [network policy](../governance/). The sandbox routes
traffic through either a forward proxy or a transparent proxy depending on the
client's configuration. Both enforce the network policy; only the forward proxy
[injects credentials](credentials.md) for AI services.
@@ -1,263 +0,0 @@
---
title: Policies
weight: 30
description: Configure network access rules for sandboxes.
keywords: docker sandboxes, policies, network access, allow rules, deny rules
---
Sandboxes are [network-isolated](isolation.md) from your host and from each
other. A policy system controls what a sandbox can access over the network.
Use the `sbx policy` command to configure network access rules. Rules apply
to all sandboxes on the machine when you use the global scope. Network allow,
deny, list, and remove commands can also target one sandbox.
If your organization manages sandbox policies centrally, organization rules
take precedence over the local rules described on this page. See
[Organization governance](governance.md).
## Network policies
The only way traffic can leave a sandbox is through an HTTP/HTTPS proxy on
your host, which enforces access rules on every outbound request.
Non-HTTP TCP traffic, including SSH, can be allowed by adding a policy rule
for the destination IP address and port (for example,
`sbx policy allow network -g "10.1.2.3:22"`). UDP and ICMP traffic is blocked
at the network layer and can't be unblocked with policy rules.
### Initial policy selection
On first start, and after running `sbx policy reset`, the daemon prompts you to
choose a network policy:
```plaintext
Choose a default network policy:
1. Open — All network traffic allowed, no restrictions.
2. Balanced — Default deny, with common dev sites allowed.
3. Locked Down — All network traffic blocked unless you allow it.
Use ↑/↓ to navigate, Enter to select, or press 13.
```
| Policy | Description |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Open | All outbound traffic is allowed. No restrictions. Equivalent to adding a wildcard allow rule with `sbx policy allow network -g "**"`. |
| Balanced | Default deny, with a baseline allowlist covering AI provider APIs, package managers, code hosts, container registries, and common cloud services. You can extend this with `sbx policy allow`. |
| Locked Down | All outbound traffic is blocked, including model provider APIs (for example, `api.anthropic.com`). You must explicitly allow everything you need. |
You can change your effective policy at any time using `sbx policy allow` and
`sbx policy deny`, or start over by running `sbx policy reset`.
> [!NOTE]
> If your organization manages sandbox policies centrally, organization rules
> take precedence over the policy you select here. See
> [Organization governance](governance.md).
### Non-interactive environments
In non-interactive environments such as CI pipelines or headless servers, the
interactive prompt can't be displayed. Use `sbx policy set-default` to set the
default network policy before running any other `sbx` commands:
```console
$ sbx policy set-default balanced
```
Available values are `allow-all`, `balanced`, and `deny-all`. After setting the
default, you can customize further with `sbx policy allow` and
`sbx policy deny` as usual.
### Default policy
All outbound HTTP/HTTPS traffic is blocked by default unless an explicit rule
allows it. The **Balanced** policy ships with a baseline allowlist covering AI provider
APIs, package managers, code hosts, container registries, and common cloud
services. Run `sbx policy ls` to see the active rules for your installation.
### Managing rules
Use [`sbx policy allow`](/reference/cli/sbx/policy/allow/) and
[`sbx policy deny`](/reference/cli/sbx/policy/deny/) to add network access
rules. Changes take effect immediately. Pass `-g` to apply a rule to all
sandboxes:
```console
$ sbx policy allow network -g api.anthropic.com
$ sbx policy deny network -g ads.example.com
```
Pass a sandbox name to scope a rule to one sandbox:
```console
$ sbx policy allow network my-sandbox api.example.com
$ sbx policy deny network my-sandbox ads.example.com
```
Specify multiple hosts in one command with a comma-separated list:
```console
$ sbx policy allow network -g "api.anthropic.com,*.npmjs.org,*.pypi.org"
```
List all active policy rules with `sbx policy ls`:
```console
$ sbx policy ls
NAME TYPE ORIGIN DECISION STATUS RESOURCES
balanced-dev network local allow active api.anthropic.com
ads-block network local deny active ads.example.com
kit:my-sandbox network sandbox:my-sandbox allow active api.example.com
kit:my-sandbox:deny network sandbox:my-sandbox deny active telemetry.example.com
```
The columns are:
- `NAME`: the rule name.
- `TYPE`: the rule type, such as `network`.
- `ORIGIN`: where the rule applies. `local` means the rule is global and
applies to all sandboxes. `sandbox:<name>` means the rule is scoped to the
named sandbox.
- `DECISION`: whether the rule allows or denies the resource.
- `STATUS`: whether the rule is currently in effect. A rule may be inactive if
it's overridden by another rule, for example.
- `RESOURCES`: the hosts or patterns the rule applies to.
Use `--type network` to show only network policies. Without a sandbox argument,
`sbx policy ls` shows every rule across all sandboxes. Pass a sandbox name to
filter the list to global rules and rules scoped to that sandbox only:
```console
$ sbx policy ls my-sandbox
```
Remove a policy by resource or by rule ID:
```console
$ sbx policy rm network -g --resource ads.example.com
$ sbx policy rm network -g --id 2d3c1f0e-4a73-4e05-bc9d-f2f9a4b50d67
```
To remove a sandbox-scoped policy, include the sandbox name:
```console
$ sbx policy rm network my-sandbox --resource api.example.com
```
### Resetting to defaults
To remove all custom policies and restore the default policy, use
`sbx policy reset`:
```console
$ sbx policy reset
```
This deletes the local policy store and stops the daemon. When the daemon
restarts on the next command, you are prompted to choose a new network policy.
If sandboxes are running, they stop when the daemon shuts down. You are prompted for
confirmation unless you pass `--force`:
```console
$ sbx policy reset --force
```
### Switching to allow-by-default
If you prefer a permissive policy where all outbound traffic is allowed, add
a wildcard allow rule:
```console
$ sbx policy allow network -g "**"
```
This lets agents install packages and call any external API without additional
configuration. You can still deny specific hosts with `sbx policy deny`.
### Wildcard syntax
Rules support exact domains (`example.com`), wildcard subdomains
(`*.example.com`), and optional port suffixes (`example.com:443`).
Note that `example.com` doesn't match subdomains, and `*.example.com` doesn't
match the root domain. Specify both to cover both.
### Common patterns
Allow access to package managers so agents can install dependencies:
```console
$ sbx policy allow network -g "*.npmjs.org,*.pypi.org,files.pythonhosted.org,github.com"
```
The **Balanced** policy already includes AI provider APIs, package managers,
code hosts, and container registries. You only need to add allow rules for
additional domains your workflow requires. If you chose **Locked Down**, you
must explicitly allow everything.
> [!WARNING]
> Allowing broad domains like `github.com` permits access to any content on
> that domain, including user-generated content. Only allow domains you trust
> with your data.
### Monitoring
Use `sbx policy log` to see which hosts your sandboxes have contacted:
```console
$ sbx policy log
Blocked requests:
SANDBOX TYPE HOST PROXY RULE REASON LAST SEEN COUNT
my-sandbox network blocked.example.com transparent domain-blocked default-deny 10:15:25 29-Jan 1
Allowed requests:
SANDBOX TYPE HOST PROXY RULE REASON LAST SEEN COUNT
my-sandbox network api.anthropic.com forward domain-allowed 10:15:23 29-Jan 42
my-sandbox network registry.npmjs.org forward-bypass domain-allowed 10:15:20 29-Jan 18
my-sandbox network app.example.com browser-open 10:15:10 29-Jan 1
```
The **PROXY** column shows how the request left the sandbox:
| Value | Description |
| ---------------- | -------------------------------------------------------------------------------------------------------------- |
| `forward` | Routed through the forward proxy. Supports [credential injection](credentials.md). |
| `forward-bypass` | Routed through the forward proxy without credential injection. |
| `transparent` | Intercepted by the transparent proxy. Policy is enforced but credential injection is not available. |
| `network` | Non-HTTP traffic (raw TCP, UDP, ICMP). TCP can be allowed with a policy rule; UDP and ICMP are always blocked. |
| `browser-open` | A sandbox process requested opening a URL in the host browser. Policy is enforced before opening the URL. |
The **RULE** column identifies the policy rule that matched the request. The
**REASON** column includes extra context when the daemon records one.
Filter by sandbox name by passing it as an argument:
```console
$ sbx policy log my-sandbox
```
Use `--limit N` to show only the last `N` entries, `--json` for
machine-readable output, or `--type network` to filter by policy type.
## Precedence
All outbound traffic is blocked by default unless an explicit rule allows it.
If a domain matches both an allow and a deny rule, the deny rule wins
regardless of specificity. A sandbox-scoped deny rule can block a domain for
one sandbox even when a global rule permits the same domain.
To unblock a domain, find the deny rule with `sbx policy ls` and remove it
with `sbx policy rm`.
If your organization manages sandbox policies centrally, organization rules
take precedence and local rules are not evaluated unless the admin delegates
that rule type. See [Organization governance](governance.md).
## Troubleshooting
### Policy changes not taking effect
If policy changes aren't taking effect, run `sbx policy reset` to wipe the
local policy store and restart the daemon. On next start, you are prompted to
choose a new network policy.
@@ -30,7 +30,7 @@ data. Create fresh sandboxes afterwards.
## Agent can't install packages or reach an API
Sandboxes use a [deny-by-default network policy](security/policy.md).
Sandboxes use a [deny-by-default network policy](governance/local.md).
If the agent fails to install packages or call an external API, the target
domain is likely not in the allow list. Check which requests are being blocked:
@@ -52,7 +52,7 @@ $ sbx policy allow network -g "**"
If `sbx policy allow` doesn't unblock the request, your organization may
manage sandbox policies centrally and take precedence over local rules. See
[Organization governance](security/governance.md).
[Organization governance](governance/org.md).
## SSH and other non-HTTP connections fail
@@ -106,7 +106,7 @@ If credentials are configured correctly but API calls still fail, check
the `transparent` proxy don't get credential injection. This can happen when a
client inside the sandbox (such as a process in a Docker container) isn't
configured to use the forward proxy. See
[Monitoring network activity](security/policy.md#monitoring)
[Monitoring network activity](governance/monitoring.md)
for details.
## Docker build export fails with an ownership error
+1 -1
View File
@@ -442,7 +442,7 @@ needs:
- [Custom templates and kits](customize/) let you package reusable agent
configurations, MCP servers, base images, and per-project policies. Every
developer pulls them down with their workspace.
- [Organization governance](security/governance.md) lets admins define
- [Organization governance](governance/org.md) lets admins define
network and filesystem rules in the Docker Admin Console. The rules apply
across every developer's sandboxes and take precedence over local policy.
Available on a separate paid subscription.
@@ -0,0 +1,759 @@
openapi: "3.1.0"
info:
title: Docker AI Governance Policy API
version: "1"
description: |
HTTP+JSON API for managing Docker governance policies and rules.
**Resource model.** An organization owns one or more policies. Each policy
contains a list of rules grouped into a single domain: either `network` or
`filesystem`. A policy's domain is derived from its rule actions; mixing
domains within a single policy is not permitted.
**Lifecycle.** Create a policy with CreatePolicy, then add rules with
CreateRule. Rules can be updated in place with UpdateRule or removed with
DeleteRule. Deleting all rules does not delete the policy itself.
**Rule evaluation.** All rules in a policy are tested against every request.
`deny` always wins: if any rule matches with `decision: deny`, the request
is denied regardless of any `allow` rules.
**Enforcement.** Organization policies take precedence over local sandbox
policies and cannot be overridden by individual users.
**Propagation.** Policy changes take up to five minutes to reach developer
machines after being written.
See https://docs.docker.com/ai/sandboxes/governance/ for product
documentation.
contact:
name: Docker
url: https://www.docker.com/products/ai-governance/
tags:
- name: policies
description: Policy lifecycle management
- name: rules
description: Rule management within an allowlist policy
servers:
- url: https://hub.docker.com/v2
security:
- bearerAuth: []
paths:
/orgs/{org_name}/governance/policies:
parameters:
- $ref: "#/components/parameters/OrgName"
get:
operationId: listPolicies
tags: [policies]
summary: List policies
description: >
Returns a shallow summary of all policies for the org.
The rule set is not included; use GetPolicy to fetch the full object.
responses:
"200":
description: Array of policy summaries. Rule sets are not included; use GetPolicy to fetch a full policy.
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/PolicySummary"
examples:
default:
value:
- id: pol_06evsmp24r1pg71cm8500546pkbn
name: "Security Research — hardened"
org: my-org
scope:
profiles: [security]
created_at: "2026-04-22T00:00:00Z"
updated_at: "2026-04-22T00:00:00Z"
type: allowlist_v0
"401":
$ref: "#/components/responses/Unauthenticated"
"403":
$ref: "#/components/responses/PermissionDenied"
"500":
$ref: "#/components/responses/InternalError"
post:
operationId: createPolicy
tags: [policies]
summary: Create policy
description: >
Creates a new policy with an empty rule set. Rules are added separately
via the rules sub-resource.
requestBody:
description: Policy name and optional scope.
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreatePolicyRequest"
examples:
default:
value:
name: "Security Research — hardened"
scope:
profiles: [security]
responses:
"201":
description: Policy created. Returns the new policy without its rule set.
content:
application/json:
schema:
$ref: "#/components/schemas/Policy"
examples:
default:
value:
id: pol_06evsmp24r1pg71cm8500546pkbn
name: "Security Research — hardened"
org: my-org
scope:
profiles: [security]
created_at: "2026-04-22T00:00:00Z"
updated_at: "2026-04-22T00:00:00Z"
"400":
$ref: "#/components/responses/InvalidArgument"
"401":
$ref: "#/components/responses/Unauthenticated"
"403":
$ref: "#/components/responses/PermissionDenied"
"409":
$ref: "#/components/responses/Conflict"
"500":
$ref: "#/components/responses/InternalError"
/orgs/{org_name}/governance/policies/{policy_id}:
parameters:
- $ref: "#/components/parameters/OrgName"
- $ref: "#/components/parameters/PolicyID"
get:
operationId: getPolicy
tags: [policies]
summary: Get policy
description: Returns the full policy including its `allowlist_v0` rule set.
responses:
"200":
description: Full policy including its `allowlist_v0` rule set.
content:
application/json:
schema:
$ref: "#/components/schemas/Policy"
examples:
default:
value:
id: pol_06evsmp24r1pg71cm8500546pkbn
name: "Security Research — hardened"
org: my-org
scope:
profiles: [security]
created_at: "2026-04-22T00:00:00Z"
updated_at: "2026-04-22T00:00:00Z"
allowlist_v0:
domain: network
rules:
- id: rule_06evsm9qjm1pdsk0a8nkfaxy7jna
name: allow research mirrors
actions: [connect:tcp, connect:udp]
resources: [research.mitre.org, cve.mitre.org]
decision: allow
"401":
$ref: "#/components/responses/Unauthenticated"
"403":
$ref: "#/components/responses/PermissionDenied"
"404":
$ref: "#/components/responses/NotFound"
"500":
$ref: "#/components/responses/InternalError"
/orgs/{org_name}/governance/policies/{policy_id}/rules:
parameters:
- $ref: "#/components/parameters/OrgName"
- $ref: "#/components/parameters/PolicyID"
post:
operationId: createRule
tags: [rules]
summary: Create rule
description: |
Adds a rule to the policy's rule set. All rules in a policy must share
the same domain (network or filesystem); mixing domains is rejected.
**Network** actions: `connect:tcp`, `connect:udp`. Resources are
hostnames (for example, `example.com`), wildcard subdomains (`*.example.com`
for one level, `**.example.com` for any depth), hostnames with an optional
port (for example, `example.com:443`), or CIDRs in IPv4 or IPv6 notation
(for example, `10.0.0.0/8` or `2001:db8::/32`).
**Filesystem** actions: `read`, `write`. Resources are paths (for example,
`/data`). Use `*` to match within a single path segment and `**` to match
recursively across segments (for example, `/data/**`).
Changes may take up to five minutes to reach developer machines.
requestBody:
description: Rule definition including actions, resources, and decision.
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateRuleRequest"
examples:
network:
summary: Network rule
value:
name: allow research mirrors
actions: [connect:tcp, connect:udp]
resources: [research.mitre.org, cve.mitre.org]
decision: allow
filesystem:
summary: Filesystem rule
value:
name: allow data directory
actions: [read, write]
resources: [/data]
decision: allow
responses:
"201":
description: Rule created and added to the policy's rule set.
content:
application/json:
schema:
$ref: "#/components/schemas/Rule"
examples:
network:
summary: Network rule
value:
id: rule_06evsm9qjm1pdsk0a8nkfaxy7jna
name: allow research mirrors
actions: [connect:tcp, connect:udp]
resources: [research.mitre.org, cve.mitre.org]
decision: allow
filesystem:
summary: Filesystem rule
value:
id: rule_07fwtnr0kn2qetl1b9olfbyz8kob
name: allow data directory
actions: [read, write]
resources: [/data]
decision: allow
"400":
$ref: "#/components/responses/InvalidArgument"
"401":
$ref: "#/components/responses/Unauthenticated"
"403":
$ref: "#/components/responses/PermissionDenied"
"404":
$ref: "#/components/responses/NotFound"
"500":
$ref: "#/components/responses/InternalError"
/orgs/{org_name}/governance/policies/{policy_id}/rules/{rule_id}:
parameters:
- $ref: "#/components/parameters/OrgName"
- $ref: "#/components/parameters/PolicyID"
- $ref: "#/components/parameters/RuleID"
patch:
operationId: updateRule
tags: [rules]
summary: Update rule
description: |
Partially updates a rule using JSON Merge Patch semantics (RFC 7396).
Only fields present in the request body are updated; absent fields are
left unchanged. Returns the rule in both its old and new states.
Changing `actions` across domains (for example, from network actions to
filesystem actions) is rejected. Changes may take up to five minutes to
reach developer machines.
requestBody:
description: Fields to update. Absent fields are left unchanged.
required: true
content:
application/merge-patch+json:
schema:
$ref: "#/components/schemas/UpdateRuleRequest"
examples:
default:
value:
resources: ["research.mitre.org"]
responses:
"200":
description: Rule updated, returns old and new states.
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateRuleResponse"
examples:
default:
value:
old:
id: rule_06evsm9qjm1pdsk0a8nkfaxy7jna
name: allow research mirrors
actions: [connect:tcp, connect:udp]
resources: [research.mitre.org, cve.mitre.org]
decision: allow
new:
id: rule_06evsm9qjm1pdsk0a8nkfaxy7jna
name: allow research mirrors
actions: [connect:tcp, connect:udp]
resources: [research.mitre.org]
decision: allow
"400":
$ref: "#/components/responses/InvalidArgument"
"401":
$ref: "#/components/responses/Unauthenticated"
"403":
$ref: "#/components/responses/PermissionDenied"
"404":
$ref: "#/components/responses/NotFound"
"500":
$ref: "#/components/responses/InternalError"
delete:
operationId: deleteRule
tags: [rules]
summary: Delete rule
description: |
Deletes a rule from the policy. Returns the deleted rule. Changes may
take up to five minutes to reach developer machines.
responses:
"200":
description: Rule deleted, returns the deleted rule.
content:
application/json:
schema:
$ref: "#/components/schemas/DeleteRuleResponse"
examples:
default:
value:
deleted:
id: rule_06evsm9qjm1pdsk0a8nkfaxy7jna
name: allow research mirrors
actions: [connect:tcp, connect:udp]
resources: [research.mitre.org, cve.mitre.org]
decision: allow
"401":
$ref: "#/components/responses/Unauthenticated"
"403":
$ref: "#/components/responses/PermissionDenied"
"404":
$ref: "#/components/responses/NotFound"
"500":
$ref: "#/components/responses/InternalError"
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: |
Short-lived JWT obtained by exchanging Docker Hub credentials at
`POST https://hub.docker.com/v2/users/login`. Pass the JWT in the
`Authorization: Bearer <token>` header. Tokens expire after a short
period; request a fresh one when you receive a `401`.
The `password` field of the login request accepts any of the following
credential types:
| Type | Format | Notes |
|------|--------|-------|
| Password | Plain text | Your Docker Hub account password. |
| Personal Access Token (PAT) | `dckr_pat_*` | Recommended over passwords. Create one under Account Settings → Security. |
| Organization Access Token (OAT) | `dckr_oat_*` | Scoped to an organization. Create one under Organization Settings → Access Tokens. |
PAT and OAT strings can't be used directly as a bearer token. They must
be exchanged at the login endpoint first.
See [Docker Hub authentication](https://docs.docker.com/reference/api/hub/latest/#tag/authentication-api/operation/AuthCreateAccessToken)
for full details.
parameters:
OrgName:
name: org_name
in: path
required: true
description: Docker Hub organization name.
schema:
type: string
examples:
default:
value: my-org
PolicyID:
name: policy_id
in: path
required: true
description: Unique policy identifier.
schema:
type: string
examples:
default:
value: pol_06evsmp24r1pg71cm8500546pkbn
RuleID:
name: rule_id
in: path
required: true
description: Unique rule identifier within the policy.
schema:
type: string
examples:
default:
value: rule_06evsm9qjm1pdsk0a8nkfaxy7jna
schemas:
PolicySummary:
type: object
description: Shallow policy representation returned by ListPolicies. Excludes the rule set.
required: [id, name, org, scope, created_at, updated_at, type]
properties:
id:
type: string
examples:
- pol_06evsmp24r1pg71cm8500546pkbn
name:
type: string
description: Human-readable label, unique within the organization.
examples:
- "Security Research — hardened"
org:
type: string
examples:
- my-org
scope:
$ref: "#/components/schemas/Scope"
created_at:
type: string
format: date-time
examples:
- "2026-04-22T00:00:00Z"
updated_at:
type: string
format: date-time
examples:
- "2026-04-22T00:00:00Z"
type:
type: string
description: >
Identifies the rule-set format. Always `allowlist_v0`, corresponding
to the `allowlist_v0` property on the full Policy object.
examples:
- allowlist_v0
Policy:
type: object
description: Full policy representation including the allowlist rule set.
required: [id, name, org, scope, created_at, updated_at]
properties:
id:
type: string
examples:
- pol_06evsmp24r1pg71cm8500546pkbn
name:
type: string
description: Human-readable label, unique within the organization.
examples:
- "Security Research — hardened"
org:
type: string
examples:
- my-org
scope:
$ref: "#/components/schemas/Scope"
created_at:
type: string
format: date-time
examples:
- "2026-04-22T00:00:00Z"
updated_at:
type: string
format: date-time
examples:
- "2026-04-22T00:00:00Z"
allowlist_v0:
$ref: "#/components/schemas/AllowlistV0"
Scope:
type: object
description: Restricts the policy to specific profiles or teams. Empty or absent lists mean the policy applies org-wide.
properties:
profiles:
type: array
items:
type: string
examples:
- ["security"]
teams:
type: array
items:
type: string
AllowlistV0:
type: object
description: |
Network or filesystem allowlist containing a list of rules. Present on
Policy when `PolicySummary.type` is `allowlist_v0`; omitted when the
policy has no rules yet. All rules in an allowlist share the same domain.
All rules are evaluated on every request: `deny` always wins over `allow`.
required: [rules]
properties:
domain:
type: string
description: >
The access-control domain shared by all rules in this allowlist.
Derived from rule actions: network actions (`connect:tcp`,
`connect:udp`) produce `network`; filesystem actions (`read`,
`write`) produce `filesystem`. Present when `rules` is non-empty;
absent when the allowlist has no rules.
enum: [network, filesystem]
examples:
- network
rules:
type: array
items:
$ref: "#/components/schemas/Rule"
Rule:
type: object
description: A single allow or deny rule within an allowlist policy.
required: [id, name, actions, resources, decision]
properties:
id:
type: string
examples:
- rule_06evsm9qjm1pdsk0a8nkfaxy7jna
name:
type: string
description: Human-readable label for the rule.
examples:
- allow research mirrors
actions:
$ref: "#/components/schemas/RuleActions"
resources:
$ref: "#/components/schemas/RuleResources"
decision:
$ref: "#/components/schemas/RuleDecision"
CreatePolicyRequest:
type: object
description: Fields required to create a new policy.
required: [name]
properties:
name:
type: string
description: Policy name, unique within the organization.
examples:
- "Security Research — hardened"
scope:
$ref: "#/components/schemas/Scope"
CreateRuleRequest:
type: object
description: Fields required to create a new rule within a policy's rule set.
required: [name, actions, resources, decision]
properties:
name:
type: string
description: Human-readable label for the rule.
examples:
- allow research mirrors
actions:
$ref: "#/components/schemas/RuleActions"
resources:
$ref: "#/components/schemas/RuleResources"
decision:
$ref: "#/components/schemas/RuleDecision"
UpdateRuleRequest:
type: object
description: JSON Merge Patch (RFC 7396). Only present fields are updated.
properties:
name:
type: string
description: Human-readable label for the rule.
examples:
- allow research mirrors
actions:
$ref: "#/components/schemas/RuleActions"
resources:
$ref: "#/components/schemas/RuleResources"
decision:
$ref: "#/components/schemas/RuleDecision"
UpdateRuleResponse:
type: object
description: The rule state before and after the update.
required: [old, new]
properties:
old:
$ref: "#/components/schemas/Rule"
new:
$ref: "#/components/schemas/Rule"
DeleteRuleResponse:
type: object
description: The deleted rule.
required: [deleted]
properties:
deleted:
$ref: "#/components/schemas/Rule"
RuleActions:
type: array
items:
type: string
enum: [connect:tcp, connect:udp, read, write]
minItems: 1
description: >
Network actions: `connect:tcp`, `connect:udp`.
Filesystem actions: `read`, `write`.
All actions in a rule must belong to the same domain; mixing network
and filesystem actions in one rule is rejected.
examples:
- ["connect:tcp", "connect:udp"]
RuleResources:
type: array
items:
type: string
minItems: 1
description: >
Network domain: hostnames (for example, `example.com`), wildcard
subdomains (`*.example.com` or `**.example.com`), hostnames with port
(for example, `example.com:443`), or CIDRs in IPv4 or IPv6 notation
(for example, `10.0.0.0/8` or `2001:db8::/32`). Filesystem domain:
paths (for example, `/data`); `*` matches within one path segment,
`**` matches recursively (for example, `/data/**`).
examples:
- ["research.mitre.org", "cve.mitre.org"]
RuleDecision:
type: string
enum: [allow, deny]
description: >
Outcome applied when this rule matches a request. `deny` always
wins: if any rule in the policy matches with `decision: deny`, the
request is denied even if other rules match with `decision: allow`.
examples:
- allow
Error:
type: object
description: Error envelope returned on all non-2xx responses.
required: [error]
properties:
error:
type: object
description: Error detail.
required: [code, message]
examples:
- code: not_found
message: policy not found
properties:
code:
type: string
description: >
Machine-readable error code. `not_found`: the requested resource
does not exist. `conflict`: a resource with the same name already
exists. `invalid_argument`: the request body is malformed or
fails validation. `unauthenticated`: missing or invalid
credentials. `permission_denied`: the caller lacks the required
permission, or the org is not entitled to use governance.
`unimplemented`: the endpoint or feature is not yet available.
`internal`: unexpected server error.
enum:
- not_found
- conflict
- invalid_argument
- unauthenticated
- permission_denied
- unimplemented
- internal
message:
type: string
responses:
NotFound:
description: Not found
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
examples:
default:
value:
error:
code: not_found
message: policy not found
Conflict:
description: Conflict
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
examples:
default:
value:
error:
code: conflict
message: policy name already in use
InvalidArgument:
description: Bad request
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
examples:
default:
value:
error:
code: invalid_argument
message: "name is required"
Unauthenticated:
description: Missing or invalid credentials
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
examples:
default:
value:
error:
code: unauthenticated
message: unauthenticated
PermissionDenied:
description: >
Caller lacks the required permission for this org, or the org is not
entitled to use governance.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
examples:
default:
value:
error:
code: permission_denied
message: permission denied
InternalError:
description: Internal server error
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
examples:
default:
value:
error:
code: internal
message: internal error
@@ -0,0 +1,9 @@
---
layout: api-reference
title: Docker AI Governance API
linkTitle: AI Governance
description: HTTP API reference for managing Docker AI Governance policies and rules programmatically.
keywords: docker sandboxes, governance API, policy API, organization policy, REST API, openapi
aliases:
- /ai/sandboxes/governance/api/
---
+139
View File
@@ -0,0 +1,139 @@
{{/* Build a curl command for an OpenAPI operation.
Input dict:
api — unmarshaled spec root
op — operation object
method — http method (lowercase string)
path — path string (e.g. "/orgs/{org_name}/policies")
sharedParams — already-resolved path-level parameters
Output: a multi-line curl command string (line-continued with " \\\n ").
Path placeholders are filled from parameter examples when available;
unresolved path params fall back to `<name>`. Query params without an
example are omitted. Bearer-auth schemes contribute an Authorization
header with the literal `$TOKEN` placeholder.
*/}}
{{- $api := .api -}}
{{- $op := .op -}}
{{- $method := .method -}}
{{- $path := .path -}}
{{- $sharedParams := .sharedParams -}}
{{/* Combine path-level + operation-level parameters, resolving $refs. */}}
{{- $params := $sharedParams -}}
{{- with index $op "parameters" -}}
{{- range . -}}
{{- $p := partial "api-ref/resolve.html" (dict "api" $api "node" .) -}}
{{- $params = $params | append $p -}}
{{- end -}}
{{- end -}}
{{/* Substitute path placeholders. */}}
{{- $url := $path -}}
{{- range $params -}}
{{- if eq .in "path" -}}
{{- $val := printf "<%s>" .name -}}
{{- with .example -}}{{- $val = . | string -}}{{- end -}}
{{- with .examples -}}
{{- $picked := false -}}
{{- with index . "default" -}}
{{- with .value -}}
{{- $val = . | string -}}{{- $picked = true -}}
{{- end -}}
{{- end -}}
{{- if not $picked -}}
{{- range . -}}
{{- if not $picked -}}
{{- with .value -}}
{{- $val = . | string -}}{{- $picked = true -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- $url = replace $url (printf "{%s}" .name) $val -}}
{{- end -}}
{{- end -}}
{{/* Append query string from query parameters that have examples. */}}
{{- $qs := slice -}}
{{- range $params -}}
{{- if eq .in "query" -}}
{{- $val := "" -}}
{{- with .example -}}{{- $val = . | string -}}{{- end -}}
{{- with .examples -}}
{{- $picked := false -}}
{{- with index . "default" -}}
{{- with .value -}}
{{- $val = . | string -}}{{- $picked = true -}}
{{- end -}}
{{- end -}}
{{- if not $picked -}}
{{- range . -}}
{{- if not $picked -}}
{{- with .value -}}
{{- $val = . | string -}}{{- $picked = true -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if $val -}}
{{- $qs = $qs | append (printf "%s=%s" .name $val) -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if $qs -}}{{- $url = printf "%s?%s" $url (delimit $qs "&") -}}{{- end -}}
{{/* Prepend base URL from the first server entry. */}}
{{- $base := "" -}}
{{- with index $api.servers 0 -}}{{- $base = .url -}}{{- end -}}
{{- $fullURL := printf "%s%s" $base $url -}}
{{- $lines := slice (printf "curl -X %s '%s'" (upper $method) $fullURL) -}}
{{/* Bearer auth header if the spec defines a bearer scheme. */}}
{{- $hasBearer := false -}}
{{- with $api.components -}}
{{- with .securitySchemes -}}
{{- range . -}}
{{- if and (eq .type "http") (eq .scheme "bearer") -}}
{{- $hasBearer = true -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if $hasBearer -}}
{{- $lines = $lines | append "-H 'Authorization: Bearer $TOKEN'" -}}
{{- end -}}
{{/* Request body example. Picks the first content type declared. */}}
{{- with $op.requestBody -}}
{{- $body := partial "api-ref/resolve.html" (dict "api" $api "node" .) -}}
{{- $ct := "" -}}
{{- $media := dict -}}
{{- range $name, $m := $body.content -}}
{{- if not $ct -}}{{- $ct = $name -}}{{- $media = $m -}}{{- end -}}
{{- end -}}
{{- if $ct -}}
{{- $lines = $lines | append (printf "-H 'Content-Type: %s'" $ct) -}}
{{- $example := "" -}}
{{- with $media.examples -}}
{{- with index . "default" -}}
{{- with .value -}}{{- $example = . | jsonify -}}{{- end -}}
{{- end -}}
{{- if not $example -}}
{{- range . -}}
{{- if not $example -}}
{{- with .value -}}{{- $example = . | jsonify -}}{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- if $example -}}
{{- $lines = $lines | append (printf "-d '%s'" $example) -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- return (delimit $lines " \\\n ") -}}
+86
View File
@@ -0,0 +1,86 @@
{{/* Right-rail navigation for an OpenAPI reference page.
Input: the unmarshaled spec root (plain map from transform.Unmarshal).
Lists Authentication, every tag with its operations (linking to the same
anchors the operation cards expose), and the Schemas section heading.
*/}}
{{- $api := . -}}
{{- $paths := $api.paths -}}
{{- $methods := slice "get" "post" "put" "patch" "delete" -}}
{{- $methodColors := dict
"get" "text-blue-700 dark:text-blue-300"
"post" "text-green-700 dark:text-green-300"
"put" "text-yellow-700 dark:text-yellow-300"
"patch" "text-yellow-700 dark:text-yellow-300"
"delete" "text-red-700 dark:text-red-300"
-}}
<p
class="mb-3 text-xs font-semibold tracking-wider text-gray-500 uppercase dark:text-gray-400"
>
On this page
</p>
<ul class="space-y-1">
{{- with $api.components }}
{{- if .securitySchemes }}
<li>
<a
href="#authentication"
class="block py-0.5 text-gray-700 hover:text-blue-600 dark:text-gray-300 dark:hover:text-blue-400"
>
Authentication
</a>
</li>
{{- end }}
{{- end }}
{{- range $api.tags }}
{{- $tagName := .name }}
<li class="mt-3">
<a
href="#tag-{{ $tagName | urlize }}"
class="block py-0.5 text-xs font-semibold tracking-wider text-gray-500 uppercase hover:text-blue-600 dark:text-gray-400 dark:hover:text-blue-400"
>
{{ $tagName | title }}
</a>
<ul class="mt-1 space-y-0.5">
{{- range $path, $item := $paths }}
{{- range $methods }}
{{- $method := . }}
{{- with index $item $method }}
{{- $op := . }}
{{- if in $op.tags $tagName }}
{{- $label := $op.summary | default $op.operationId | default $path }}
<li>
<a
href="#operation-{{ $op.operationId }}"
class="flex items-baseline gap-2 py-0.5 text-gray-700 hover:text-blue-600 dark:text-gray-300 dark:hover:text-blue-400"
>
<span
class="{{ index $methodColors $method }} w-12 flex-none font-mono text-[10px] font-bold uppercase"
>{{ upper $method }}</span
>
<span class="min-w-0 truncate">{{ $label }}</span>
</a>
</li>
{{- end }}
{{- end }}
{{- end }}
{{- end }}
</ul>
</li>
{{- end }}
{{- with $api.components }}
{{- if .schemas }}
<li class="mt-3">
<a
href="#schemas"
class="block py-0.5 text-xs font-semibold tracking-wider text-gray-500 uppercase hover:text-blue-600 dark:text-gray-400 dark:hover:text-blue-400"
>
Schemas
</a>
</li>
{{- end }}
{{- end }}
</ul>
+26
View File
@@ -0,0 +1,26 @@
{{/* Resolve a possibly-$ref'd OpenAPI node against the spec root.
Input dict:
api — the unmarshaled spec root (plain map from transform.Unmarshal)
node — the node to resolve; may be a map, slice, primitive, or nil
Returns:
- if node is a map containing a "$ref" key, returns the target node
reached by walking the JSON-Pointer fragment (only internal "#/" refs
are supported)
- otherwise returns node unchanged
*/}}
{{- $node := .node -}}
{{- if reflect.IsMap $node -}}
{{- with index $node "$ref" -}}
{{- if hasPrefix . "#/" -}}
{{- $parts := after 1 (split . "/") -}}
{{- $cur := $.api -}}
{{- range $parts -}}
{{- $cur = index $cur . -}}
{{- end -}}
{{- $node = $cur -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- return $node -}}
+700
View File
@@ -0,0 +1,700 @@
{{/* Wide-mode API reference layout.
Reads the colocated *.yaml Page Resource, unmarshals it into a plain Hugo
map with transform.Unmarshal, and renders the OpenAPI 3 spec inline using
Docker docs styling. Overrides baseof's "main" block so the article spans
the full available width; the built-in right-rail TOC is replaced with a
custom endpoint navigator (partials/api-ref/nav.html) sticky-pinned to
the viewport.
$ref nodes are resolved on the fly through partials/api-ref/resolve.html;
no oapi-codegen or third-party JS runtime is involved.
*/}}
{{ define "main" }}
{{- $specRes := .Resources.GetMatch "*.yaml" -}}
{{- if not $specRes -}}
{{- errorf "api-reference: no .yaml resource found alongside %s" .File.Path -}}
{{- end -}}
{{- $api := $specRes | transform.Unmarshal -}}
<div class="flex w-full min-w-0 gap-8">
<div class="min-w-0 flex-1">
{{ partial "breadcrumbs.html" . }}
<h1 data-pagefind-weight="10" class="mb-2 text-3xl font-bold">
{{ .Title | safeHTML }}
</h1>
{{ with .Description }}
<p class="text-gray-600 dark:text-gray-400">{{ . }}</p>
{{ end }}
{{ template "api-ref-meta" (dict "api" $api "spec" $specRes) }}
{{ partialCached "md-dropdown.html" "-" "-" }}
{{ template "api-ref-overview" $api }}
{{ template "api-ref-auth" $api }}
{{ template "api-ref-tags" $api }}
{{ template "api-ref-schemas" $api }}
</div>
<aside class="hidden w-56 flex-none lg:block">
<nav
class="sticky top-20 max-h-[calc(100vh-6rem)] overflow-y-auto text-sm"
>
{{ partial "api-ref/nav.html" $api }}
</nav>
</aside>
</div>
{{ end }}
{{/* ── Meta strip: version, base URL, download link ──────────────────────── */}}
{{ define "api-ref-meta" }}
{{- $api := .api -}}
{{- $spec := .spec -}}
<div class="mt-4 flex flex-wrap items-center gap-x-6 gap-y-2 text-sm">
{{- with $api.info }}
{{- with .version }}
<div>
<span class="font-medium text-gray-500 dark:text-gray-400"
>Version</span
>
<code
class="ml-1 rounded bg-gray-100 px-1.5 py-0.5 text-xs dark:bg-gray-800"
>{{ . }}</code
>
</div>
{{- end }}
{{- end }}
{{- range $api.servers }}
<div>
<span class="font-medium text-gray-500 dark:text-gray-400"
>Base URL</span
>
<code
class="ml-1 rounded bg-gray-100 px-1.5 py-0.5 text-xs text-blue-600 dark:bg-gray-800 dark:text-blue-400"
>{{ .url }}</code
>
</div>
{{- end }}
<a
href="{{ $spec.Permalink }}"
class="ml-auto text-blue-600 hover:underline dark:text-blue-400"
>Download OpenAPI specification</a
>
</div>
{{ end }}
{{/* ── Overview ─────────────────────────────────────────────────────────── */}}
{{ define "api-ref-overview" }}
{{- $api := . -}}
{{- with $api.info }}
{{- with .description }}
<section class="mb-10">
<div class="prose dark:prose-invert max-w-none">
{{ . | markdownify }}
</div>
</section>
{{- end }}
{{- end }}
{{ end }}
{{/* ── Authentication ───────────────────────────────────────────────────── */}}
{{ define "api-ref-auth" }}
{{- $api := . -}}
{{- with $api.components }}
{{- with .securitySchemes }}
<section class="mb-12">
<h2
id="authentication"
class="mb-4 scroll-mt-24 text-2xl font-semibold"
>
Authentication
</h2>
{{- range $name, $scheme := . }}
<div class="mb-4 flex flex-wrap items-center gap-x-4 gap-y-1 text-sm">
<code
class="rounded bg-gray-100 px-1.5 py-0.5 text-xs dark:bg-gray-800"
>{{ $name }}</code
>
{{- with $scheme.type }}
<span class="text-gray-500 dark:text-gray-400"
>type: <code class="text-xs">{{ . }}</code></span
>
{{- end }}
{{- with $scheme.scheme }}
<span class="text-gray-500 dark:text-gray-400"
>scheme: <code class="text-xs">{{ . }}</code></span
>
{{- end }}
{{- with $scheme.bearerFormat }}
<span class="text-gray-500 dark:text-gray-400"
>bearer format: <code class="text-xs">{{ . }}</code></span
>
{{- end }}
</div>
{{- with $scheme.description }}
<div class="prose dark:prose-invert max-w-none">
{{ . | markdownify }}
</div>
{{- end }}
{{- end }}
</section>
{{- end }}
{{- end }}
{{ end }}
{{/* ── Endpoints grouped by tag ─────────────────────────────────────────── */}}
{{ define "api-ref-tags" }}
{{- $api := . -}}
{{- $paths := $api.paths -}}
{{- $methods := slice "get" "post" "put" "patch" "delete" -}}
{{- range $api.tags }}
{{- $tagName := .name -}}
<section class="mb-14">
<h2
id="tag-{{ $tagName | urlize }}"
class="mb-1 scroll-mt-24 text-2xl font-semibold"
>
{{ $tagName | title }}
</h2>
{{- with .description }}
<p class="mb-6 text-sm text-gray-500 dark:text-gray-400">{{ . }}</p>
{{- end }}
{{- range $path, $item := $paths }}
{{/* Resolve shared (path-level) parameters once. */}}
{{- $sharedParams := slice -}}
{{- with index $item "parameters" -}}
{{- range . -}}
{{- $p := partial "api-ref/resolve.html" (dict "api" $api "node" .) -}}
{{- $sharedParams = $sharedParams | append $p -}}
{{- end -}}
{{- end -}}
{{- range $methods -}}
{{- $method := . -}}
{{- with index $item $method -}}
{{- $op := . -}}
{{- if in $op.tags $tagName -}}
{{ template "api-ref-operation" (dict
"api" $api
"op" $op
"method" $method
"path" $path
"sharedParams" $sharedParams
)
}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end }}
</section>
{{- end }}
{{ end }}
{{/* ── Single operation card ────────────────────────────────────────────── */}}
{{ define "api-ref-operation" }}
{{- $api := .api -}}
{{- $op := .op -}}
{{- $method := .method -}}
{{- $path := .path -}}
{{- $sharedParams := .sharedParams -}}
{{- $methodColors := dict
"get" "bg-blue-100 text-blue-800 dark:bg-blue-900/40 dark:text-blue-300"
"post" "bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-300"
"put" "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/40 dark:text-yellow-300"
"patch" "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/40 dark:text-yellow-300"
"delete" "bg-red-100 text-red-800 dark:bg-red-900/40 dark:text-red-300"
-}}
{{- $anchor := "" -}}
{{- with $op.operationId -}}
{{- $anchor = printf "operation-%s" . -}}
{{- end -}}
{{- $curl := partial "api-ref/curl.html" (dict
"api" $api
"op" $op
"method" $method
"path" $path
"sharedParams" $sharedParams
)
-}}
<div
{{ with $anchor }}id="{{ . }}"{{ end }}
class="mb-8 scroll-mt-24 overflow-hidden rounded-lg border border-gray-200 dark:border-gray-700"
>
<div
class="flex flex-wrap items-center gap-3 border-b border-gray-200 bg-gray-50 px-4 py-3 dark:border-gray-700 dark:bg-gray-900"
>
<span
class="{{ index $methodColors $method }} inline-block rounded px-2 py-0.5 font-mono text-xs font-bold uppercase"
>{{ upper $method }}</span
>
<code class="font-mono text-sm">{{ $path }}</code>
<div class="ml-auto flex items-center gap-3">
{{- with $op.summary }}
<span class="text-sm font-medium text-gray-700 dark:text-gray-300"
>{{ . }}</span
>
{{- end }}
<button
x-data="{ copied: false }"
@click="navigator.clipboard.writeText({{ $curl | jsonify }}).then(() => { copied = true; setTimeout(() => copied = false, 2000) })"
:title="copied ? 'Copied!' : 'Copy as cURL'"
class="inline-flex cursor-pointer items-center gap-1 rounded border border-gray-200 bg-white px-2 py-1 text-xs font-medium text-gray-700 transition-colors hover:bg-gray-50 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
>
<span class="icon-svg icon-sm" x-show="!copied" x-cloak>
{{ partialCached "icon" "document-duplicate" "document-duplicate" }}
</span>
<span
class="icon-svg icon-sm text-green-600 dark:text-green-400"
x-show="copied"
x-cloak
>
{{ partialCached "icon" "check-circle" "check-circle" }}
</span>
<span x-text="copied ? 'Copied' : 'cURL'"></span>
</button>
</div>
</div>
<div class="p-5">
{{- with $op.description }}
<div class="prose dark:prose-invert prose-sm mb-5 max-w-none">
{{ . | markdownify }}
</div>
{{- end }}
{{ template "api-ref-params" (dict
"api" $api
"op" $op
"sharedParams" $sharedParams
)
}}
{{ template "api-ref-body" (dict "api" $api "op" $op) }}
{{ template "api-ref-responses" (dict "api" $api "op" $op) }}
</div>
</div>
{{ end }}
{{/* ── Parameters table (path + operation; in: path/query/header) ───────── */}}
{{ define "api-ref-params" }}
{{- $api := .api -}}
{{- $op := .op -}}
{{- $params := .sharedParams -}}
{{- with index $op "parameters" -}}
{{- range . -}}
{{- $p := partial "api-ref/resolve.html" (dict "api" $api "node" .) -}}
{{- $params = $params | append $p -}}
{{- end -}}
{{- end -}}
{{- if $params }}
<div class="mb-5">
<h4
class="mb-2 text-xs font-semibold tracking-wider text-gray-500 uppercase dark:text-gray-400"
>
Parameters
</h4>
<table class="w-full border-collapse text-sm">
<thead>
<tr class="border-b border-gray-200 dark:border-gray-700">
<th
class="w-36 py-1.5 pr-4 text-left font-medium text-gray-700 dark:text-gray-300"
>
Name
</th>
<th
class="w-16 py-1.5 pr-4 text-left font-medium text-gray-700 dark:text-gray-300"
>
In
</th>
<th
class="w-20 py-1.5 pr-4 text-left font-medium text-gray-700 dark:text-gray-300"
>
Type
</th>
<th
class="w-20 py-1.5 pr-4 text-left font-medium text-gray-700 dark:text-gray-300"
>
Required
</th>
<th
class="py-1.5 text-left font-medium text-gray-700 dark:text-gray-300"
>
Description
</th>
</tr>
</thead>
<tbody>
{{- range $params }}
<tr
class="border-b border-gray-100 last:border-0 dark:border-gray-800"
>
<td class="py-1.5 pr-4 align-top">
<code class="rounded bg-gray-100 px-1 text-xs dark:bg-gray-800"
>{{ .name }}</code
>
</td>
<td
class="py-1.5 pr-4 align-top text-xs text-gray-500 dark:text-gray-400"
>
{{ .in }}
</td>
<td
class="py-1.5 pr-4 align-top font-mono text-xs text-gray-500 dark:text-gray-400"
>
{{ template "api-ref-type" (dict "api" $api "schema" .schema) }}
</td>
<td
class="py-1.5 pr-4 align-top text-xs text-gray-500 dark:text-gray-400"
>
{{ if .required }}yes{{ else }}no{{ end }}
</td>
<td
class="py-1.5 align-top text-sm text-gray-600 dark:text-gray-400"
>
{{ with .description }}{{ . | markdownify }}{{ end }}
</td>
</tr>
{{- end }}
</tbody>
</table>
</div>
{{- end }}
{{ end }}
{{/* ── Request body ─────────────────────────────────────────────────────── */}}
{{ define "api-ref-body" }}
{{- $api := .api -}}
{{- $op := .op -}}
{{- with $op.requestBody }}
{{- $body := partial "api-ref/resolve.html" (dict "api" $api "node" .) }}
<div class="mb-5">
<h4
class="mb-2 text-xs font-semibold tracking-wider text-gray-500 uppercase dark:text-gray-400"
>
Request body
</h4>
{{- with $body.description }}
<p class="mb-2 text-sm text-gray-600 dark:text-gray-400">
{{ . | markdownify }}
</p>
{{- end }}
{{- $ct := "" -}}
{{- $media := dict -}}
{{- range $name, $m := $body.content -}}
{{- if not $ct -}}{{- $ct = $name -}}{{- $media = $m -}}{{- end -}}
{{- end -}}
{{- if $ct }}
<p class="mb-2 text-xs text-gray-500 dark:text-gray-400">
Content type:
<code class="rounded bg-gray-100 px-1 dark:bg-gray-800"
>{{ $ct }}</code
>
</p>
{{ template "api-ref-schema-link" (dict "api" $api "schema" $media.schema) }}
{{ template "api-ref-examples" (dict
"examples" $media.examples
"prefix" "req"
)
}}
{{- end }}
</div>
{{- end }}
{{ end }}
{{/* ── Responses ────────────────────────────────────────────────────────── */}}
{{ define "api-ref-responses" }}
{{- $api := .api -}}
{{- $op := .op -}}
{{- $statusColors := dict
"200" "bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-300"
"201" "bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-300"
"204" "bg-green-100 text-green-800 dark:bg-green-900/40 dark:text-green-300"
"400" "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/40 dark:text-yellow-300"
"401" "bg-red-100 text-red-800 dark:bg-red-900/40 dark:text-red-300"
"403" "bg-red-100 text-red-800 dark:bg-red-900/40 dark:text-red-300"
"404" "bg-red-100 text-red-800 dark:bg-red-900/40 dark:text-red-300"
"409" "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/40 dark:text-yellow-300"
"500" "bg-red-100 text-red-800 dark:bg-red-900/40 dark:text-red-300"
-}}
{{- with $op.responses }}
<div>
<h4
class="mb-2 text-xs font-semibold tracking-wider text-gray-500 uppercase dark:text-gray-400"
>
Responses
</h4>
<div class="space-y-2">
{{- range $code, $resp := . }}
{{- $r := partial "api-ref/resolve.html" (dict "api" $api "node" $resp) }}
<details
{{ if hasPrefix $code "2" }}open{{ end }}
class="group rounded border border-gray-200 dark:border-gray-700"
>
<summary
class="flex cursor-pointer list-none items-center gap-2 rounded-t bg-gray-50 px-3 py-2 dark:bg-gray-900 [&::-webkit-details-marker]:hidden"
>
<span
class="inline-block text-gray-400 transition-transform group-open:rotate-90 dark:text-gray-500"
>&#9656;</span
>
<span
class="{{ index $statusColors $code | default "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300" }} inline-block rounded px-1.5 py-0.5 font-mono text-xs font-semibold"
>{{ $code }}</span
>
<span class="text-sm text-gray-600 dark:text-gray-400"
>{{ $r.description }}</span
>
</summary>
{{- $ct := "" -}}
{{- $media := dict -}}
{{- range $name, $m := $r.content -}}
{{- if not $ct -}}
{{- $ct = $name -}}{{- $media = $m -}}
{{- end -}}
{{- end -}}
{{- if $ct }}
<div class="px-3 pt-2 pb-3">
{{ template "api-ref-schema-link" (dict "api" $api "schema" $media.schema) }}
{{ template "api-ref-examples" (dict
"examples" $media.examples
"prefix" (printf "resp-%s" $code)
)
}}
</div>
{{- end }}
</details>
{{- end }}
</div>
</div>
{{- end }}
{{ end }}
{{/* ── Example block with optional tabs ─────────────────────────────────── */}}
{{ define "api-ref-examples" }}
{{- $examples := .examples -}}
{{- $prefix := .prefix -}}
{{- $tabs := slice -}}
{{- range $name, $ex := $examples -}}
{{- $tabs = $tabs | append (dict "name" $name "ex" $ex) -}}
{{- end -}}
{{- if $tabs }}
{{- $first := (index $tabs 0).name -}}
<div
{{ if gt (len $tabs) 1 }}x-data="{ tab: {{ $first | jsonify }} }"{{ end }}
>
{{- if gt (len $tabs) 1 }}
<div class="mb-2 flex flex-wrap gap-1">
{{- range $tabs }}
{{- $label := .name -}}
{{- with .ex.summary }}{{ $label = . }}{{ end -}}
<button
@click="tab = {{ .name | jsonify }}"
:class="tab === {{ .name | jsonify }} ? 'bg-blue-100 text-blue-800 dark:bg-blue-900/40 dark:text-blue-300' : 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400'"
class="rounded px-2 py-0.5 text-xs font-medium"
>
{{ $label }}
</button>
{{- end }}
</div>
{{- end }}
{{- range $tabs }}
<div
{{ if gt (len $tabs) 1 }}
x-show="tab === {{ .name | jsonify }}"
{{ end }}
>
{{- with .ex.value -}}
<div
class="syntax-light dark:syntax-dark not-prose overflow-hidden rounded border border-gray-200 dark:border-gray-700"
>
{{ highlight (. | jsonify (dict "indent" " ")) "json" "" }}
</div>
{{- end }}
</div>
{{- end }}
</div>
{{- end }}
{{ end }}
{{/* ── Inline schema-reference link (used in body/response) ─────────────── */}}
{{ define "api-ref-schema-link" }}
{{- $api := .api -}}
{{- $schema := .schema -}}
{{- if reflect.IsMap $schema -}}
{{- with index $schema "$ref" -}}
{{- $parts := split . "/" -}}
{{- $name := index $parts (sub (len $parts) 1) -}}
<p class="mb-2 text-xs text-gray-500 dark:text-gray-400">
Schema:
<a
href="#schema-{{ $name | urlize }}"
class="font-mono text-blue-600 hover:underline dark:text-blue-400"
>{{ $name }}</a
>
</p>
{{- end -}}
{{- end -}}
{{ end }}
{{/* ── Type rendering for parameter / property tables ───────────────────── */}}
{{ define "api-ref-type" }}
{{- $api := .api -}}
{{- $schema := .schema -}}
{{- if not (reflect.IsMap $schema) -}}
{{- else -}}
{{- with index $schema "$ref" -}}
{{- $parts := split . "/" -}}
{{- $name := index $parts (sub (len $parts) 1) -}}
<a
href="#schema-{{ $name | urlize }}"
class="text-blue-600 hover:underline dark:text-blue-400"
>{{ $name }}</a
>
{{- else -}}
{{- with $schema.type -}}
{{- if eq . "array" -}}
array&lt;{{ template "api-ref-type" (dict "api" $api "schema" $schema.items) }}&gt;
{{- else -}}
{{ . }}
{{- end -}}
{{- else -}}
{{- if $schema.allOf -}}
object
{{- else if $schema.anyOf -}}
any
{{- else if $schema.oneOf -}}
any
{{- else -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{ end }}
{{/* ── Schemas section ──────────────────────────────────────────────────── */}}
{{ define "api-ref-schemas" }}
{{- $api := . -}}
{{- with $api.components -}}
{{- with .schemas }}
<section class="mb-12">
<h2 id="schemas" class="mb-6 scroll-mt-24 text-2xl font-semibold">
Schemas
</h2>
{{- range $name, $schema := . }}
<div
id="schema-{{ $name | urlize }}"
class="mb-8 scroll-mt-24 rounded border border-gray-200 p-4 dark:border-gray-700"
>
<h3 class="mb-1 font-mono text-base font-semibold">{{ $name }}</h3>
{{- with $schema.description }}
<div
class="prose dark:prose-invert prose-sm mb-3 max-w-none text-sm"
>
{{ . | markdownify }}
</div>
{{- end }}
{{- with $schema.enum }}
<p class="mb-3 text-sm text-gray-500 dark:text-gray-400">
Enum:
{{- range $i, $v := . -}}
{{- if $i }},{{ end -}}
<code
class="rounded bg-gray-100 px-1 text-xs dark:bg-gray-800"
>{{ $v }}</code
>
{{- end -}}
</p>
{{- end }}
{{- with $schema.properties }}
{{ template "api-ref-properties" (dict
"api" $api
"schema" $schema
)
}}
{{- else }}
{{- with $schema.items }}
<p class="text-sm text-gray-500 dark:text-gray-400">
Items:
<span class="font-mono"
>{{ template "api-ref-type" (dict "api" $api "schema" .) }}</span
>
</p>
{{- end }}
{{- end }}
</div>
{{- end }}
</section>
{{- end }}
{{- end }}
{{ end }}
{{/* ── Property table for a schema ──────────────────────────────────────── */}}
{{ define "api-ref-properties" }}
{{- $api := .api -}}
{{- $schema := .schema -}}
{{- $required := $schema.required | default slice -}}
<table class="w-full border-collapse text-sm">
<thead>
<tr class="border-b border-gray-200 dark:border-gray-700">
<th
class="w-40 py-1.5 pr-4 text-left font-medium text-gray-700 dark:text-gray-300"
>
Property
</th>
<th
class="w-32 py-1.5 pr-4 text-left font-medium text-gray-700 dark:text-gray-300"
>
Type
</th>
<th
class="w-20 py-1.5 pr-4 text-left font-medium text-gray-700 dark:text-gray-300"
>
Required
</th>
<th
class="py-1.5 text-left font-medium text-gray-700 dark:text-gray-300"
>
Description
</th>
</tr>
</thead>
<tbody>
{{- range $propName, $prop := $schema.properties }}
<tr class="border-b border-gray-100 last:border-0 dark:border-gray-800">
<td class="py-1.5 pr-4 align-top">
<code class="rounded bg-gray-100 px-1 text-xs dark:bg-gray-800"
>{{ $propName }}</code
>
</td>
<td
class="py-1.5 pr-4 align-top font-mono text-xs text-gray-500 dark:text-gray-400"
>
{{ template "api-ref-type" (dict "api" $api "schema" $prop) }}
</td>
<td
class="py-1.5 pr-4 align-top text-xs text-gray-500 dark:text-gray-400"
>
{{ if in $required $propName }}yes{{ else }}no{{ end }}
</td>
<td class="py-1.5 align-top text-sm text-gray-600 dark:text-gray-400">
{{- $resolved := partial "api-ref/resolve.html" (dict "api" $api "node" $prop) -}}
{{- with $resolved.description -}}
{{ . | markdownify }}
{{- end -}}
</td>
</tr>
{{- end }}
</tbody>
</table>
{{ end }}
+185
View File
@@ -0,0 +1,185 @@
{{- /*
Markdown rendering of an OpenAPI reference page.
Mirrors the HTML api-reference layout: unmarshals the colocated YAML
Page Resource into a plain Hugo map and walks the spec to produce a
flat markdown document. Used by the "View Markdown" / "Copy Markdown"
actions and the .md alternate link.
*/ -}}
{{- $specRes := .Resources.GetMatch "*.yaml" -}}
{{- $api := $specRes | transform.Unmarshal -}}
{{- $methods := slice "get" "post" "put" "patch" "delete" -}}
{{- $paths := $api.paths -}}
# {{ .Title }}
{{ with .Description }}{{ . }}{{ end }}
{{ with $api.info }}
{{- with .version }}- **Version**: `{{ . }}`
{{ end -}}
{{ end -}}
{{- range $api.servers }}- **Base URL**: `{{ .url }}`
{{ end }}
- **OpenAPI specification**: [`{{ path.Base $specRes.RelPermalink }}`]({{ $specRes.Permalink }})
{{ with $api.info }}{{ with .description }}{{ . }}{{ end }}{{ end }}
{{ with $api.components }}{{ with .securitySchemes }}
## Authentication
{{ range $name, $scheme := . }}
**`{{ $name }}`**{{ with $scheme.type }} — type: `{{ . }}`{{ end }}{{ with $scheme.scheme }}, scheme: `{{ . }}`{{ end }}{{ with $scheme.bearerFormat }}, bearer format: `{{ . }}`{{ end }}
{{ with $scheme.description }}{{ . }}{{ end }}
{{ end -}}
{{ end }}{{ end }}
{{- range $api.tags }}
## {{ .name | title }}
{{ with .description }}{{ . }}{{ end }}
{{- $tagName := .name -}}
{{- range $path, $item := $paths -}}
{{- $sharedParams := slice -}}
{{- with index $item "parameters" -}}
{{- range . -}}
{{- $p := partial "api-ref/resolve.html" (dict "api" $api "node" .) -}}
{{- $sharedParams = $sharedParams | append $p -}}
{{- end -}}
{{- end -}}
{{- range $methods -}}
{{- $method := . -}}
{{- with index $item $method -}}
{{- $op := . -}}
{{- if in $op.tags $tagName }}
### `{{ upper $method }}` `{{ $path }}`
{{ with $op.summary }}**{{ . }}**{{ end }}
{{ with $op.description }}{{ . }}{{ end }}
{{- $params := $sharedParams -}}
{{- with index $op "parameters" -}}
{{- range . -}}
{{- $p := partial "api-ref/resolve.html" (dict "api" $api "node" .) -}}
{{- $params = $params | append $p -}}
{{- end -}}
{{- end -}}
{{ if $params }}
**Parameters**
{{ range $params -}}
- `{{ .name }}` ({{ .in }}{{ with .schema }}{{ with .type }}, {{ . }}{{ end }}{{ end }}{{ if .required }}, required{{ end }}){{ with .description }} — {{ . | strings.TrimSpace }}{{ end }}
{{ end }}
{{- end }}
{{- with $op.requestBody -}}
{{- $body := partial "api-ref/resolve.html" (dict "api" $api "node" .) }}
**Request body**{{ with $body.description }} — {{ . | strings.TrimSpace }}{{ end }}
{{- $ct := "" -}}
{{- $media := dict -}}
{{- range $n, $m := $body.content -}}
{{- if not $ct -}}{{- $ct = $n -}}{{- $media = $m -}}{{- end -}}
{{- end -}}
{{ if $ct }}
Content type: `{{ $ct }}`
{{ template "api-ref-md-schema-link" (dict "schema" $media.schema) }}
{{ range $exName, $ex := $media.examples }}{{ with $ex.value }}
```json
{{ . | jsonify (dict "indent" " ") }}
```
{{ end }}{{ end }}
{{- end }}
{{- end }}
{{- with $op.responses }}
**Responses**
{{ range $code, $resp := . -}}
{{- $r := partial "api-ref/resolve.html" (dict "api" $api "node" $resp) }}
`{{ $code }}` — {{ with $r.description }}{{ . | strings.TrimSpace }}{{ end }}
{{- $ct := "" -}}
{{- $media := dict -}}
{{- range $n, $m := $r.content -}}
{{- if not $ct -}}{{- $ct = $n -}}{{- $media = $m -}}{{- end -}}
{{- end }}
{{ if $ct }}
{{ template "api-ref-md-schema-link" (dict "schema" $media.schema) }}
{{ range $exName, $ex := $media.examples }}{{ with $ex.value }}
```json
{{ . | jsonify (dict "indent" " ") }}
```
{{ end }}{{ end }}
{{- end }}
{{ end }}
{{ end -}}
{{ end -}}
{{- end -}}
{{- end -}}
{{- end }}
{{- end }}
{{- with $api.components }}{{ with .schemas }}
## Schemas
{{ range $name, $schema := . }}
### `{{ $name }}`
{{ with $schema.description }}{{ . | strings.TrimSpace }}{{ end }}
{{ with $schema.enum -}}
Enum: {{ range $i, $v := . }}{{ if $i }}, {{ end }}`{{ $v }}`{{ end }}
{{ end }}
{{- with $schema.properties }}
{{- $required := $schema.required | default slice }}
| Property | Type | Required | Description |
| -------- | ---- | -------- | ----------- |
{{ range $propName, $prop := . -}}
| `{{ $propName }}` | {{ template "api-ref-md-type" $prop }} | {{ if in $required $propName }}yes{{ else }}no{{ end }} | {{ with $prop.description }}{{ . | strings.TrimSpace | strings.ReplaceRE "\\s*\\n\\s*" " " }}{{ end }} |
{{ end }}
{{ end -}}
{{ end -}}
{{ end }}{{ end }}
{{- /* ── Helpers ───────────────────────────────────────────────────────── */ -}}
{{- define "api-ref-md-type" -}}
{{- $s := . -}}
{{- if reflect.IsMap $s -}}
{{- with index $s "$ref" -}}
{{- $parts := split . "/" -}}
{{- $name := index $parts (sub (len $parts) 1) -}}
`{{ $name }}`
{{- else -}}
{{- with $s.type -}}
{{- if eq . "array" -}}
`array<`{{ template "api-ref-md-type" $s.items }}`>`
{{- else -}}
`{{ . }}`
{{- end -}}
{{- else -}}
{{- if $s.allOf -}}`object`
{{- else if or $s.anyOf $s.oneOf -}}`any`
{{- else -}}—{{- end -}}
{{- end -}}
{{- end -}}
{{- else -}}—{{- end -}}
{{- end -}}
{{- define "api-ref-md-schema-link" -}}
{{- $schema := .schema -}}
{{- $indent := .indent | default "" -}}
{{- if reflect.IsMap $schema -}}
{{- with index $schema "$ref" -}}
{{- $parts := split . "/" -}}
{{- $name := index $parts (sub (len $parts) 1) -}}
{{ $indent }}Schema: `{{ $name }}`
{{- end -}}
{{- end -}}
{{- end -}}