# Copyright 2026 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. openapi: "3.0.3" info: title: sandboxd Filesystem & Runtime REST API description: > Stateless REST interface for sandbox file system operations and runtime metadata. Part of the Agent Runtime Protocol hybrid gRPC/REST architecture defined in KEP-539.2. High-performance raw file transfers (read/write) are served over HTTP REST, while interactive process execution is served over gRPC on a dedicated port. Compatibility & Migration Note: This specification replaces the unversioned python-runtime endpoints (/upload, /download, /list, /exists) and FileEntry wire representation. Supported SDK clients select this versioned /v1/ surface explicitly; they do not auto-detect the runtime from environment variables. See KEP-539.2 for the endpoint and wire-format differences. Security note: sandboxd listens on the pod network by default. Protect both runtime ports with pod isolation and NetworkPolicy, or bind to loopback for local-only access. Never expose the runtime API directly to an untrusted public network. When sandboxd runs with --auth-token-sha256 or --auth-token-file, every request except GET /v1/health must carry "Authorization: Bearer "; otherwise it is refused with 401 UNAUTHENTICATED. version: "v1" security: - {} - bearerAuth: [] servers: - url: http://localhost:8080/v1 description: sandboxd REST server (default port 8080; listen address is configurable) paths: /files/{path}: parameters: - name: path in: path required: true schema: type: string description: > Sandbox-relative path (e.g. main.py or src/main.py). Nested paths may use literal '/' separators or percent-encoded '%2F'. The supported SDKs encode the complete path value. The server decodes the request path before resolving it against the sandbox root. get: summary: Read a file or list a directory description: > If the path points to a file, returns raw file bytes (application/octet-stream). If the path points to a directory, returns a structured directory listing (application/json). responses: "200": description: File contents or directory listing content: application/octet-stream: schema: type: string format: binary application/json: schema: $ref: "#/components/schemas/DirectoryListing" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "500": $ref: "#/components/responses/InternalServerError" head: summary: Check whether a file or directory exists description: Returns the same status and metadata headers as GET without a response body. responses: "200": description: Path exists "403": description: Path escapes sandbox root, or permission is denied during path lookup or file open "404": description: Path does not exist within the sandbox "500": description: Permission is denied while reading a directory, or another internal filesystem error put: summary: Write or overwrite a file (Idempotent) description: > Atomically writes request body to the given path using a temporary file and rename strategy. Parent directories are created automatically. Supports both new file creation and overwriting. parameters: - name: mode in: query required: false schema: type: string pattern: "^0[0-7]{3}$" default: "0644" description: Octal permission bits applied to the written file (e.g. "0755" or "0644"). requestBody: required: true content: application/octet-stream: schema: type: string format: binary multipart/form-data: schema: type: object required: - file properties: file: type: string format: binary responses: "204": description: File written successfully "400": $ref: "#/components/responses/BadRequest" "403": $ref: "#/components/responses/Forbidden" "500": $ref: "#/components/responses/InternalServerError" delete: summary: Remove a file or directory parameters: - name: recursive in: query required: false schema: type: boolean default: false description: If true, recursively removes directory and all its contents. responses: "204": description: Removed successfully "400": $ref: "#/components/responses/BadRequest" "403": $ref: "#/components/responses/Forbidden" "404": $ref: "#/components/responses/NotFound" "409": $ref: "#/components/responses/Conflict" "500": $ref: "#/components/responses/InternalServerError" /health: get: security: [] summary: Sandbox readiness and heartbeat check description: > Used by orchestrators and Kubernetes liveness/readiness probes to verify sandboxd is healthy. responses: "200": description: Healthy content: application/json: schema: $ref: "#/components/schemas/HealthResponse" "503": description: Service unavailable or not ready content: application/json: schema: $ref: "#/components/schemas/Error" /metadata: get: summary: Runtime environment metadata description: > Returns workload-scoped environment configuration injected by the orchestrator (e.g. sandbox ID, workspace path, resource limits). Security restriction: this endpoint must only serve non-sensitive configuration. Orchestrator credentials, Kubernetes API tokens, and cloud provider IAM keys must NEVER be served or placed in /v1/metadata since untrusted agent code running in the sandbox can query localhost. responses: "200": description: Runtime metadata content: application/json: schema: $ref: "#/components/schemas/MetadataResponse" components: schemas: DirectoryListing: type: object required: - path - entries properties: path: type: string example: "/workspace" entries: type: array items: $ref: "#/components/schemas/FileEntry" FileEntry: type: object required: - name - size - type - modified_at properties: name: type: string example: "main.py" size: type: integer format: int64 example: 1024 type: type: string enum: [file, directory] example: "file" modified_at: type: string format: date-time mode: type: string description: Octal permission bits example: "0644" HealthResponse: type: object required: - status properties: status: type: string enum: [ok] example: "ok" uptime_seconds: type: integer example: 3600 MetadataResponse: type: object properties: env: type: object additionalProperties: type: string description: Key-value environment variables injected by the orchestrator. Error: type: object required: - code - message properties: code: type: string example: "PERMISSION_DENIED" message: type: string example: "Path traversal outside sandbox root is forbidden." securitySchemes: bearerAuth: type: http scheme: bearer description: Required on every request except GET /v1/health when sandboxd runs with --auth-token-sha256 or --auth-token-file. responses: Unauthorized: description: Missing or invalid bearer token (only when sandboxd runs with --auth-token-sha256 or --auth-token-file) headers: WWW-Authenticate: schema: type: string content: application/json: schema: $ref: "#/components/schemas/Error" BadRequest: description: Invalid request parameters or payload content: application/json: schema: $ref: "#/components/schemas/Error" Forbidden: description: Path escapes sandbox root or permission denied content: application/json: schema: $ref: "#/components/schemas/Error" NotFound: description: Path does not exist within the sandbox content: application/json: schema: $ref: "#/components/schemas/Error" Conflict: description: Directory not empty when recursive=false content: application/json: schema: $ref: "#/components/schemas/Error" InternalServerError: description: Internal filesystem error content: application/json: schema: $ref: "#/components/schemas/Error"