Your own bucket and the lent key

About 20 minutesCloudflare R2

A Private bucket is a Cloudflare R2 bucket you own. Mulno's servers sign one-object upload links and never receive the footage, and they can't read it back (the other choice, Mulno Cloud, is our own storage). The lent key is an opt-in, per project, that lets our servers read a project's sessions so they can show them in the browser and answer AI questions.

What works today

Mulno Cloud keeps both builds and testers' sessions, and needs no setup. A bucket of your own works two ways: for a project, on the project's Storage screen in the web app, and for a campaign, in Studio's Campaigns screen against our broker (Send a build to testers). Reviewing a Private project in the browser, and the lent key, are not open: they are marked Soon below.

1. Where a project's recordings live

ModeWho holds the footageStatus
Private bucketYou. Testers upload straight into your R2 bucket; our servers never read it.Works today, for a project and for a campaign. Reviewing it in the browser: Soon.
Private bucket with the lent keyYou, still. Our servers may read it, under one opt-in you turn on and off.Soon
Mulno CloudUs: our R2, counted against your plan's quota.Works today: it keeps builds and your testers' sessions.

Storage is chosen per project: a jam game can sit in Mulno Cloud while a publisher's NDA game sits in that publisher's bucket, in one workspace. A project's builds, sessions, notes and proxies all live in one place. Moving a project between storages is a copy job, not a feature.

2. Set up an R2 bucket

Cloudflare R2 has no write-only object permission. Its narrowest scope, Object Read & Write, can also list and read the whole bucket, and can overwrite and delete. That is Cloudflare's design, not something we can fix, so Mulno shrinks the damage instead: testers never hold the key (they get one-object, one-hour upload links), each campaign or project gets its own bucket, and a lock rule on incoming/ stops a leaked key from deleting or overwriting what has already been uploaded.

  1. Create the bucket. One bucket for this campaign or project only; --location is a hint, so pick one near your testers.
  2. Lock incoming/. R2 answers 409 to an overwrite or a delete while the rule holds. Seven days is the default; a tester's deletion request can then be honoured once the window has passed, so set it with your legal duties in mind.
  3. Make a token. In the Cloudflare dashboard: R2 › Manage R2 API Tokens › an Object Read & Write token scoped to this one bucket.
PowerShell
npx wrangler r2 bucket create studio-playtest-spring-4 --location apac
npx wrangler r2 bucket lock add studio-playtest-spring-4 mulno-incoming incoming/ --retention-days 7
# then, in the Cloudflare dashboard: R2 › Manage R2 API Tokens › an "Object Read & Write" token
# scoped to this one bucket
  1. Give the bucket to Studio and check it. The endpoint, bucket name and key go into the Bucket form, and the key is never shown again. Check my bucket then runs four checks in plain words: it can write; it can't overwrite (on R2 that means the lock rule is there); and it can't read or list. On R2 the last two fail, which is expected.
  2. Accept the sentence R2 needs. Uploads start only after you accept this statement by name (its id is tester_key_can_read_bucket/v3), which is stored with who accepted it and when, and cleared if the bucket changes:
A leaked copy of this campaign's bucket key — or Mulno's own sealed working copy of it — could list and read every session in this bucket, and could write, overwrite or delete anything outside incoming/, and anything inside it once the bucket lock rule's retention has passed.

Mulno does hold a sealed working copy of the key, so that it can sign upload links: encrypted with AES-256-GCM, outside the database. The only requests our servers make to your bucket themselves are the check's probe: a 32-byte object under incoming/ that it writes, tries to overwrite, and tries to read back and list. The answers to the last two are thrown away.

A project's Storage screen can't take the acceptance yet

Studio's Campaigns screen has an Accept button under the check. The per-project Storage screen in the web app runs the check but has no button for it: the acceptance goes in as acknowledge_read_risk on the api's verify call.

Amazon S3 isn't offered

A studio's S3 bucket could be write-only, but nothing S3 is tested or promised for now: the setup offers R2 only. The S3 code is kept, and comes back when a studio needs it.

3. A separate bucket for builds

A Private project keeps its builds in a second bucket, because mulno build push has to ask whether a chunk is already there (HeadObject), so its key needs read. On R2 a token covers a whole bucket, so a builds key that shared the sessions bucket could read footage. The setup therefore refuses an R2 builds bucket that is also the project's sessions bucket, and it checks the write and no-overwrite rules and the lock rule, but not no-read or no-list. The project's Storage screen in the web app serves both buckets: Sessions bucket and Builds bucket.

4. Browser playback needs a CORS rule Soon

Reviewing a Private project in the browser isn't open yet (section 5), so you don't need this rule today. To play sessions in the browser, the sessions bucket will need a CORS rule for the app's origin. These are the values we measured against real R2; add them in the bucket's CORS settings in the Cloudflare dashboard. The setup screen doesn't apply them for you.

Allowed originsThe exact app origin, and nothing wider
Allowed methodsGET, HEAD
Allowed headersrange, authorization, x-amz-date, x-amz-content-sha256, x-amz-security-token
Exposed headersContent-Range, Content-Length, ETag, Accept-Ranges
Max age3600 seconds

5. How your reviewers read a Private bucket

By default, with their own key. Studio reads a bucket folder with the reviewer's own read key, kept in Windows Credential Manager. Never Mulno's, and never the upload key. In the browser (Soon), a signer page on its own sibling origin holds that key and hands the app links that last at most 300 seconds, so a script injected into the app can read only while the tab is open and can't carry the key away. Our servers can't read the footage.

Or with the lent key. Your reviewers then need no key of their own, and our servers can do more. See the next section.

6. The lent key Soon

An admin or owner can turn it on per project. It is one switch, all or nothing, and it asks for one statement, by name (mulno_reads_this_projects_sessions/v1), which is stored with who agreed and when:

Mulno's servers will read this project's sessions — video, audio, events, notes and transcripts — to index them, show them in the browser and answer AI questions. The files stay in your bucket.
  • What we may then do: sign browser reads, index the session files (events, positions, notes, the Catalog, transcripts), answer remote MCP questions from them, and decode frames for the agent tools. The statement also covers hosted AI with frames, behind your admin's own switch; hosted AI isn't built.
  • What it makes us: the processor of what we derive, as for Mulno Cloud. The DPA applies to it.
  • The key must be read-only. Before it is stored, a list under incoming/ has to work and a test write and a test delete both have to be refused. It is kept in a separate Worker with no public route of its own; every read of your bucket through it is written to your workspace's audit log.
  • Turn it off and the acknowledgement is cleared and what we derived from the project is deleted. Changing the project's bucket clears the acknowledgement too, so you agree again for the new bucket. Turning it on again asks again.
  • Without it a Private project's index holds no content: the api answers empty, never a refusal, for a project whose lent key is off.

There is no screen for the key itself yet

The project's Storage screen has the switch and the statement. Handing over the read-only key is an api call (PUT …/projects/<id>/lent-key/bucket), and remote MCP note-writing is refused for a Private project: its notes are written with the reviewer's own key, straight to your bucket.

7. What Mulno holds for a Private project

Held: ids (workspace, project, branch, build, campaign, session), states, times, byte counts, which streams were uploaded, capture-health numbers, and the names people type (project, branch, build version, campaign). Where you type one, Studio says "Mulno can see this name. Use a code name for work under NDA."

Never held without the lent key: footage, events, positions, notes, the Catalog, change notes (they sit next to the build, in your bucket), build bytes, or any key that can read your sessions. The R2 exception above is a key that could read them and that we use only to sign uploads. The full per-mode list is What we store.