Skip to content

fix(files): serve mothership chat attachments stored under a workspace key - #6789

Merged
waleedlatif1 merged 1 commit into
stagingfrom
fix/mothership-attachment-serve
Aug 17, 2026
Merged

fix(files): serve mothership chat attachments stored under a workspace key#6789
waleedlatif1 merged 1 commit into
stagingfrom
fix/mothership-attachment-serve

Conversation

@icecrasher321

@icecrasher321 icecrasher321 commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Chat attachment thumbnails and click-throughs were 404ing with {"error":"FileNotFoundError","message":"File not found"}. A mothership attachment is minted with the same key shape as a workspace file (resolveUploadStorage calls generateWorkspaceFileKey for mothership_attachment) but its row carries context = 'mothership', so the serve route's key-prefix branch sent it into the workspace-file use case, which matches on context = 'workspace' and resolved it to nothing. Regressed in fix(files): preserve principals when serving generated documents #6654.
  • Serve resolves the storage context from the key's stored binding (resolveStoredFileContext) rather than the prefix alone, and threads it into the cloud/local handlers instead of re-inferring. Genuine workspace files still go through the authorized use case.
  • Fixes previews in chat history, restored drafts, queued messages, and HEIC at upload time — all build the same serve URL. A freshly picked PNG still previewed because that thumbnail is a local blob URL, which is why this looked partial.

Second commit states the contract the fix depends on, so the next reader doesn't re-derive module ownership from the prefix:

  • Prefix = bucket + tenancy. Row = module owner. workspace_files.context is server-authored like the key, but unlike the key it is mutablematerialize_file promotes an attachment to a workspace file by flipping that column. Encoding a mutable fact in an immutable key would mean copying the bytes on every transition just to restate them, which is why the prefix can't own this and executeSave stays a metadata-only flip.
  • resolveTrustedFileContext claimed the prefix was flatly "authoritative" — the claim that made routing on it look safe. Now scoped to what it actually defends: a caller-supplied context still can never relabel a private key. resolveStoredFileContext is documented as the sanctioned reader, not a workaround.
  • verifyWorkspaceFileAccess filtered its binding lookup to context = 'workspace', so attachments missed the row and fell through to object metadata, which can't see a soft delete. It now matches either workspace-scoped context — which is what callers already wanted, since the LLM-attachment and presigned-URL paths pass 'workspace' for attachment keys today. A soft-deleted attachment is now denied on all of them.
  • The parse route carried the same gate, labelling parsed attachments with the raw storage segment instead of the uploaded filename.
  • Module-scoped filters are deliberately untouched: the Files module, folder manager, forking and the workspace-file use cases match context = 'workspace' because they mean the Files module, not the bucket.

Type of Change

  • Bug fix

Testing

Both regressions are pinned by tests that fail against the pre-fix code, and in both cases the reason they shipped green was a mock asserting something the real function never does:

  • The serve tests mocked inferContextFromKey to return 'mothership' for a workspace/… key. Made honest — 9 of them fail against the old route.
  • The new authorization tests use a getFileMetadataByKey mock that honors the context/includeDeleted arguments, so they actually exercise the filter. 2 fail against the old authorizer; the workspace-context cases still pass, showing no Files-module regression.

bun run test app/api/files lib/uploads lib/workspace-files — 861 pass (77 files). type-check, lint, and all 29 audits clean. Not exercised against a running app.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

…e key

A mothership chat attachment is minted with the same storage key shape as a
workspace file — `resolveUploadStorage` calls `generateWorkspaceFileKey` for
`mothership_attachment` — but its row is written with `context = 'mothership'`.
The serve route branched on the key prefix alone, so every attachment entered
the workspace-file use case, which matches on `context = 'workspace'` and
resolved it to nothing: `{"error":"FileNotFoundError","message":"File not
found"}` for every chat thumbnail and click-through. The `context=mothership`
query param on the serve URL is decorative; the route never read it.

Serve now resolves the storage context from the key's stored binding, which is
server-authored at upload and the only thing that separates the two, and passes
it into the cloud and local handlers instead of re-inferring. Genuine workspace
files still go through the authorized use case.

`verifyWorkspaceFileAccess` takes the context too, so an attachment authorizes
from its database row rather than falling through to storage-object metadata.
A soft-deleted attachment is now denied, matching workspace files.

The route tests are what let this ship: they mocked `inferContextFromKey` to
return 'mothership' for a `workspace/…` key, which it never does. With the mock
made honest, nine of them fail against the old route.
@vercel

vercel Bot commented Aug 17, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Aug 17, 2026 10:50pm

Request Review

@cursor

cursor Bot commented Aug 17, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches file serve routing and authorization for all non-public keys with a workspace prefix; behavior change is intentional but could affect edge cases where metadata is missing or stale.

Overview
Fixes 404s when serving mothership chat attachments whose storage keys look like workspace files (workspace/…) but are recorded with context = 'mothership'.

The serve route now uses resolveStoredFileContext (DB binding on the key) instead of inferring context from the prefix alone, so attachments skip the workspace-files use case and go through session auth + verifyFileAccess with the correct context. That resolved context is passed into local and cloud handlers instead of re-inferring from the key.

Authorization threads workspace vs mothership through verifyWorkspaceFileAccess and lookupWorkspaceFileByKey, so metadata lookups and soft-deleted attachment denial match workspace files.

Tests cover resolveStoredFileContext and a regression case for cloud serve of a mothership attachment under a workspace key.

Reviewed by Cursor Bugbot for commit 26ac161. Configure here.

@greptile-apps

greptile-apps Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR fixes serving mothership chat attachments whose workspace-shaped storage keys previously routed them through the workspace-files use case.

  • Resolves storage context from the active metadata binding rather than the key prefix alone.
  • Threads the resolved context consistently through authorization, local serving, and cloud serving.
  • Adds regression coverage for mothership attachments, workspace files, and context fallback behavior.

Confidence Score: 5/5

The PR appears safe to merge, with no concrete blocking or independently actionable non-blocking issues identified.

The active metadata binding is unique per key, and the resolved context is consistently applied across route selection, authorization, deletion checks, and storage reads.

Important Files Changed

Filename Overview
apps/sim/lib/uploads/server/metadata.ts Adds deterministic context resolution from the unique active metadata binding, with prefix inference retained for unbound and non-workspace keys.
apps/sim/app/api/files/serve/[...path]/route.ts Uses the resolved storage context consistently to select authentication and drive local or cloud authorization and reads.
apps/sim/app/api/files/authorization.ts Extends workspace-scoped metadata authorization to mothership bindings while preserving deleted-record denial and workspace permission checks.
apps/sim/app/api/files/serve/[...path]/route.test.ts Adds regression coverage proving workspace-prefixed mothership attachments bypass the workspace-file use case and are served under the mothership context.
apps/sim/lib/uploads/server/metadata.test.ts Covers stored mothership and workspace bindings, unbound workspace keys, and non-workspace inference without a database lookup.

Sequence Diagram

sequenceDiagram
  participant Client
  participant Serve as File Serve Route
  participant Metadata
  participant Auth
  participant Storage
  Client->>Serve: GET /api/files/serve/workspace/...
  Serve->>Metadata: resolveStoredFileContext(key)
  Metadata-->>Serve: mothership or workspace
  alt Workspace file
    Serve->>Auth: internalWorkspaceFileServeAuth
  else Mothership attachment
    Serve->>Auth: session/internal authentication
  end
  Serve->>Auth: verifyFileAccess(key, resolvedContext)
  Auth-->>Serve: authorized
  Serve->>Storage: read key using resolvedContext
  Storage-->>Client: attachment bytes
Loading

Reviews (1): Last reviewed commit: "fix(files): serve mothership chat attach..." | Re-trigger Greptile

@waleedlatif1
waleedlatif1 merged commit 1d43639 into staging Aug 17, 2026
30 checks passed
@waleedlatif1
waleedlatif1 deleted the fix/mothership-attachment-serve branch August 17, 2026 23:11
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