Skip to content

fix(bedrock): use deployment credentials for AWS requests - #36160

Merged
mateo-berri merged 8 commits into
BerriAI:litellm_internal_stagingfrom
daleselaji-dev:codex/bedrock-s3-credentials-36155
Aug 11, 2026
Merged

fix(bedrock): use deployment credentials for AWS requests#36160
mateo-berri merged 8 commits into
BerriAI:litellm_internal_stagingfrom
daleselaji-dev:codex/bedrock-s3-credentials-36155

Conversation

@daleselaji-dev

@daleselaji-dev daleselaji-dev commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Bedrock file uploads and batch calls ignored deployment AWS credentials
  • Signing silently fell back to the server's ambient AWS identity
  • Router credential lookup dropped session tokens and assume-role settings
  • Callers could choose which AWS profile on the host signs their requests

How it solves it:

  • Merge deployment params into request params in the Bedrock transforms
  • Keep s3_region_name authoritative so SigV4 matches the S3 URL region
  • Preserve every AWS auth param in router credential resolution
  • Reject caller-supplied AWS identity selectors at the proxy boundary

User Flow

Before: a batch developer's upload dies because the gateway signs with the server's ambient AWS identity instead of the deployment's

  1. The proxy admin adds a bedrock/us.anthropic.claude-haiku-4-5-20251001-v1:0 deployment whose litellm_params carry the deployment's own AWS key, secret, session token, region us-west-2, an SSE-KMS-locked S3 bucket, and a batch role ARN
  2. A developer sends POST http://localhost:PORT/v1/files?model=bedrock-batch-haiku with purpose=batch and a JSONL of chat requests
  3. The upload returns 500 with "Unable to locate credentials": the deployment's keys were never consulted and the server itself holds none
  4. POST http://localhost:PORT/v1/batches with an input_file_id fails the same way, so no batch job is ever created
  5. On a server that does hold its own AWS credentials, the upload is signed with that unrelated identity instead, so the KMS-locked bucket rejects it or the data lands under the wrong account
  6. Holding nothing but an ordinary API key, that same developer can add "aws_profile_name" to a plain POST http://localhost:PORT/v1/chat/completions body and the gateway signs as that profile on the host rather than the deployment's own identity, and naming a profile that does not exist returns "The config profile (X) could not be found", so the host's profile names can be probed one guess at a time

After: the same requests succeed because the deployment's own identity signs every call

  1. The proxy admin adds the same bedrock/us.anthropic.claude-haiku-4-5-20251001-v1:0 deployment with the same litellm_params
  2. The developer sends the same POST http://localhost:PORT/v1/files?model=bedrock-batch-haiku with purpose=batch
  3. The upload returns 200 with a file-... id and the object lands in the deployment's SSE-KMS bucket
  4. POST http://localhost:PORT/v1/batches returns a batch_... id whose decoded ARN names the deployment's own account and us-west-2
  5. GET http://localhost:PORT/v1/batches/{batch_id} shows the job go validating then in progress, read back with the deployment's credentials
  6. Adding "aws_profile_name" to any of those bodies, or to a chat completion, or nesting it under extra_body, now comes back refused with "aws_profile_name is not allowed in request body" before the request reaches AWS, so the developer can no longer choose the signing identity or probe the host's profile names, and only a proxy admin can re-enable it

Relevant issues

Fixes #36155

Linear ticket

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

Live proxies against real AWS (account 439158074652, us-west-2), real Bedrock batch jobs, no mocks. Every proxy booted with zero ambient AWS identity: no AWS_* credential env vars in the process, AWS_CONFIG_FILE and AWS_SHARED_CREDENTIALS_FILE pointed at files holding no [default] section, instance metadata disabled, and .env loading suppressed. So the deployment's litellm_params were the only possible credential source: temporary STS credentials (key, secret, session token), s3_bucket_name on a bucket whose policy denies PutObject without SSE-KMS under one specific key, s3_encryption_key_id, and aws_batch_role_arn. Three commits, each in its own worktree and venv on its own port: the merge base 79d412efc2, this branch's pre-hardening commit b5eed5e526, and the tip 1b488f7c2f

1. The reported symptom: dead at the merge base, working at the tip

Before, at the merge base 79d412efc2:

$ curl -sS -X POST "http://127.0.0.1:45817/v1/files?model=bedrock-batch-haiku" \
    -H "Authorization: Bearer sk-1234" -F "purpose=batch" -F "file=@batch_input.jsonl;type=application/jsonl"
{"error":{"message":"Unable to locate credentials","type":"None","param":"None","code":"500"}}

$ curl -sS -X POST "http://127.0.0.1:45817/v1/batches" -H "Authorization: Bearer sk-1234" \
    -H "Content-Type: application/json" \
    -d '{"input_file_id":"s3://litellm-kms-qa-35148/bogus-upload-never-happened.jsonl","endpoint":"/v1/chat/completions","completion_window":"24h","model":"bedrock-batch-haiku"}'
{"error":{"message":"Unable to locate credentials","type":"internal_server_error","param":"None","code":"500"}}

The upload produced no id at all, so the batch create above deliberately carries a bogus input_file_id to show the same credential failure on that route; it dies before the id is ever validated. The proxy's own debug line for those requests reads aws_access_key_id=[set=False] aws_secret_access_key=[set=False] aws_session_token=[set=False] while aws_region_name=us-west-2 is set, so the deployment's params reach credential resolution but its credentials do not. A chat completion on that same proxy process, same alias params, returned 200, which rules out bad QA credentials

After, at the tip 1b488f7c2f:

$ curl -sS -X POST "http://127.0.0.1:41733/v1/files?model=bedrock-batch-haiku" \
    -H "Authorization: Bearer sk-1234" -F purpose=batch -F custom_llm_provider=bedrock \
    -F "file=@batch_input.jsonl;type=application/jsonl"
{"id":"file-bGl0ZWxsbTpzMzovL2xpdGVsbG0ta21z...","object":"file","purpose":"batch","status":"uploaded",...}

$ curl -sS -X POST http://127.0.0.1:41733/v1/batches -H "Authorization: Bearer sk-1234" \
    -H "Content-Type: application/json" \
    -d '{"input_file_id": "file-bGl0ZWxsbTpzMzovL2xpdGVsbG0ta21z...", "endpoint": "/v1/chat/completions", "completion_window": "24h", "model": "bedrock-batch-haiku"}'
{"id":"batch_bGl0ZWxsbTphcm46YXdzOmJlZHJvY2s6dXMtd2VzdC0y...","object":"batch","status":"validating",...}

$ python3 -c 'import base64; print(base64.b64decode("bGl0ZWxsbTphcm46YXdzOmJlZHJvY2s6dXMtd2VzdC0y..." + "==").decode())'
litellm:arn:aws:bedrock:us-west-2:439158074652:model-invocation-job/t1d8lvxpk6s9;model,bedrock-batch-haiku

$ curl -sS -X GET "http://127.0.0.1:41733/v1/batches/batch_bGl0ZWxsbTphcm46YXdz...?model=bedrock-batch-haiku" \
    -H "Authorization: Bearer sk-1234"
{"status":"validating","in_progress_at":1786426626,"request_counts":{"completed":0,"failed":0,"total":0},
 "metadata":{"job_arn":"arn:aws:bedrock:us-west-2:439158074652:model-invocation-job/t1d8lvxpk6s9",
 "model_arn":"arn:aws:bedrock:us-west-2:439158074652:inference-profile/us.anthropic.claude-haiku-4-5-20251001-v1:0",
 "input_s3_uri":"s3://litellm-kms-qa-35148/litellm-bedrock-files-...jsonl",
 "output_s3_uri":"s3://litellm-kms-qa-35148/litellm-batch-outputs/litellm-batch-163ba06a/"},...}

$ aws s3api head-object --bucket litellm-kms-qa-35148 --region us-west-2 \
    --key litellm-bedrock-files-us.anthropic.claude-haiku-4-5-20251001-v1-0-6f36e4da-...jsonl
{"ContentLength":396,"ServerSideEncryption":"aws:kms",
 "SSEKMSKeyId":"arn:aws:kms:us-west-2:439158074652:key/fa719dd3-a738-4b20-940a-a8df971041cd"}

The upload only succeeds if the PUT carries both the deployment's SigV4 identity and the SSE-KMS headers, since the bucket policy denies anything else, and head-object confirms the object landed under the pinned key. The batch job ARN names the deployment's own account and region, so all three handlers demonstrably used the deployment's own credentials

2. Caller-chosen signing identity: honored before the hardening, refused at the tip

The escalation is easiest to see as a contrast on one proxy: the same request twice, differing only by one caller-supplied field.

At b5eed5e526, this branch after the credential fix but before the boundary hardening, on port 45911. aws_profile_name is not on the banned list at that commit:

$ curl -sS -X POST "http://127.0.0.1:45911/v1/batches" -H "Authorization: Bearer sk-1234" \
    -H "Content-Type: application/json" \
    -d '{"input_file_id":"file-bGl0ZWxsbTpzMzovL2x...","endpoint":"/v1/chat/completions","completion_window":"24h","model":"bedrock-batch-haiku","aws_profile_name":"attacker"}'
{"error":{"message":"{\"message\":\"The security token included in the request is invalid.\"}","type":"internal_server_error","param":"None","code":"403"}}

$ # byte-identical body minus that one field, replayed 12 seconds later on the same proxy
$ curl -sS -X POST "http://127.0.0.1:45911/v1/batches" -H "Authorization: Bearer sk-1234" \
    -H "Content-Type: application/json" \
    -d '{"input_file_id":"file-bGl0ZWxsbTpzMzovL2x...","endpoint":"/v1/chat/completions","completion_window":"24h","model":"bedrock-batch-haiku"}'
{"id":"batch_bGl0ZWxsbTphcm46YXdz...","object":"batch","status":"validating",...}

$ # a name that is not configured on the host answers differently again
$ curl -sS -X POST "http://127.0.0.1:45911/v1/batches" -H "Authorization: Bearer sk-1234" \
    -H "Content-Type: application/json" \
    -d '{"input_file_id":"file-bGl0ZWxsbTpzMzovL2x...","endpoint":"/v1/chat/completions","completion_window":"24h","model":"bedrock-batch-haiku","aws_profile_name":"no-such-profile-xyz"}'
{"error":{"message":"The config profile (no-such-profile-xyz) could not be found","type":"internal_server_error","param":"None","code":"500"}}

The proxy log for the 403 shows the caller's field surviving auth, reaching credential resolution as aws_access_key_id=[set=True] ... aws_profile_name=attacker, and AWS answering from bedrock.us-west-2.amazonaws.com/model-invocation-job. The deployment's real key was present on that very call and the caller's profile won anyway. The bogus AWS credentials used for the attacker profile are the public example pair from AWS's own documentation

This is not a hole this PR opened. At the merge base the same trick already worked on chat completions, where deployment credentials were being resolved correctly all along:

$ curl -sS -X POST "http://127.0.0.1:47231/v1/chat/completions" -H "Authorization: Bearer sk-1234" \
    -H "Content-Type: application/json" \
    -d '{"model":"bedrock-chat-haiku","messages":[{"role":"user","content":"Reply with one word: ready"}],"max_tokens":16}'
{"choices":[{"message":{"content":"Ready",...}}],...}

$ curl -sS -X POST "http://127.0.0.1:47231/v1/chat/completions" -H "Authorization: Bearer sk-1234" \
    -H "Content-Type: application/json" \
    -d '{"model":"bedrock-chat-haiku","messages":[{"role":"user","content":"Reply with one word: ready"}],"max_tokens":16,"aws_profile_name":"attacker"}'
{"error":{"message":"litellm.AuthenticationError: BedrockException Invalid Authentication - {\"message\":\"The security token included in the request is invalid.\"}. Received Model Group=bedrock-chat-haiku","type":null,"param":null,"code":"403"}}

So fixing the credential merge without also closing the boundary would have extended a pre-existing chat-route hole onto uploads and batches. At the tip that chat-route hole is closed too, control then treatment on one proxy:

$ curl -sS -X POST http://127.0.0.1:46127/v1/chat/completions -H "Authorization: Bearer sk-1234" \
    -H "Content-Type: application/json" \
    -d '{"model": "bedrock-chat-haiku", "messages": [{"role": "user", "content": "Reply with one word: ready"}], "max_tokens": 16}'
{"choices":[{"message":{"content":"Ready","role":"assistant"},"finish_reason":"stop","index":0}],"usage":{"completion_tokens":4,"prompt_tokens":13,"total_tokens":17},...}

$ curl -sS -X POST http://127.0.0.1:46127/v1/chat/completions -H "Authorization: Bearer sk-1234" \
    -H "Content-Type: application/json" \
    -d '{"model": "bedrock-chat-haiku", "messages": [{"role": "user", "content": "Reply with one word: ready"}], "max_tokens": 16, "aws_profile_name": "attacker"}'
{"error":{"message":"Authentication Error, Rejected Request: aws_profile_name is not allowed in request body. Clientside passthrough requires explicit admin opt-in via either `general_settings.allow_client_side_credentials = true` (proxy-wide) or `configurable_clientside_auth_params` on the deployment in your proxy config.yaml. ...","type":"auth_error","param":"None","code":"401"}}

The log slice for that rejected call contains no bedrock-runtime call, no outbound request, and no SigV4 signing at all, against one of each in the control slice, so nothing was signed before the refusal

And on uploads and batches at the tip, on port 41733:

$ curl -sS -X POST "http://127.0.0.1:41733/v1/files?model=bedrock-batch-haiku" -H "Authorization: Bearer sk-1234" \
    -F purpose=batch -F aws_profile_name=attacker -F "file=@batch_input.jsonl;type=application/jsonl"
{"error":{"message":"Authentication Error, Rejected Request: aws_profile_name is not allowed in request body. Clientside passthrough requires explicit admin opt-in via either `general_settings.allow_client_side_credentials = true` (proxy-wide) or `configurable_clientside_auth_params` on the deployment in your proxy config.yaml. ...","type":"auth_error","param":"None","code":"401"}}

$ # same on /v1/batches, and again with the field nested under extra_body
{"error":{"message":"Authentication Error, Rejected Request: aws_profile_name is not allowed in request body. ...","code":"401"}}
{"error":{"message":"Authentication Error, Rejected Request: aws_profile_name is not allowed in request body. ...","code":"401"}}

$ # the clean upload still works on that same proxy, after the three rejections
{"id":"file-bGl0ZWxsbTpzMzovL2xpdGVsbG0ta21z...","object":"file","purpose":"batch","status":"uploaded",...}

The tip proxy's access log carries the whole contrast in order: POST /v1/files 200, POST /v1/batches 200, GET /v1/batches/... 200, then POST /v1/files 401, POST /v1/batches 401, POST /v1/batches 401, then POST /v1/files 200. Exactly one Bedrock job id appears anywhere in that log, so no rejected request reached AWS

Notes for the reviewer, from the runs rather than the diff

The refusal comes back as 401 with "type":"auth_error", not 400, because the banned-param check runs inside the auth dependency. This PR neither causes nor changes that: it is the same envelope every previously-banned param already produced, and the PR only appends three names to that list

On the uploads route the caller's extra multipart field was dropped before credential resolution even at b5eed5e526, so the observed escalation was specific to /v1/batches and chat completions. The banned-param check still covers the uploads route, and multipart fields do reach it, which is why the 401 above fires there too

Three things the runs surfaced that this PR leaves alone. A successful upload reports "bytes": 0 for a file that head-object shows as 396 bytes. Batch create returns "model": null while the later retrieve fills in metadata.model_arn. And putting aws_batch_role_arn in a deployment's litellm_params makes plain chat on that same alias fail validation with Extra inputs are not permitted, so one alias cannot currently serve both chat and batch; that one was measured at the merge base and not re-measured at the tip, which is why the chat checks above use a batch-field-free alias

How the earlier gaps were caught

The live run is also what caught a second gap on this branch's earlier head f72ddedf39: the router's credential resolution round-trips litellm_params through a strict schema that only knew aws_access_key_id, aws_secret_access_key, and aws_region_name, so the deployment's aws_session_token and its assume-role, profile, and web-identity settings were silently dropped and S3 answered InvalidAccessKeyId. b63ba63655 adds those fields plus a mutation-tested regression test that fails on the old schema and passes on the new one

Type

🐛 Bug Fix

Caveats (if any)

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

@greptile-apps

greptile-apps Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR routes Bedrock file and batch requests through deployment AWS credentials and preserves the complete AWS authentication configuration during router credential resolution

  • Merges deployment and request parameters before constructing and signing Bedrock and S3 requests
  • Prevents caller identity selectors from overriding configured static deployment credentials
  • Extends router and generated dashboard schemas with session, profile, role, web-identity, STS, and external-ID fields
  • Adds request-safety and credential-propagation regression coverage

Confidence Score: 5/5

The PR appears safe to merge

No blocking failure remains

Important Files Changed

Filename Overview
litellm/llms/bedrock/common_utils.py Adds centralized merging of deployment and request AWS parameters with deployment identity precedence
litellm/llms/bedrock/files/transformation.py Uses merged deployment parameters for S3 endpoint selection, region selection, encryption resolution, and request signing
litellm/llms/bedrock/batches/transformation.py Uses merged deployment parameters for batch endpoint construction and create or retrieve signing
litellm/proxy/auth/auth_utils.py Expands blocked client-side AWS identity selectors unless the proxy-wide credential opt-in is enabled
litellm/types/router.py Preserves additional AWS authentication fields in router credential models
ui/litellm-dashboard/src/lib/http/schema.d.ts Regenerates dashboard API declarations for the expanded AWS credential schema

Reviews (5): Last reviewed commit: "fix(proxy): ban caller-supplied aws iden..." | Re-trigger Greptile


monkeypatch.setattr(config, "_sign_s3_request", capture_signing)

result = config.transform_create_file_request(

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.

P2 Tests monkeypatch signing internals

These tests replace signing methods on configuration objects instead of injecting mock dependencies, coupling the regression coverage to internal object structure and making refactors brittle. The same pattern occurs on lines 48 and 77.

Context Used: CLAUDE.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread litellm/llms/bedrock/batches/transformation.py
@veria-ai

veria-ai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 1 · PR risk: 0/10

@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed-hq

codspeed-hq Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing daleselaji-dev:codex/bedrock-s3-credentials-36155 (1b488f7) with litellm_internal_staging (79d412e)1

Open in CodSpeed

Footnotes

  1. No successful run was found on litellm_internal_staging (6f36bee) during the generation of this report, so 79d412e was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@CLAassistant

CLAassistant commented Aug 7, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ mateo-berri
❌ daleselaji-dev
You have signed the CLA already but the status is still pending? Let us recheck it.

@mateo-berri

mateo-berri commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

@greptileai

@mateo-berri

mateo-berri commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor

bugbot run

Comment thread litellm/llms/bedrock/common_utils.py
@mateo-berri

Copy link
Copy Markdown
Contributor

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor

bugbot run

@cursor cursor Bot 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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 1b488f7. Configure here.

@mateo-berri mateo-berri 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.

LGTM. Thanks for the contribution!

@mateo-berri
mateo-berri merged commit c4656d8 into BerriAI:litellm_internal_staging Aug 11, 2026
85 checks passed
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.

[Bug]: Bedrock batch S3 upload ignores the deployment's own AWS credentials and region

3 participants