Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,3 +244,7 @@ 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/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).
31 changes: 31 additions & 0 deletions .agents/evidence/verify/2026-10-27-managed-api-key-ci.md
Original file line number Diff line number Diff line change
@@ -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.
55 changes: 55 additions & 0 deletions .agents/evidence/verify/2026-10-27-managed-api-key.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# 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/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.

## 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='<candidate CLI>' 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. 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

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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Decision: Use consented Express managed API keys
Status: implemented

## 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).

- [Supported remote CI acceptance](../../../evidence/verify/2026-10-27-managed-api-key-ci.md).
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 2 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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。
4 changes: 4 additions & 0 deletions client-sdk/src/agent_sandbox/_standard/PATCHES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion client-sdk/src/agent_sandbox/_standard/connection_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
Original file line number Diff line number Diff line change
@@ -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"]
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading