You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Aws\S3\PostObjectV4 produces inconsistent casing for X-Amz-Security-Token between the policy condition and the actual form field. When the S3 client uses temporary credentials (STS — any source: AssumeRole profile, EKS IRSA, EKS Pod Identity, EC2 instance role), the generated presigned POST is rejected by S3.
The other four SigV4 fields in the same function (X-Amz-Date, X-Amz-Credential, X-Amz-Algorithm, X-Amz-Signature) are consistently camel-cased on both sides. Only x-amz-security-token (the STS one) is lowercased on the policy side but camel-cased on the form side.
This appears to be related in pattern to #3274 / #2882 (lowercase blacklist vs camel-cased header in SignatureV4), but in a different file and code path.
Regression Issue
Select this option if this issue appears to be a regression.
Expected Behavior
PostObjectV4 should emit matching casing for X-Amz-Security-Token in both the policy condition and the form field, so that S3 accepts the POST when using temporary credentials — consistent with how the other four SigV4 fields are handled in the same function.
S3 enforces case-sensitive matching between form field names and policy condition keys (at least for SigV4 + virtual-hosted bucket in regions such as ap-southeast-1). Because the form field X-Amz-Security-Token does not have a matching X-Amz-Security-Token entry in the policy conditions, S3 rejects the upload as having unexpected fields.
A real captured policy (base64-decoded) shows the mismatch clearly:
Note X-Amz-Security-Token (CamelCase) is the only field whose name does not exactly match any condition key in the policy.
Reproduction Steps
Use any source of STS temporary credentials (AWS_PROFILE with role_arn, AssumeRoleWithWebIdentity, EKS IRSA / Pod Identity, EC2 instance role). The access key will be prefixed ASIA* and Credentials::getSecurityToken() returns non-empty.
Construct an Aws\S3\PostObjectV4 against a virtual-hosted-style bucket in a SigV4 region (reproduced in ap-southeast-1):
$client = new \Aws\S3\S3Client([
'version' => 'latest',
'region' => 'ap-southeast-1',
// credentials resolved from the default chain — STS-backed in this case
]);
$post = new \Aws\S3\PostObjectV4(
$client,
'my-bucket',
['key' => 'pending/test.html', 'Content-Type' => 'text/html'],
[
['eq', '$key', 'pending/test.html'],
['eq', '$Content-Type', 'text/html'],
['content-length-range', 1, 10_485_760],
],
'+5 minutes'
);
POST the file with the produced getFormAttributes()['action'] URL and getFormInputs() fields (e.g. via curl -F ... -F 'file=@...').
S3 rejects with an Extra input fields error referencing X-Amz-Security-Token.
Possible Solution
Change line 59 so the policy condition uses the same casing as the form field. The form field name (X-Amz-Security-Token) is the AWS-documented casing for HTTP transit; matching it on the policy side is consistent with how the other four SigV4 fields are handled in the same function:
This brings X-Amz-Security-Token into line with the existing camel-cased treatment of X-Amz-Date / X-Amz-Credential / X-Amz-Algorithm in getPolicyAndSignature().
This bug is invisible when using static long-lived IAM user credentials (AKIA*) because getSecurityToken() returns null and the buggy branch is skipped.
It surfaces in any environment that uses STS — which, since EKS Pod Identity / IRSA have effectively become the recommended way to give AWS permissions to workloads, is increasingly common.
Currently impacted projects appear to be working around it by switching to presigned PUT URLs or by reverting to static IAM keys.
SDK version used
3.380.3 (also confirmed on 3.382.2 and current master)
Environment details (Version of PHP (php -v)? OS name and version, etc.)
PHP 8.4, Linux (Alpine in EKS), region ap-southeast-1, virtual-hosted-style addressing
Describe the bug
Aws\S3\PostObjectV4produces inconsistent casing forX-Amz-Security-Tokenbetween the policy condition and the actual form field. When the S3 client uses temporary credentials (STS — any source:AssumeRoleprofile, EKS IRSA, EKS Pod Identity, EC2 instance role), the generated presigned POST is rejected by S3.The other four SigV4 fields in the same function (
X-Amz-Date,X-Amz-Credential,X-Amz-Algorithm,X-Amz-Signature) are consistently camel-cased on both sides. Onlyx-amz-security-token(the STS one) is lowercased on the policy side but camel-cased on the form side.This appears to be related in pattern to #3274 / #2882 (lowercase blacklist vs camel-cased header in
SignatureV4), but in a different file and code path.Regression Issue
Expected Behavior
PostObjectV4should emit matching casing forX-Amz-Security-Tokenin both the policy condition and the form field, so that S3 accepts the POST when using temporary credentials — consistent with how the other four SigV4 fields are handled in the same function.Current Behavior
src/S3/PostObjectV4.phplines 58–60:S3 enforces case-sensitive matching between form field names and policy condition keys (at least for SigV4 + virtual-hosted bucket in regions such as
ap-southeast-1). Because the form fieldX-Amz-Security-Tokendoes not have a matchingX-Amz-Security-Tokenentry in the policyconditions, S3 rejects the upload as having unexpected fields.A real captured policy (base64-decoded) shows the mismatch clearly:
{ "expiration": "2026-05-28T03:18:32Z", "conditions": [ ["eq", "$key", "pending/share/.../test.html"], ["eq", "$Content-Type", "text/html"], ["content-length-range", 1, 10485760], {"x-amz-security-token": "IQoJ..."}, {"X-Amz-Date": "20260528T031332Z"}, {"X-Amz-Credential": "ASIA.../20260528/ap-southeast-1/s3/aws4_request"}, {"X-Amz-Algorithm": "AWS4-HMAC-SHA256"} ] }…while the form fields emitted by
getFormInputs()are:Note
X-Amz-Security-Token(CamelCase) is the only field whose name does not exactly match any condition key in the policy.Reproduction Steps
AWS_PROFILEwithrole_arn,AssumeRoleWithWebIdentity, EKS IRSA / Pod Identity, EC2 instance role). The access key will be prefixedASIA*andCredentials::getSecurityToken()returns non-empty.Aws\S3\PostObjectV4against a virtual-hosted-style bucket in a SigV4 region (reproduced inap-southeast-1):getFormAttributes()['action']URL andgetFormInputs()fields (e.g. viacurl -F ... -F 'file=@...').Extra input fieldserror referencingX-Amz-Security-Token.Possible Solution
Change line 59 so the policy condition uses the same casing as the form field. The form field name (
X-Amz-Security-Token) is the AWS-documented casing for HTTP transit; matching it on the policy side is consistent with how the other four SigV4 fields are handled in the same function:This brings
X-Amz-Security-Tokeninto line with the existing camel-cased treatment ofX-Amz-Date/X-Amz-Credential/X-Amz-AlgorithmingetPolicyAndSignature().Additional Information/Context
PostObjectV4in 2017.AKIA*) becausegetSecurityToken()returns null and the buggy branch is skipped.SDK version used
3.380.3 (also confirmed on 3.382.2 and current master)
Environment details (Version of PHP (
php -v)? OS name and version, etc.)PHP 8.4, Linux (Alpine in EKS), region ap-southeast-1, virtual-hosted-style addressing