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] 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