Skip to content

fix(minidump): Streaming uploads from external relays#5977

Merged
jjbayer merged 14 commits into
masterfrom
test/minidump-external
May 22, 2026
Merged

fix(minidump): Streaming uploads from external relays#5977
jjbayer merged 14 commits into
masterfrom
test/minidump-external

Conversation

@jjbayer
Copy link
Copy Markdown
Member

@jjbayer jjbayer commented May 11, 2026

Out of caution, the upload endpoint rejected uploads for which the length was unknown upfront for untrusted relays. But with recent work on the minidump endpoint, we now can have external managed relays submitting attachment uploads through the upload endpoint.

It is safe to use Defer-Length: 1 on the upload as long as the final placeholder has a validated length that matches the actual bytes uploaded to objectstore, since this is the length used for rate limiting.

This PR introduces a strongly typed differentiation between provisional upload locations (as requested by the client), and final upload locations (the location URL containing the number of uploaded bytes as a query parameter).

Fixes INGEST-912

Comment thread relay-server/src/utils/tus.rs
@jjbayer jjbayer changed the title test(minidump): Upload from external fix(minidump): Streaming uploads from external relays May 21, 2026
@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 21, 2026

INGEST-912

headers: HeaderMap,
Path(upload::LocationPath { project_id, key }): Path<upload::LocationPath>,
Query(upload::LocationQueryParams { length, signature }): Query<upload::LocationQueryParams>,
Query(LocationQueryParams { length, signature }): Query<LocationQueryParams<Provisional>>,
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.

Introducing the differentiation between Final and Provisional type might seem like a lot of overhead, but I think it's good to encode this difference in the type system.

Comment on lines +342 to +343
Upload::Create(_, tx) => tx.send(Err(Error::LoadShed)),
Upload::Upload(_, tx) => tx.send(Err(Error::LoadShed)),
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.

These messages now have different response types: one is Provisional, the other one Final.

relay_log::trace!("Validating headers");
let upload_length = tus::validate_post_headers(&headers, meta.request_trust().is_trusted())
.map_err(Error::from)?;
let upload_length = tus::validate_post_headers(&headers).map_err(Error::from)?;
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.

This is the functional change of this PR: We now always allow the Defer-Length: 1 header (i.e. open-ended uploads for which the length is unknown upfront). Correct rate limiting is guaranteed by the SignedLocation<Final> type.

@jjbayer jjbayer marked this pull request as ready for review May 21, 2026 11:34
@jjbayer jjbayer requested a review from a team as a code owner May 21, 2026 11:34
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 0fbc612. Configure here.

Comment thread relay-server/src/services/upload.rs Outdated
Comment thread relay-server/src/services/upload.rs Outdated
/// to validate whether the URI (especially the length) has been tempered with.
#[derive(Debug)]
pub struct Location {
pub struct Location<L: UploadLength> {
Copy link
Copy Markdown
Contributor

@loewenheim loewenheim May 21, 2026

Choose a reason for hiding this comment

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

Not that it matters very much, but is this trait bound necessary?

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.

Nope. I like how it declares what the L is expected to be, but it's unnecessarily strict so I'll remove it.

Comment thread relay-server/src/services/upload.rs Outdated
}

#[derive(Deserialize)]
struct Helper {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It seems like either this Helper struct or the Deserialize bound on UploadLength should be unnecessary. Is there a reason LocationQueryParams can't be straightforwardly Deserialize?

In fact, a quick test seems to suggest that they are both unnecessary.

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.

Thanks, I think the missing puzzle piece was #[serde(transparent)] on the Provisional type, without it the parser considered the length field to be required.

Comment thread relay-server/src/services/upload.rs
Comment thread relay-server/src/services/upload.rs Outdated
Comment on lines +556 to +559
impl<L: UploadLength + std::fmt::Debug> SignedLocation<L>
where
LocationQueryParams<L>: for<'de> Deserialize<'de>,
{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

IMO move both bounds to where.

Suggested change
impl<L: UploadLength + std::fmt::Debug> SignedLocation<L>
where
LocationQueryParams<L>: for<'de> Deserialize<'de>,
{
impl<L> SignedLocation<L>
where
L: UploadLength + std::fmt::Debug,
LocationQueryParams<L>: for<'de> Deserialize<'de>,
{

@jjbayer jjbayer requested a review from loewenheim May 21, 2026 14:55
Comment thread relay-server/src/services/upload.rs
Copy link
Copy Markdown
Contributor

@loewenheim loewenheim left a comment

Choose a reason for hiding this comment

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

Approved, subject to the comment from the bot about Final.

@jjbayer jjbayer enabled auto-merge May 22, 2026 07:51
@jjbayer jjbayer added this pull request to the merge queue May 22, 2026
Merged via the queue into master with commit 18baf91 May 22, 2026
33 checks passed
@jjbayer jjbayer deleted the test/minidump-external branch May 22, 2026 08:32
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.

2 participants