Repository navigation
S3CrtClient sends absolute-form request-target (PUT https://host/... HTTP/1.1) instead of origin-form #3956
Description
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:
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.
Activity
feat: log HTTP status on S3-compatible upload failures (#49)