#2045

fix: require a capability to read files from the local backend

Draft4pmtongwants to merge intofrom

Summary

When the backend runs under the Desktop app, its file routes now require a capability.

  • GET/HEAD /files/stream and GET /files/preview/... require a file access grant. Grants come from a new POST /files/access endpoint, which needs the Desktop capability like the other control APIs.
  • A grant covers one project file root (email, project, and optional Space and user). It expires after one hour and is signed with a key that exists only in the running backend process, so restarting the app revokes every grant.
  • GET /files (listing), POST /files (upload) and GET /chat/{project_id}/status now require the Desktop capability when it is configured, the same as the other routes the app calls with headers.

Why the grant is in the URL

The app displays files with <img>, <iframe> (PDF and HTML previews) and media elements, and can open them in the system browser. None of these can send the capability header. So when a file is opened, the app requests a grant and adds it to that file's URL.

For HTML previews the grant is a path segment (/files/preview/{grant}/{path}). Relative links and assets inside the previewed page then resolve to URLs that carry the grant, and they keep the Space and user scope.

App changes

  • Opening a file in Files or in the file preview requests a grant first (authorizeBrainFile in src/lib/brainFileAccess.ts). Listing URLs don't change, so file identity, selection and the file tree are unaffected.
  • HTML previews build their base URL from the grant. When a previewed page references other files from the same project root, those URLs get the same grant.
  • If no grant can be obtained, for example from a backend that doesn't have this change, the file URL stays as it was and the previous preview URL format is used.

Compatibility

  • Web and self-hosted deployments, which run without the Desktop capability, keep their existing router-level authentication, and /files/stream doesn't require a grant there. HTML previews use the grant-based URL in every mode. POST /files/access is available there under the same authentication as the other routes.
  • The preview route changes from /files/preview/{email}/{project_id}/{path} to /files/preview/{grant}/{path}. The app in this repository is its only caller.
  • Works alongside #2044. This PR doesn't change SecurityHeadersMiddleware, so the framing rules that PR sets for /files/stream and /files/preview/ still apply.

Known limitation

If a preview stays open for more than an hour, loading more of the file can fail, for example when scrolling far into a very large PDF or seeking in a long video. Reopening the file gets a new grant.

Testing

  • Backend: pytest tests/app/auth/test_file_access.py tests/app/auth/test_local_control.py tests/app/controller/test_file_controller.py (42 passed). The tests cover allowed and denied requests for streams, previews, listing, upload and grant issuance with the Desktop capability configured, and the behavior without it.
  • Backend, neighboring suites: pytest tests/app/workspace_runtime/test_registration.py tests/app/test_main_security_defaults.py tests/app/agent/toolkit/test_terminal_processes.py (42 passed)
  • Frontend: vitest run test/unit/lib/brainFileAccess.test.ts test/unit/components/Folder test/unit/lib/filePreviewLoader.test.ts (116 passed), npm run type-check, eslint and prettier on the changed files, git diff --check

Still to verify by hand in the packaged app: PDF, image, video and HTML previews, including an HTML report with relative assets, in both Files and the session preview panel.

Activity

1

This pull request is a draft

Merging is unavailable until it is marked ready for review.