Skip to content

feat(auth): add a Claude Platform on AWS credential tier - #61

Open
bluedoors wants to merge 2 commits into
anthropics:mainfrom
bluedoors:add-aws-auth-tier
Open

bluedoors wants to merge 2 commits into
anthropics:mainfrom
bluedoors:add-aws-auth-tier

Conversation

@bluedoors

@bluedoors bluedoors commented Jun 15, 2026 •

Copy link
Copy Markdown

Summary

Adds a Claude Platform on AWS credential tier, built the same way as the Claude Platform on Google Cloud tier that landed in 1.40.0.

Today the CLI can reach an AWS workspace only by hand-setting an AWS Console API key, --base-url https://aws-external-anthropic.<region>.api.aws and --workspace-id. There's no way to sign requests with SigV4, so IAM roles, SSO and IRSA don't work, and that's what CI on AWS uses. This blocks the version-controlled Managed Agents workflow (ant apply, ant beta:agents create < agent.yaml) on AWS.

export AWS_REGION=us-east-1
export ANTHROPIC_AWS_WORKSPACE_ID=wrkspc_...
ant models list

How it works

Setting --aws-region, --aws-workspace-id or ANTHROPIC_AWS_WORKSPACE_ID selects the tier. AWS_REGION and AWS_PROFILE on their own don't, because most AWS machines set them for unrelated reasons (the Google tier ignores GOOGLE_CLOUD_PROJECT for the same reason). The region falls back to AWS_REGION, then AWS_DEFAULT_REGION.

Requests go through the SDK's aws/ package, in one of two modes:

  • SigV4, signed with whatever the AWS default credential chain finds (AWS_PROFILE, environment keys, an IAM role).
  • An AWS Console API key from ANTHROPIC_AWS_API_KEY or --aws-api-key-stdin, sent as x-api-key. The key only works in the region it was generated in.

It sits in the same place as the Google tier in the precedence order. An API key, an auth token, a named profile, federation or Google Cloud settings win over it, and it wins over the implicit profile. AWS shows up in the shared multi-auth notice and as a normal row in ant auth status.

AWS credentials are only sent to the gateway. --base-url / ANTHROPIC_BASE_URL aren't used (a one-off notice says so), and --aws-base-url / ANTHROPIC_AWS_BASE_URL overrides the gateway URL.

Compared with the Google Cloud tier

Google Cloud AWS
Code google_cloud.go aws.go, same shape: AnySet(), host(), noteIgnoredBaseURL(), requestOptions()
Selected by --google-cloud-* / ANTHROPIC_GOOGLE_CLOUD_* --aws-region, --aws-workspace-id, ANTHROPIC_AWS_WORKSPACE_ID
Ambient setting ignored for selection GOOGLE_CLOUD_PROJECT AWS_REGION, AWS_PROFILE
Credentials Application Default Credentials AWS credential chain (SigV4), or a Console API key
Gateway override --google-cloud-base-url --aws-base-url
Precedence, auth status, ant apply origin ✅ ✅ same

The differences come from AWS itself: there's a Console API key option, and the first-party --workspace-id header isn't sent on the AWS path because the workspace travels as a header there (on Google it's part of the URL) and would otherwise be overwritten.

What changed

All hand-written files. No generated command files are touched.

  • pkg/cmd/aws.go (new): the tier.
  • pkg/cmd/cmdutil.go: the AWS case in the precedence switch, and AWS in warnIfMultipleAuthSources.
  • pkg/cmd/extras.go: --aws-region, --aws-workspace-id, --aws-api-key-stdin, --aws-base-url. The --workspace-id help now points at --aws-workspace-id instead of saying it's required on AWS.
  • pkg/cmd/credential_stdin.go: --aws-api-key-stdin joins the existing stdin-credential path.
  • pkg/cmd/cmd_auth.go: the AWS row and gateway in ant auth status.
  • pkg/cmd/apply.go: awsInUse, so ant apply records the AWS gateway as the lockfile origin and names the credentials correctly.
  • internal/debugmiddleware: X-Amz-Security-Token is redacted in --debug output.
  • README.md: a Claude Platform on AWS section after the Google Cloud one.
  • go.mod / go.sum: the AWS SDK modules the aws/ package pulls in. The anthropic-sdk-go version is unchanged.

Tests

pkg/cmd/aws_test.go follows google_cloud_test.go: it captures requests at the transport, after signing, with no network.

  • TestAWSTier: gateway host and path, the SigV4 credential scope and signed headers, X-Amz-Security-Token with temporary credentials, x-api-key from the env var and from stdin, the workspace header sent once, --aws-base-url used and --base-url never.
  • TestAWSSelection: what does and doesn't turn the tier on.
  • TestAWSPrecedence: against an API key, a named profile, Google Cloud and the implicit profile.
  • TestAuthStatusAWS, TestAWSInUse, TestAWSCredentialsError.

go test ./... passes apart from the generated command tests, which need the mock server on localhost:4010.

Live, 14 Oct 2026

Against a Claude Platform on AWS workspace:

Scenario Result
SigV4 with an assumed role (us-east-1) ✅ auth status, messages create 200, beta:agents create, list, retrieve, update with optimistic locking, archive, error cases; session token redacted in --debug
ant apply (SigV4) ✅ create, no-op, update, --prune; lockfile origin is the regional gateway
Console API key via ANTHROPIC_AWS_API_KEY (ap-southeast-2) ✅ same harness, no AWS credentials present
Console API key via --aws-api-key-stdin ✅ 200, key redacted in --debug

Not covered

ant beta:worker doesn't work on AWS yet. The SDK's worker helper needs an environment key, and AWS workers authenticate with IAM. Details and the SDK issue are in this comment.

@bluedoors
bluedoors marked this pull request as ready for review June 15, 2026 05:31
@bluedoors
bluedoors requested a review from a team as a code owner June 15, 2026 05:31
@Gehan-Panapitiya

Gehan-Panapitiya commented Jun 17, 2026 •

Copy link
Copy Markdown

Great. Upvoted +100

@bluedoors

bluedoors commented Jun 18, 2026 •

Copy link
Copy Markdown
Author

Known gap: beta:worker (self-hosted sandboxes) is not covered by this AWS tier

Updated 2026-10-12 after rebasing onto 1.40.0 (SDK v1.80.0).

This PR gives full control-plane parity on AWS via the getDefaultRequestOptions short-circuit (pkg/cmd/cmdutil.go:89): beta:agents, beta:environments (incl. the raw :work poll/ack/stop/heartbeat/stats commands), beta:sessions, beta:skills, beta:memory-stores, beta:vaults, beta:files, messages, models, and ant apply.

It does not cover beta:worker (poll / run), the self-hosted sandbox worker. beta:worker builds its client outside getDefaultRequestOptions, via newWorkerClient (pkg/cmd/worker.go:295), so --aws never reaches it. More fundamentally, the SDK worker helper it wraps can't express the AWS auth model.

This is a supported product configuration. Per the self-hosted sandboxes docs, on Claude Platform on AWS the worker authenticates with AWS IAM (SigV4) or an AWS-Console API key, not an environment key (attach the AnthropicSelfHostedEnvironmentAccess managed policy; on EKS that's the pod's IRSA role). Console environment keys don't work against the AWS endpoint. So the IAM principal is the worker credential.

The blocker is upstream in anthropic-sdk-go, not in the CLI. As of the latest release, v1.80.0, its lib/environments helper still:

  • requires a non-empty environment key to poll: NewWorkPoller (poller.go:283-284) and EnvironmentWorker.Run (worker.go:191);
  • injects WithHeaderDel("X-Api-Key") + WithAuthToken(envKey) on every work call via bearerReqOpts (poller.go:58-64), which strips or overrides the AWS credential the worker needs.

There is still no SkipAuth / auth-mode field on WorkPollerOptions or EnvironmentWorkerOptions. The Python SDK worker has the same requirement (environment_key is required to poll).

What changed since this was first posted:

  • beta:worker run and work secrets. Since about v1.56, EnvironmentWorker.HandleItem can authorize a single item with the sessions token carried in the work item's secret, with no environment key, and main's beta:worker run exposes that (--work-secret-file / ANTHROPIC_WORK_SECRET). That could in principle work on AWS, but it doesn't close the gap: polling still needs an environment key; #389 reported in July that on the AWS endpoint the work secret came back empty on poll, get and ack; and newWorkerClient doesn't route to the AWS gateway either way.
  • anthropics/anthropic-sdk-go#389 is closed. Built-in user.tool_result posts from a SigV4/IRSA runner returned 403 for a few days in July. Anthropic support confirmed it was a server-side change, fixed around 9 July. That report came from a hand-rolled SigV4 worker whose poll/ack/heartbeat worked, which supports keeping the remaining gap scoped to the SDK helper.

Ask / next step: unchanged. anthropics/anthropic-sdk-go#366 (still open) requests an AWS / client-auth mode in lib/environments: an explicit opt-in (e.g. UseClientAuth bool / AuthMode on EnvironmentWorkerOptions + WorkPollerOptions) that skips the environment-key requirement and makes the bearer injection a no-op, so the base client's SigV4 / x-api-key flows through. Backward-compatible. Once that lands, the CLI side is a small change: route newWorkerClient through the AWS client options, relax the required --environment-key under --aws, and forward AWS creds rather than the environment key in the --on-work / run container path (worker.go:261).

Tracking worker-on-AWS as a separate follow-up gated on that SDK change, to keep this control-plane PR clean and upstreamable.

Add a new highest-priority, opt-in credential tier that routes requests
through the Claude Platform on AWS gateway (which does not support OAuth),
unblocking the version-controlled Managed Agents workflow on AWS.

Selected only by explicit opt-in — the --aws flag or the persistent
ANTHROPIC_USE_AWS toggle (not ambient AWS env vars). When active,
getDefaultRequestOptions short-circuits before the first-party credential
switch and builds request options from the SDK's aws/ backend (SigV4 via
the default AWS credential chain, or x-api-key when --aws-api-key is set).

All Stainless-generated commands work unchanged over the AWS transport.
Changes are confined to hand-written files:

- extras.go: register --aws, --aws-region, --aws-workspace-id, --aws-api-key
  and --aws-base-url global flags (each paired with its env Source).
- cmdutil.go: buildAWSConfig helper (workspace falls back to the global
  --workspace-id), awsHost, awsClientError (adds the CLI flag names to the
  SDK's "no region / no workspace" errors), the AWS tier-0 short-circuit, and
  warnIfAWSConflict (warns when --aws overrides a first-party credential,
  including stdin-supplied keys and Google Cloud settings; excludes
  --aws-api-key, which only selects API-key mode). All read from the root
  command, matching the other credential flags. Like the Google Cloud tier,
  --base-url / ANTHROPIC_BASE_URL is not applied under --aws (a one-shot
  notice says so), so AWS credentials never go to a host configured for
  Anthropic credentials; --aws-base-url / ANTHROPIC_AWS_BASE_URL is the only
  gateway override. Like the Google Cloud tier,
  --base-url / ANTHROPIC_BASE_URL is not applied under --aws (a one-shot
  notice says so), so AWS credentials never go to a host configured for
  Anthropic credentials; --aws-base-url / ANTHROPIC_AWS_BASE_URL is the only
  gateway override.
- cmd_auth.go: awsAuthStatus renders AWS as the tier-0 winner with the active
  mode label and backend-resolved base URL, redacting the API key.
- apply.go: `ant apply` records the AWS gateway as the origin host, names the
  AWS credentials, and never treats a profile as in use under --aws.
- cmd_aws_test.go: buildAWSConfig pass-through and workspace fallback,
  --base-url not used under --aws, client error hints,
  --base-url not used under --aws, client error hints,
  conflict warning fires/silent, auth status row, apply origin, and the
  cross-site precedence invariant.

go.mod only records the AWS SDK transitive indirect deps that the
anthropic-sdk-go aws/ subpackage newly makes reachable.

@dtmeadows-ant dtmeadows-ant left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks so much for putting this up! I think with a few tweaks we should be able to get this out the door.

  1. Could we add a small section to the README about this?
  2. Could you scope out if we need to redact X-Amz-Security-Token anywhere specific, eg in --debug handling?

Comment thread pkg/cmd/cmd_auth.go Outdated

fmt.Fprintln(out)
fmt.Fprintln(out, "Credentials")
apiKey := root.String("aws-api-key")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're trying to move away from supporting credentials like this as standard options. Could we move this to only support ANTHROPIC_AWS_API_KEY and --aws-api-key-stdin?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense. --aws-api-key is gone; it's now just ANTHROPIC_AWS_API_KEY or --aws-api-key-stdin. The stdin flag goes through the same path as --api-key-stdin, so it gets the same level of checks

Comment thread pkg/cmd/cmdutil.go Outdated
}
// Credential precedence mirrors the WIF User Guide's "Credential resolution" section:

// Tier 0 — Claude Platform on AWS. Opt-in only (--aws / ANTHROPIC_USE_AWS,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a lot of these comments could get trimmed down a bit. Most of the code here I think can speak for itself so I don't think we need to spell out exactly what the code's doing. Maybe look at the comments for the other provider(s) we've added support for to calibrate on what the right length should be?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair call, they'd got a bit out of hand (and i should have stripped Opus 5's verboseness) . I took your pointer to the Google tier further and rebuilt the AWS one to match it. It lives in pkg/cmd/aws.go, shaped like google_cloud.go (an awsPlatform with AnySet(), host(), noteIgnoredBaseURL() and requestOptions()), and the case in getDefaultRequestOptions is about as long as the Google one now. Comments are trimmed to match.

It also behaves like the Google tier now. --aws / ANTHROPIC_USE_AWS are gone. Setting --aws-region, --aws-workspace-id or ANTHROPIC_AWS_WORKSPACE_ID turns the tier on, and AWS_REGION / AWS_PROFILE on their own don't, for the same reason the Google tier ignores GOOGLE_CLOUD_PROJECT. It sits in the same place in the precedence order: an API key, auth token, named profile, federation or Google Cloud settings win over it, and it wins over the implicit profile. It also uses the shared multi-auth notice and a normal auth status row.

The two places it still differs are both down to AWS itself. There's the Console API key option. And --workspace-id isn't sent on the AWS path, because there the workspace is a header (it's in the URL on Google), so the first-party one would overwrite it. I've updated the --workspace-id help to point at --aws-workspace-id, since it used to say it was required on AWS.

Comment thread pkg/cmd/cmd_aws_test.go Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add some tests asserting the actual contents of the request itself? Those are pretty necessary to ensure we're not misformatting any important headers or URLs for things like authorization. Feel free to borrow from pkg/cmd/google_cloud_test.go.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, they were missing. Again i borrowed from the Google implementation and now have tests for

  • host aws-external-anthropic.<region>.api.aws and the path
  • the Authorization credential scope (key, date, region, aws-external-anthropic, aws4_request), with host, x-amz-date and anthropic-workspace-id in SignedHeaders
  • X-Amz-Security-Token when there's a session token
  • API-key mode sends x-api-key and no Authorization, from the env var or --aws-api-key-stdin
  • the workspace header is sent once, and --workspace-id doesn't override it
  • --aws-base-url is honoured, and --base-url / ANTHROPIC_BASE_URL never get the request

There's also a table test of what does and doesn't turn the tier on, precedence against an API key, named profile, Google and the implicit profile, TestAuthStatusAWS on the shared runStatus (same checks as the Google one), and the ant apply origin. The file's renamed to aws_test.go to pair with aws.go.

Address review on anthropics#61:

- Move the tier into pkg/cmd/aws.go, shaped like google_cloud.go: an
  awsPlatform carrier with host(), noteIgnoredBaseURL() and
  requestOptions(). Comments trimmed to match.
- Selection and precedence match the Google Cloud tier. --aws-region,
  --aws-workspace-id or ANTHROPIC_AWS_WORKSPACE_ID select AWS (AWS_REGION
  and AWS_PROFILE on their own don't); --aws / ANTHROPIC_USE_AWS are gone.
  An API key, an auth token, a named profile, federation and Google Cloud
  settings win over AWS; AWS wins over the implicit profile. AWS is a source
  in the shared multi-auth notice and a row in `ant auth status`, replacing
  the separate short-circuit, status view and conflict warning.
- Replace the --aws-api-key flag with --aws-api-key-stdin, read through the
  shared stdin-credential path; ANTHROPIC_AWS_API_KEY still works.
- Redact X-Amz-Security-Token in --debug output.
- Tests assert the request itself (gateway host and path, the SigV4
  Authorization credential scope and signed headers, the session token,
  x-api-key, the workspace header), plus precedence, auth status and
  `ant apply` origin.
- README: a Claude Platform on AWS section next to the Google Cloud one.
@bluedoors

Copy link
Copy Markdown
Author

Thanks heaps for the review. Everything below is in the latest push.

README: there's a "Claude Platform on AWS" section straight after the Google Cloud one, same layout. It also mentions that an AWS Console API key only works in the region it was generated in, because the gateway's 401 for a mismatch ("Credential should be scoped to a valid region") doesn't make that obvious.

X-Amz-Security-Token: good catch, it was leaking. With temporary creds (IRSA, SSO, an assumed role, so most CI) --debug printed the session token in full. Authorization was already fine, it shows as AWS4-HMAC-SHA256 <REDACTED>. I've added x-amz-security-token to the sensitive headers in internal/debugmiddleware with a test, and checked it live with an assumed role: redacted in the log, still sent on the wire.

I also looked for anywhere else it could surface. The SDK's apierror.RedactHeaders doesn't cover it either, but the CLI never prints those dumps, so nothing's exposed today. Probably worth adding upstream. Unrelated, but debugMiddlewareOption in cmdutil.go dumps requests with no redaction and nothing uses it. Happy to delete it here or in its own PR.

I've re-run everything live against a Claude Platform on AWS workspace: SigV4 with an assumed role, the Console API key through both the env var and --aws-api-key-stdin, and an ant apply create/update/prune cycle. Results are in the description.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants