Security

Security starts with organization-exclusive VMs.

Spinnery treats customer code, dev URL traffic, and agent activity as scoped product surfaces. Auth0 for identity, GitHub App repository scope, Spinnery authorization, organization-exclusive VMs, SSM SecureString/KMS for workspace secrets, minimal IAM on runtime hosts, signed dev links, optional Tailscale private access, configurable root policy, workspace event history, and awake-only runtime boundaries all stay visible to the team operating the workspace.

Product surface map

Security follows the same product surfaces users touch.

Security copy should map to the same path the buyer sees: login and repo scope, hosted workspace hosts, reviewer links, and runtime inputs. That keeps trust boundaries concrete before architecture details expand.

Login and repo scopeAuth0 + Spinnery roles

Identity is checked before product access.

Auth0 identifies the user, GitHub App scope limits repositories, and Spinnery roles decide who can open shells, share links, or change policy.

Hosted workspace hostsorg-exclusive shell and runtime VMs

Customer code stays inside one organization boundary.

Shell and runtime hosts are assigned to one customer organization, with workspace-level sessions, resource namespaces, and billing events.

Reviewer linksigned app session

Public review stops at the app surface.

Signed dev links cover web, same-origin /api, assets, and realtime traffic while blocked operational surfaces stay named and closed.

Runtime inputsSSM references

Secrets move by reference.

Workspace secrets use SSM SecureString/KMS references and declared runtime inputs instead of appearing in browser links, route metadata, or logs.

Hosted workspace model

Hosted workspaces have two VM surfaces and one public review surface.

The security model should be legible from the first workspace handoff: spin login creates the authenticated product session, spin up opens the hosted shell, the runtime wakes separately for service work, and signed dev links expose only the app surface.

  1. IdentityAuth0 + Spinnery roles

    Auth0 establishes the CLI and dashboard user session. Spinnery then checks organization membership, workspace permissions, signed-link issuance rights, and billing visibility before opening any workspace surface.

  2. Repo contractspinnery.yml

    The workspace shape is committed beside the app: service commands, ports, inputs, dev routes, resources, checks, runtime size, and idle sleep are reviewable before a branch workspace runs.

  3. Shell boundaryone-org shell VM

    spin up opens an organization-exclusive shell VM with Git, tmux, Claude, Codex, logs, and the branch checkout. Another customer organization never shares that host.

  4. Runtime boundaryawake service VM

    Web, /api, workers, checks, and resources wake a runtime VM inside the same organization boundary only while service work needs capacity. Sleeping the runtime stops billing without changing access policy.

  5. Public handoffsigned app session

    A signed dev.spinnery.dev link covers web, same-origin /api, assets, and realtime routes for one service, workspace, organization, and expiration. It never grants SSH, raw logs, secrets, root, private consoles, or runtime admin.

First-release trust

First-release trust contract.

These are the public commitments the first release needs to make plain before a buyer asks for deeper architecture detail.

VM contractone-org-per-VM

No customer organization shares a shell or runtime VM with another customer organization, even when multiple branch workspaces are active.

IdentityAuth0 identifies; Spinnery authorizes

Auth0 establishes the user session and login context. Spinnery checks membership, roles, GitHub App repository scope, workspace permission, signed dev link issuance, root policy, and billing visibility.

Reviewer accesssigned dev.spinnery.dev URLs

Reviewer access is a signed app session for one service, workspace, organization, and expiration. It does not open SSH, logs, secrets, root, private consoles, or runtime admin.

Runtime costawake runtime meter

Sleep stops paid service capacity, but Auth0, workspace event history, signed-link policy, root policy, and workspace metadata stay governed.

Access Policy receipt

One Organization per VM, governed root, and referenced secrets are the launch contract.

Security should be stated in product terms before architecture detail: Organization placement, Project and Workspace permissions, Service and Resource inputs, Shell and Runtime boundaries, signed dev link scope, CLI access, and the Access Policy that governs them.

Organizationone-org-per-VM

Each Shell VM and Runtime VM belongs to one customer Organization. Project and Workspace placement can share only that Organization boundary; cross-customer code, dev traffic, logs, and Service processes never co-tenant.

Access Policyroles + root posture

Spinnery checks the user Organization, the selected Project and Workspace grants, signed dev link issuance, billing visibility, and root policy before opening a product surface.

SecretsSSM references only

Service inputs reference SSM SecureString/KMS values at Runtime wake. Raw secrets do not appear in the Shell, signed dev links, logs, route labels, private consoles, or customer handoffs.

Rootdisabled / break-glass / elevation

Root is governed by Organization policy and recorded through Workspace events. A signed dev link never grants root, SSH, runtime admin, private consoles, logs, or secrets.

Launch receipt

A first workspace should show the trust receipt before anyone shares it.

Security copy should describe the observable receipt a customer sees: identity and repo scope, host placement, signed review access, optional private access, and the fact that sleep changes cost without changing authorization.

Identity and repo scopeAuth0 + GitHub App

The first workspace should show the Auth0 organization, Spinnery role, and GitHub App installation scope before shell access or repo checkout happens.

Host placementone-org-per-VM

The shell and runtime hosts belong to one customer organization. Branch workspaces can share that org boundary, but another customer never lands on the same VM.

Public reviewsigned dev link

The reviewer gets an expiring browser session for web, same-origin /api, assets, and realtime traffic. The signed dev link includes backend /api, while SSH, raw logs, secrets, root, private consoles, and runtime admin stay closed.

Private accessoptional Tailscale

Teams can route shell or private runtime surfaces through an approved customer tailnet without expanding the public signed dev link surface.

Billing statesleep changes cost only

Runtime sleep stops metered service capacity, but Auth0, Spinnery authorization, root policy, signed-link revocation, and workspace event history remain active.

First workspace flow

The first spin up should expose security state before review traffic.

The launch path should make security observable in the same moment a developer or agent opens the workspace: identity is authorized, hosts are placed, root and secret policy are named, and the reviewer link stays unissued until the app surface is ready to share.

spin upsecurity receipt

$spin up acme/scheduler --branch signed-links

Auth0: acme developer authorized by Spinnery

GitHub: acme/scheduler selected repository installation

placement: shell vm org=acme; runtime vm org=acme

root: disabled unless break-glass is approved

secrets: SSM SecureString references only; raw values hidden

signed dev: not issued until spin share

blocked: SSH, logs, secrets, root, private consoles, runtime admin

  1. AuthorizeAuth0 org -> Spinnery role

    Identity becomes product permission.

    The CLI starts from the Auth0 session, then Spinnery checks organization membership, project/workspace role, GitHub App repository scope, and signed-link issuance rights before shell access opens.

  2. Place hostsone-org-per-VM

    Shell and runtime hosts stay inside one organization.

    The shell attaches inside the customer organization boundary. If service work wakes a runtime VM, that runtime stays in the same organization boundary and never co-tenants with another customer.

  3. Apply policyroot disabled / SSM refs

    Root and secrets resolve as policy.

    The first receipt names the root posture and SSM SecureString references. Raw secret values are not printed, logged, or included in dev link metadata.

  4. Share laterspin share

    Reviewer access is issued after readiness.

    A signed dev link can cover web, same-origin /api, assets, and realtime traffic. SSH, logs, secrets, root, private consoles, and runtime admin remain closed.

Responsibility split

The security page should say who owns each boundary.

Production-facing security copy is stronger when it separates product controls from customer decisions and names what a signed dev link will never grant.

Spinnery controls

Organization-exclusive VM placement, signed dev gateway checks, Auth0 session mapping, Spinnery product authorization, runtime sleep state, and platform-secret separation are product controls.

Customer controls

Repository selection, spinnery.yml review, workspace role assignment, root policy choice, Tailscale policy, customer-cloud resource ownership, and cleanup decisions stay with the customer organization.

Shared handoff

Readiness receipts, usage receipts, signed dev link scope, resource modes, and command history are the shared artifacts support, operators, developers, and agents can inspect.

Out of signed links

SSH, raw logs, secrets, private consoles, root elevation, runtime admin, and customer-cloud credentials are never granted by a reviewer dev URL.

Controls

The security model is product-visible.

Operators should see the same boundaries the platform enforces: runtime placement, dev link scope, root policy, private access, and secret injection are explicit settings rather than hidden host assumptions.

VM boundary

Shell and runtime VMs are assigned to one customer organization. Workspaces can share the owning organization boundary, but Spinnery never packs another customer organization onto the same VM.

Authorization

Auth0 proves identity, but Spinnery owns authorization. Organization membership, product roles, workspace access, dev link scope, and root policy are checked before any workspace surface opens.

Secret handling

Platform secrets stay in the control plane and are never copied into shell or runtime hosts. Workspace secrets are referenced through SSM SecureString paths protected by KMS and injected only into declared runtime inputs.

Dev URL boundary

Signed dev links open web, same-origin /api, assets, and WebSockets while SSH, secrets, logs, MinIO console, Mailpit, and runtime admin stay private by default.

Tailscale private access

Teams can route shell or private runtime services through tagged Tailscale devices without turning private operational ports into public dev URLs.

Root policy

Root access is a configurable organization policy: disabled, approved break-glass, or workspace elevation for root-heavy work. Signed dev links never inherit it.

Awake-only runtime

Runtime isolation does not depend on billing state. Sleep stops metered service CPU and memory while organization policy, signed-link state, and workspace event history remain in force.

Minimal IAM

Runtime hosts use least-privilege IAM for declared workspace resources. Broad platform roles and secret-management permissions stay on Spinnery control-plane services.

Identity and authorization

Auth0 authenticates; Spinnery decides what that identity can do.

Spinnery does not outsource authorization to the identity provider. Product checks live in Spinnery so organization membership, workspace role, signed dev link issuance, private access, and root elevation all use the same policy model.

Identity providerAuth0 authentication

Users authenticate through Auth0 for CLI and dashboard sessions. Auth0 establishes who the user is; it does not grant workspace access by itself.

Product authorizationSpinnery-owned roles

Spinnery evaluates organization membership, project role, workspace permission, signed dev link issuance rights, and root policy before allowing an action.

Workspace boundaryservice / workspace / org

Signed dev link sessions and runtime actions carry the target service, workspace, and organization so access stays scoped to the selected branch environment.

Root access

Root access stays governed from request to closeout.

Spinnery does not treat root as a hidden host default. Organizations decide whether root is disabled, break-glass only, or available in an approved workspace elevation mode, then every exception follows a visible approval path.

  1. Default

    Root starts closed

    Root stays disabled unless the organization policy allows break-glass access or a dedicated workspace elevation mode.

  2. Request

    The reason is recorded first

    A developer or agent names the package install, host repair, or debugging task that cannot be expressed in spinnery.yml or a base image update.

  3. Approval

    Elevation is scoped and time-boxed

    An admin approves the workspace, requester, expiration window, and allowed elevation mode before the shell can run root-level work.

  4. Closeout

    The workspace returns to policy

    Spinnery records the requester, approver, workspace, start and end times, then drops the workspace back to its normal root posture.

Secret delivery

Secrets move by reference, not by dev link.

Spinnery separates platform credentials from workspace runtime inputs. SSM SecureString is the handoff point for workspace secrets, while signed browser sessions, route labels, logs, and public dev links never receive raw values.

StoreSSM SecureString

Workspace secret values live in SSM SecureString parameters protected by KMS. Raw values do not appear in dev sessions, route labels, or event rows.

Declarespinnery.yml inputs

Services ask for inputs such as DATABASE_URL or API tokens by name, keeping secret contracts reviewable beside the code without committing values.

InjectRuntime wake

When a workspace runtime wakes, Spinnery resolves the approved references and injects values only into the declared service environment.

RecordMetadata only

Workspace events record who changed secret metadata, which workspace consumed a reference, and when policy changed without copying raw secrets.

Access surfaces

Every access path has an allowlist and a blocked surface.

Signed dev links are intentionally narrower than authenticated workspace access, and private network access can be enabled without turning every service into a public URL.

Signed dev links

Public review stops at the app boundary.

Signed dev links are not a tunnel into the VM. The gateway validates the signed service, workspace, organization, and expiration before it wakes or routes to the app surface.

  • Bind

    A signed dev link binds one service, workspace, organization, and expiration before gateway traffic can reach the runtime.

  • Allow

    Reviewers can use the app surface: web, same-origin /api, assets, and WebSockets for that workspace session.

  • Block

    SSH, secrets, logs, runtime admin, Tailscale private routes, root access, and resource consoles stay outside the signed browser session.

Lifecycle controls

Signed links should be disposable without losing the workspace.

Review access can be granted, rotated, expired, or revoked while the authenticated shell, runtime state, and workspace event history remain separate product surfaces.

  1. Issue

    Only an authorized organization member can create a dev link for an assigned workspace service. The link records issuer, service, workspace, organization, and expiration.

  2. Use

    The gateway validates the signed scope before setting the browser session, then rechecks the session on app, same-origin /api, asset, and WebSocket requests.

  3. Expire or revoke

    Expired and revoked links stop at the gateway, even if the runtime is awake. Revocation does not tear down the workspace shell or delete runtime state.

  4. Record

    Dev link issue, first use, expiration, revocation, and allowlist changes stay visible without logging raw secrets, cookies, or private resource console data.

SurfaceAllowedBlocked
CLI and dashboard

Auth0 organization members with Spinnery product roles

Unknown users, wrong org context, revoked CLI sessions

GitHub App

Selected organizations and repositories in the installation scope

Broad personal tokens and repos outside the GitHub App installation

Signed dev link

Workspace-scoped browser sessions with expiration and service allowlists

SSH, secrets, logs, runtime admin, private consoles

Tailscale private access

Tagged shell/runtime devices joined to the customer tailnet

Public gateway access when the workspace is Tailscale-only

Runtime host

Same-organization workspaces according to placement, root policy, and least-privilege runtime IAM

Cross-customer co-tenancy and broad platform IAM permissions

Runtime and billing

Sleep changes the bill, not the security boundary.

Runtime billing and access control are separate concerns. Sleeping a runtime stops metered service CPU and memory, but it does not expose a dormant workspace, bypass Auth0, widen signed dev link scope, or erase workspace event history.

Shell baseline

Authenticated shell access, Git checkout, tmux sessions, logs, and workspace metadata stay available outside the runtime meter.

Runtime wake

Dev URL traffic, tests, service commands, or agent jobs wake the organization-owned runtime VM for app, API, worker, database, cache, mail, and storage work.

Idle sleep

Auto-sleep stops runtime billing without deleting the workspace or changing who can authenticate to the shell later.

Event history

Workspace security work should leave a timeline.

First-release support needs enough event history to explain who opened a workspace, what woke the runtime, which dev link was used, and which policy changed.

  • Workspace event history records workspace created, shell connected, runtime started, and runtime slept.
  • Signed dev link created, used, expired, or revoked.
  • Signed dev link allowlist changed for web, same-origin /api, assets, or WebSockets.
  • Secret metadata changed without exposing raw values in logs or UI.
  • Tailscale device joined, tagged, disconnected, or cleaned up.
  • Root policy changed or elevated runtime access requested.

Security reporting

Report security issues with enough context to act.

Spinnery security reports should go to a direct product security channel, include the affected boundary, and avoid copying raw secrets or unrelated customer data into the report.

response pathsecurity inbox
Acknowledge
one business day
Triage
severity + owner
Resolve
fix + customer notice
Include scope

Name the organization, workspace, signed dev link host, affected service, and whether the report involves shell, runtime, gateway, or dashboard access.

Include impact

Describe the data or capability at risk, such as cross-organization access, secret exposure, private logs, root elevation, or unsigned dev traffic.

Include evidence

Share timestamps, request IDs, command output, screenshots, and minimal reproduction steps without sending raw secrets or unrelated customer data.