Skip to content

feat(anthropic): optional SigV4 request signing behind a defaulted trait method - #2387

Open
awsmadi wants to merge 1 commit into
0xPlaygrounds:mainfrom
awsmadi:feat/anthropic-sigv4
Open

feat(anthropic): optional SigV4 request signing behind a defaulted trait method#2387
awsmadi wants to merge 1 commit into
0xPlaygrounds:mainfrom
awsmadi:feat/anthropic-sigv4

Conversation

@awsmadi

@awsmadi awsmadi commented Aug 19, 2026

Copy link
Copy Markdown

Fixes #2386

Adds optional AWS SigV4 authentication to the Anthropic provider, so it can
reach an Anthropic-compatible endpoint that sits behind AWS. Behind a
default-off sigv4 feature; nothing changes for anyone who doesn't opt in.

Implementation

AnthropicCompatibleProvider gains one method with a default:

fn sigv4_region(&self) -> Option<&str> { None }

None means no signing is attempted, so every existing implementor is
unaffected and none needed editing
. AnthropicExt overrides it;
AnthropicKey gains a SigV4 { region } variant alongside ApiKey(String).

Going through the trait rather than a concrete field is deliberate — completions
run through GenericCompletionModel<Ext, T>, so a concrete field isn't
reachable from the generic path. This follows the provider-integration
checklist in CONTRIBUTING.md: wire-dialect differences belong in the trait's
hooks.

Signing happens in both request builders, immediately before the body is
attached and never earlier. The SigV4 payload hash covers the exact bytes sent,
so signing has to follow every mutation of the body — and the SigV4 variant
emits no static header, because the signature covers the clock as well as the
body and can't be computed once at client construction.

Selecting SigV4 in a build without the feature is a hard error, not a silently
unsigned request: an unsigned request 401s with a message about a missing API
key, which points at the wrong problem.

Two things worth a reviewer's attention

The sigv4 feature enables two aws-config features, and both are
load-bearing.
The workspace pins aws-config with default-features = false
(rig-bedrock supplies its own), while load_defaults walks the full credential
chain:

feature without it
default-https-client a http_client is required
rt-tokio An async sleep implementation is required for retry to work

Both fail at runtime, not compile time, so cargo check can't catch either
— I only found them by running the tests. Note also that feature unification
means enabling sigv4 gives rig-bedrock's aws-config those features too.
That's additive, but it's a real consequence and I'd rather flag it than have it
discovered later.

aws-credential-types and aws-sigv4 are new workspace dependencies, added
as bare-major floors per the documented policy in the root manifest, since this
code uses only long-stable API from both. aws-config was already there.

Verification

cargo clippy -p rig-core --features sigv4 --all-targets    0 warnings
cargo test   -p rig-core --features sigv4 --lib            1551 passed, 0 failed
cargo test   -p rig-core --lib                             1550 passed, 0 failed

The last line is the control that matters for existing users — it's what proves
the default-off path is unaffected.

The +1 with the feature on is signed_headers_never_include_host, which asserts
the signer doesn't emit a host header: the HTTP client sets its own, and
sending both breaks the signature. It carries an anti-vacuity assertion too,
because "no host header" passes trivially if nothing was produced at all.

Not included

No cassette-backed regression test. Signing is request-shaping rather than a
response-parsing behaviour, so the assertion that matters is on the emitted
header list, which is what the included test does. Happy to add one if you'd
prefer the coverage there instead.

…ait method

Lets the Anthropic provider talk to an Anthropic-compatible endpoint that sits
behind AWS SigV4, without changing anything for anyone who does not opt in.

Why not rig-bedrock
-------------------
rig-bedrock speaks the Bedrock Runtime InvokeModel API. This is the other
shape: an endpoint that accepts Anthropic's own /v1/messages request body, and
therefore carries Anthropic-shaped parameters, but authenticates with SigV4
instead of an x-api-key header. Different wire protocols against different
endpoints, so neither substitutes for the other.

What changed
------------
`AnthropicCompatibleProvider` gains one method with a default:

    fn sigv4_region(&self) -> Option<&str> { None }

Returning `None` — the default — means no signing is attempted, so every
existing implementor is unaffected and none needs editing. `AnthropicExt`
overrides it; `AnthropicKey` gains a `SigV4 { region }` variant alongside
`ApiKey(String)`.

Going through the trait rather than a concrete field is deliberate: completions
run through `GenericCompletionModel<Ext, T>`, so a concrete field would not be
reachable from the generic path. This follows the provider-integration
checklist in CONTRIBUTING.md, which asks for wire-dialect differences to live
in the trait's hooks.

Signing happens in both request builders, immediately before the body is
attached and never earlier: the SigV4 payload hash covers the exact bytes
sent, so signing has to follow every mutation of the body. The SigV4 variant
emits no static header, because the signature covers the clock as well as the
body and so cannot be computed once at client construction.

Feature-gating
--------------
Behind a default-off `sigv4` feature. Default-off on purpose: the AWS
credential chain has no place in a wasm build, nor in builds that only ever
talk to api.anthropic.com.

Selecting SigV4 in a build without the feature is a hard error, not a silently
unsigned request — an unsigned request would 401 with a message about a
missing API key, which points at the wrong problem.

`#[cfg_attr(not(feature = "sigv4"), allow(unused_mut))]` on the request builder
keeps the no-feature build warning-free; without it the `mut` that only signing
needs would warn in every default build.

Two aws-config features are load-bearing, and both fail at RUNTIME rather than
at compile time, so `cargo check` cannot catch either. The workspace pins
aws-config with `default-features = false` (rig-bedrock supplies its own), while
`load_defaults` walks the full credential chain:

  default-https-client — without it: "a http_client is required"
  rt-tokio             — without it: "An async sleep implementation is required
                          for retry to work"

aws-config was already a workspace dependency; aws-credential-types and
aws-sigv4 are added as bare-major floors, consistent with the documented policy
in the root manifest, since this code uses only long-stable API from both.

Verification
------------
    cargo clippy -p rig-core --features sigv4 --all-targets   0 warnings
    cargo test   -p rig-core --features sigv4 --lib           1551 passed, 0 failed
    cargo test   -p rig-core --lib                            1550 passed, 0 failed

The last line is the control that matters for existing users: it proves the
default-off path is unaffected. The +1 with the feature on is the new
`signed_headers_never_include_host` test, which asserts the signer does not
emit a `host` header — the HTTP client sets its own, and sending both breaks
the signature. That test carries its own anti-vacuity assertion, because
"no host header" passes trivially if nothing was produced at all.
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.

feat: optional AWS SigV4 auth for the Anthropic provider

1 participant