Skip to content

fix(s3): return XML error responses for presigned POST failures#13

Open
Meemaw wants to merge 3 commits intomainfrom
devin/1775815290-presigned-post-xml-error
Open

fix(s3): return XML error responses for presigned POST failures#13
Meemaw wants to merge 3 commits intomainfrom
devin/1775815290-presigned-post-xml-error

Conversation

@Meemaw
Copy link
Copy Markdown
Member

@Meemaw Meemaw commented Apr 10, 2026

Summary

Presigned POST error responses (e.g. policy condition failures like content-type mismatch) are currently thrown as AwsException, which propagates to the global AwsExceptionMapper and is serialized as JSON. Real AWS S3 and LocalStack return XML error responses for presigned POST operations.

This wraps handlePresignedPost in a try/catch that converts AwsException to XML via the existing xmlErrorResponse() helper.

Type of change

  • Bug fix (fix:)
  • New feature (feat:)
  • Breaking change (feat!: or fix!:)
  • Docs / chore

AWS Compatibility

AWS S3 presigned POST error responses use the standard XML error format:

<?xml version="1.0" encoding="UTF-8"?>
<Error>
  <Code>AccessDenied</Code>
  <Message>Invalid according to Policy: ...</Message>
  <RequestId>...</RequestId>
</Error>

The global AwsExceptionMapper returns JSON, which breaks clients (including the AWS SDK) that expect XML.

Updates since last revision

  • Added presignedPostReturnsXmlErrorResponseBody integration test that asserts the exact XML wire format using matchesRegex (only RequestId is dynamic). This verifies the full response including &quot;-encoded quotes in the <Message> element, matching what downstream consumers (e.g. seadn) parse from the raw response body.

⚠️ Review closely

  • xmlErrorResponse status code handling — verify it respects the status code from AwsException (e.g. 403 for AccessDenied, 400 for InvalidArgument) rather than defaulting to a fixed code.
  • &quot; encoding — the XML builder encodes double quotes as &quot; in element text. The new test asserts this exact encoding in the raw response. Confirm this matches real AWS S3 behavior (it does per the XML spec, and LocalStack produces the same output).

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

Presigned POST errors (e.g. policy condition failures) were being thrown
as AwsException and caught by the global AwsExceptionMapper, which
returns JSON. Real AWS S3 and LocalStack return XML error responses for
presigned POST operations.

Wrap handlePresignedPost in a try/catch that converts AwsException to
XML via xmlErrorResponse(), matching the AWS/LocalStack behavior.

Co-Authored-By: Matej Snuderl <ematej.snuderl@gmail.com>
@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

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: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 2 additional findings.

Open in Devin Review

Adds a test that extracts the raw response body and asserts it contains
the exact XML substring that downstream consumers (e.g. seadn) expect:

  <Code>AccessDenied</Code><Message>Invalid according to Policy: ...
  [&quot;eq&quot;, &quot;$Content-Type&quot;, &quot;image/png&quot;]</Message>

This complements the existing hasXPath-based assertions which auto-
unescape XML entities and therefore don't verify the wire format.

Co-Authored-By: Matej Snuderl <ematej.snuderl@gmail.com>

// Verify the raw XML contains the expected structure with XML-escaped quotes.
// This is the exact substring that the seadn test suite asserts against.
assertThat(responseBody, containsString(
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Can we assert exact string?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Updated — now asserts the exact full XML response with matchesRegex. The only dynamic part is RequestId (random UUID), matched with a regex pattern. Everything else is asserted literally including the &quot; encoding, XML declaration, and element structure.

Replace containsString with matchesRegex to assert the full XML
response structure. The only dynamic part is RequestId (random UUID),
matched with a regex pattern.

Co-Authored-By: Matej Snuderl <ematej.snuderl@gmail.com>
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