From 0e6e27aa97797dd33791352ff6d801846d39ab8c Mon Sep 17 00:00:00 2001 From: anycodes <21079031+anycodes@users.noreply.github.com> Date: Tue, 27 Oct 2026 07:01:18 +0000 Subject: [PATCH 1/4] feat: resolve explicitly prepared Express API keys through shared engine --- .agents/README.md | 2 + .../verify/2026-10-27-managed-api-key.md | 52 +++++++ .../2026-10-27-managed-api-key.md | 41 +++++ CHANGELOG.md | 4 + README.md | 2 + README.zh-CN.md | 2 + .../src/agent_sandbox/_standard/PATCHES.md | 4 + .../_standard/connection_config.py | 3 +- .../providers/alicloud_express/__init__.py | 7 + .../alicloud_express/managed_api_key.py | 145 ++++++++++++++++++ .../providers/alicloud_express/provider.py | 13 +- .../test_providers/test_managed_api_key.py | 85 ++++++++++ docs/en/guide/authentication.md | 36 +---- docs/en/guide/permissions.md | 23 +++ docs/zh/guide/authentication.md | 32 +--- docs/zh/guide/permissions.md | 23 +++ e2e/README.md | 2 + e2e/README.zh-CN.md | 2 + e2e/test_managed_api_key.py | 92 +++++++++++ 19 files changed, 511 insertions(+), 59 deletions(-) create mode 100644 .agents/evidence/verify/2026-10-27-managed-api-key.md create mode 100644 .agents/notes/proposed/architecture/2026-10-27-managed-api-key.md create mode 100644 client-sdk/src/agent_sandbox/providers/alicloud_express/managed_api_key.py create mode 100644 client-sdk/tests/test_providers/test_managed_api_key.py create mode 100644 docs/en/guide/permissions.md create mode 100644 docs/zh/guide/permissions.md create mode 100644 e2e/test_managed_api_key.py diff --git a/.agents/README.md b/.agents/README.md index 642af9e0..6e55b773 100644 --- a/.agents/README.md +++ b/.agents/README.md @@ -244,3 +244,5 @@ Builtin transport: [direct HTTP](notes/implemented/architecture/2026-10-18-nativ - [Current public a4 guidance and bounded metadata propagation](notes/implemented/process/2026-10-25-current-public-a4.md) · [verification](evidence/verify/2026-10-25-public-a4-delivery.md). - [Shared application authentication vectors](notes/proposed/feature/2026-10-26-application-auth-vectors.md) · [verification](evidence/verify/2026-10-26-application-auth-vectors.md). + +- [Consented Express managed API keys](notes/proposed/architecture/2026-10-27-managed-api-key.md) · [verification](evidence/verify/2026-10-27-managed-api-key.md). diff --git a/.agents/evidence/verify/2026-10-27-managed-api-key.md b/.agents/evidence/verify/2026-10-27-managed-api-key.md new file mode 100644 index 00000000..9c510dfa --- /dev/null +++ b/.agents/evidence/verify/2026-10-27-managed-api-key.md @@ -0,0 +1,52 @@ +# Verification: Shared Express managed API keys in Python + +- Date: 2026-10-27 +- Change: [Issue #143](https://github.com/Alibaba-Agent-Sandbox/python-sdk/issues/143); [decision](../../notes/proposed/architecture/2026-10-27-managed-api-key.md). +- Result: PARTIAL — client and cloud acceptance passed; unchanged server full-suite failure blocks local make ci. +- Packages: candidate client/server 0.4.0a6 built from main 5d855223e3ec8797b3c4ad7d01644a8a5145b501 plus this PR; no package publication. +- Environment: Linux, Python 3.12.13 (supplementary 3.13.13 checks), Node 22.22.2, real Express cn-hangzhou; candidate CLI from the companion feature branch. Credentials supplied through ignored environment configuration; values not recorded. + +## What was verified + +Express owns the official Sandbox 2026-08-20 key-resource API. A new or nearly expired key requires explicit consent; the literal `--ci` flag may provide it, while ambient CI does not. Explicit keys retain priority. Reuse is read-only, disabled/deleted keys are not replaced, and unknown writes remain pending. A 30-day resource is shared privately by CLI and Python consumers, with no background renewal promise. English/Chinese docs explain feature/action/resource scopes and RAM authorization. + +## Commands run + +```sh +python -m pip install -e './server-sdk[dev]' -e './client-sdk[dev]' +make ci +make lint typecheck +make format-check build dist-check +cd client-sdk && pytest tests/ -m 'not integration' -q +ABX_BIN='' pytest e2e/test_managed_api_key.py -q +bash scripts/check-repo-health.sh +bash scripts/check-no-local-paths.sh +``` + +## Results + +| Scenario | Result | Detail | +|---|---|---| +| Full client suite, Python 3.12 | PASS | 2338 pass, 27 skip, 10 deselect | +| New managed-key unit coverage | PASS | Eight tests cover explicit priority, readonly resolution, explicit prepare consent, unsafe references and standard E2B override | +| Provider isolation/standard authentication regression | PASS | 29 focused tests after routing standard fallback through the registry composition root | +| Server suite, Python 3.12 | FAIL | 874 pass, 1 fail, 1 skip; malformed WebSocket upgrade expects 400 but gets 501 | +| Clean main comparison | FAIL, same baseline | Same malformed-upgrade failure on unchanged main, Python 3.12/3.13; removing proxy environment variables did not resolve it | +| Lint/format/typecheck | PASS | Ruff; mypy server 56 files and client 101 files | +| Distribution validation | PASS | Both wheels and sdists, twine checks, py.typed and combined clean installation/import | +| Health/hygiene | PASS | Health 106 pass/1 warning/0 fail; hygiene clean | +| Real SDK managed-key acceptance | PASS | One subprocess acceptance test: reject missing confirm before engine IO; explicit preparation; sync Sandbox, AsyncSandbox and ProviderSandbox create/exec/files/kill; shared engine and private storage | + +An early direct concrete-provider import in the standard client failed the core isolation test; the final implementation uses the registry boundary, and the full client suite passed after that correction. The server baseline remains unchanged and is not counted as a passing make ci run. + +## Cleanup + +Acceptance uses an isolated user directory and only its tracked key/sandboxes. Each sandbox is killed in finally. The owned key is disabled/deleted through the real shared engine, its get operation confirms resource-not-found, and the test stops its isolated engine in finally. Companion CLI acceptance independently verified sandbox HTTP 404 and key DescribeApiKey 404. No general all-account janitor was invoked. + +## Problems found + +The pre-existing server malformed-WebSocket assertion prevents full local make ci success. See follow-up linked in the PR; no server behavior was changed by this client feature. This PR depends on the CLI's new api-key.resolve/api-key.prepare operations landing first. Existing direct API-key SDK use still needs no CLI. + +## Not verified + +Remote Python 3.10/3.11 matrix, Windows ACL behavior, alternate regions, the full slow cloud matrix and an independently restricted RAM identity were not verified here. Direct cloud SDK behavior is owned/tested by the companion CLI, not reimplemented in Python. No PyPI release was requested or performed. diff --git a/.agents/notes/proposed/architecture/2026-10-27-managed-api-key.md b/.agents/notes/proposed/architecture/2026-10-27-managed-api-key.md new file mode 100644 index 00000000..fd04f5e9 --- /dev/null +++ b/.agents/notes/proposed/architecture/2026-10-27-managed-api-key.md @@ -0,0 +1,41 @@ +# Decision: Use consented Express managed API keys +Status: proposed + +## Problem + +AK/SK users cannot use runtime APIs without manually provisioning a sandbox key. The retired guessed temporary-token implementation was not a valid solution. Users need minimal migration, explicit consent and exact permission instructions. Issue: https://github.com/Alibaba-Agent-Sandbox/python-sdk/issues/143. + +## Decision + +The Express provider owns official 2026-08-20 credential creation, private persistent storage, identity/region/Team isolation, concurrency and expiry. A managed resource expires in 30 days; within its last day use requires consent to extend that same resource. No background renewal is promised for existing clients. CLI asks before creation/renewal; explicit --ci may authorize it. Plain noninteractive execution and CI environment variables do not grant consent. Explicit keys win and rejection never switches credentials. Disabled keys are not replaced. Unknown write outcomes stay journaled pending; no blind replay. SDK uses the shared engine and explicit prepare functions; no duplicated cloud implementation. + +## API Design + +Provider catalog: api-key.resolve (read), api-key.prepare (confirmed write), returning nonsecret metadata and a private local file reference. Python Express exposes async prepare_api_key and prepare_api_key_sync, both requiring confirm=True for writes. Native/standard runtime construction resolves existing prepared credentials. Generic CLI UI only interprets provider-owned authentication/consent callbacks. + +## Alternatives considered + +- Restore guessed RPC exchange: no confirmed temporary-token contract. +- Silent credential creation: contradicts the required user experience. +- One key per command: resource churn and unclear ownership. +- Independent cloud SDKs in every consumer: violates shared provider ownership. + +## Dependencies + +Locked official @alicloud/sandbox20260820 1.2.0, existing provider-commands/1 engine. Companion: https://github.com/Alibaba-Agent-Sandbox/agent-sandbox/issues/200. Existing API-key SDK use remains independent of the CLI; the optional AK/SK path needs a matching CLI. + +## Test Strategy + +Consent denial and --ci, explicit-key priority, official generated models, real expiry request, reuse/renewal, identity/Team isolation, concurrency, private storage, disabled keys, definite permission denial and uncertain writes. Run required local checks and real CLI/SDK cloud lifecycle, with exact owned-resource cleanup. Keep cloud and mocked evidence separate. + +## Acceptance criteria + +- No credential write without explicit consent or --ci. +- Existing E2B standard users retain their credentials and calling contract. +- AK/SK-only CLI and Python clients use shared managed credentials. +- Exact feature/action/resource mapping, policy and recovery documented in English and Chinese. +- Required checks and real component acceptance recorded; no secrets or leaked owned resources. + +## Evidence + +- [Verification](../../../evidence/verify/2026-10-27-managed-api-key.md). diff --git a/CHANGELOG.md b/CHANGELOG.md index 319a1da2..5bd127a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/). ## [Unreleased] +### Added + +- Express AK/SK managed-key preparation with explicit consent, private shared storage, expiry renewal and feature-scoped RAM permission guidance. / 极速版支持经确认的 AK/SK 管理 Key 准备、私有共享存储、到期续期及按功能划分的 RAM 授权指南。 + ### Tests - Add canonical application authentication vectors for the Python, CLI and Node clients; SDK runtime/configuration APIs remain unchanged. diff --git a/README.md b/README.md index 4aadb892..563c5522 100644 --- a/README.md +++ b/README.md @@ -233,3 +233,5 @@ We welcome contributions! Please read the [Contributing Guide](.github/CONTRIBUT Provider-owned pause/resume and Snapshot commands share one implementation across CLI and SDK; see the [registration and usage guide](docs/en/guide/provider-commands.md). Current API / 当前接口: [development contract](docs/en/guide/current-api.md). + +[Express authentication and required permissions](docs/en/guide/permissions.md): existing API key or explicitly confirmed AK/SK-managed key. diff --git a/README.zh-CN.md b/README.zh-CN.md index bc9c6d85..3dde7827 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -227,3 +227,5 @@ graph TB 命名命令支持原生 `list[str]` JSON 参数,校验和服务器兼容性见[服务器 API 指南](docs/zh/guide/template-server-api.md#字符串数组命令参数)。 Current API / 当前接口: [development contract](docs/zh/guide/current-api.md). + +[极速版认证与所需权限](docs/zh/guide/permissions.md):已有 API Key,或经显式确认的 AK/SK 管理 Key。 diff --git a/client-sdk/src/agent_sandbox/_standard/PATCHES.md b/client-sdk/src/agent_sandbox/_standard/PATCHES.md index 0c5abc09..00b0cfcb 100644 --- a/client-sdk/src/agent_sandbox/_standard/PATCHES.md +++ b/client-sdk/src/agent_sandbox/_standard/PATCHES.md @@ -11,6 +11,10 @@ Local changes beyond namespace relocation: - Use project package metadata and version; do not require installed E2B distributions. - Read canonical E2B connection settings with project config-file fallback; default to Alibaba Express, derive explicit domains correctly, and accept Alibaba-issued API keys. +- When the Express project configuration supplies only AK/SK, resolve an already + prepared managed key through its provider-owned helper and shared local engine. + Credential creation/renewal requires explicit preparation consent. Existing + E2B/explicit API keys short-circuit this path; no public standard signatures change. - Support Alibaba's documented optional snapshot `name` list filter in sync and async clients/paginators, which the fixed Python SDK does not expose. Preserve all existing positional arguments. Encode qualified snapshot names in the template deletion path. diff --git a/client-sdk/src/agent_sandbox/_standard/connection_config.py b/client-sdk/src/agent_sandbox/_standard/connection_config.py index 8d1bccd7..f1a1d7ba 100644 --- a/client-sdk/src/agent_sandbox/_standard/connection_config.py +++ b/client-sdk/src/agent_sandbox/_standard/connection_config.py @@ -90,7 +90,8 @@ def _debug(): @staticmethod def _api_key(): - return os.getenv("E2B_API_KEY") or ConnectionConfig._project_value("sandbox_api_key") + from agent_sandbox.providers.registry import builtin_provider + return os.getenv("E2B_API_KEY") or builtin_provider().standard_api_key() @staticmethod def _validate_api_key(): diff --git a/client-sdk/src/agent_sandbox/providers/alicloud_express/__init__.py b/client-sdk/src/agent_sandbox/providers/alicloud_express/__init__.py index 380c07dc..96abb834 100644 --- a/client-sdk/src/agent_sandbox/providers/alicloud_express/__init__.py +++ b/client-sdk/src/agent_sandbox/providers/alicloud_express/__init__.py @@ -1 +1,8 @@ """Provider-owned backend implementation.""" + +from agent_sandbox.providers.alicloud_express.managed_api_key import ( + prepare_api_key, + prepare_api_key_sync, +) + +__all__ = ["prepare_api_key", "prepare_api_key_sync"] diff --git a/client-sdk/src/agent_sandbox/providers/alicloud_express/managed_api_key.py b/client-sdk/src/agent_sandbox/providers/alicloud_express/managed_api_key.py new file mode 100644 index 00000000..e16d963c --- /dev/null +++ b/client-sdk/src/agent_sandbox/providers/alicloud_express/managed_api_key.py @@ -0,0 +1,145 @@ +"""Consume the CLI's shared managed-key operation; cloud credential APIs stay provider-owned.""" + +from __future__ import annotations + +import json +import os +import re +import stat +from pathlib import Path +from typing import TYPE_CHECKING, Any + +from agent_sandbox.models.errors import InvalidAPIKeyError, ProviderProtocolError + +if TYPE_CHECKING: + from agent_sandbox.transport.config import TransportConfig + + +async def resolve_sandbox_api_key(config: TransportConfig) -> str: + """Reuse an explicit key, or resolve an existing AK/SK-managed key through abx. + + The engine owns the official API mapping, private cache, expiry renewal and + cross-process lock. Resolution cannot create or renew credentials. + """ + if config.sandbox_api_key and config.sandbox_api_key.strip(): + return config.sandbox_api_key + if not (config.access_key_id and config.access_key_secret): + raise InvalidAPIKeyError( + "Configure a Sandbox API key or both Alibaba Cloud AccessKey credentials.", + suggestion=( + "Set ABX_SANDBOX_API_KEY, or ALICLOUD_ACCESS_KEY_ID and " + "ALICLOUD_ACCESS_KEY_SECRET. AK/SK preparation needs " + "sandbox:CreateApiKey, sandbox:DescribeApiKey and sandbox:UpdateApiKey " + "on the target Team's API keys." + ), + ) + from agent_sandbox.providers.commands import run_command + from agent_sandbox.providers.engine_client import abx_dir + + result = await run_command( + "alicloud-express", + "api-key.resolve", + access_key_id=config.access_key_id, + access_key_secret=config.access_key_secret, + region=config.region, + http_timeout=config.http_timeout, + ) + if not isinstance(result, dict) or not isinstance(result.get("secret_file"), str): + raise ProviderProtocolError( + "API-key preparation returned no private file reference; use a matching abx version." + ) + filename = Path(result["secret_file"]) + directory = abx_dir() / "credentials" / "alicloud-express" + if filename.parent != directory or not re.fullmatch(r"[a-f0-9]{64}\.json", filename.name): + raise ProviderProtocolError("API-key preparation returned an unexpected file location.") + fd: int | None = None + try: + if directory.is_symlink() or directory.parent.is_symlink(): + raise ValueError("Credential directory is a symlink") + fd = os.open(filename, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0)) + metadata = os.fstat(fd) + if not stat.S_ISREG(metadata.st_mode) or ( + os.name != "nt" and stat.S_IMODE(metadata.st_mode) & 0o077 + ): + raise ValueError("Credential file is not private") + with os.fdopen(fd, encoding="utf-8") as stream: + fd = None + record = json.load(stream) + if ( + not isinstance(record, dict) + or record.get("state") != "ready" + or record.get("api_key_id") != result.get("api_key_id") + or record.get("team_id") != result.get("team_id") + or not isinstance(record.get("api_key_value"), str) + or not record["api_key_value"].strip() + ): + raise ValueError("Credential preparation is incomplete") + return str(record["api_key_value"]) + except (OSError, ValueError, TypeError) as error: + raise ProviderProtocolError( + "Cannot safely read the prepared API key; inspect local credential storage." + ) from error + finally: + if fd is not None: + os.close(fd) + + +async def prepare_api_key( + *, confirm: bool = False, team_id: str | None = None, **config: Any +) -> dict[str, Any]: + """Explicitly consent to preparing/renewing one Express-managed API key. + + Args: + confirm: Must be True to authorize a credential-resource mutation. + team_id: Target Team; omitted uses the API's default Team. + **config: Canonical Express settings, including access_key_id, + access_key_secret and region. Defaults come from project/user config. + + Returns: + Nonsecret credential metadata and its private local file reference. + The corresponding abx CLI must be installed; no cloud SDK is duplicated. + """ + from agent_sandbox.models.errors import ProviderTrustError + from agent_sandbox.providers.commands import run_command + + if confirm is not True: + raise ProviderTrustError( + "API-key preparation requires explicit confirm=True.", + suggestion=( + "This creates or renews a 30-day Team-scoped API key and stores it " + "privately on this machine. Required RAM actions: sandbox:CreateApiKey, " + "sandbox:DescribeApiKey, sandbox:UpdateApiKey." + ), + ) + result = await run_command( + "alicloud-express", + "api-key.prepare", + {"team_id": team_id} if team_id is not None else {}, + confirm=True, + **config, + ) + if not isinstance(result, dict): + raise ProviderProtocolError("API-key preparation returned invalid metadata.") + return result + + +def prepare_api_key_sync( + *, confirm: bool = False, team_id: str | None = None, **config: Any +) -> dict[str, Any]: + """Synchronous explicit preparation; accepts the same inputs as prepare_api_key.""" + from agent_sandbox.utils.async_bridge import run_sync + + return run_sync(prepare_api_key(confirm=confirm, team_id=team_id, **config)) + + +def standard_api_key() -> str | None: + """Resolve the standard sync/async client's project-config fallback only when needed.""" + from agent_sandbox.transport.config import load_config + from agent_sandbox.utils.async_bridge import run_sync + + config = load_config(provider="alicloud-express") + if config.sandbox_api_key: + return config.sandbox_api_key + if config.access_key_id or config.access_key_secret: + return run_sync(resolve_sandbox_api_key(config)) + return None diff --git a/client-sdk/src/agent_sandbox/providers/alicloud_express/provider.py b/client-sdk/src/agent_sandbox/providers/alicloud_express/provider.py index d6db9afe..ecf23657 100644 --- a/client-sdk/src/agent_sandbox/providers/alicloud_express/provider.py +++ b/client-sdk/src/agent_sandbox/providers/alicloud_express/provider.py @@ -97,6 +97,12 @@ def __init__(self) -> None: def config_keys(self): # type: ignore[no-untyped-def] return ALICLOUD_EXPRESS_KEYS + def standard_api_key(self) -> str | None: + """Resolve standard Express credentials without leaking backend code into core.""" + from agent_sandbox.providers.alicloud_express.managed_api_key import standard_api_key + + return standard_api_key() + create_options = frozenset({"volume_mounts"}) def session_protocols(self, http: Any) -> dict[str, Any]: @@ -160,6 +166,7 @@ async def open_terminal(self, sandbox: Any, **options: Any) -> Any: async def build_infra(self, config: TransportConfig) -> ProviderInfra: from agent_sandbox.providers.alicloud_express.auth import create_auth_provider + from agent_sandbox.providers.alicloud_express.managed_api_key import resolve_sandbox_api_key from agent_sandbox.providers.shared.e2b.code_interpreter import UnsupportedCodeProtocol from agent_sandbox.providers.shared.e2b.filesystem import FilesystemProtocol from agent_sandbox.providers.shared.e2b.process import ProcessProtocol @@ -167,7 +174,11 @@ async def build_infra(self, config: TransportConfig) -> ProviderInfra: from agent_sandbox.transport.http import HttpClient auth = create_auth_provider( - api_key=config.sandbox_api_key, + api_key=( + await resolve_sandbox_api_key(config) + if config.access_key_id or config.access_key_secret + else config.sandbox_api_key + ), ) http = HttpClient(config, auth) from agent_sandbox.providers.feature_gate import gate_operation_protocols diff --git a/client-sdk/tests/test_providers/test_managed_api_key.py b/client-sdk/tests/test_providers/test_managed_api_key.py new file mode 100644 index 00000000..51161a0d --- /dev/null +++ b/client-sdk/tests/test_providers/test_managed_api_key.py @@ -0,0 +1,85 @@ +"""Consent, explicit credential priority and the shared engine's private-file boundary.""" + +from __future__ import annotations + +import json +from unittest.mock import AsyncMock + +import pytest + +from agent_sandbox.models.errors import ProviderProtocolError, ProviderTrustError +from agent_sandbox.providers import commands, engine_client +from agent_sandbox.providers.alicloud_express import prepare_api_key +from agent_sandbox.providers.alicloud_express.managed_api_key import resolve_sandbox_api_key +from agent_sandbox.transport.config import TransportConfig + + +def config(**options): + return TransportConfig( + access_key_id="placeholder-ak", access_key_secret="placeholder-secret", **options + ) + + +async def test_explicit_key_never_contacts_engine(monkeypatch): + call = AsyncMock() + monkeypatch.setattr(commands, "run_command", call) + assert ( + await resolve_sandbox_api_key(config(sandbox_api_key="placeholder-explicit")) + == "placeholder-explicit" + ) + call.assert_not_called() + + +async def test_readonly_resolution_reuses_private_key(tmp_path, monkeypatch): + monkeypatch.setattr(engine_client, "abx_dir", lambda: tmp_path) + directory = tmp_path / "credentials" / "alicloud-express" + directory.mkdir(parents=True) + filename = directory / ("a" * 64 + ".json") + filename.write_text( + json.dumps( + { + "state": "ready", + "api_key_id": "owned", + "team_id": "team", + "api_key_value": "placeholder-prepared", + } + ) + ) + filename.chmod(0o600) + result = {"secret_file": str(filename), "api_key_id": "owned", "team_id": "team"} + call = AsyncMock(return_value=result) + monkeypatch.setattr(commands, "run_command", call) + assert await resolve_sandbox_api_key(config()) == "placeholder-prepared" + assert call.call_args.args == ("alicloud-express", "api-key.resolve") + assert "confirm" not in call.call_args.kwargs + + +async def test_preparation_requires_explicit_consent(monkeypatch): + call = AsyncMock(return_value={"api_key_id": "owned"}) + monkeypatch.setattr(commands, "run_command", call) + with pytest.raises(ProviderTrustError, match="confirm=True"): + await prepare_api_key() + call.assert_not_called() + assert await prepare_api_key(confirm=True, team_id="chosen") == {"api_key_id": "owned"} + assert call.call_args.args == ("alicloud-express", "api-key.prepare", {"team_id": "chosen"}) + assert call.call_args.kwargs["confirm"] is True + + +@pytest.mark.parametrize( + "result", [None, {}, {"secret_file": "/etc/passwd"}, {"secret_file": "relative.json"}] +) +async def test_invalid_file_reference_is_rejected(monkeypatch, result): + monkeypatch.setattr(commands, "run_command", AsyncMock(return_value=result)) + with pytest.raises(ProviderProtocolError): + await resolve_sandbox_api_key(config()) + + +async def test_standard_explicit_e2b_key_bypasses_managed_resolution(monkeypatch): + from agent_sandbox._standard.connection_config import ConnectionConfig + from agent_sandbox.providers.alicloud_express import managed_api_key + + monkeypatch.setenv("E2B_API_KEY", "placeholder-existing") + monkeypatch.setattr( + managed_api_key, "standard_api_key", lambda: pytest.fail("must not resolve") + ) + assert ConnectionConfig._api_key() == "placeholder-existing" diff --git a/docs/en/guide/authentication.md b/docs/en/guide/authentication.md index 91c2b5b6..b07470ff 100644 --- a/docs/en/guide/authentication.md +++ b/docs/en/guide/authentication.md @@ -6,7 +6,7 @@ For current SDK installation, use the [installation guide](install-python.md). > **Note:** The `abx` commands shown on this page are provided by the Node.js CLI in the `cli` repository ([CLI binary installation](https://github.com/Alibaba-Agent-Sandbox/agent-sandbox/blob/main/docs/en/guide/binary-installation.md)). CLI-specific documentation (CLI design, tutorial, reference) lives there too; this repository documents the Python SDK (`agent-sandbox-client`) and the container-side runtime (`agent-sandbox-server`). -Sandbox runtime requests require an **API Key**. **AK/SK (Alibaba Cloud AccessKey)** is configured separately for official control-plane operations. +Sandbox runtime requests require an **API Key**. **AK/SK (Alibaba Cloud AccessKey)** can explicitly prepare that key for Express, and authenticates native control-plane operations; see [permissions](permissions.md). Credentials belong to a [provider](providers.md). The built-in provider is **Alibaba Cloud Express Edition (阿里云极速版)**, id `alicloud-express`, and it is the default. The sandbox key is `ABX_SANDBOX_API_KEY`. Alibaba Cloud access keys use the official names `ALICLOUD_ACCESS_KEY_ID` and `ALICLOUD_ACCESS_KEY_SECRET`. The region is `ALIBABA_CLOUD_REGION_ID`. Each key has one name. @@ -19,7 +19,7 @@ Credentials belong to a [provider](providers.md). The built-in provider is **Ali Agent Sandbox provides three authentication paths for different scenarios: - **API Key mode**: The most common approach. Accesses the platform API via the `Authorization: Bearer {api_key}` header. Suitable for personal development, CI/CD, SDK calls, and most other scenarios. -- **AK/SK control plane**: Used by official template/image/ACR operations. It does not automatically create or exchange a sandbox API key. +- **AK/SK control plane**: Used by official template/image/ACR operations. Preparation requires explicit consent; see [permissions](permissions.md). - **envd internal authentication**: After sandbox creation, the SDK communicates with the in-sandbox envd service using `envdAccessToken` (obtained from the creation response). This is an internal mechanism managed automatically by the SDK — no manual handling required. --- @@ -80,35 +80,9 @@ sandbox = await Sandbox.connect("sbx-xxxx", sandbox_api_key="your-api-key") --- -## AK/SK control plane and migration - -The previous automatic AK/SK-to-sandbox-key exchange has been removed. With only -AccessKey credentials, sandbox authentication now raises `InvalidAPIKeyError` -(E1001) locally, before any exchange/network request. Configure -`ABX_SANDBOX_API_KEY` or pass `sandbox_api_key=` explicitly for -sandbox create/connect/list and data-plane requests. - -Keep `ALICLOUD_ACCESS_KEY_ID`, `ALICLOUD_ACCESS_KEY_SECRET` and the configured -region for official template/image and ACR control-plane operations. These -operations have their own clients and credentials; their supported endpoints -are unchanged. API key and AccessKey credentials may be configured together for -a workflow that needs both runtime and template deployment. - -The locked official FCSandbox SDK has a `CreateApiKey` operation, but models it -as `POST /pop/2026-05-09/api-keys` with a structured `apiKey.apiKeyValue` response. -That credential-resource API does not establish an STS-style one-hour exchange -or safe automatic refresh lifecycle. The old guessed RPC GET/signing/parser -implementation is retired; the runtime never mints credentials implicitly. - -For an application-owned custom token service, the exported `AkSkAuth` adapter -still supports an explicitly supplied `set_exchange_func()` callback. It must -return a non-empty string; objects/empty values fail without being cached. -The application owns endpoint, credential lifecycle and documented TTL. Legacy -cache constants are a local policy, not a claim about Alibaba token validity. -The default factory never injects a callback. Direct imports of the removed -private exchange/signing helpers must migrate to their own documented service. +## AK/SK managed authentication ---- +Express AK/SK-only clients use a key prepared by the shared provider engine. Explicitly call `prepare_api_key(confirm=True)` or `prepare_api_key_sync(confirm=True)` before first use or renewal. SDK calls never prompt or silently create credentials. [Permissions, policy and examples](permissions.md). ## Configuration Priority Chain @@ -130,7 +104,7 @@ The generic sandbox key (`sandbox_api_key`, environment variable `ABX_SANDBOX_AP Every plugin option follows one rule: the canonical name is `ABX__` (dashes become underscores), and a manifest may add the vendor's own variable as an alias with `env = [...]`. Secret fields (`secret = true`) live only in the environment or `.env` files (mode 0600) and are refused on the command line; other fields can also live under `[providers.]` in `config.toml`. Verified with OpenSandbox on 2026-10-13: `ABX_OPENSANDBOX_API_KEY` and `ABX_OPENSANDBOX_BASE_URL` were enough to drive a real server from both the CLI and the SDK; `OPEN_SANDBOX_API_KEY` and `OPEN_SANDBOX_DOMAIN` are its declared native aliases, and the canonical name wins when both are set. A plugin that declares `[core] inherit = ["sandbox_api_key"]` receives the generic key only after you consent at install time, and `abx provider info ` shows that list under "Inherits". -The sandbox API key is never derived from AK/SK. When both are configured, runtime calls use the API key: +AK/SK-managed preparation requires explicit consent. When both are configured, runtime calls use the API key: - If both `sandbox_api_key` and `access_key_id` are provided, API Key is used - If both `ABX_SANDBOX_API_KEY` and `ALICLOUD_ACCESS_KEY_ID` are set, `ABX_SANDBOX_API_KEY` is used diff --git a/docs/en/guide/permissions.md b/docs/en/guide/permissions.md new file mode 100644 index 00000000..2bb78ca5 --- /dev/null +++ b/docs/en/guide/permissions.md @@ -0,0 +1,23 @@ +# Express features and permissions + +The shared provider implementation and complete task-to-RAM-Action/resource policy tables are maintained in the [CLI permissions guide](https://github.com/Alibaba-Agent-Sandbox/agent-sandbox/blob/main/docs/en/guide/permissions.md). It includes API-key preparation/reuse/renewal, Team/template/Volume/Quota/Template Cache operations, ACR, storage execution roles and administrator policy steps. + +Existing API-key users need no additional AccessKey. Explicit keys always win. AK/SK-only SDK calls resolve a previously prepared key; they never interactively prompt or silently create/renew a credential. Install a matching `abx` CLI (or set `ABX_BIN`) for this optional AK/SK path. Direct API-key SDK use does not need the CLI. + +Grant `sandbox:CreateApiKey`, `sandbox:DescribeApiKey` and `sandbox:UpdateApiKey` on `acs:sandbox:::teams//apikeys/*`, then explicitly authorize preparation: + +```python +from agent_sandbox.providers.alicloud_express import prepare_api_key_sync +from agent_sandbox import Sandbox + +prepare_api_key_sync(confirm=True) # creates/renews a private 30-day managed key +sandbox = Sandbox.create(template="base") +try: + print(sandbox.commands.run("echo ready").stdout) +finally: + sandbox.kill() +``` + +Async code uses `await prepare_api_key(confirm=True)` and may then use `AsyncSandbox` or `ProviderSandbox`. Default `confirm=False` rejects before engine/cloud I/O. Preparation returns metadata, never plaintext. Reuse checks cloud status. Near-expiry keys need fresh explicit preparation; already constructed clients do not renew in the background. Disabling or deleting a key is respected. The engine owns expiry and shared private storage, with no duplicated cloud SDK in Python. + +The optional `team_id=` selector must match subsequent engine selection. For persistent selection use `abx config set team_id ` so CLI and SDK share the same context; otherwise both use the API's default Team. In CI, `abx --ci sandbox list --format json` explicitly authorizes preparation before starting the SDK application. See the owner guide for recovery after uncertain writes and exact cleanup permissions. diff --git a/docs/zh/guide/authentication.md b/docs/zh/guide/authentication.md index d4940667..811c2662 100644 --- a/docs/zh/guide/authentication.md +++ b/docs/zh/guide/authentication.md @@ -6,7 +6,7 @@ > **说明:** 本页出现的 `abx` 命令由 `cli` 仓库中的 Node.js CLI 提供([CLI binary installation](https://github.com/Alibaba-Agent-Sandbox/agent-sandbox/blob/main/docs/en/guide/binary-installation.md))。CLI 专属文档(CLI 设计、教程、参考)也在该仓库;本仓库记录 Python SDK(`agent-sandbox-client`)与容器侧运行时(`agent-sandbox-server`)。 -沙箱运行时请求需要 **API Key**;**AK/SK(阿里云 AccessKey)**用于独立的官方控制面操作。 +沙箱运行时请求需要 **API Key**;**AK/SK(阿里云 AccessKey)**经明确确认后可为极速版准备该 Key,也用于原生控制面操作;见[功能与权限](permissions.md)。 凭证属于某个[提供方](providers.md)。内置提供方是**阿里云极速版(Alibaba Cloud Express Edition)**,id 为 `alicloud-express`,也是默认提供方。沙箱密钥是 `ABX_SANDBOX_API_KEY`。阿里云 AccessKey 使用官方名字 `ALICLOUD_ACCESS_KEY_ID` 和 `ALICLOUD_ACCESS_KEY_SECRET`。地域是 `ALIBABA_CLOUD_REGION_ID`。每个键只有一个名字。 @@ -19,7 +19,7 @@ Agent Sandbox 存在三条认证路径,适用于不同场景: - **API Key 模式**:最常用。通过 `Authorization: Bearer {api_key}` 请求头访问平台 API。适用于个人开发、CI/CD、SDK 调用等绝大多数场景。 -- **AK/SK 控制面**:用于官方模板、镜像与 ACR 操作,不会自动创建或交换沙箱 API Key。 +- **AK/SK 控制面**:用于官方模板、镜像与 ACR 操作,显式确认后可准备管理 Key,见[权限](permissions.md)。 - **envd 内部认证**:沙箱创建后,SDK 与沙箱内部 envd 服务之间的通信认证。使用 `envdAccessToken`(从创建响应中获取),由 SDK 自动管理,用户无需手动处理。 --- @@ -80,29 +80,9 @@ sandbox = await Sandbox.connect("sbx-xxxx", sandbox_api_key="your-api-key") --- -## AK/SK 控制面与迁移 +## AK/SK 管理式认证 -过去自动将 AK/SK 换成沙箱密钥的默认路径已删除。仅配置 AccessKey 时,沙箱鉴权 -会在本地、任何交换请求或网络请求之前抛出 `InvalidAPIKeyError`(E1001)。 -沙箱创建、连接、列表及运行时请求请配置 `ABX_SANDBOX_API_KEY`,或显式传入 -`sandbox_api_key=`。 - -官方模板、镜像和 ACR 控制面仍使用 `ALICLOUD_ACCESS_KEY_ID`、 -`ALICLOUD_ACCESS_KEY_SECRET` 与对应区域;这些操作使用独立客户端,已有支持的 -端点不变。需要运行时和模板部署的工作流,可以同时配置 API Key 与 AccessKey。 - -锁定的官方 FCSandbox SDK 确实有 `CreateApiKey`,但其模型是 -`POST /pop/2026-05-09/api-keys`,返回结构化 `apiKey.apiKeyValue`。这个凭据资源 -API 不能证明存在 STS 式的一小时交换或安全自动刷新生命周期。因此退役旧的猜测式 -RPC GET、签名与解析实现,运行时不会隐式创建凭据。 - -应用自行拥有 token 服务时,公开的 `AkSkAuth` 仍可通过 -`set_exchange_func()` 显式设置回调。回调必须返回非空字符串,结构对象或空值会 -失败且不进入缓存。端点、凭据生命周期与准确 TTL 由应用负责;旧缓存常量只是本地 -策略,不代表阿里云 token 有效期。默认工厂不再注入回调。直接导入旧私有交换/ -签名辅助函数的代码,应迁移到自己确认的服务契约。 - ---- +极速版只有 AK/SK 时使用共享 Provider 引擎准备的 Key。首次使用或续期前,显式调用 `prepare_api_key(confirm=True)` 或 `prepare_api_key_sync(confirm=True)`。SDK 不弹交互提示,也不静默创建凭据。[权限策略及示例](permissions.md)。 ## 配置优先级链 @@ -124,7 +104,7 @@ GitHub token 和区域走同一条链。空白或只含空格的值会被跳过 插件的每个选项都遵循同一条规则:规范名是 `ABX__`(连字符变下划线),清单可以用 `env = [...]` 把厂商自己的变量加为别名。密钥字段(`secret = true`)只存在于环境变量或 `.env` 文件(权限 0600),命令行上会被拒绝;其他字段也可以放在 `config.toml` 的 `[providers.]` 下。2026-10-13 用 OpenSandbox 验证:只要 `ABX_OPENSANDBOX_API_KEY` 和 `ABX_OPENSANDBOX_BASE_URL`,CLI 和 SDK 都能驱动真实服务器;`OPEN_SANDBOX_API_KEY` 与 `OPEN_SANDBOX_DOMAIN` 是它声明的原生别名,两者同时设置时规范名优先。声明了 `[core] inherit = ["sandbox_api_key"]` 的插件,只有在你于安装时同意之后才会收到通用密钥;`abx provider info ` 会在「Inherits」下显示这份清单。 -沙箱 API Key 不会由 AK/SK 自动生成。同时配置时,运行时使用 API Key: +AK/SK 管理 Key 需要显式准备确认。同时配置时,运行时使用 API Key: - 若同时提供了 `sandbox_api_key` 和 `access_key_id`,使用 API Key - 若同时设置了 `ABX_SANDBOX_API_KEY` 和 `ALICLOUD_ACCESS_KEY_ID`,使用 `ABX_SANDBOX_API_KEY` @@ -190,7 +170,7 @@ envd 认证需要以下 HTTP 头(已由 SDK 自动设置): [E1002] envdAccessToken not available. Sandbox may not be created or connected. ``` -**解决**:重新连接以获得有效 envd token,或检查显式配置的自定义交换;API Key 不会自动创建或刷新。 +**解决**:重新连接以获得有效 envd token。管理 API Key 的创建和续期需要单独确认,见[权限指南](permissions.md)。 --- diff --git a/docs/zh/guide/permissions.md b/docs/zh/guide/permissions.md new file mode 100644 index 00000000..26a6cd16 --- /dev/null +++ b/docs/zh/guide/permissions.md @@ -0,0 +1,23 @@ +# 极速版功能与权限 + +共享 Provider 实现和完整的“功能 → RAM Action → 资源范围”策略表由 [CLI 权限指南](https://github.com/Alibaba-Agent-Sandbox/agent-sandbox/blob/main/docs/zh/guide/permissions.md)维护,包括 Key 准备/复用/续期、Team/模板/Volume/Quota/Template Cache、ACR、存储执行角色及管理员授权步骤。 + +已有 API Key 用户无需额外 AccessKey,显式 Key 始终优先。只有 AK/SK 时,SDK 只解析已经准备的 Key,不弹交互提示,不静默创建或续期。该可选路径需安装配套 `abx`(或设置 `ABX_BIN`);直接用 API Key 的 SDK 调用不需要 CLI。 + +向调用身份授予 `sandbox:CreateApiKey`、`sandbox:DescribeApiKey`、`sandbox:UpdateApiKey`,限定在 `acs:sandbox:::teams//apikeys/*`,然后显式确认准备: + +```python +from agent_sandbox.providers.alicloud_express import prepare_api_key_sync +from agent_sandbox import Sandbox + +prepare_api_key_sync(confirm=True) # 创建/续期本机私有的 30 天管理 Key +sandbox = Sandbox.create(template="base") +try: + print(sandbox.commands.run("echo ready").stdout) +finally: + sandbox.kill() +``` + +异步使用 `await prepare_api_key(confirm=True)`,然后可用 `AsyncSandbox` 或 `ProviderSandbox`。默认 `confirm=False` 在引擎/云端 I/O 前拒绝。准备只返回元数据,不返回明文。复用时检查云端状态,临近到期需要重新显式准备;已构造客户端不会后台续期。禁用和删除不会被绕过。引擎负责有效期和共享私有存储,Python 不重复实现云 SDK。 + +可选 `team_id=` 必须与后续引擎选择一致。建议用 `abx config set team_id ` 持久化,使 CLI/SDK 使用同一上下文;省略时均使用接口默认 Team。CI 可先运行 `abx --ci sandbox list --format json` 明确授权准备,再启动 SDK 应用。未知写入结果的恢复步骤和精确清理权限见上述完整指南。 diff --git a/e2e/README.md b/e2e/README.md index bea52532..ddfc9ac3 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -1,5 +1,7 @@ # Real-cloud E2E suite (Python SDK) +Run `ABX_BIN= pytest e2e/test_managed_api_key.py -q` with AK/SK and the Express endpoint/domain to verify explicit SDK preparation and standard/provider sandbox exec/files lifecycle through the real shared engine. The test uses an isolated home, confirms key deletion and stops its engine. Cleanup additionally needs `sandbox:DeleteApiKey`; see [permissions](../docs/en/guide/permissions.md). + End-to-end tests of `agent-sandbox` (client-sdk) and `agent-sandbox-server` (server-sdk) against the **real Alibaba Cloud FC agent sandbox platform**: real sandboxes, real envd, real in-sandbox server. Templates are built and deployed with `abx template build|deploy`, which the CLI's own E2E suite covers. Mocks live in `client-sdk/tests` and `server-sdk/tests`; this suite exists diff --git a/e2e/README.zh-CN.md b/e2e/README.zh-CN.md index b9f8339a..a1d90fab 100644 --- a/e2e/README.zh-CN.md +++ b/e2e/README.zh-CN.md @@ -1,5 +1,7 @@ # 真实云端 E2E 套件(Python SDK) +使用 `ABX_BIN= pytest e2e/test_managed_api_key.py -q`,配置 AK/SK 和极速版端点/域名,验证 SDK 显式准备,以及标准/Provider 沙箱命令和文件操作。测试使用独立用户目录,确认 Key 删除并停止本次引擎。清理需额外的 `sandbox:DeleteApiKey`,见[权限指南](../docs/zh/guide/permissions.md)。 + 针对**真实阿里云 FC 智能体沙箱平台**对 `agent-sandbox`(client-sdk)与 `agent-sandbox-server`(server-sdk)做端到端测试: 真实沙箱、真实 envd、真实沙箱内 server。模板的构建与部署由 `abx template build|deploy` 负责,由 CLI 自己的 E2E 套件覆盖。mock 测试位于 `client-sdk/tests` 与 `server-sdk/tests`;本套件的存在 是因为 mock 无法证明产品可用(见 [`../.agents/notes/DEFINITION-OF-DONE.md`](../.agents/notes/DEFINITION-OF-DONE.md) 中的完成定义)。 diff --git a/e2e/test_managed_api_key.py b/e2e/test_managed_api_key.py new file mode 100644 index 00000000..b4973a6f --- /dev/null +++ b/e2e/test_managed_api_key.py @@ -0,0 +1,92 @@ +"""AK/SK-only SDK acceptance through the actual CLI engine; own credential and sandboxes only.""" + +from __future__ import annotations + +import os +import subprocess +import sys + +import pytest + +import e2e_helpers as H + +pytestmark = pytest.mark.provider_live + + +def test_managed_key_consent_and_shared_runtime(tmp_path): + if not all( + os.environ.get(k) + for k in ("ALICLOUD_ACCESS_KEY_ID", "ALICLOUD_ACCESS_KEY_SECRET", "ABX_BIN") + ): + pytest.skip("Requires AccessKeys and the candidate ABX_BIN; no API-key values are printed") + environment = dict(os.environ) + for key in list(environment): + if key.startswith(("E2B_", "ABX_")) and key.endswith(("API_KEY", "TOKEN")): + environment.pop(key) + environment.update(HOME=str(tmp_path), USERPROFILE=str(tmp_path)) + source = r""" +import asyncio +from agent_sandbox import Sandbox, AsyncSandbox, ProviderSandbox +from agent_sandbox.models.errors import ProviderTrustError, ProviderResourceNotFoundError +from agent_sandbox.providers.alicloud_express import prepare_api_key_sync +from agent_sandbox.providers.commands import run_command + +try: + prepare_api_key_sync() + raise AssertionError("Missing consent was accepted") +except ProviderTrustError: + pass +metadata = prepare_api_key_sync(confirm=True) +async def native(): + for client in (AsyncSandbox, ProviderSandbox): + sandbox = await client.create(template="base", timeout=120) + try: + assert (await sandbox.commands.run("echo managed-native")).stdout.strip() == "managed-native" + await sandbox.files.write("/tmp/managed-native.txt", "verified") + assert await sandbox.files.read("/tmp/managed-native.txt") == "verified" + finally: + await sandbox.kill() +async def cleanup(): + identity = {"api_key_id": metadata["api_key_id"]} + await run_command("alicloud-express", "api-key.update", {**identity, "status": "inactive"}, confirm=True) + await run_command("alicloud-express", "api-key.delete", identity, confirm=True) + try: + await run_command("alicloud-express", "api-key.get", identity) + except ProviderResourceNotFoundError: + return + raise AssertionError("Owned API key deletion not confirmed") +try: + sandbox = Sandbox.create(template="base", timeout=120) + try: + assert sandbox.commands.run("echo managed-standard").stdout.strip() == "managed-standard" + sandbox.files.write("/tmp/managed-standard.txt", "verified") + assert sandbox.files.read("/tmp/managed-standard.txt") == "verified" + finally: + sandbox.kill() + asyncio.run(native()) + print("PASS: SDK explicit preparation; standard/provider create, exec, files and kill") +finally: + asyncio.run(cleanup()) + print("PASS: owned credential cleanup and absence readback") +""" + try: + result = subprocess.run( + [sys.executable, "-c", source], + cwd=tmp_path, + env=environment, + capture_output=True, + text=True, + timeout=240, + ) + assert result.returncode == 0, H.mask(result.stdout + result.stderr) + assert result.stdout.count("PASS:") == 2 + finally: + import shlex + + subprocess.run( + [*shlex.split(environment["ABX_BIN"]), "provider", "engine", "stop"], + cwd=tmp_path, + env=environment, + capture_output=True, + timeout=30, + ) From d493cb6f21af0bd0cd8a46acc6c5e286f5aae454 Mon Sep 17 00:00:00 2001 From: anycodes <21079031+anycodes@users.noreply.github.com> Date: Tue, 27 Oct 2026 07:03:54 +0000 Subject: [PATCH 2/4] docs: clarify managed-key recovery and verification status --- .agents/evidence/verify/2026-10-27-managed-api-key.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.agents/evidence/verify/2026-10-27-managed-api-key.md b/.agents/evidence/verify/2026-10-27-managed-api-key.md index 9c510dfa..d0fb4500 100644 --- a/.agents/evidence/verify/2026-10-27-managed-api-key.md +++ b/.agents/evidence/verify/2026-10-27-managed-api-key.md @@ -1,6 +1,7 @@ # Verification: Shared Express managed API keys in Python - Date: 2026-10-27 +- Pull request: https://github.com/Alibaba-Agent-Sandbox/python-sdk/pull/145. Tested feature source: 0e6e27aa97797dd33791352ff6d801846d39ab8c (subsequent documentation-only corrections do not change SDK behavior). - Change: [Issue #143](https://github.com/Alibaba-Agent-Sandbox/python-sdk/issues/143); [decision](../../notes/proposed/architecture/2026-10-27-managed-api-key.md). - Result: PARTIAL — client and cloud acceptance passed; unchanged server full-suite failure blocks local make ci. - Packages: candidate client/server 0.4.0a6 built from main 5d855223e3ec8797b3c4ad7d01644a8a5145b501 plus this PR; no package publication. @@ -45,7 +46,7 @@ Acceptance uses an isolated user directory and only its tracked key/sandboxes. E ## Problems found -The pre-existing server malformed-WebSocket assertion prevents full local make ci success. See follow-up linked in the PR; no server behavior was changed by this client feature. This PR depends on the CLI's new api-key.resolve/api-key.prepare operations landing first. Existing direct API-key SDK use still needs no CLI. +The pre-existing server malformed-WebSocket assertion prevents full local make ci success. Tracked in [baseline issue #144](https://github.com/Alibaba-Agent-Sandbox/python-sdk/issues/144); no server behavior was changed by this client feature. This PR depends on the CLI's new api-key.resolve/api-key.prepare operations landing first. Existing direct API-key SDK use still needs no CLI. ## Not verified From 7b54b4ed47cb40a1b852b425b6d89964bac4c681 Mon Sep 17 00:00:00 2001 From: anycodes <21079031+anycodes@users.noreply.github.com> Date: Tue, 27 Oct 2026 07:14:56 +0000 Subject: [PATCH 3/4] docs: record supported managed-key CI acceptance --- .agents/README.md | 4 ++- .../verify/2026-10-27-managed-api-key-ci.md | 31 +++++++++++++++++++ .../verify/2026-10-27-managed-api-key.md | 4 ++- .../2026-10-27-managed-api-key.md | 4 ++- 4 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 .agents/evidence/verify/2026-10-27-managed-api-key-ci.md rename .agents/notes/{proposed => implemented}/architecture/2026-10-27-managed-api-key.md (96%) diff --git a/.agents/README.md b/.agents/README.md index 6e55b773..5f56d8db 100644 --- a/.agents/README.md +++ b/.agents/README.md @@ -245,4 +245,6 @@ Builtin transport: [direct HTTP](notes/implemented/architecture/2026-10-18-nativ - [Shared application authentication vectors](notes/proposed/feature/2026-10-26-application-auth-vectors.md) · [verification](evidence/verify/2026-10-26-application-auth-vectors.md). -- [Consented Express managed API keys](notes/proposed/architecture/2026-10-27-managed-api-key.md) · [verification](evidence/verify/2026-10-27-managed-api-key.md). +- [Consented Express managed API keys](notes/implemented/architecture/2026-10-27-managed-api-key.md) · [verification](evidence/verify/2026-10-27-managed-api-key.md). + +- [Managed-key supported CI acceptance](evidence/verify/2026-10-27-managed-api-key-ci.md). diff --git a/.agents/evidence/verify/2026-10-27-managed-api-key-ci.md b/.agents/evidence/verify/2026-10-27-managed-api-key-ci.md new file mode 100644 index 00000000..ded3faa7 --- /dev/null +++ b/.agents/evidence/verify/2026-10-27-managed-api-key-ci.md @@ -0,0 +1,31 @@ +# Verification: Managed API-key remote CI acceptance + +- Date: 2026-10-27 +- Change: https://github.com/Alibaba-Agent-Sandbox/python-sdk/pull/145. +- Result: PASS for the tested feature implementation and supported CI matrix. Final record-only head is checked again before merge. +- Tested source: d493cb6f21af0bd0cd8a46acc6c5e286f5aae454; client/server 0.4.0a6, no version bump or publication. +- Environment: GitHub Actions Ubuntu; Python 3.10, 3.11 and 3.12. + +## What was verified + +The repository's complete CI passed for the managed-key feature. This provides supported-environment coverage beyond the local runtime results in [the initial evidence](2026-10-27-managed-api-key.md). The local malformed-WebSocket baseline does not reproduce in that CI run; its cause is not claimed to be fixed. + +## Commands run + +The [CI workflow run](https://github.com/Alibaba-Agent-Sandbox/python-sdk/actions/runs/40464862377) executed the repository workflow: Ruff, format check, mypy and full client/server tests for each supported Python version, paired package build/twine/py.typed/combined install, standard E2B callable contract, Python-to-Node engine integration, public runtime fixture checks and hygiene. The [Markdown link run](https://github.com/Alibaba-Agent-Sandbox/python-sdk/actions/runs/40464862356) also passed. + +## Results + +All 13 reported checks passed: client-sdk and server-sdk on Python 3.10/3.11/3.12, package build, pinned E2B contract, Node provider engine, both public runtime fixtures, hygiene and Markdown links. No required review or check is bypassed. The final documentation/record head is rechecked through normal CI before merge. + +## Cleanup + +This workflow acceptance does not claim new billable cloud coverage. Real AK/SK-only SDK acceptance and its exact owned-key/sandbox cleanup are recorded separately in the initial evidence. No credentials or raw cloud logs are included. + +## Problems found + +Local baseline #144 remains an environment-specific test follow-up. Required repository CI for this source passed; preserving a local failure report is not a claim that it was repaired. + +## Not verified + +No package publication, Windows ACL matrix, alternate region or separate restricted RAM identity was added. The new provider operations must land in [CLI PR #202](https://github.com/Alibaba-Agent-Sandbox/agent-sandbox/pull/202) before this consumer. Merge state and final-head checks are verified from the PR itself, not inferred from this record. diff --git a/.agents/evidence/verify/2026-10-27-managed-api-key.md b/.agents/evidence/verify/2026-10-27-managed-api-key.md index d0fb4500..8b076996 100644 --- a/.agents/evidence/verify/2026-10-27-managed-api-key.md +++ b/.agents/evidence/verify/2026-10-27-managed-api-key.md @@ -2,7 +2,7 @@ - Date: 2026-10-27 - Pull request: https://github.com/Alibaba-Agent-Sandbox/python-sdk/pull/145. Tested feature source: 0e6e27aa97797dd33791352ff6d801846d39ab8c (subsequent documentation-only corrections do not change SDK behavior). -- Change: [Issue #143](https://github.com/Alibaba-Agent-Sandbox/python-sdk/issues/143); [decision](../../notes/proposed/architecture/2026-10-27-managed-api-key.md). +- Change: [Issue #143](https://github.com/Alibaba-Agent-Sandbox/python-sdk/issues/143); [decision](../../notes/implemented/architecture/2026-10-27-managed-api-key.md). - Result: PARTIAL — client and cloud acceptance passed; unchanged server full-suite failure blocks local make ci. - Packages: candidate client/server 0.4.0a6 built from main 5d855223e3ec8797b3c4ad7d01644a8a5145b501 plus this PR; no package publication. - Environment: Linux, Python 3.12.13 (supplementary 3.13.13 checks), Node 22.22.2, real Express cn-hangzhou; candidate CLI from the companion feature branch. Credentials supplied through ignored environment configuration; values not recorded. @@ -51,3 +51,5 @@ The pre-existing server malformed-WebSocket assertion prevents full local make c ## Not verified Remote Python 3.10/3.11 matrix, Windows ACL behavior, alternate regions, the full slow cloud matrix and an independently restricted RAM identity were not verified here. Direct cloud SDK behavior is owned/tested by the companion CLI, not reimplemented in Python. No PyPI release was requested or performed. + +Supported remote CI subsequently passed; see [CI acceptance](2026-10-27-managed-api-key-ci.md). The local failures above remain part of this run history. diff --git a/.agents/notes/proposed/architecture/2026-10-27-managed-api-key.md b/.agents/notes/implemented/architecture/2026-10-27-managed-api-key.md similarity index 96% rename from .agents/notes/proposed/architecture/2026-10-27-managed-api-key.md rename to .agents/notes/implemented/architecture/2026-10-27-managed-api-key.md index fd04f5e9..78aa3d58 100644 --- a/.agents/notes/proposed/architecture/2026-10-27-managed-api-key.md +++ b/.agents/notes/implemented/architecture/2026-10-27-managed-api-key.md @@ -1,5 +1,5 @@ # Decision: Use consented Express managed API keys -Status: proposed +Status: implemented ## Problem @@ -39,3 +39,5 @@ Consent denial and --ci, explicit-key priority, official generated models, real ## Evidence - [Verification](../../../evidence/verify/2026-10-27-managed-api-key.md). + +- [Supported remote CI acceptance](../../../evidence/verify/2026-10-27-managed-api-key-ci.md). From 2269487066c72f3a65c6c54b240ad58d29b30cf2 Mon Sep 17 00:00:00 2001 From: anycodes <21079031+anycodes@users.noreply.github.com> Date: Tue, 27 Oct 2026 07:21:15 +0000 Subject: [PATCH 4/4] docs: specify minimum SDK migration and managed-key prerequisites --- docs/en/guide/migrate-from-e2b.md | 6 ++++++ docs/en/guide/troubleshooting.md | 2 +- docs/en/reference/configuration.md | 2 +- docs/zh/guide/migrate-from-e2b.md | 6 ++++++ docs/zh/guide/troubleshooting.md | 2 +- docs/zh/reference/configuration.md | 2 +- 6 files changed, 16 insertions(+), 4 deletions(-) diff --git a/docs/en/guide/migrate-from-e2b.md b/docs/en/guide/migrate-from-e2b.md index c6922b9a..8a9480d7 100644 --- a/docs/en/guide/migrate-from-e2b.md +++ b/docs/en/guide/migrate-from-e2b.md @@ -2,6 +2,12 @@ The standard Python API targets `e2b==2.31.0` and `e2b-code-interpreter==2.8.1`, the versions recommended in [Alibaba's compatibility contract](https://help.aliyun.com/zh/agent-sandbox/e2b-compatibility-explanation). It owns its protocol implementation and installs neither E2B package. This contract applies to Alibaba Express; other providers use the explicit `ProviderSandbox` extension API. Node SDK migration is outside this change. +## Minimum migration + +For an application already using Express through the E2B SDK, install this client, change imports as shown below, and keep the existing API key, `E2B_API_KEY`, `E2B_API_URL`, `E2B_DOMAIN` (or explicit `api_key=`), and sync/async calling style. Standard entries retain these E2B settings. No replacement key/template, AccessKey or CLI is required. Provider extensions use the project's `ABX_*` settings. + +Choosing AK/SK-managed keys adds a matching CLI with `api-key.prepare` / `api-key.resolve`, AK/SK and region configuration, Team-scoped RAM permissions, and one explicit `prepare_api_key_sync(confirm=True)` before the application starts (async: `await prepare_api_key(confirm=True)`). Existing standard business calls then remain unchanged; renewal needs fresh consent. Add permissions for template builds, ACR and Volume only when using those features: see [permissions](permissions.md). These preparation APIs require source containing this feature or a subsequent matching release; arbitrary older installations do not provide them. + ## Installation and imports Install the published client with `python -m pip install --index-url https://pypi.org/simple agent-sandbox-client==0.4.0a6`. Configure the Express account with `ABX_SANDBOX_API_KEY`, `ABX_SANDBOX_BASE_URL` and `ABX_SANDBOX_DOMAIN`; these project settings work for standard `Sandbox` / `AsyncSandbox`, `code_interpreter` and `ProviderSandbox`. `abx config` and project/home configuration files also feed these settings. The standard client's public `api_key=` parameter remains part of its pinned contract. A separate application still using the third-party E2B SDK uses that SDK's native configuration names; do not send credentials from another provider account. diff --git a/docs/en/guide/troubleshooting.md b/docs/en/guide/troubleshooting.md index 09c801a1..9efce864 100644 --- a/docs/en/guide/troubleshooting.md +++ b/docs/en/guide/troubleshooting.md @@ -75,7 +75,7 @@ export ALICLOUD_ACCESS_KEY_SECRET="your-ak-secret" [E1002] envdAccessToken not available. Sandbox may not be created or connected. ``` -**Solution**: Check that the system clock is accurate. Reconnect for a current envd token or inspect an explicitly configured custom exchange; API keys are not automatically minted or refreshed. +**Solution**: Check that the system clock is accurate. Reconnect for a current envd token. Managed API-key creation and renewal require explicit confirmation; see [permissions](permissions.md). --- diff --git a/docs/en/reference/configuration.md b/docs/en/reference/configuration.md index 5e00452e..bc252204 100644 --- a/docs/en/reference/configuration.md +++ b/docs/en/reference/configuration.md @@ -62,7 +62,7 @@ Resolving for provider `P` never reads `ABX__*` or `[providers.Q]` for anothe --- -Sandbox calls require `ABX_SANDBOX_API_KEY`; AK/SK settings configure separate official control-plane operations and cannot substitute for it. See [authentication migration](../guide/authentication.md). +Sandbox calls use an explicit API key or an Express-managed key prepared with AK/SK and explicit consent. The AK/SK path needs the matching abx engine; native control-plane operations have separate permissions. See [authentication](../guide/authentication.md) and [feature permissions](../guide/permissions.md). ## Environment Variables diff --git a/docs/zh/guide/migrate-from-e2b.md b/docs/zh/guide/migrate-from-e2b.md index 86d7826c..66ce2005 100644 --- a/docs/zh/guide/migrate-from-e2b.md +++ b/docs/zh/guide/migrate-from-e2b.md @@ -2,6 +2,12 @@ 标准 Python API 以阿里云推荐的 `e2b==2.31.0`、`e2b-code-interpreter==2.8.1` 为行为基线,范围遵循[阿里云 E2B 兼容说明](https://help.aliyun.com/zh/agent-sandbox/e2b-compatibility-explanation)。项目自行持有协议实现,不安装这两个 E2B 包。本契约针对极速版;其他 Provider 通过独立的 `ProviderSandbox` 扩展 API 使用。本次不涉及 Node SDK。 +## 最小迁移步骤 + +已通过 E2B SDK 使用极速版的应用:安装本项目客户端,按下表修改导入,保留已有 API Key、`E2B_API_KEY`、`E2B_API_URL`、`E2B_DOMAIN`(或原有 `api_key=` 参数)以及同步/异步调用方式。标准入口继续读取这些 E2B 配置;无需重建 Key/模板,也不要求新增 AK/SK 或 CLI。Provider 扩展入口使用项目的 `ABX_*` 配置。 + +只有在你选择 AK/SK 管理 Key 时,才需安装包含 `api-key.prepare` / `api-key.resolve` 的配套 CLI、配置 AK/SK 和地域、授予 Team 级权限,并在 SDK 首次运行前调用一次 `prepare_api_key_sync(confirm=True)`(异步用 `await prepare_api_key(confirm=True)`)。之后沿用现有标准业务调用,续期仍需明确确认。新增模板构建、ACR、Volume 等功能按需增加权限,见[完整权限指南](permissions.md)。这些准备接口需包含本功能的 CLI/SDK 源码或后续配套发行版,不能假定任意旧安装已经支持。 + ## 安装与导入 使用已发布客户端:`python -m pip install --index-url https://pypi.org/simple agent-sandbox-client==0.4.0a6`。通过 `ABX_SANDBOX_API_KEY`、`ABX_SANDBOX_BASE_URL` 和 `ABX_SANDBOX_DOMAIN` 配置极速版账号;本项目标准 `Sandbox` / `AsyncSandbox`、`code_interpreter` 和 `ProviderSandbox` 均支持这些项目配置。也可使用 `abx config` 与项目/用户配置文件。标准客户端公开 `api_key=` 参数仍属于其固定契约。仍使用第三方 E2B SDK 的独立应用按该 SDK 的原生配置操作,不要混用不同提供商账号的凭据。 diff --git a/docs/zh/guide/troubleshooting.md b/docs/zh/guide/troubleshooting.md index bb97a39b..1b490c43 100644 --- a/docs/zh/guide/troubleshooting.md +++ b/docs/zh/guide/troubleshooting.md @@ -75,7 +75,7 @@ export ALICLOUD_ACCESS_KEY_SECRET="your-ak-secret" [E1002] envdAccessToken not available. Sandbox may not be created or connected. ``` -**解决**:检查系统时钟是否准确。重新连接获取有效 envd token,或检查显式配置的自定义交换;API Key 不会被自动创建或刷新。 +**解决**:检查系统时钟是否准确。重新连接获取有效 envd token。管理 API Key 的创建和续期需要明确确认,见[功能与权限](permissions.md)。 --- diff --git a/docs/zh/reference/configuration.md b/docs/zh/reference/configuration.md index d10edec5..14b7f6ca 100644 --- a/docs/zh/reference/configuration.md +++ b/docs/zh/reference/configuration.md @@ -62,7 +62,7 @@ --- -沙箱调用需要 `ABX_SANDBOX_API_KEY`;AK/SK 用于独立官方控制面,不能替代沙箱密钥。见[鉴权迁移](../guide/authentication.md)。 +沙箱调用使用显式 API Key,或经明确确认、由极速版使用 AK/SK 准备的管理 Key。AK/SK 路径需配套 abx 引擎;原生控制面功能另行授权。见[认证](../guide/authentication.md)和[功能与权限](../guide/permissions.md)。 ## 环境变量