Bug Report
Description
s5cmd cp (local → S3) fails with BadRequest 400 when uploading to a cross-account
S3 bucket with default SSE-KMS encryption. All IAM, KMS, and bucket permissions are
verified correct. The same operation succeeds with AWS CLI using the same IAM role.
Steps to Reproduce
- Run a container in Account A (
us-east-1)
- Download files from a source S3 bucket to local disk using s5cmd
- Upload local files to a KMS-encrypted S3 bucket in Account B (
eu-west-1):
s5cmd --json cp /tmp/staging/part-00000-xxxx.snappy.parquet \
s3://account-b-bucket/data/part-00000-xxxx.snappy.parquet
Expected Behavior
File uploads successfully to the destination bucket using the bucket's default KMS encryption.
Actual Behavior
{
"operation": "cp",
"command": "cp /tmp/staging/part-00000-xxxx.snappy.parquet s3://account-b-bucket/data/part-00000-xxxx.snappy.parquet",
"error": "BadRequest: Bad Request\n\tstatus code: 400, request id: N2E3G725KKPRC61S, host id: wXOiImny5jJFhh+3DrHyCvC1NcnK8IIugRcTlfzNbzRGOafK6oJNCTZ4+g5xKaHhtkLSlKjmC4GaxK3yhYf0hAZB+WiQcTfF"
}
---
Environment
- s5cmd version: pip-installed (pip install s5cmd)
- Platform: AWS Batch Fargate (Linux/amd64)
- Source: Account A (111111111111), us-east-1
- Destination: Account B (222222222222), eu-west-1
- Bucket encryption: Default SSE-KMS (aws:kms), BucketKeyEnabled: true, no Deny policy
---
Permissions Verified
All three permission layers are correctly configured:
┌───────────────────────────────────────────────────────────────────────────────┬────────┐
│ Layer │ Status │
├───────────────────────────────────────────────────────────────────────────────┼────────┤
│ IAM role (Account A): kms:GenerateDataKey, kms:Decrypt │ │ ✅ │
├───────────────────────────────────────────────────────────────────────────────┼────────┤
│ KMS key policy (Account B): explicitly grants above actions to Account A role │ ✅ │
├───────────────────────────────────────────────────────────────────────────────┼────────┤
│ Bucket policy (Account B): explicitly allows s3:PutObject for Account A role │ ✅ │
└───────────────────────────────────────────────────────────────────────────────┴────────┘
The error is 400 Bad Request, not 403 AccessDenied, ruling out a permissions issue.
---
What Works
AWS CLI (same role, same bucket, same KMS key):
aws s3 cp /tmp/staging/part-00000-xxxx.snappy.parquet \
s3://account-b-bucket/data/part-00000-xxxx.snappy.parquet \
--sse aws:kms \
--region eu-west-1
s5cmd Go binary with explicit SSE flag:
s5cmd cp --sse aws:kms \
/tmp/staging/part-00000-xxxx.snappy.parquet \
s3://account-b-bucket/data/part-00000-xxxx.snappy.parquet
---
Bug Report
Description
s5cmd cp(local → S3) fails withBadRequest 400when uploading to a cross-accountS3 bucket with default SSE-KMS encryption. All IAM, KMS, and bucket permissions are
verified correct. The same operation succeeds with AWS CLI using the same IAM role.
Steps to Reproduce
us-east-1)eu-west-1):s5cmd --json cp /tmp/staging/part-00000-xxxx.snappy.parquet \ s3://account-b-bucket/data/part-00000-xxxx.snappy.parquetExpected Behavior
File uploads successfully to the destination bucket using the bucket's default KMS encryption.
Actual Behavior
{ "operation": "cp", "command": "cp /tmp/staging/part-00000-xxxx.snappy.parquet s3://account-b-bucket/data/part-00000-xxxx.snappy.parquet", "error": "BadRequest: Bad Request\n\tstatus code: 400, request id: N2E3G725KKPRC61S, host id: wXOiImny5jJFhh+3DrHyCvC1NcnK8IIugRcTlfzNbzRGOafK6oJNCTZ4+g5xKaHhtkLSlKjmC4GaxK3yhYf0hAZB+WiQcTfF" } --- Environment - s5cmd version: pip-installed (pip install s5cmd) - Platform: AWS Batch Fargate (Linux/amd64) - Source: Account A (111111111111), us-east-1 - Destination: Account B (222222222222), eu-west-1 - Bucket encryption: Default SSE-KMS (aws:kms), BucketKeyEnabled: true, no Deny policy --- Permissions Verified All three permission layers are correctly configured: ┌───────────────────────────────────────────────────────────────────────────────┬────────┐ │ Layer │ Status │ ├───────────────────────────────────────────────────────────────────────────────┼────────┤ │ IAM role (Account A): kms:GenerateDataKey, kms:Decrypt │ │ ✅ │ ├───────────────────────────────────────────────────────────────────────────────┼────────┤ │ KMS key policy (Account B): explicitly grants above actions to Account A role │ ✅ │ ├───────────────────────────────────────────────────────────────────────────────┼────────┤ │ Bucket policy (Account B): explicitly allows s3:PutObject for Account A role │ ✅ │ └───────────────────────────────────────────────────────────────────────────────┴────────┘ The error is 400 Bad Request, not 403 AccessDenied, ruling out a permissions issue. --- What Works AWS CLI (same role, same bucket, same KMS key): aws s3 cp /tmp/staging/part-00000-xxxx.snappy.parquet \ s3://account-b-bucket/data/part-00000-xxxx.snappy.parquet \ --sse aws:kms \ --region eu-west-1 s5cmd Go binary with explicit SSE flag: s5cmd cp --sse aws:kms \ /tmp/staging/part-00000-xxxx.snappy.parquet \ s3://account-b-bucket/data/part-00000-xxxx.snappy.parquet ---