Skip to content

Derive file S3 location server-side on upload - #150

Merged
wasimxyz merged 4 commits into
stagingfrom
wasimamiri/eng-1450-arbitrary-s3-location-injection-via-file-patch-enables-data
Jul 16, 2026
Merged

Derive file S3 location server-side on upload#150
wasimxyz merged 4 commits into
stagingfrom
wasimamiri/eng-1450-arbitrary-s3-location-injection-via-file-patch-enables-data

Conversation

@wasimxyz

@wasimxyz wasimxyz commented Jul 16, 2026

Copy link
Copy Markdown
Member

Summary

The PATCH /api/v1/files/:fileId endpoint accepted arbitrary s3_bucket / s3_key values with no validation. Any files:update caller (which includes every browser-authenticated user via wildcard session scopes) could repoint a file record at any S3 object. Downstream download redirects and Lambda reprocessing trust those DB values, enabling silent data tampering and unauthorized object access.

Rather than validate client input against an allowlist/prefix, this removes client control entirely:

  • patchFileBody no longer accepts s3_bucket / s3_key (unknown keys are dropped by the Zod schema; the OpenAPI spec regenerates automatically).
  • On the → uploaded transition the handler derives the canonical {instrumentId}/{runId}/{filename} key from trusted DB state and sets the bucket from getS3RawDataBucket().

This is safe because the watcher only ever echoed back the same bucket/key that request-upload-url already computed server-side (watcher/src/data_hub_watcher/uploader.py), so these fields carried no new information. Wire behavior is unchanged for legitimate callers; the value derived is byte-for-byte identical.

Out of scope (follow-ups)

  • The Lambda create path (POST .../runs/:runId/files) still accepts caller-specified s3_bucket / s3_key — it legitimately registers processed artifacts under non-raw prefixes, so it would need its own allowlist rather than the canonical-key treatment.

Test plan

  • make check-all (ruff, pyright, ultracite, tsc) — clean
  • Full integration suite — 299/299 passing
  • Updated the detected → uploaded test to send a hostile s3_bucket/s3_key payload and assert the record still lands on the derived canonical location (ENG-1450 regression)
  • Live-verified on the local dev server: the original exploit payload is now ignored and the download redirect stays on the canonical location

Made with Cursor

The PATCH /api/v1/files/:fileId endpoint accepted arbitrary s3_bucket and
s3_key values, letting any files:update caller repoint a record at any S3
object. Downstream download redirects and Lambda reprocessing trusted those
DB values, enabling data tampering and unauthorized object access.

The watcher only ever echoed back the canonical bucket/key that
request-upload-url already computed, so these fields carried no new
information. Drop them from patchFileBody and rebuild the canonical
{instrumentId}/{runId}/{filename} key from trusted DB state on the uploaded
transition instead of validating client input.

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel

vercel Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
data-hub Ready Ready Preview, Comment Jul 16, 2026 5:25pm

Request Review

@wasimxyz wasimxyz changed the title Derive file S3 location server-side on upload (ENG-1450) Derive file S3 location server-side on upload Jul 16, 2026
@wasimxyz wasimxyz self-assigned this Jul 16, 2026
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
The upload PATCH no longer sends s3_bucket / s3_key now that the server
derives the canonical S3 location itself; the watcher only reports status
and content type. Bump the watcher to 0.5.1.

Co-authored-by: Cursor <cursoragent@cursor.com>
@wasimxyz
wasimxyz merged commit 395a595 into staging Jul 16, 2026
6 checks passed
@wasimxyz
wasimxyz deleted the wasimamiri/eng-1450-arbitrary-s3-location-injection-via-file-patch-enables-data branch July 16, 2026 17:28
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