Kinotic
Kinotic OS

Defense in Depth

The layered enforcement strategies applied to every request path in Kinotic OS.

Overview

The platform is built so that no single defect — a buggy query filter, corrupted stored data, a misbehaving client, a replayed token — can widen access on its own. Every request path crosses at least two independent enforcement layers that would each have to fail for an unauthorized action to succeed. This page catalogs those layers; the operational security model (scopes, credentials, OIDC) is on System Security.

Two principles run through all of it:

  • Enforcement is structural, not pattern-based. Authorization decisions read typed values — the participant type, the zone parsed from the CRI — never string patterns a crafted input might slip past.
  • Internal faults are logged, not leaked. When an internal invariant is violated, the full detail (CRIs, ids) goes to the server log for operators, and the caller receives a generic error indistinguishable from a routine failure.

Network Architecture

Kinotic runs three servers, one per plane — system, organization and application — and each has its own public hosts, gateway, SecurityService and session cookie. What a server serves is fixed in its server module, so isolation comes from what code runs behind which host, not from configuration flags.

CALLERS · UNTRUSTED UNTIL A GATEWAY AUTHENTICATES THEMApp usersUIs · API clientsRuntime workloadsAPP_RUNTIMEGitHub webhooksOrg membersportal · CLI · MCP hostsSync workloadsPROJECT_SYNCOperatorssystem consolevm-managersworkload nodespublishes intoapp.<org>.<app>syncs entitydefinitionshostsVmManagerAPP PLANEkinotic-server-appapps-api.kinotic.io and *.apps-apiApplicationSecurityServiceapp users · clients · machines+ its organization's runtimesone application per API host__Host-kinotic-app-sessionORG PLANEkinotic-server-managementapi.kinotic.aiOrganizationSecurityServiceorg users · clients · machines, except runtimeslogin · signup · invites · OAuth · MCPCLI device grant · GitHub webhook (HMAC)__Host-kinotic-management-sessionSYSTEM PLANEkinotic-server-systemsystem-api.kinotic.aiSystemSecurityServiceoperators · clients · vm-managerslogin · OAuth · MCPdeployments · cluster singletons__Host-kinotic-system-sessionhosts app-apiand app.<org>.<app>hosts management-apireaches system-api, app-apihosts system-apiand management-apiONE IGNITE CLUSTER — EACH SERVER HOSTS ONLY ITS OWN ZONESSHARED DATASCOPED ACCESS — SYSTEM · ORG · APPevery query scoped to the calling participantElasticsearchos-data · entity indicesLoki logsworkload logsKINOTIC OS · PRIVATE NETWORK — ONLY THE GATEWAYS LISTEN PUBLICLY, ON :443

Reading the diagram: solid lines are network paths, and each ends at the gateway of the server whose host the caller named; amber callers are processes on the workload nodes; dotted lines are data access, which every server makes through the same scoped access, System, Org, or App; dashed enclosures are the planes. The three servers join one Ignite cluster, and each hosts only its own zones: a call into another plane's zone crosses the cluster to the server that hosts it, as the management server's calls into system-api and app-api do.

The layers, from the outside in:

  1. Network — each server is public only at its own HTTPS hosts, on :443; the cluster's own ports stay on the private network.
  2. Authentication — each server's SecurityService admits only its own participant types, and a credential of any other type fails as invalid. A session cookie authenticates only on the host that issued it.
  3. Authorization — zone rules, narrowed to the zones the server hosts and reaches. A machine identity carries the role of the process that holds it: the app server refuses a sync workload's identity, and the management server a runtime's.
  4. Isolation — workloads run in sandboxed VMs whose network reaches only the hosts their deployment allows. Each deploy reissues a workload's secret, which stops the previous one from working; the workload record keeps it masked, and only the node that starts the guest receives it.
  5. Data — every access scoped System, Org, or App over shared clusters.

Zone Authorization

Every routable address carries its zone in the CRI itself, so the isolation boundary travels with the message and is validated wherever the message goes. Which zones a participant may address is derived from the participant type in one place — ZoneRules — and every enforcement point uses that same derivation. The full participant × zone matrix is in the CRI format reference.

The derivation defends its own inputs:

  • Ids are validated before they form a zone. An organization or application id must be a single dot-free label. A crafted id containing a dot would shift the app.<organizationId>.<applicationId> label structure — letting one (org, app) pair produce the same zone as a different pair plus a sub-zone — so such an id fails authentication instead of widening access.
  • Sub-zone matching respects label boundaries. app.acme-org.orders-app.billing is a sub-zone of app.acme-org.orders-app, but app.acme-org.orders-app-2 is not — the match requires a dot at the boundary, so a similarly-prefixed zone name grants nothing.
  • Un-zoned addresses are system-only. An address without a zone carries no isolation boundary, so only participants allowed to send anywhere may address it.
  • Each server serves its own zones. A server's zone partitioning narrows every participant's rules to the zones that server reaches and hosts: the management server never routes to an application's zone or lets a runtime publish into one, and the app server never reaches management-api or system-api, whoever the participant. Below the gateway, the cluster manager enforces the same partitioning: a node advertises a consumer only in a zone it hosts, and a send to a zone it does not reach finds no handlers.

STOMP Endpoint

Every frame on a connection is checked against the participant's zones — authenticating once does not exempt any later send or subscribe:

  • Sends must target a zone the participant may address, or match a single-use, exact-CRI temporary grant issued by the server for a specific expected message.
  • Subscriptions are limited to the participant's subscribable zones plus its own reply destinations. Reply destinations are scoped by a server-generated replyToId — the client cannot pick a guessable or colliding value, and a connection can never subscribe to another connection's replies.
  • management-api and app-api are hosted in-process only. No external connection may subscribe in those zones, so no external node can register itself as a platform service and intercept calls.

Browser Sessions

A browser authenticates with one session cookie per API host, and every login in the session is bound to the page that made it:

  • A login authenticates only its own page. The page is the request's Origin, or for a same-origin request, which carries none, the origin of its Referer. A page that shares the cookie's site without having logged in, such as a published UI on a sibling host, finds no login under its origin, so it cannot ride another page's session. Logging out ends only the calling page's login; the session ends once no page's login, and no sign-in another page has in progress, remains.
  • A session is bound to the host that issued it. Each login records the host it was established on, and a session presented on any other host is destroyed and the request refused, so a cookie replayed by hand against another API host authenticates nothing.
  • A redirect login binds to the page that started it. An identity provider returns the browser with a top-level navigation that carries no Origin, so the flow records the starting page's origin when it begins; the completed login is bound to that page and the browser is sent back there.
  • An application's users sign in only from its own UIs. The app server starts a login, by password or through an identity provider, only from a page whose host is one of the application's sites, <applicationId>--<uiName>.<organizationId> under the sites domain, with or without a tenant before the application, and its CORS pattern holds pages to the sites domain. A UI of another application shares that domain, so without the check it could start a single sign-on the identity provider completes without a prompt and hold the login on this application's host.

MCP Endpoint

An MCP tools/call crosses two independent authorization layers:

  1. Resolution is scoped. The tool lookup queries Elasticsearch with the caller's zone visibility filter, narrowed to the zones this server reaches — a tool outside them is not found at all, exactly as if it did not exist.
  2. Dispatch is re-authorized. Before the call is sent, the invoker re-checks the resolved CRI against the same ZoneRules the STOMP path enforces. If the stored directory data or the visibility query were ever wrong, the dispatch is refused, the CRIs are logged as a server fault, and the caller sees an unknown tool.

The surrounding surface is hardened the same way:

  • Tool names are always minted, never parsed. A name is derived from the service's qualified name and function, making it unique system wide; no override exists, and nothing ever parses a name apart to make a decision — resolution is a caller-scoped directory query and authorization reads the zone from the stored CRI, so the name is never a trust input.
  • Listings never expose dispatch addresses. The tool listing query excludes the CRI at the Elasticsearch _source level, so the internal address never leaves the data store for a listing.
  • Duplicate names are refused, not resolved. Minted names are unique system wide, so more than one match for a name means the directory index is corrupted. The platform never picks a winner: the providing CRIs are logged for operators and the caller receives a generic internal error.

GitHub Install Binding

Linking a GitHub account stores the record that authorizes every later repository operation for that org — repo creation, pushes, token minting all trace back to it — so the bind itself crosses three independent proofs before it is persisted:

  • The state token binds the round-trip to the org. startInstall mints a single-use, 10-minute state bound to the caller's organization; completeInstall consumes it atomically and rejects a state staged for a different org. A replayed or forged callback dies here.
  • The user-authorization code binds the round-trip to a GitHub user. The App requests user authorization (OAuth) during installation, so GitHub's post-install redirect carries a code that only the person who authenticated at GitHub in that browser can produce. The server exchanges it — using the GitHub App's own OAuth credential — for that user's access token.
  • GitHub attests the user controls the installation. The installation_id in the redirect is browser-supplied and therefore attacker-controlled; installation ids are small sequential integers, and the App itself can resolve any of its installations, so no App-credential lookup can vet one. Instead the server asks GitHub which installations of this App the authorizing user can access (GET /user/installations with the user's token) and persists the binding only when the claimed id — with a matching App id — appears on that list. Claiming another customer's installation fails with an authorization error and nothing is stored.

The account login and type stored on the binding are read from the attested /user/installations entry, never from a lookup made with the App's own credentials.

Authentication Cross-Checks

Documented in full on System Security, one check is defense-in-depth by design:

  • JWT scope claims are cross-checked against the user record. A Kinotic-minted JWT carries organizationId/applicationId claims, and authentication requires them to match the ParticipantIdentity the sub claim resolves to — a token minted before the user was moved or re-scoped is rejected even though its signature verifies.

Credential failures are also uniform: an unknown email and a wrong password produce the same Invalid credentials error, so the login surface cannot be used to enumerate which accounts exist.

Copyright © 2026