Skip to content

S3CrtClient sends absolute-form request-target (PUT https://host/... HTTP/1.1) instead of origin-form #3956

Open

Description

@davehorton

Describe the bug

S3CrtClient sends every request with an absolute-form request-target, for example:

PUT https://storage.example.com/bucket/2026/10/16/file.mp3 HTTP/1.1
Host: storage.example.com

instead of the origin-form a client should use when it connects directly to the origin server:

PUT /bucket/2026/10/16/file.mp3 HTTP/1.1
Host: storage.example.com

The cause is Aws::Http::HttpRequest::ToCrtHttpRequest(). It sets the CRT message path to scheme://authority[:port] followed by the path and query:

https://github.com/aws/aws-sdk-cpp/blob/1.11.500/src/aws-cpp-sdk-core/source/http/HttpRequest.cpp#L88-L95

aws-c-s3 copies that path into the requests it sends, and aws-c-http writes it verbatim as the request line. aws-c-http's own proxy support (aws_http_rewrite_uri_for_proxy_request) converts a request to absolute-form only when it goes through a forward proxy (RFC 7230 §5.3.2). That suggests callers are expected to pass origin-form. RFC 9112 §3.2.1 likewise says a client talking directly to an origin server sends only the path and query as the request-target.

This line has been the same since the S3 CRT client was introduced (cd509e9) and is unchanged on main.

Why it matters

S3 and most S3-compatible servers accept absolute-form, so uploads work. But security tooling in front of S3-compatible endpoints treats absolute-form as open-proxy probing. CrowdSec's crowdsecurity/http-open-proxy scenario is a trigger: a single 400 or 405 response to a request matching ^http[s]?:// bans the source IP.

In our case, one upload to a customer's MinIO endpoint (behind CrowdSec) stalled, was dropped by the client after about 30 seconds, and was logged as a 400 by their proxy. The uploader's IP was then banned for 4 hours. Every later upload from that host failed with an HTML 403 until the customer removed the ban.

Expected behavior

Origin-form request-target (/path?query) for direct connections. The Host header is already set correctly.

Current behavior

Absolute-form request-target on every request.

Reproduction steps

Raw-socket listener that prints the request line:

import socket
s = socket.socket(); s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("127.0.0.1", 9000)); s.listen(1)
c, _ = s.accept(); data = b""
while b"\r\n\r\n" not in data:
    data += c.recv(65536)
print(data.split(b"\r\n")[0].decode())
c.sendall(b"HTTP/1.1 200 OK\r\nETag: \"x\"\r\nContent-Length: 0\r\nConnection: close\r\n\r\n")

Client:

#include <aws/core/Aws.h>
#include <aws/core/auth/AWSCredentialsProvider.h>
#include <aws/s3-crt/S3CrtClient.h>
#include <aws/s3-crt/model/PutObjectRequest.h>
#include <iostream>

int main() {
  Aws::SDKOptions options; Aws::InitAPI(options);
  {
    Aws::S3Crt::ClientConfiguration config;
    config.region = "us-east-1";
    config.scheme = Aws::Http::Scheme::HTTP;
    config.endpointOverride = "127.0.0.1:9000";
    Aws::S3Crt::S3CrtClient client(
      Aws::MakeShared<Aws::Auth::SimpleAWSCredentialsProvider>("t", Aws::Auth::AWSCredentials("AKIATEST", "secret")),
      config, Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy::Never, false /* path-style */);
    Aws::S3Crt::Model::PutObjectRequest req;
    req.SetBucket("bucket"); req.SetKey("test.mp3");
    auto body = Aws::MakeShared<Aws::StringStream>("t"); *body << "hello";
    req.SetBody(body);
    std::cout << "success=" << client.PutObject(req).IsSuccess() << std::endl;
  }
  Aws::ShutdownAPI(options);
}

Listener output:

PUT http://127.0.0.1:9000/bucket/test.mp3 HTTP/1.1

Possible solution

In ToCrtHttpRequest(), set the path to only the URL-encoded path plus the query string, without the scheme and authority. Note that ToCrtHttpRequest() is also used by AWSAuthV4Signer (SigV4a) and by s3-transfer's CrtOperations, so the change would need signing test coverage.

Additional Information/Context

Reproduced with path-style addressing. The path is built the same way regardless of addressing style, because ToCrtHttpRequest() always prepends the scheme and authority.

AWS CPP SDK version used

1.11.500 (aws-crt-cpp 0.30.1). Also reproduced on 1.11.900.

Compiler and Version used

GCC 12.2.0 (Debian 12) in production; Apple clang 17.0.0 for the repro.

Operating System and version

Debian 12 (bookworm), aarch64. macOS (Homebrew aws-sdk-cpp 1.11.900) for the repro.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions