Security and privacy¶
Trust boundaries¶
authoritative browser state
|
v
full bounded context -> public-only projection -> optional provider
|
v
event append <- domain projection <- schema parser <- untrusted output
|
v
state projection -> authored world, HUD, audio, and inert text/image display
Prompt instructions are not a security boundary. Exact schemas, public projection, revision echoes, finite-data checks, domain invariants, source authority, expected-head append, inert rendering, and lifecycle cancellation are the enforceable boundaries.
Secrets¶
.envand.env.*are ignored by Git and Docker context.- Browser code does not read
VITE_DEEPSEEK_API_KEYorVITE_MINIMAX_API_KEY. - Paid launchers read the selected key in Node and do not forward provider keys to their Vite child.
- Model Setup keys are user input and remain memory-only unless Remember setup is explicitly selected after its warning.
- Controller and UI snapshots expose only provider/model choices and key-configured booleans.
- Logs, bounded diagnostics, screenshots, prompts, simulation events, and docs exclude raw keys.
- Clearing setup removes the versioned provider record and generated in-memory image data when storage permits it; failure reports manual clear-site-data guidance.
Provider setup and campaign persistence are separate records. The simulation event envelope and event-log serializer recursively reject fields named like API keys, authorization, bearer data, tokens, or secrets. Remembering a provider key does not make it part of a run snapshot or event replay.
Direct browser BYOK remains vulnerable to same-origin script compromise, malicious extensions, shared-device access, and localStorage inspection. The UI must state this before persistence is enabled.
Browser speech¶
Speech is optional progressive enhancement supplied by the user agent. The microphone starts only after a MIC action, but browser or operating-system speech recognition may send audio to its own service; that implementation and its privacy terms are outside the application's provider controls. The app does not record or persist audio. A returned transcript is capped at 500 characters and remains a draft until submitted through the normal conversation boundary.
Browser synthesis can read accepted coworker beats aloud and may expose them to nearby people; captions remain available and voice output can be disabled. Closing the conversation, persisted page suspension, or terminal disposal aborts recognition and cancels synthesis. A BFCache entry therefore retains no active microphone session while the page is suspended.
Knowledge privacy¶
The browser records memory and conversation visibility as private, shared,
or public and validates each audience. Internal context builders can use that
information to determine what each participant knows. Remote calls receive a
second projection:
- a foreground fact survives only when it is public to every selected participant;
- a Curator knowledge summary survives only when it is public to the full authored cast;
- private and shared commitments, memories, observed events, and conversation summaries stay in the browser;
- provider setup, storage content, unobserved future state, and generated caption history are not context fields.
Responses are validated against both the public request and the full browser context. A response cannot refer to a private memory ID that was absent from its schema, broaden an audience, add an actor, or apply after its world revision, room, or participant state becomes stale.
Donna's campaign exclusion is enforced in context selection, schemas, projection, reducer invariants, runtime activities, and D&D state. It is not left to prompt wording.
Generated text¶
All provider strings and stored event text are bounded finite data. Conversation beats are time-bounded, actor IDs and targets come from browser enumerations, and state proposals pass transport, schema, knowledge, source, and domain checks. Unknown fields, stale echoes, unknown IDs, absent participants, invalid provenance, impossible time ranges, and unauthorized Donna or campaign material are rejected before append.
The UI renders accepted text with text nodes or textContent. It does not:
- execute generated JavaScript;
- create elements from generated markup;
- set arbitrary A-Frame component strings;
- compile generated shaders or meshes;
- navigate to provider-supplied URLs;
- perform provider-selected network requests;
- interpret actor intents as movement or schedule commands.
Conversation prose can become a bounded record or a source for explicitly projected events, but it cannot directly set geometry, routes, doors, time, employment, or the whole state snapshot.
Generated images¶
Base64 image data is untrusted binary input. Before display, the MiniMax client caps the response body, accepts exactly one item, validates complete Base64 decoding and decoded size, checks static JPEG/PNG structure and exact 512 by 512 dimensions, rejects APNG animation, and waits for native browser decode under a separate deadline. Generated SVG, HTML, arbitrary data types, and provider URLs are not accepted.
The adapter accepts only the versioned fictional Donna prompt. It excludes real-person imitation, real logos, readable brand names, campaign participation, and claims of documentary authenticity. A generated portrait is memory-only and cannot enter the event log, snapshot, artifact system, or build.
Persistence¶
The local event log validates exact versions, run IDs, contiguous sequences, snapshot invariants, finite event shapes, and complete tail replay before use. Compare-and-append detects stale heads, including an independent same-page store that changed localStorage between read and write. Compaction preserves the validated projection while bounding the tail.
Corrupt or unsupported current-run data is removed and replaced with a fresh run. If localStorage cannot be read or written, the simulation uses a memory event store and displays that the run is temporary. There is no cloud sync, server account, or background upload of campaign state.
Request and page lifecycle¶
Foreground text, Curator planning, and image requests use AbortSignal, bounded
deadlines, and request identity checks. Closing a conversation aborts its text
request; closing or replacing image work aborts that request; opening setup
invalidates Curator planning. Late responses are ignored after abort, timeout,
setup change, authoritative revision change, or disposal.
On a persisted pagehide, the living office:
- suspends campaign advancement;
- aborts foreground text, Curator planning, image generation, and speech recognition;
- cancels speech synthesis;
- clears transient beat playback;
- closes setup and artifact surfaces;
- retains validated campaign state for BFCache restoration.
A persisted pageshow resets the frame-time baseline and resumes only when the
tour is active and visible. A non-persisted pagehide disposes the office
runtime, stage, world, audio, requests, animations, listeners, reservations, and
generated in-memory data. No model request is allowed to survive terminal page
disposal.
Test isolation¶
Ordinary browser tests copy only required source and configuration into a
disposable context, mount dependencies read-only, and run a digest-pinned
Playwright image as a non-root user with a read-only root, private shared memory,
dropped capabilities, no-new-privileges, and disabled container networking.
The repository .env, .git, build outputs, and unrelated files are not
mounted; recursive copy filtering rejects nested .env* and .git entries.
Production-composition tests also disable networking and build the tour in
container /tmp. The production bundle must ignore the development-only
?office-test=1 hook. Paid launchers are separate commands that require an
explicit provider and forward only that provider's key. Ambient PAID_*
variables cannot convert ordinary tests or deployment into a paid run.
Content retention¶
Provider retention terms are external to this static application. Model Setup links to provider terms and warns players not to submit confidential data. The deterministic provider is the privacy-preserving default.