Every remote playtest passes a little metadata through our broker (codes, session state, upload authorisation), whatever bucket the footage itself goes to. What differs by mode is where the recording goes, and whether our servers can read it.
Private bucket (S3 or R2)
Your own Amazon S3 or Cloudflare R2 bucket, set up and verified project by project.
- We never receive the recording. Our servers sign one-object, time-limited upload URLs, and the recording goes from the tester's PC straight to your bucket. The only requests our servers make to your bucket themselves are the setup check's (next point): it writes a 32-byte probe object under
incoming/<campaign>/, tries to overwrite it, and tries to read it back and list that prefix — to prove the key can't. On R2 those last two succeed (below), and our servers discard the answer unread; nothing from it is logged or stored. - The bucket policy we require, and verify before your first upload: the signed key can write, and cannot overwrite (a conditional write, or an R2 lock rule on
incoming/); on R2 specifically we also verify the lock rule is present, since R2 has no write-only scope. - The one documented exception (R2 only): R2's narrowest object permission also lists and reads the whole bucket. We hold your key, sealed (AES-256-GCM, outside the database) — you accept this by name before the first upload: "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." This acceptance is stored with who sent it and when, and cleared by a bucket change. - What our database holds for a Private project: ids (workspace, project, branch, build, campaign, session), states, times, byte counts, the streams uploaded, capture-health numbers, and the names you type (project, branch, build version, campaign name) — the UI tells you when a name you're typing is one we can see, and suggests a code name for anything under NDA.
- Never held: footage, events, positions, notes, the Catalog, change notes (those live next to the build in your own bucket), build bytes, or a key we use to read your sessions — unless you turn on the lent-key opt-in below. (The R2 exception above is a key that *could* read them, which we use only to sign uploads. For the same reason, don't make an R2 builds bucket the same bucket as your sessions: its key needs read access, so we would then hold a key that can read your sessions without the lent-key opt-in. Our servers refuse that setup, whichever of the two buckets you set first.)
A lent read key (Private bucket, opt-in per project)
One switch, all-or-nothing, asked with a named statement (mulno_reads_this_projects_sessions/v1): "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."
- With the key lent, we may sign browser reads, index your session files (events, positions, notes, the Catalog, transcripts), answer remote MCP questions from them, decode frames, and run hosted AI with frames (still behind an admin's own switch, see the AI statement).
- Without it, nothing changes from the Private-bucket row above.
- We are the processor of what we derive while the key is lent, the same as for Mulno Cloud storage — the DPA applies to it.
- Turning it off clears the acknowledgement and deletes what we derived from your project's side. Changing the project's bucket clears it too, so it must be given again for the new bucket.
- Built today: indexing your sessions under the key, and remote MCP reads of them (remote MCP isn't reachable at a public address yet). The key is kept in a separate service with no public address, and every read made under it is written to your workspace's audit log. A Private project's session index holds no content unless its lent key is turned on. Frames and hosted AI aren't built.
Mulno Cloud
Our own Cloudflare R2, one sessions bucket and one builds bucket per region (mulno-sessions-<region>, mulno-builds-<region>) — never shared with a Private project's own bucket.
- A new project's default. A Mulno Cloud project keeps its builds and its testers' recordings here: a tester's uploader writes a session straight into the region's sessions bucket, with one-object upload links our api signs (the broker that checks the tester's code never holds a key that can read the bucket).
- We are the processor of everything stored there (the DPA applies in full).
- Shared buckets, separate keys. A region's sessions bucket and builds bucket hold every Mulno Cloud project in that region, kept apart by object key (a build's objects under its project's id); every signed URL is for one object, and only a member of the project gets one.
- What's held: the recording itself (gameplay video, game audio, and whichever of mic, webcam and voice notes a tester allowed), events, positions, notes, the Catalog, transcripts, and the same project/campaign metadata as a Private project.
- Access: members get short-lived signed reads; nothing is public.
- Storage is counted against your plan's quota (see retention) — each build or session object is charged when its upload is signed, at the size signed (a finished session's charge then becomes the size that actually landed), and signing is rate-limited. A Private project is never charged.
- How long a session is kept: the plan's number on the retention page, counted from when the session finished uploading (or was last restored). Then it is hidden from everyone (out of the session list and search, not served), and its files are deleted for good 30 days later; within those 30 days an admin can still restore or export it through our api (a session a tester's request is hiding can't be exported until an admin restores it). When a plan change shortens the number, nothing is deleted for 30 days and the workspace's owners are told by e-mail (Terms, section 12); that hold doesn't delay the deletion of a session a tester asked to have deleted. Every step is written to the workspace's audit log.
- Deleting one session: an owner or admin deletes a session from its review in the web app. By default it is hidden from everyone at once and taken out of search and the index, and its recording and notes are deleted for good 30 days later; until then it can be restored. Or choose right away: the recording, its notes and its index rows are deleted at once, the storage is given back, and nothing can bring it back. When a tester asks on our deletion page for a session to be deleted, it is hidden at once and its recording, notes and index rows are deleted for good 30 days after the request, together with the broker's records of it, unless an owner or admin restores it first (do that only if you are sure the request wasn't the tester's own, or the law lets you keep the recording: anyone who has seen a session ID can make one); the studio can still delete it right away from its campaign. If the tester asks again after a restore, the session isn't hidden again: the owners are e-mailed once and the request is listed again with Delete now and Keep. Each deletion is written to the audit log.
- Exporting: an admin can list a Mulno Cloud project's sessions and download each one's files through short-lived links our api signs; each export is written to the audit log. Studio has no button for it yet.
- Deleting a build: an admin can delete a build that no branch has live; its chunks that no other build uses, and its manifest, are deleted from Mulno Cloud's bucket and the room is given back.
Every mode: the broker's own records
Whatever bucket the recording goes to, a remote playtest's invite code, session and upload state pass through our broker, which keeps, the same in every mode:
| Held | Retention |
|---|---|
| Invite codes, as a one-way hash, plus a 4-character hint | 90 days after the campaign closes, expires or is killed |
| A session's state (including whether the tester withdrew), the yes/no age answer, times, byte counts, which files it uploaded | 90 days after the campaign closes, expires or is killed |
| An NDA acceptance: the NDA's version, a SHA-256 of its text and when, stored against the invite code's hash (never the text, never a name) | 90 days after the campaign closes, expires or is killed |
| Which version of the studio's privacy notice for its testers the Launcher showed a tester: the notice's version, a SHA-256 of its text and when, stored against the session (never the text, never a name; it records that the notice was shown, not that anyone agreed to it) | With the session's row, above: 90 days after the campaign closes, expires or is killed |
| Upload tickets, as a one-way hash | Valid until the session closes, at most 72 hours; the hash itself is deleted with the session's row (above) |
| A capture-health record: recorder/encoder versions, Windows build, GPU vendor (a closed set), capture and upload numbers | 90 days |
| A keyed, truncated hash of the tester's IP address, for rate limiting | About an hour |
| When a tester asked on our deletion page for the session to be deleted (shown to the studio, which can restore a hidden Mulno Cloud session within 30 days; for one in the studio's own storage, the studio decides) | With the session's row, above, or until the studio deletes the session; for a Mulno Cloud session the broker's record of it goes when Mulno's copy of the recording does, 30 days after the request |
| Mulno's own traffic log (Thailand's Computer-Related Offences Act s.26): per request, the time, method, route pattern, status and the readable source IP address. It holds no session, campaign or studio id, so it can't be joined to a recording; Mulno keeps it as its own legal obligation, not for the studio | 90 days |
| The campaign itself: its name, settings and limits — filed under the workspace's own opaque id, never the name your members typed | While the workspace exists; when it is closed, deleted with everything above within 30 days (below) |
| The studio's own NDA text, when the campaign has one — its plain wording, not a hash (unlike the acceptance record above, which keeps only a SHA-256 of it) | Same as the campaign itself, above |
| The studio's own privacy notice for its testers, when the campaign has one — its plain wording and version, which the Launcher shows before a build downloads. It is the studio's notice (the studio is the controller of its testers' data); we keep only the current text, a SHA-256 of it and when it was saved | Same as the campaign itself, above |
| The broker's audit log of campaign actions (codes minted or revoked, campaigns killed, bucket changes, redemptions and uploads, without a person's name) | 1 year, also after the workspace is closed |
A campaign with no end date never starts its 90-day clock: set an expiry, or close it when the playtest ends.
Separately, each workspace has its own audit log in its own region (sign-ins, member changes, storage and lent-key changes), kept a year. Every read our servers make of a Private project's recordings under a lent key is written there too (D114). Reads of a Mulno Cloud project's own storage aren't logged one by one.
Never held, in any mode: a tester's name, e-mail address or birth date, or their IP address in readable form anywhere but the traffic log above. Deleted rows can remain in Cloudflare's point-in-time database backups for up to 30 more days, which cannot be turned off.
When a workspace is closed
An owner closes a workspace in its settings; it can't be closed while a subscription still bills. At once, every member, invitation and AI connection is removed, its campaigns are stopped, and testers can no longer upload to it or download its builds. Then:
| When | What we delete |
|---|---|
| From day 14 after the closing (a job that runs every hour) | Every Mulno Cloud recording and build of its projects, each project's session index (including what a lent key let us derive), and every row our api holds for the workspace: projects, builds, branches, campaigns, sessions, storage and billing records, its legal name and its Terms record |
| Right after that, retried until done, within 30 days of the closing | Everything our broker holds for it: campaigns and their NDA text, invite-code hashes, sessions with their upload records and ticket hashes, NDA acceptances, the records of which privacy notice was shown, capture-health records, sealed bucket credentials and the workspace's broker tokens |
What stays, and why:
| Kept | Until |
|---|---|
| The workspace's audit log, and the broker's audit log of its campaigns (fixed fields: the action, the id of what it touched, the outcome, the time). The workspace's own entries name the member who acted until we delete the workspace's rows; then every member's address, account id and IP-address hash is taken off them | Each entry's own year ends |
| A record at the broker that the workspace existed and was deleted: its opaque id, plan and dates, marked suspended, with the name "deleted" and no contact, so the id can never be given access again | As long as the broker runs |
| A record that the closing was asked for and done: the workspace's id, the id of the account that asked, the times | Up to 3 years |
| The traffic log (it holds no workspace, campaign or session id) | 90 days from each request |
| Rate-limit counters (keyed hashes) | About an hour |
| Cloudflare's point-in-time database backups | Up to 30 more days |
Not ours to delete: the recordings in your own Private bucket. We delete our sealed copy of its key, never its objects.
One caveat that applies to region, in every mode
A workspace can choose where its own rows live (Asia-Pacific, the EU's guaranteed jurisdiction, or the US). Everything in the broker table above — the campaign, invite-code hashes, sessions and their upload records, NDA acceptances, privacy-notice records, capture-health records and the broker's audit log — still lives in one Asia-Pacific database today (a Singapore location preference, not a guarantee), whatever the workspace's region, until the broker itself becomes regional. The broker never learns a workspace's typed name either way: it files a campaign under the workspace's own opaque id, not the name your members chose for it.
An EU workspace cannot create a campaign at all today. Rather than run one whose rows would sit outside the EU regardless of your choice, we refuse the request outright, in plain words, until the broker itself is regional. Every other feature (projects, builds, branches, members, storage) is unaffected — only creating a *new* campaign is blocked for an EU workspace.
Where Mulno is a controller, not your processor
- Statistics from capture-health records. We combine the capture-health records of every studio into statistics about recorders, encoders, graphics hardware and uploads, to find and fix faults in our recording software. That purpose is ours, not yours, so for that step we are a controller in our own right (GDPR Art. 28(10)), relying on our legitimate interest in recording software that works (GDPR Art. 6(1)(f); PDPA s.24(5)). The statistics carry no session id, campaign id, studio or anything else that points to a tester; the records they come from are deleted after 90 days as above. A tester who objects can write to privacy@mulno.com with their session ID, and we leave that record out. A studio can also turn capture-health off.
- Your own people. The e-mail addresses your members sign in with, the audit log's record of who did what, and your billing contact: Mulno decides how these are used, as a controller, under its own privacy notice, not under the DPA.
- Download records. When a signed-in account asks for download links for Studio, the Unity package or the Claude Code plugin (the Downloads page makes a link for each file it lists), or its signed-in Studio checks for updates (from Studio 0.4.2 the check carries a token we can link to the account), we keep a record for each link: the account id, which file the link was made for (app, version, file name) or, for an update check, the platform, a random link id, and when the link was made and expires. No IP address and no e-mail address. We use it only to trace a download link that was passed on or misused, or an update-check token copied off a PC, back to the account it was made for (a link opens one file for 15 minutes), not to see how often you use Mulno. The request that makes a link is also in our request log and traffic log; the file itself and the update check are answered by updates.mulno.com, which keeps no log of the request. Download records are deleted after 90 days and update-check records after 14, and with the account if it is erased first. It is Mulno's own record, not a studio's data; the privacy notice has the details.
- The beta waitlist. What someone types on the waitlist form on our website (an e-mail address and, if given, a name, studio, what they make, engine, team size and a message) is Mulno's own record of who asked to join the beta, not a studio's data. The privacy notice says what is kept and for how long.
What changed on 2026-10-18
- Where Mulno is a controller: download records for gated downloads and Studio's update check, under the privacy notice.
What changed on 2026-10-08
- Where Mulno is a controller: the beta waitlist, under the privacy notice.
What changed on 2026-10-04
- Mulno Cloud (a new project's default) holds testers' recordings as well as builds: how a session is charged, how long it is kept, the 30-day hold after a plan change, deleting one session (30 days to undo, or right away; a tester's request hides it at once and deletes it after 30 days unless the studio restores it), and exporting.
- When a workspace is closed: a new section with what is deleted, when, and what stays (the audit logs, the workspace's without its members' details, the broker's record that the workspace existed, the record of the closing, the traffic log, backups).
- The broker's campaign rows now go when the workspace is closed, not "when the agreement ends".