DomiDo is a platform that handles personal accounts, designs that belong to their authors, payment cards, designer payouts, sensitive support conversations, and a steady stream of uploaded files and AI-generated artefacts. The security model has to keep all of that safe from accidental disclosure, deliberate attack, and the everyday mistakes of a small operating team. This page describes how the platform achieves that. It walks through identity and sessions first, then through what each role is allowed to touch, then through payment and payout safety, then through every other surface where attacker pressure or accidental leakage could hurt customers, designers, or the business itself. The controls described here are the active controls for the platform as it stands today: Google, Apple, and Facebook for OAuth sign-in; no card processor at all during the Phase A interest-reservation period; Stripe SetupIntent for the invited no-capture pre-orders that open in Phase A.5; Stripe payment capture for Phase B orders; and Stripe Connect for designer payout readiness in Phase A.5 and payout release in Phase B.
Identity on DomiDo is always borrowed from a federated provider. The only providers exposed to users are Google, Apple, and Facebook; the platform does not offer email-and-password sign-in, third-party social providers beyond those three, or self-hosted identity. When a user completes an OAuth flow, the backend defends the round trip against the standard family of attacks — cross-site request forgery on the redirect, replay of an old authorisation response, and reuse of an OAuth identity that already belongs to a different DomiDo account. Sessions, once created, can expire, refresh, and sign out without the frontend ever holding the upstream provider's credential; the provider token stays server-side and the client receives only a server-issued session or access credential. The exact shape of that credential is intentionally unspecified at the requirements layer, so the implementation is free to evolve cookies, bearer tokens, or any equivalent without the contract changing.
Account linking is an explicit operation. The platform refuses to attach one OAuth identity to multiple unrelated DomiDo accounts: if a Google identity is already linked to one account it cannot be silently attached to another, and any linking attempt that would merge separate users requires the explicit identity-linking flow rather than slipping through a sign-in. Authenticated state is also kept out of caches that public users would otherwise share — the authentication modal, the account modal, the checkout modal, the designer modal, the admin-help modal, and the support modal all set response headers and avoid presentation patterns that would leak personalised content into a shared cache.
Browser sessions on Web and Progressive Web App use the cookie-authenticated path. For any unsafe HTTP method on that path (anything other than a read), the request must carry server-issued CSRF state or pass an equivalent same-site origin control; the bare cookie is not enough. Native iOS and Android clients, when those targets open, can authenticate with bearer credentials over the same authorisation layer, and the CSRF requirement is structured so the move to native does not require a separate authentication stack.
Every API path is either explicitly public in the canonical API contract or it requires authentication plus resource authorisation. Public browsing of the gallery, public listing pages, public designer profiles, published help content, and published legal pages are openly readable; every other surface — accounts, draft creation, checkout, designer dashboards, support, admin, payout, orders, and audit — is private, and every private request is checked against both who the actor is and what resource they are reaching for.
DomiDo authorisation is resource-based. Owning a session does not grant power over the platform; it grants power over the resources that the session's actor owns or has been granted access to. A normal user can read and mutate only what is theirs: their own design drafts, their interest reservations, their cart and saved carts, their checkout state, their Phase A.5 pre-orders, their orders and order notifications, their profile and avatar, their saved addresses, their saved Stripe payment references, their privacy and notification and security and session settings, their account-lifecycle requests, the trust-challenge records that belong to them, the Help comments they are authorised to create or report, and the support conversations they have opened. Nothing else.
A designer carries the same user-scope, plus mutation rights over the designer-side surfaces that belong to their designer profile: their listings and the editor state behind those listings, their review and question replies, the FAQ candidates they propose, the designer message threads they participate in, their claim actions, their Promo Studio projects and assets, their payout settings and statements and messages and action items, the moderation rerun requests and copy-change requests they raise, their ad-boost settings, and the Phase A disabled-remix acknowledgements that record their stance on a feature that has not yet opened.
A public user — anyone who has not signed in at all — has read access to public listing data, public designer pages, published help content, public legal pages, and the public gallery. Private listings are visible only to their owner and to authorised staff; unlisted listings require an explicit link or an authorised access decision. Order actions validate that the acting user is the order's owner or holds an authorised support or trust role; the same ownership check guards interest cancellation, pre-order cancellation, payment retry, order cancellation, return, return-fee assessment, replacement, dispute, refund, address change, and delivery preference. Payout, tax, payout schedule, payout readiness, payout request, failed payout retry, reserve-and-dispute, and tax-statement export actions all require designer ownership of the relevant payout account, and where the risk warrants it (changing a payout destination, raising a below-minimum payout, modifying tax handling) the platform demands a stronger reauthentication or equivalent verification before the change goes through.
Two surfaces sit outside ordinary user scope. Workshop and Help content can be mutated only by administrators — no designer, no buyer, no support agent — and that boundary is enforced at the route level rather than left to convention. Trust restrictions, including the blacklist and any user-level restriction, can be created, updated, removed, or internally reviewed only by authorised support and trust roles; the user the restriction applies to can still see and act on the challenge-and-dispute routes that exist for their own restriction, but they cannot edit the restriction itself.
DomiDo does not store raw card numbers, full security codes, or raw payment credentials. Every checkout payment, every new-card capture, and every card verification is handled by Stripe; the only payment data the platform keeps locally is the masked Stripe reference the user interface needs in order to recognise the card again — brand and display label, the last four digits when Stripe provides them, the expiry label, the verification state, and the provider display. Those saved references are deletable from account settings, and they reveal nothing beyond the masked fields just listed. The frontend does not advertise or implement non-Stripe payment providers; the accepted-card copy users see is derived from the card types Stripe supports in the user's jurisdiction, not from a hard-coded list baked into the application.
The phase boundary is enforced at the security layer, not just at the product layer. During Phase A — the public interest beta — no card data is collected for interest reservations, no Stripe customer or payment object is created, no pre-order or charge or receipt or invoice or shipment is generated, and no payout is released. When Phase A.5 opens, an invited eligible reservation can be moved into a checkout that uses Stripe SetupIntent (or an equivalent Stripe card-verification semantic) to verify the card and save the reference; that verification on its own never creates a charge, never produces a receipt or invoice, never makes a shipment promise, and never releases a payout. Phase B captures funds through PaymentIntent against the verified reference and only then issues receipts, invoices, and the corresponding payout-release events.
Designer payouts are kept symmetric to that posture. Stripe Connect onboarding preserves the separation between the routing details, account details, and tax identifier data that live inside Stripe and the local payout settings DomiDo stores; routing and bank information are never copied into the DomiDo database. Payout option changes, payout-on-request actions, below-minimum payout requests, and payout-readiness changes all require explicit authorisation, an explicit confirmation step, and audit logging that records who made the change. Phase A.5 can collect a designer's payout readiness and surface a projected royalty preview, but payout release stays blocked until Phase B captures funds, delivery becomes eligible, the reserve and dispute state allow it, and the KYC and Connect requirements are satisfied. Statement exports, payout balances, payout transfers, reserve ledgers, dispute records, and Stripe Connect requirements are accessible only to the owning designer and to authorised finance or support roles; nothing about a designer's earnings, reserves, or disputes is visible across designer boundaries. When a payout fails and remediation is needed, the flow uses a Stripe-managed or Stripe-referenced handoff rather than exposing bank-account secrets, raw routing details, or sensitive provider payloads to the frontend.
The upload surface is where untrusted user content meets the platform's processing pipeline, so every upload passes through a layered set of checks before the platform spends any expensive work on it. Model and source uploads are restricted to GLB, GLTF, OBJ, and STL files no larger than one hundred megabytes; uploads outside that envelope are rejected before they are queued. On the way in, the backend normalises the filename, validates the declared format, checks the content type or signature wherever that is practical for the format, enforces the size limit before any downstream processing is allocated, and rejects path-traversal, null-byte, and executable-payload attempts. Once a file has been accepted, it is scanned or otherwise validated before it is used for previews, projection generation, model generation, or public listing media. Raster and reference images are normalised in a way that strips executable payloads and unsafe metadata rather than preserving them. Model and source files are parsed only inside bounded worker contexts that enforce mesh-and-voxel complexity limits, so a maliciously crafted file cannot exhaust workers or memory before it is rejected.
The visibility envelope is just as strict as the validation envelope. Private draft media, projection outputs, model outputs, block-kit payloads, model feedback, Promo Studio scenes and assets, and assembly media all remain private to their owner until the owner explicitly publishes the relevant asset to a listing; the only exceptions are authorised backend analysis of model feedback and authorised generation troubleshooting, both of which leave audit traces. Published listing media exposes only the assets that are intended for public viewing — nothing leaks across from the same listing's private surfaces. Promo Studio generated media is treated as a private asset of the listing owner until the listing's gallery state moves it into public listing media; custom Promo Studio environment uploads and the assets generated from them pass through the same media-safety checks that any other listing media has to clear.
When generation jobs fail, the error messages surfaced to users are sanitised so they do not expose internal secrets, provider credentials, implementation paths, or any prompt or provider payload that the user is not entitled to see. The same minimisation applies to translation work: the LLM translation provider receives only the user-generated text plus the minimal translation context required for quality and safety. It never receives raw payment data, credentials, full addresses, tax identifiers, OAuth profile data, private media URLs, or unrelated account context, regardless of what data happens to be available on the calling resource.
Interest, checkout, and pre-order actions are tamper-resistant because the server, not the client, is the source of truth for everything that matters at money time. Listing eligibility, owner attribution, prices, delivery quotes, jurisdictional Value-Added Tax, discount eligibility, the supported-jurisdiction state, the expected charge totals, and the Phase A, Phase A.5, and Phase B gate state are all recalculated server-side at the moment the action runs; the client cannot raise its own discount, change the owner attribution, or shift a charge into a jurisdiction it does not support. Every order-life-cycle action — interest cancellation, pre-order cancellation, payment retry, order cancellation, return request, return-fee assessment, replacement, dispute, refund, address change, delivery preference — validates that the actor owns the reservation, pre-order, or order and that the resource is in a state that allows the requested transition.
Receipts and invoices are guarded the same way. Receipt and invoice URLs require authorisation, are not guessable, and exist only for resources that legitimately have one — Phase A interest reservations and Phase A.5 no-capture pre-orders do not expose receipt or invoice URLs because no charge has occurred. Support conversations tied to orders redact payment credentials and sensitive payout information from the visible thread, so a support agent reading an order's history does not incidentally learn the customer's card details or the designer's payout posture. Refund, return-fee, and return-state data preserve the royalty and statement adjustments that the designer needs to see while still keeping the buyer's private data out of the designer-facing surface.
The build companion — the post-delivery surface that walks a customer through assembly — is a privacy-sensitive area, because it stores payloads, progress, stuck-help interactions, finished-build photos, profile-sharing settings, designer feedback, and build-experience ratings. All of those are visible only to the buyer, to the designer feedback views that have been explicitly permitted, and to authorised support roles. Offline-ready build-companion data does not expose other orders, private account data, raw payment data, payout data, trust-restriction data, or unrelated private media; the offline cache is bounded to the order at hand.
Trust restrictions sit slightly outside the ordinary access model. Blacklist and restriction details are access-limited, the system avoids storing unnecessary abusive content alongside the restriction, and the affected user retains a defined challenge-and-dispute path. Interest reservations, saved carts, checkout drafts, pre-order notifications, order update notifications, shipment tracking, ETA deltas, delivery preferences, order messages, and order action attachments are visible only to the owning user and to authorised support, finance, or trust roles — every cross-user view of those surfaces requires an explicit role.
Designer-facing actions carry their own validation layer because designers can change commercial terms, surface moderated content, and consume Promo Studio budget. Royalty-percent and listed-price changes validate designer ownership of the listing and clamp the royalty value to the supported range of zero to twenty-five percent; royalty values preserve the Value-Added-Tax-inclusion metadata they need to be accurate, and they do not expose buyer-private tax or payment data to the designer side. Ad-boost budget changes validate designer ownership and stay inside the configured bounds. The listing editor's internal aggregates — moderation evidence, similarity matches, owner analytics, copy-change requests, and moderation rerun jobs — validate listing ownership or an authorised staff workflow before any disclosure or mutation; a designer cannot read the moderation evidence behind someone else's listing.
Phase A remix is a feature that is not yet active. Remix actions in Phase A validate the feature state and return a typed disabled response rather than partially creating remix, split, or payout records that would have to be cleaned up later. Designer-to-designer messaging prevents one designer from reading private messages that are not addressed to them, and staff-only listing notes — internal annotations that support agents leave on a listing — are stripped from public listing responses so they never leak through the customer-facing API.
Promo Studio scene, job, asset, download, and add-to-listing actions all validate listing ownership and audit any authorisation failure that occurs while attempting them; custom environment uploads and generated assets pass the same media-safety checks that apply to any other listing media before they can be published. Designer dashboards that show attributed pre-orders, sales, disputes, reserves, payouts, and tax-statement views do not expose buyer-private account details beyond what is genuinely required for royalty or order context. Review replies, question-and-answer replies, FAQ candidates, designer message reports, and claim actions validate listing ownership or run inside an authorised support workflow before they disclose or mutate anything; designer message and claim attachments are access-controlled by thread or claim participation and are not exposed through the public listing APIs.
The Workshop and Help surfaces are an editorial product, not a user-generated one, so the platform restricts content mutation to administrators. Only administrators may create, update, publish, unpublish, archive, delete, reorder, or trigger Docusaurus publication for Workshop and Help content. The Docusaurus output that lands publicly is readable to anyone, but the draft and unpublished source content, plus any unpublished publication artefacts, are visible only to administrators and authorised staff. Every admin content change is versioned and audited, and any non-admin mutation attempt is rejected and audited rather than silently dropped.
The reader-side discussion controls — Help comments, replies, likes, and reports — validate the actor and session state on every action, sanitise authored text, apply rate limits and moderation where the configuration calls for it, and preserve the boundary that only administrators may mutate articles, topics, kinds, or trigger Docusaurus publication. Translated variants of Help comments, support conversations, designer messages, reviews, questions, and reports inherit the same authorisation, moderation, retention, deletion, and public-or-private visibility state as the source text; a translation never makes a private comment public, and the deletion of a source removes its translations.
Cookie preferences are persisted with the user or session identity, the version of the preference contract that was accepted, the categories the user opted into, and a timestamp. The privacy, data-request, terms, accessibility, and sitemap surfaces are publicly reachable so that a user does not have to be signed in to act on their rights. Account deletion, export, and other privacy requests all require authenticated ownership and preserve a visible status history so the user can see where their request stands and what has been completed.
Logs are written defensively. Operational logs redact OAuth secrets, raw card data, payment-provider credentials, payout-account secrets, and private media URLs; full tax identifiers are redacted unless they are required for a specific authorised operation, and trust-restriction details are kept out of logs except where they are strictly needed. Translation requests and responses against the external LLM provider are treated as data-processing events: they are provider-audited, redacted in operational logs, subject to the configured retention window, and explicitly excluded from training or any other secondary use unless an explicit approved data-processing agreement and a product decision permit it.
The API is structured so that abusive or accidental traffic produces typed responses rather than ambiguous failures. The platform defines standard error envelopes for the 401, 403, 409, 413, 422, 429, and 503 codes, and rate-limit responses include a retry hint such as Retry-After so well-behaved clients back off without re-attacking. Rate and quota limits cover every surface that an attacker or a malfunctioning client could amplify: public reads, search, newsletter signup, OAuth start and complete, upload registration, checkout SetupIntent and session creation, job creation, LLM translation and text assistance, Help comments and reports, support conversation creation, and admin publication are all rate-limited or quota-limited by a combination of IP, actor, session, resource, and operation. Search, pagination, free-text prompts, support and comment bodies, and non-upload JSON requests are bounded by maximum length or maximum page size before any database or provider work is allocated, so an oversized request never reaches the expensive stage.
Long-running jobs — model generation, voxelisation, block-kit generation, Promo Studio, LLM translation, and publication — enforce configured per-actor and per-resource concurrency limits. Requests that exceed those limits return a typed retryable or quota error rather than enqueueing duplicate work. Calls to external AI, LLM, and Stripe providers run inside configured timeouts, retry budgets, idempotency keys, and circuit-breaker or budget-stop behaviour, so provider slowness or attacker-driven volume cannot exhaust worker capacity or spend. Externally triggered mutations that create commitments, jobs, provider calls, uploads, support or help records, publication artefacts, payout changes, order actions, or payment state all accept or derive an idempotency key; a replay with the same request hash returns the stored response, while a replay with a different hash returns an idempotency-conflict error rather than producing duplicate effects.
Stripe webhooks are handled with the same care. Each webhook verifies the raw request body signature, rejects stale or replayed signatures inside the provider's tolerance window, persists a safe webhook record before any business-state mutation runs, and ignores duplicate provider event IDs so a redelivery cannot double-apply. Across all of these controls, rate-limit hits, idempotency conflicts, invalid signatures, CSRF failures, upload validation failures, job-quota rejections, and provider circuit-breaker openings are observable signals; the security-sensitive cases are audited, but the audit records do not include raw secrets or private payloads.
Every security-significant event flows into the same audit event system described on the Audit and logging page. Each event carries the actor, the resource, the action, the result, the timestamp, the request correlation identifier, and the source surface — enough information to reconstruct what happened without storing the underlying secrets that the action touched. Security-related logs and the audit records do not include raw secrets or raw card data; the audit shows that a payment reference was modified, but it never shows the card number that was involved.