Skip to content

fix(s3): enforce presigned POST policy conditions (eq, starts-with, content-type)#2

Open
Meemaw wants to merge 3 commits intomainfrom
devin/1775299142-s3-presigned-post-policy-conditions
Open

fix(s3): enforce presigned POST policy conditions (eq, starts-with, content-type)#2
Meemaw wants to merge 3 commits intomainfrom
devin/1775299142-s3-presigned-post-policy-conditions

Conversation

@Meemaw
Copy link
Copy Markdown
Member

@Meemaw Meemaw commented Apr 4, 2026

Summary

Presigned POST uploads were only validating content-length-range policy conditions. All other conditions (bucket, key, Content-Type, etc.) were silently ignored, allowing uploads that violate the policy.

This PR replaces the narrow validateContentLengthRange method with validatePolicyConditions, which enforces:

  • Object-form exact match: {"bucket": "X"}, {"key": "X"}, {"Content-Type": "X"}
  • Array-form eq: ["eq", "$Content-Type", "image/png"]
  • Array-form starts-with: ["starts-with", "$key", "uploads/"]
  • Array-form content-length-range: ["content-length-range", min, max] (existing, preserved)

Policy violations return 403 AccessDenied with AWS-compatible error messages:

Invalid according to Policy: Policy Condition failed: ["eq", "$Content-Type", "image/png"]

Discovered via seadn#1921 — a unit test expects S3 to reject presigned POST uploads when Content-Type doesn't match the policy.

Type of change

  • Bug fix (fix:)

AWS Compatibility

Incorrect behavior: Floci accepted presigned POST uploads regardless of policy conditions (except content-length-range). Real AWS S3 rejects with 403 AccessDenied when any condition is not met.

Corrected behavior: Now matches AWS S3 — mismatched Content-Type, key, bucket, or failed starts-with prefix checks return 403.

Updates since last revision

  • Policy JSON is now parsed via Jackson ObjectMapper.readTree()JsonNode, matching the pattern used elsewhere in the codebase (e.g. AwsJsonController). Removed all hand-rolled string manipulation helpers (findMatchingBracket, skipWhitespace, unquote, splitJsonArray).

  • All error-path tests assert exact XML error responses via XPath instead of containsString:

    • contentType("application/xml")
    • hasXPath("/Error/Code", equalTo(...))
    • hasXPath("/Error/Message", equalTo(...))

    This applies to the 4 new policy-enforcement tests and the 4 pre-existing error tests (EntityTooLarge, InvalidArgument ×2, NoSuchBucket).

Things to review carefully

  1. No test for ["eq", ...] array-form — the tests cover object-form exact match ({"Content-Type": "X"}validateExactMatchCondition) and starts-with array-form, but not the ["eq", "$field", "value"] array syntax which exercises a different code path (validateArrayCondition's eq branch).
  2. Case-sensitive field lookupfields.get("Content-Type") depends on the multipart form field name matching exactly. This matches AWS behavior but is worth verifying against real SDK-generated policies.
  3. asLong() for content-length-range boundsJsonNode.asLong() returns 0 for non-numeric nodes. If a malformed policy somehow has string values for min/max, the range check would silently use 0. Real SDK-generated policies always emit numeric values here.

Checklist

  • ./mvnw test passes locally
  • New or updated integration test added
  • Commit messages follow Conventional Commits

Link to Devin session: https://app.devin.ai/sessions/06b7e28f3e664783b17ed18060e5c267
Requested by: @Meemaw


Open with Devin

@devin-ai-integration
Copy link
Copy Markdown

Original prompt from Matej

switch from localstsvk to floci. Minimal vode changes, just change the container image

@devin-ai-integration
Copy link
Copy Markdown

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

devin-ai-integration bot and others added 2 commits April 4, 2026 11:22
…ontent-type)

Co-Authored-By: Matej Snuderl <ematej.snuderl@gmail.com>
Co-Authored-By: Matej Snuderl <ematej.snuderl@gmail.com>
@devin-ai-integration devin-ai-integration bot force-pushed the devin/1775299142-s3-presigned-post-policy-conditions branch from a70433a to 6351e75 Compare April 4, 2026 11:23
Co-Authored-By: Matej Snuderl <ematej.snuderl@gmail.com>
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 4 additional findings in Devin Review.

Open in Devin Review

@Meemaw Meemaw marked this pull request as ready for review April 4, 2026 11:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant