Stalwart currently hardcodes S3 path-style access in crates/store/src/backend/s3/mod.rs:
let bucket = Bucket::new(&config.bucket, region, credentials)?
.with_path_style()
.with_request_timeout(...);
This works for MinIO and some S3-compatible services, but breaks Alibaba Cloud OSS, which only supports virtual-hosted-style access for security reasons. Attempting to use OSS as the blob store results in
HTTP 403 SecondLevelDomainForbidden.
Error log
ERROR S3 error (store.s3-error) reason = "<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>SecondLevelDomainForbidden</Code>
<Message>Please use virtual hosted style to access.</Message>
<RequestId>...</RequestId>
<HostId>oss-cn-shanghai.aliyuncs.com</HostId>
</Error>
", code = 403
Official reference
Alibaba Cloud OSS documentation explicitly states:
│ “Based on security considerations, OSS only supports virtual hosted access, that is, the bucket name must be used as a subdomain.”
│
│ — https://help.aliyun.com/zh/oss/developer-reference/compatibility-with-amazon-s3
And for the SecondLevelDomainForbidden error:
│ “When accessing OSS resources over the Internet, use the full virtual-hosted-style URL: https://./”
│
│ — https://help.aliyun.com/zh/oss/user-guide/http-403-error-code
Steps to reproduce
- Create an Alibaba Cloud OSS bucket, e.g. mybucket in oss-cn-shanghai.
- Configure Stalwart to use S3 blob store with endpoint https://oss-cn-shanghai.aliyuncs.com.
- Start Stalwart.
- Observe SecondLevelDomainForbidden / 403 errors in logs.
Expected behavior
Stalwart should allow users to disable path-style access so that buckets like Alibaba Cloud OSS can be used. The request URL should become:
https://mybucket.oss-cn-shanghai.aliyuncs.com/...
instead of:
https://oss-cn-shanghai.aliyuncs.com/mybucket/...
Proposed solution
Since Stalwart manages configuration through the registry/schema system and the WebUI, the proper fix would be to add a force-path-style boolean option to the S3 store configuration. This would allow
administrators to toggle path-style access from the settings page.
Suggested default:
• force-path-style: true — keeps backward compatibility with MinIO and other self-hosted S3-compatible stores.
• force-path-style: false — uses virtual-hosted-style, required for Alibaba Cloud OSS and AWS S3.
For reference, a minimal local workaround using an environment variable looks like this:
let force_path_style = env::var("STALWART_S3_FORCE_PATH_STYLE")
.map(|v| !v.eq_ignore_ascii_case("false") && v != "0")
.unwrap_or(true);
let mut bucket = Bucket::new(&config.bucket, region, credentials)?;
if force_path_style {
bucket = bucket.with_path_style();
}
However, I understand that Stalwart prefers configuration through the schema/registry rather than environment variables, so I would be happy to implement this as a proper config option if the maintainers
can confirm the preferred approach.
Environment
• Stalwart version: 0.16.9
• Storage backend: Alibaba Cloud OSS
• Deployment: Docker
Would you like to contribute?
I have a working local patch and am happy to open a PR once the maintainers confirm the preferred design direction.