From 16617fa28c50fa3b7ae6cdb6e14620bec6f9c59e Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Mon, 25 May 2026 16:45:16 +0200 Subject: [PATCH 1/6] sbx: restructure governance docs and add governance API reference Splits the existing security/governance and security/policy pages into a new governance section with concepts, local, organization, monitoring, and API reference sub-pages, and adds a custom api-reference Hugo layout that renders the colocated OpenAPI 3 spec directly from the spec file. The layout reads the spec via transform.Unmarshal, resolves $ref nodes through a small returning partial, and overrides baseof's main block to take the full content width. The built-in right-rail TOC is replaced with a sticky endpoint navigator that groups operations by tag with method-colored pills. Each operation card shows method/path, summary, description, parameters, request body, and responses. Responses use native
elements so the list of status codes stays scannable; 2xx defaults to open. JSON examples are wrapped in the site's syntax-light/dark utility so Chroma classes pick up the theme. Each operation also exposes a copy-as-cURL button that assembles a ready-to-paste command from the spec's path and query parameter examples, the bearer-auth scheme (with $TOKEN as a literal placeholder), and the request body's default JSON example. A companion api-reference.markdown.md template renders the same spec as a clean markdown document so the page's "Copy Markdown" / "View Markdown" actions and the .md alternate link return real content instead of just the page title. Co-Authored-By: Claude Sonnet 4.6 Co-Authored-By: Claude Opus 4.7 --- content/manuals/ai/sandboxes/_index.md | 2 +- content/manuals/ai/sandboxes/architecture.md | 2 +- content/manuals/ai/sandboxes/faq.md | 6 +- content/manuals/ai/sandboxes/get-started.md | 6 +- .../manuals/ai/sandboxes/governance/_index.md | 39 + .../ai/sandboxes/governance/api/api.yaml | 759 ++++++++++++++++++ .../ai/sandboxes/governance/api/index.md | 8 + .../ai/sandboxes/governance/concepts.md | 106 +++ .../manuals/ai/sandboxes/governance/local.md | 159 ++++ .../ai/sandboxes/governance/monitoring.md | 107 +++ .../governance.md => governance/org.md} | 74 +- .../manuals/ai/sandboxes/security/_index.md | 7 +- .../manuals/ai/sandboxes/security/defaults.md | 2 +- .../ai/sandboxes/security/isolation.md | 2 +- .../manuals/ai/sandboxes/security/policy.md | 263 ------ .../manuals/ai/sandboxes/troubleshooting.md | 6 +- content/manuals/ai/sandboxes/usage.md | 2 +- layouts/_partials/api-ref/curl.html | 125 +++ layouts/_partials/api-ref/nav.html | 86 ++ layouts/_partials/api-ref/resolve.html | 26 + layouts/api-reference.html | 682 ++++++++++++++++ layouts/api-reference.markdown.md | 173 ++++ .../manuals/ai/sandboxes/governance/api.yaml | 759 ++++++++++++++++++ 23 files changed, 3072 insertions(+), 329 deletions(-) create mode 100644 content/manuals/ai/sandboxes/governance/_index.md create mode 100644 content/manuals/ai/sandboxes/governance/api/api.yaml create mode 100644 content/manuals/ai/sandboxes/governance/api/index.md create mode 100644 content/manuals/ai/sandboxes/governance/concepts.md create mode 100644 content/manuals/ai/sandboxes/governance/local.md create mode 100644 content/manuals/ai/sandboxes/governance/monitoring.md rename content/manuals/ai/sandboxes/{security/governance.md => governance/org.md} (58%) delete mode 100644 content/manuals/ai/sandboxes/security/policy.md create mode 100644 layouts/_partials/api-ref/curl.html create mode 100644 layouts/_partials/api-ref/nav.html create mode 100644 layouts/_partials/api-ref/resolve.html create mode 100644 layouts/api-reference.html create mode 100644 layouts/api-reference.markdown.md create mode 100644 static/manuals/ai/sandboxes/governance/api.yaml diff --git a/content/manuals/ai/sandboxes/_index.md b/content/manuals/ai/sandboxes/_index.md index 2a39b478dd10..30cfd0f773d8 100644 --- a/content/manuals/ai/sandboxes/_index.md +++ b/content/manuals/ai/sandboxes/_index.md @@ -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. diff --git a/content/manuals/ai/sandboxes/architecture.md b/content/manuals/ai/sandboxes/architecture.md index fab768d60552..e24a1e3603c9 100644 --- a/content/manuals/ai/sandboxes/architecture.md +++ b/content/manuals/ai/sandboxes/architecture.md @@ -36,7 +36,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/local.md) 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 diff --git a/content/manuals/ai/sandboxes/faq.md b/content/manuals/ai/sandboxes/faq.md index b8584cac88ca..d0b473253f01 100644 --- a/content/manuals/ai/sandboxes/faq.md +++ b/content/manuals/ai/sandboxes/faq.md @@ -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 @@ -30,7 +30,7 @@ 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. -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 +99,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/local.md), [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 diff --git a/content/manuals/ai/sandboxes/get-started.md b/content/manuals/ai/sandboxes/get-started.md index 2e1d97d380af..f178251bb8d4 100644 --- a/content/manuals/ai/sandboxes/get-started.md +++ b/content/manuals/ai/sandboxes/get-started.md @@ -114,7 +114,7 @@ Use ↑/↓ to navigate, Enter to select, or press 1–3. **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 @@ -269,4 +269,4 @@ working tree are unaffected. with kits - [Credentials](security/credentials.md) — credential storage and management - [Workspace trust](security/workspace.md) — review agent changes safely -- [Policies](security/policy.md) — control outbound access +- [Policies](governance/local.md) — control outbound access diff --git a/content/manuals/ai/sandboxes/governance/_index.md b/content/manuals/ai/sandboxes/governance/_index.md new file mode 100644 index 000000000000..f09e86ce1a5f --- /dev/null +++ b/content/manuals/ai/sandboxes/governance/_index.md @@ -0,0 +1,39 @@ +--- +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 that +build on each other: + +**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](api.md). Rules defined at the org level 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 extend the org policy with additional allow rules. +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](api.md): manage org policies programmatically via the + Governance API diff --git a/content/manuals/ai/sandboxes/governance/api/api.yaml b/content/manuals/ai/sandboxes/governance/api/api.yaml new file mode 100644 index 000000000000..0ef5f3dc6ed3 --- /dev/null +++ b/content/manuals/ai/sandboxes/governance/api/api.yaml @@ -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 and delegation.** Organization policies take precedence over + local sandbox policies and cannot be overridden by individual users. When + an administrator enables delegation for a rule type, users may supplement + the organization policy with local allow rules; organization-level deny + rules remain binding regardless of delegation. Delegation is configured + through the governance settings API, not through this API. + + **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/api/governance/v1 + +security: + - bearerAuth: [] + +paths: + /orgs/{org_name}/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}/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}/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}/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/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 ` 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. | + + Note: PAT and OAT strings cannot 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. Currently 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`. Omitted for empty allowlists. + 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. `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 + 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 diff --git a/content/manuals/ai/sandboxes/governance/api/index.md b/content/manuals/ai/sandboxes/governance/api/index.md new file mode 100644 index 000000000000..b1ac25c8ab67 --- /dev/null +++ b/content/manuals/ai/sandboxes/governance/api/index.md @@ -0,0 +1,8 @@ +--- +layout: api-reference +title: Governance API reference +linkTitle: API reference +weight: 30 +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 +--- diff --git a/content/manuals/ai/sandboxes/governance/concepts.md b/content/manuals/ai/sandboxes/governance/concepts.md new file mode 100644 index 000000000000..db3c0afa1764 --- /dev/null +++ b/content/manuals/ai/sandboxes/governance/concepts.md @@ -0,0 +1,106 @@ +--- +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](api.md). 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 each policy level independently. A deny in an +organization policy can't be overridden by a local allow. + +## Precedence + +When only local policies are active, local rules determine what sandboxes can +access. + +When organization governance is active: + +- Organization rules take effect across all developer machines. +- Local rules are not evaluated by default. +- Admins can [delegate](org.md#delegate-rules-to-local-policy) specific rule + types back to local control. Delegated local rules can expand access for + targets the organization hasn't explicitly denied, but can't override + organization-level deny rules. + +Within any level, deny rules beat allow rules regardless of specificity or +order. diff --git a/content/manuals/ai/sandboxes/governance/local.md b/content/manuals/ai/sandboxes/governance/local.md new file mode 100644 index 000000000000..e201df5eec05 --- /dev/null +++ b/content/manuals/ai/sandboxes/governance/local.md @@ -0,0 +1,159 @@ +--- +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 work differently depending on whether your organization uses +governance: + +- **No org governance**: local rules fully control what sandboxes can access. +- **Org governance, delegation enabled**: local rules of the delegated type + are evaluated alongside org rules. You can extend access for domains your org + hasn't explicitly denied, but org-level denials still take precedence. +- **Org governance, no delegation**: local rules are inactive. `sbx policy + allow` and `sbx policy deny` have no effect for that rule type. + +See [Organization policy](org.md) for how admins configure delegation. + +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 1–3. +``` + +| 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 without +delegating that rule type to local control. Run `sbx policy ls` to check: if +the output starts with a `Governance: managed by ` header, org governance +is active. If your rules appear with `inactive` status, org governance is +suppressing them. + +To use local rules alongside org rules, ask your admin to enable delegation for +the relevant rule type in the Admin Console. See +[Delegate rules to local policy](org.md#delegate-rules-to-local-policy). + +### A domain is still blocked after adding an allow rule + +If a domain remains blocked after you add a local allow rule, an org-level deny +rule may be covering it. [Delegation](org.md#delegate-rules-to-local-policy) +lets local rules expand access, but org deny rules always take precedence. Run `sbx policy ls` to check whether a rule +with `remote` origin and `deny` decision matches the domain. If so, the block +can only be lifted by updating the org policy in the Admin Console or via the +[API](api.md). + diff --git a/content/manuals/ai/sandboxes/governance/monitoring.md b/content/manuals/ai/sandboxes/governance/monitoring.md new file mode 100644 index 000000000000..1c0b6df08e55 --- /dev/null +++ b/content/manuals/ai/sandboxes/governance/monitoring.md @@ -0,0 +1,107 @@ +--- +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:` 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 and hasn't delegated that rule type to local control). +- `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 — 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 +``` + +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. diff --git a/content/manuals/ai/sandboxes/security/governance.md b/content/manuals/ai/sandboxes/governance/org.md similarity index 58% rename from content/manuals/ai/sandboxes/security/governance.md rename to content/manuals/ai/sandboxes/governance/org.md index 7185c78b10f4..23036edfb8da 100644 --- a/content/manuals/ai/sandboxes/security/governance.md +++ b/content/manuals/ai/sandboxes/governance/org.md @@ -1,22 +1,21 @@ --- -title: Organization governance -linkTitle: Org governance -weight: 35 +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/ --- -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). +[Local policies](local.md) give individual developers control over what their +sandboxes can access. Organization policy extends this to the admin level: +rules defined in the [Docker Admin Console](https://app.docker.com/admin) apply +uniformly to every sandbox in the organization, take precedence over local +`sbx policy` rules, and can't be overridden by individual users. -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. +Admins can manage organization policies through the Admin Console UI or +programmatically using the [Governance API](api.md). > [!NOTE] > Sandbox organization governance is available on a separate paid @@ -29,42 +28,25 @@ control so developers can add additional allow rules. ### 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. +**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. -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. +For the full syntax reference (exact hostnames, wildcard subdomains, port +suffixes, and CIDR ranges), see [Policy concepts](concepts.md#network-rules). ### 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 +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. +When a rule type isn't delegated, local rules of that type still appear in +`sbx policy ls` but with an `inactive` status. See [Monitoring](monitoring.md) +for how to read the combined rule view. Delegated local rules can expand access for domains the organization hasn't explicitly denied, but can't override organization-level deny rules. This @@ -75,9 +57,9 @@ 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 +- `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 +- `sbx policy allow network -g build.corp.internal`: no effect, because the organization denies `*.corp.internal` #### Blocked values in delegated rules @@ -101,12 +83,8 @@ 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 `~`. +For path pattern syntax including the difference between `*` and `**`, see +[Policy concepts](concepts.md#filesystem-rules). ## Precedence @@ -129,7 +107,7 @@ 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. +the rule in the Admin Console or via the [API](api.md). ## Troubleshooting diff --git a/content/manuals/ai/sandboxes/security/_index.md b/content/manuals/ai/sandboxes/security/_index.md index ef6048e4446a..c2d911118d35 100644 --- a/content/manuals/ai/sandboxes/security/_index.md +++ b/content/manuals/ai/sandboxes/security/_index.md @@ -89,7 +89,7 @@ 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. -See [Organization governance](governance/) for details. +See [Organization policy](../governance/org.md) for details. ## Learn more @@ -98,7 +98,6 @@ 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 - [Workspace trust](workspace/): what to review after an agent session diff --git a/content/manuals/ai/sandboxes/security/defaults.md b/content/manuals/ai/sandboxes/security/defaults.md index 8c8a1f9a1753..07536772325e 100644 --- a/content/manuals/ai/sandboxes/security/defaults.md +++ b/content/manuals/ai/sandboxes/security/defaults.md @@ -17,7 +17,7 @@ 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 +customize network access, see [Policies](../governance/local.md). If your organization manages sandbox policies centrally, those rules apply on top of the defaults described here. See [Organization governance](governance.md). diff --git a/content/manuals/ai/sandboxes/security/isolation.md b/content/manuals/ai/sandboxes/security/isolation.md index ab220ad26ba7..27575f4d8f73 100644 --- a/content/manuals/ai/sandboxes/security/isolation.md +++ b/content/manuals/ai/sandboxes/security/isolation.md @@ -34,7 +34,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/local.md). 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. diff --git a/content/manuals/ai/sandboxes/security/policy.md b/content/manuals/ai/sandboxes/security/policy.md deleted file mode 100644 index b58175a825cb..000000000000 --- a/content/manuals/ai/sandboxes/security/policy.md +++ /dev/null @@ -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 1–3. -``` - -| 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:` 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. diff --git a/content/manuals/ai/sandboxes/troubleshooting.md b/content/manuals/ai/sandboxes/troubleshooting.md index 0430031207db..e23fda5fbb22 100644 --- a/content/manuals/ai/sandboxes/troubleshooting.md +++ b/content/manuals/ai/sandboxes/troubleshooting.md @@ -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 diff --git a/content/manuals/ai/sandboxes/usage.md b/content/manuals/ai/sandboxes/usage.md index c8fa2e8f757a..088bafeca027 100644 --- a/content/manuals/ai/sandboxes/usage.md +++ b/content/manuals/ai/sandboxes/usage.md @@ -391,7 +391,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. diff --git a/layouts/_partials/api-ref/curl.html b/layouts/_partials/api-ref/curl.html new file mode 100644 index 000000000000..366645adbb21 --- /dev/null +++ b/layouts/_partials/api-ref/curl.html @@ -0,0 +1,125 @@ +{{/* + 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 ``. 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. */}} +{{- with $op.requestBody -}} + {{- $body := partial "api-ref/resolve.html" (dict "api" $api "node" .) -}} + {{- with index $body.content "application/json" -}} + {{- $lines = $lines | append "-H 'Content-Type: application/json'" -}} + {{- $example := "" -}} + {{- with .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 ") -}} diff --git a/layouts/_partials/api-ref/nav.html b/layouts/_partials/api-ref/nav.html new file mode 100644 index 000000000000..f182e4d8a369 --- /dev/null +++ b/layouts/_partials/api-ref/nav.html @@ -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" +-}} + + +

+ On this page +

+
    + {{- with $api.components }} + {{- if .securitySchemes }} +
  • + + Authentication + +
  • + {{- end }} + {{- end }} + {{- range $api.tags }} + {{- $tagName := .name }} +
  • + + {{ $tagName | title }} + +
      + {{- 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 }} +
    • + + {{ upper $method }} + {{ $label }} + +
    • + {{- end }} + {{- end }} + {{- end }} + {{- end }} +
    +
  • + {{- end }} + {{- with $api.components }} + {{- if .schemas }} +
  • + + Schemas + +
  • + {{- end }} + {{- end }} +
diff --git a/layouts/_partials/api-ref/resolve.html b/layouts/_partials/api-ref/resolve.html new file mode 100644 index 000000000000..80c7e25fe7de --- /dev/null +++ b/layouts/_partials/api-ref/resolve.html @@ -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 -}} diff --git a/layouts/api-reference.html b/layouts/api-reference.html new file mode 100644 index 000000000000..08c622aaf36b --- /dev/null +++ b/layouts/api-reference.html @@ -0,0 +1,682 @@ +{{/* 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 -}} + + +
+
+ {{ partial "breadcrumbs.html" . }} +

+ {{ .Title | safeHTML }} +

+ {{ with .Description }} +

{{ . }}

+ {{ 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 }} +
+ + +
+{{ end }} + +{{/* ── Meta strip: version, base URL, download link ──────────────────────── */}} +{{ define "api-ref-meta" }} + {{- $api := .api -}} + {{- $spec := .spec -}} +
+ {{- with $api.info }} + {{- with .version }} +
+ Version + {{ . }} +
+ {{- end }} + {{- end }} + {{- range $api.servers }} +
+ Base URL + {{ .url }} +
+ {{- end }} + Download OpenAPI specification +
+{{ end }} + +{{/* ── Overview ─────────────────────────────────────────────────────────── */}} +{{ define "api-ref-overview" }} + {{- $api := . -}} + {{- with $api.info }} + {{- with .description }} +
+
+ {{ . | markdownify }} +
+
+ {{- end }} + {{- end }} +{{ end }} + +{{/* ── Authentication ───────────────────────────────────────────────────── */}} +{{ define "api-ref-auth" }} + {{- $api := . -}} + {{- 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 }} +
+ {{ . | markdownify }} +
+ {{- end }} + {{- end }} +
+ {{- 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 -}} +
+

+ {{ $tagName | title }} +

+ {{- with .description }} +

{{ . }}

+ {{- 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 }} +
+ {{- 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 + ) + -}} + + +
+
+ {{ upper $method }} + {{ $path }} +
+ {{- with $op.summary }} + {{ . }} + {{- end }} + +
+
+ +
+ {{- with $op.description }} +
+ {{ . | markdownify }} +
+ {{- 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) }} +
+
+{{ 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 }} +
+

+ Parameters +

+ + + + + + + + + + + + {{- range $params }} + + + + + + + + {{- end }} + +
+ Name + + In + + Type + + Required + + Description +
+ {{ .name }} + + {{ .in }} + + {{ template "api-ref-type" (dict "api" $api "schema" .schema) }} + + {{ if .required }}yes{{ else }}no{{ end }} + + {{ with .description }}{{ . | markdownify }}{{ end }} +
+
+ {{- 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" .) }} +
+

+ Request body +

+ {{- with $body.description }} +

+ {{ . | markdownify }} +

+ {{- end }} + {{- with index $body.content "application/json" }} + {{ template "api-ref-schema-link" (dict "api" $api "schema" .schema) }} + {{ template "api-ref-examples" (dict + "examples" .examples + "prefix" "req" + ) + }} + {{- end }} +
+ {{- 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 }} +
+

+ Responses +

+
+ {{- range $code, $resp := . }} + {{- $r := partial "api-ref/resolve.html" (dict "api" $api "node" $resp) }} +
+ + + {{ $code }} + {{ $r.description }} + + {{- with index $r.content "application/json" }} +
+ {{ template "api-ref-schema-link" (dict "api" $api "schema" .schema) }} + {{ template "api-ref-examples" (dict + "examples" .examples + "prefix" (printf "resp-%s" $code) + ) + }} +
+ {{- end }} +
+ {{- end }} +
+
+ {{- 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 -}} +
+ {{- if gt (len $tabs) 1 }} +
+ {{- range $tabs }} + {{- $label := .name -}} + {{- with .ex.summary }}{{ $label = . }}{{ end -}} + + {{- end }} +
+ {{- end }} + {{- range $tabs }} +
+ {{- with .ex.value -}} +
+ {{ highlight (. | jsonify (dict "indent" " ")) "json" "" }} +
+ {{- end }} +
+ {{- end }} +
+ {{- 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) -}} +

+ Schema: + {{ $name }} +

+ {{- 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) -}} + {{ $name }} + {{- else -}} + {{- with $schema.type -}} + {{- if eq . "array" -}} + array<{{ template "api-ref-type" (dict "api" $api "schema" $schema.items) }}> + {{- 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 }} +
+

+ Schemas +

+ {{- range $name, $schema := . }} +
+

{{ $name }}

+ {{- with $schema.description }} +
+ {{ . | markdownify }} +
+ {{- end }} + {{- with $schema.enum }} +

+ Enum: + {{- range $i, $v := . -}} + {{- if $i }},{{ end -}} + {{ $v }} + {{- end -}} +

+ {{- end }} + {{- with $schema.properties }} + {{ template "api-ref-properties" (dict + "api" $api + "schema" $schema + ) + }} + {{- else }} + {{- with $schema.items }} +

+ Items: + {{ template "api-ref-type" (dict "api" $api "schema" .) }} +

+ {{- end }} + {{- end }} +
+ {{- end }} +
+ {{- end }} + {{- end }} +{{ end }} + +{{/* ── Property table for a schema ──────────────────────────────────────── */}} +{{ define "api-ref-properties" }} + {{- $api := .api -}} + {{- $schema := .schema -}} + {{- $required := $schema.required | default slice -}} + + + + + + + + + + + {{- range $propName, $prop := $schema.properties }} + + + + + + + {{- end }} + +
+ Property + + Type + + Required + + Description +
+ {{ $propName }} + + {{ template "api-ref-type" (dict "api" $api "schema" $prop) }} + + {{ if in $required $propName }}yes{{ else }}no{{ end }} + + {{- $resolved := partial "api-ref/resolve.html" (dict "api" $api "node" $prop) -}} + {{- with $resolved.description -}} + {{ . | markdownify }} + {{- end -}} +
+{{ end }} diff --git a/layouts/api-reference.markdown.md b/layouts/api-reference.markdown.md new file mode 100644 index 000000000000..261a5d2d7544 --- /dev/null +++ b/layouts/api-reference.markdown.md @@ -0,0 +1,173 @@ +{{- /* + 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 }} + +{{ with index $body.content "application/json" -}} +{{- template "api-ref-md-schema-link" (dict "schema" .schema) }} +{{ range $exName, $ex := .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 }} +{{ with index $r.content "application/json" }} +{{ template "api-ref-md-schema-link" (dict "schema" .schema) }} +{{ range $exName, $ex := .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 -}} diff --git a/static/manuals/ai/sandboxes/governance/api.yaml b/static/manuals/ai/sandboxes/governance/api.yaml new file mode 100644 index 000000000000..0ef5f3dc6ed3 --- /dev/null +++ b/static/manuals/ai/sandboxes/governance/api.yaml @@ -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 and delegation.** Organization policies take precedence over + local sandbox policies and cannot be overridden by individual users. When + an administrator enables delegation for a rule type, users may supplement + the organization policy with local allow rules; organization-level deny + rules remain binding regardless of delegation. Delegation is configured + through the governance settings API, not through this API. + + **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/api/governance/v1 + +security: + - bearerAuth: [] + +paths: + /orgs/{org_name}/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}/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}/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}/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/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 ` 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. | + + Note: PAT and OAT strings cannot 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. Currently 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`. Omitted for empty allowlists. + 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. `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 + 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 From f0de13b19f47001d264f55d053a801e04deccc00 Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Tue, 26 May 2026 12:14:23 +0200 Subject: [PATCH 2/6] sbx: move governance API reference to /reference/api/ai-governance Relocates the OpenAPI reference page bundle out of the product manual tree and into the canonical /reference/api/ section. The old URL keeps working via a Hugo alias on the new page. Under /ai/sandboxes/governance the sidebar still shows an "API reference" entry: a non-rendered stub page (build.render: never) with sidebar.goto pointing at the new location, so the link in the governance sidebar cross-references the reference section directly without bouncing through a placeholder page. The duplicate static/manuals/ai/sandboxes/governance/api.yaml is dropped now that the page resource is the single source of the spec. Co-Authored-By: Claude Opus 4.7 --- .../manuals/ai/sandboxes/governance/_index.md | 4 +- .../manuals/ai/sandboxes/governance/api.md | 10 + .../api/ai-governance}/api.yaml | 0 .../api/ai-governance}/index.md | 0 .../manuals/ai/sandboxes/governance/api.yaml | 759 ------------------ 5 files changed, 12 insertions(+), 761 deletions(-) create mode 100644 content/manuals/ai/sandboxes/governance/api.md rename content/{manuals/ai/sandboxes/governance/api => reference/api/ai-governance}/api.yaml (100%) rename content/{manuals/ai/sandboxes/governance/api => reference/api/ai-governance}/index.md (100%) delete mode 100644 static/manuals/ai/sandboxes/governance/api.yaml diff --git a/content/manuals/ai/sandboxes/governance/_index.md b/content/manuals/ai/sandboxes/governance/_index.md index f09e86ce1a5f..7a009f44da45 100644 --- a/content/manuals/ai/sandboxes/governance/_index.md +++ b/content/manuals/ai/sandboxes/governance/_index.md @@ -35,5 +35,5 @@ See [Organization policy](org.md). 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](api.md): manage org policies programmatically via the - Governance API +- [API reference](/reference/api/ai-governance/): manage org policies + programmatically via the Governance API diff --git a/content/manuals/ai/sandboxes/governance/api.md b/content/manuals/ai/sandboxes/governance/api.md new file mode 100644 index 000000000000..27494b2e533b --- /dev/null +++ b/content/manuals/ai/sandboxes/governance/api.md @@ -0,0 +1,10 @@ +--- +title: API reference +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/ +--- diff --git a/content/manuals/ai/sandboxes/governance/api/api.yaml b/content/reference/api/ai-governance/api.yaml similarity index 100% rename from content/manuals/ai/sandboxes/governance/api/api.yaml rename to content/reference/api/ai-governance/api.yaml diff --git a/content/manuals/ai/sandboxes/governance/api/index.md b/content/reference/api/ai-governance/index.md similarity index 100% rename from content/manuals/ai/sandboxes/governance/api/index.md rename to content/reference/api/ai-governance/index.md diff --git a/static/manuals/ai/sandboxes/governance/api.yaml b/static/manuals/ai/sandboxes/governance/api.yaml deleted file mode 100644 index 0ef5f3dc6ed3..000000000000 --- a/static/manuals/ai/sandboxes/governance/api.yaml +++ /dev/null @@ -1,759 +0,0 @@ -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 and delegation.** Organization policies take precedence over - local sandbox policies and cannot be overridden by individual users. When - an administrator enables delegation for a rule type, users may supplement - the organization policy with local allow rules; organization-level deny - rules remain binding regardless of delegation. Delegation is configured - through the governance settings API, not through this API. - - **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/api/governance/v1 - -security: - - bearerAuth: [] - -paths: - /orgs/{org_name}/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}/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}/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}/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/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 ` 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. | - - Note: PAT and OAT strings cannot 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. Currently 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`. Omitted for empty allowlists. - 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. `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 - 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 From 9841df258ed81e5ff134fabcbc611af6065dacce Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Tue, 26 May 2026 12:21:40 +0200 Subject: [PATCH 3/6] sbx: use linkTitle "API reference" on the new governance API page Co-Authored-By: Claude Opus 4.7 --- content/reference/api/ai-governance/index.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/content/reference/api/ai-governance/index.md b/content/reference/api/ai-governance/index.md index b1ac25c8ab67..e2ca97246d63 100644 --- a/content/reference/api/ai-governance/index.md +++ b/content/reference/api/ai-governance/index.md @@ -1,8 +1,9 @@ --- layout: api-reference -title: Governance API reference +title: Docker AI Governance API linkTitle: API reference -weight: 30 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/ --- From 676863a4a9f9664d0b2ef037c71be02e840ff346 Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Tue, 26 May 2026 12:25:24 +0200 Subject: [PATCH 4/6] sbx: label the governance API sidebar entries as "AI Governance" Co-Authored-By: Claude Opus 4.7 --- content/manuals/ai/sandboxes/governance/api.md | 4 ++-- content/reference/api/ai-governance/index.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/content/manuals/ai/sandboxes/governance/api.md b/content/manuals/ai/sandboxes/governance/api.md index 27494b2e533b..d8cf54ad4b1b 100644 --- a/content/manuals/ai/sandboxes/governance/api.md +++ b/content/manuals/ai/sandboxes/governance/api.md @@ -1,6 +1,6 @@ --- -title: API reference -linkTitle: API reference +title: AI Governance API +linkTitle: AI Governance description: Programmatic management of Docker AI Governance policies and rules via HTTP+JSON. weight: 30 build: diff --git a/content/reference/api/ai-governance/index.md b/content/reference/api/ai-governance/index.md index e2ca97246d63..e9b5bc793612 100644 --- a/content/reference/api/ai-governance/index.md +++ b/content/reference/api/ai-governance/index.md @@ -1,7 +1,7 @@ --- layout: api-reference title: Docker AI Governance API -linkTitle: API reference +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: From 47c6413180a0afc09df62670c72e24d997545001 Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Tue, 26 May 2026 12:26:52 +0200 Subject: [PATCH 5/6] sbx: point governance cross-refs at /reference/api/ai-governance The sidebar stub at content/manuals/ai/sandboxes/governance/api.md has build.render: never, so relative (api.md) links from sibling files resolved to an empty href once the bundle moved to the reference section. Updates org.md, local.md, concepts.md, and _index.md to link directly at the new URL. Co-Authored-By: Claude Opus 4.7 --- content/manuals/ai/sandboxes/governance/_index.md | 2 +- content/manuals/ai/sandboxes/governance/concepts.md | 2 +- content/manuals/ai/sandboxes/governance/local.md | 2 +- content/manuals/ai/sandboxes/governance/org.md | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/content/manuals/ai/sandboxes/governance/_index.md b/content/manuals/ai/sandboxes/governance/_index.md index 7a009f44da45..97d92672e4d2 100644 --- a/content/manuals/ai/sandboxes/governance/_index.md +++ b/content/manuals/ai/sandboxes/governance/_index.md @@ -14,7 +14,7 @@ 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](api.md). Rules defined at the org level apply +via the [Governance API](/reference/api/ai-governance/). Rules defined at the org level 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 extend the org policy with additional allow rules. diff --git a/content/manuals/ai/sandboxes/governance/concepts.md b/content/manuals/ai/sandboxes/governance/concepts.md index db3c0afa1764..b2a00bdc2010 100644 --- a/content/manuals/ai/sandboxes/governance/concepts.md +++ b/content/manuals/ai/sandboxes/governance/concepts.md @@ -16,7 +16,7 @@ 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](api.md). Applies uniformly across every sandbox in 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. diff --git a/content/manuals/ai/sandboxes/governance/local.md b/content/manuals/ai/sandboxes/governance/local.md index e201df5eec05..486fb11b0f05 100644 --- a/content/manuals/ai/sandboxes/governance/local.md +++ b/content/manuals/ai/sandboxes/governance/local.md @@ -155,5 +155,5 @@ rule may be covering it. [Delegation](org.md#delegate-rules-to-local-policy) lets local rules expand access, but org deny rules always take precedence. Run `sbx policy ls` to check whether a rule with `remote` origin and `deny` decision matches the domain. If so, the block can only be lifted by updating the org policy in the Admin Console or via the -[API](api.md). +[API](/reference/api/ai-governance/). diff --git a/content/manuals/ai/sandboxes/governance/org.md b/content/manuals/ai/sandboxes/governance/org.md index 23036edfb8da..a70a64254715 100644 --- a/content/manuals/ai/sandboxes/governance/org.md +++ b/content/manuals/ai/sandboxes/governance/org.md @@ -15,7 +15,7 @@ uniformly to every sandbox in the organization, take precedence over local `sbx policy` rules, and can't be overridden by individual users. Admins can manage organization policies through the Admin Console UI or -programmatically using the [Governance API](api.md). +programmatically using the [Governance API](/reference/api/ai-governance/). > [!NOTE] > Sandbox organization governance is available on a separate paid @@ -107,7 +107,7 @@ 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 or via the [API](api.md). +the rule in the Admin Console or via the [API](/reference/api/ai-governance/). ## Troubleshooting From 70a6b8a1f5bf94d8bd828a692004b7180dedc173 Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Tue, 26 May 2026 13:33:04 +0200 Subject: [PATCH 6/6] sbx: re-vendor governance API spec from upstream Picks up upstream style fixes (drops "currently", removes meta "Note:" prefix) and overrides the doc-URL line that upstream regressed to the pre-restructure /security/governance/ path. Will be fixed upstream so the next re-vendor stays clean. Co-Authored-By: Claude Opus 4.7 --- content/reference/api/ai-governance/api.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/content/reference/api/ai-governance/api.yaml b/content/reference/api/ai-governance/api.yaml index 0ef5f3dc6ed3..10b0bdfb60a6 100644 --- a/content/reference/api/ai-governance/api.yaml +++ b/content/reference/api/ai-governance/api.yaml @@ -370,8 +370,8 @@ components: | 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. | - Note: PAT and OAT strings cannot be used directly as a bearer token — - they must be exchanged at the login endpoint first. + 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. @@ -444,8 +444,8 @@ components: type: type: string description: > - Identifies the rule-set format. Currently always `allowlist_v0`, - corresponding to the `allowlist_v0` property on the full Policy object. + Identifies the rule-set format. Always `allowlist_v0`, corresponding + to the `allowlist_v0` property on the full Policy object. examples: - allowlist_v0