anthropics / anthropics/anthropic-sdk-go
Feature request: AWS / client-auth mode for lib/environments self-hosted worker
- 主要語言
- Go
- 星號
- 1.2k
- 分支
- 213
- 平均合併
- 1 天 12 小時
- 30 天內合併 PR
- 11
描述
# Feature request: AWS / client-auth mode for `lib/environments` self-hosted worker
## Summary
The `lib/environments` self-hosted worker helpers (`EnvironmentWorker`, `WorkPoller`) hard-require
an **environment key** and inject it as a per-request `Authorization` bearer on every call. This
makes it **impossible to run a self-hosted worker against Claude Platform on AWS**, where the
documented and supported auth model is **AWS IAM (SigV4) or an AWS-Console API key — not an
environment key**.
Please add an explicit opt-in that lets the worker authenticate with the base client's
credentials (SigV4 middleware or `x-api-key`) and skip the environment-key bearer entirely.
## Background / why this matters
Per the [self-hosted sandboxes docs](https://platform.claude.com/docs/en/managed-agents/self-hosted-sandboxes):
> On Claude Platform on AWS, the worker authenticates with **AWS IAM (SigV4) or an API key
> generated in the AWS Console, not an environment key**. Attach the
> `AnthropicSelfHostedEnvironmentAccess` managed policy to the IAM principal your worker runs as.
> Environment keys generated in the Claude Console don't work with the Claude Platform on AWS
> endpoint.
So on AWS the IAM principal (e.g. an EKS pod's IRSA role) *is* the work-lease credential — the
AWS-native equivalent of the personal platform's environment key. But the Go SDK worker helper
cannot express this: it requires an environment key and overrides the AWS credential on every
request.
The `aws/` subpackage already produces a fully AWS-authed client (SigV4 middleware +
`anthropic-workspace-id` header + regional base URL, exposed as `aws.Client.Options`). The gap is
purely that the worker helper strips/overrides that auth.
## Affected code (verified identical in `v1.50.1` and `v1.50.2`)
**1. Environment key is hard-required** — guards reject an empty key:
- `lib/environments/poller.go:74-75` — `bearerReqOpts`: `if environmentKey == "" { return nil, errors.New("environments: environment key is required") }`
- `lib/environments/poller.go:249-250` — `NewWorkPoller`: `case opts.EnvironmentKey == "": p.err = errors.New("environments: WorkPollerOptions.EnvironmentKey is required")`
- `lib/environments/worker.go:138-140` — `EnvironmentWorker.Run`: requires `EnvironmentID` and `EnvironmentKey`
- `lib/environments/worker.go:225-234` — `HandleItem`: requires `EnvironmentKey`
**2. The env-key bearer is injected unconditionally on every request** —
`lib/environments/poller.go:73-81`:
```go
func bearerReqOpts(environmentKey string) ([]option.RequestOption, error) {
if environmentKey == "" {
return nil, errors.New("environments: environment key is required")
}
return []option.RequestOption{
option.WithHeaderDel("X-Api-Key"), // strips the base client's x-api-key
option.WithAuthToken(environmentKey), // sets Authorization: Bearer
}, nil
}
```
These opts are appended **last** at every call site, so they win over any base-client auth
(option header-setters are last-writer-wins, `option/requestoption.go:231-253`):
- poll / ack / stop — `poller.go:285-287`
- heartbeat / force-stop / skills download — `worker.go:280-282`
- session tool-runner stream / list / send — `worker.go:361-363`
The net effect on an AWS-authed base client: `WithHeaderDel("X-Api-Key")` removes the AWS-Console
API key, and `WithAuthToken` overwrites the SigV4 `Authorization` header — so the request reaches
the wire with the (AWS-invalid) environment-key bearer instead of the AWS credential.
**3. No escape hatch.** None of `WorkPollerOptions` (`poller.go:129-180`),
`EnvironmentWorkerOptions` (`worker.go:25-91`), or `SessionToolRunnerOptions`
(`betasessiontoolrunner.go:71-129`) exposes a SkipAuth / auth-mode / empty-key-allowed field.
**4. No caller-side workaround.** Caller-supplied `RequestOptions` are applied *before* the
env-key opts at every helper call site, so they can't re-add `x-api-key` or re-assert SigV4 after
the bearer; and the helpers hard-error on an empty key regardless. (`SessionToolRunner` used
directly is the one exception — it only appends a telemetry header — but that covers
stream/list/send only, not poll/heartbeat/skills, so there is no end-to-end workaround.)
## Why the fix is clean
The environment key flows into **nothing but** the `Authorization` header — it is never used as a
query param, request body field, or routing header (work-item routing uses the separate
`EnvironmentID`; the `anthropic-beta` header is auto-injected by the generated methods). So
removing the bearer injection and letting the base client's auth stand is sufficient at **every**
call site.
## Proposed change
Add an explicit opt-in (preferred over silently treating an empty key as "use base auth", which
would be a footgun on the personal platform — it would let the worker fall back to an org API key
on the host, the exact thing the env-key model exists to prevent):
1. Add a field to `EnvironmentWorkerOptions` and `WorkPollerOptions`, e.g.:
```go
// UseClientAuth authenticates work and session calls with the base client's
// own credentials (e.g. AWS SigV4 or an x-api-key) instead of an environment
// key. Required for Claude Platform on AWS, where the worker authenticates as
// an IAM principal and environment keys are not accepted. When set,
// EnvironmentKey is not required and is ignored.
UseClientAuth bool
```
(An `AuthMode` enum would work equally well if that fits the SDK's conventions better.)
2. When `UseClientAuth` is set:
- Relax the env-key requirement guards (`poller.go:249-250`, `worker.go:138-140`,
`worker.go:225-234`) to require only `EnvironmentID`.
- Make `bearerReqOpts` return a **no-op** slice (no `WithHeaderDel("X-Api-Key")`, no
`WithAuthToken`) so the base client's auth flows through unchanged. This single change
propagates correctly to `helperReqOpts` and all the call sites listed above (they just append
an empty auth slice).
3. `SessionToolRunner` needs **no change** — it never required the key and only appends a
telemetry header; it's fully driven by the `RequestOptions` it's handed.
This is small, localized, and backward-compatible — the default path (env-key bearer) is untouched.
## Intended usage after the change (Go, AWS SigV4)
```go
// AWS-authed base client (SigV4 + workspace header + regional base URL):
awsClient, err := aws.NewClient(ctx, aws.ClientConfig{
AWSRegion: region,
WorkspaceID: workspaceID,
})
// ...
client := anthropic.NewClient(awsClient.Options...)
worker := environments.NewEnvironmentWorker(client, environments.EnvironmentWorkerOptions{
EnvironmentID: environmentID,
UseClientAuth: true, // no EnvironmentKey on AWS
Workdir: "/workspace",
})
err = worker.Run(ctx)
```
## Notes
- The Python and TypeScript SDK worker helpers likely need the parallel change — their
self-hosted-sandbox worker examples still pass an environment key. Only the Go SDK was
source-verified here (it's what the `ant` CLI uses).
- Related downstream: the `ant` CLI's `beta:worker` command wraps this helper and is blocked on
this change for AWS support — see anthropics/anthropic-cli#61.
## Open question for the SDK / Managed Agents team
Every SDK worker *example* currently passes an environment key. Can you confirm the AWS endpoint
(`aws-external-anthropic.{region}.api.aws`) already serves the worker routes —
work `poll`/`ack`/`stop`/`heartbeat`, `sessions.events` stream/list/send, and skills download —
under IAM/SigV4 auth today (i.e. the doc reflects shipped server behavior)? That determines whether
this SDK change alone unblocks worker-on-AWS or whether server-side work is also pending.
貢獻指南
評估
這個 Issue 還沒有評估資料。