Security architecture

Your credentials never leave your Mac.

Ark8 treats security as a first-class product property, not a compliance checkbox. This page describes the exact posture the application enforces today, verified by an integration test suite.

Design principles

Four rules that govern every code path.

Secrets stay on the device.

SSH keys in ~/.ssh, kubeconfigs in ~/.kube, sudo passwords in Keychain. Ark8 never uploads any of these — nothing on the wire carries a credential.

Server-side authorization.

Every authorization decision — who can see what host, cluster, or membership — is enforced by Postgres Row-Level Security. Client-side filtering is never the security layer.

PKCE-only OIDC.

OAuth flows use PKCE with SHA-256. Google and Microsoft client secrets live in Supabase Auth server-side; they never reach the macOS app or the browser.

Append-only audit.

Sign-ins, membership changes, shares, and admin actions are recorded in an append-only audit log. UPDATE and DELETE are blocked at the database. Every insert passes through a secret-redaction trigger.

Data boundary

What travels — and what doesn't.

Every sharing event in Ark8 is descriptor-only. Secrets don't travel — Ark8 has no column to store them and no code path to read them off disk.

Your device
  • SSH passwords
  • Private keys
  • Kubeconfigs
  • Sudo passwords
  • OAuth tokens
blocked
Supabase
  • Host descriptors
  • Organization + memberships
  • RBAC + audit events
  • Shared inventory metadata
Explicit sharing is descriptor-only. The credentials that could actually reach an SSH host, a Kubernetes cluster, or a database never leave the device — Ark8 has no code path that reads them off disk and no column to store them.

Tenant isolation

Cross-organization access is impossible.

Every table in the Ark8 schema has explicit Row-Level Security policies keyed to auth.uid(). A user in Organization A cannot see Organization B — even by tampering with the URL, cookies, or request body.

Organization A
AAlice
CChris
RLS denies
Organization B
BBob
DDiana
Cross-organization access is enforced at the database. Alice cannot see Org B's memberships, hosts, or clusters — even if her browser sends the wrong organization_id.

The exhaustive list

Explicit inventory of on-wire vs on-device.

The two lists below are complete — anything not listed on the left simply doesn't have a network path in the codebase.

Uploaded (only when you explicitly share)
  • • Host name, hostname, port, username
  • • Kubernetes context name, cluster name, server URL, namespace
  • • Tunnel label, forwarding type, ports, target host name
  • • Tags and free-form notes
Never uploaded — under any circumstance
  • • Passwords of any kind
  • • SSH private keys, key passphrases, key file paths
  • • kubeconfig contents, client certificates, cluster tokens
  • • Sudo passwords or Keychain items
  • • OAuth tokens, ID tokens, refresh tokens, JWTs

Authentication

PKCE OIDC via Supabase Auth.

Ark8 uses ASWebAuthenticationSession for the OAuth handshake on macOS. Google credentials, Microsoft credentials, and provider secrets never touch the Ark8 process — the browser runs in Apple's system authentication context.

  • Fresh PKCE verifier + SHA-256 challenge on every sign-in.
  • Supabase Auth exchanges the code server-side using its own configured client secret.
  • Session tokens are stored in the macOS Keychain, not on disk in plaintext.
Ark8.app → PKCE.generate()
verifier stays on device
ASWebAuthSession → Supabase /authorize
code_challenge = SHA256(verifier)
Supabase → Google / Microsoft
client_secret is server-side
Callback → ark8://auth/callback
?code=... (single-use, PKCE-bound)
Ark8.app → Supabase /token
exchange with verifier → session
Keychain.save(session)
RLS policy — memberships
CREATE POLICY memberships_read
  ON public.memberships FOR SELECT TO authenticated
  USING (
    user_id = auth.uid()
    OR current_user_is_member_of(organization_id)
  );

CREATE POLICY memberships_admin_update
  ON public.memberships FOR UPDATE TO authenticated
  USING     (current_user_has_role_in(
              organization_id, 'admin'))
  WITH CHECK (current_user_has_role_in(
              organization_id, 'admin'));

-- Only an Owner can produce an Owner row.
CREATE TRIGGER memberships_owner_change_guard
  BEFORE INSERT OR UPDATE ON public.memberships
  FOR EACH ROW EXECUTE FUNCTION
  memberships_owner_change_guard();

Authorization

Row-Level Security is the security layer.

Every table in the Ark8 schema has explicit RLS policies. The client never receives rows it isn't authorized to see, because the database filters them out — not the UI.

  • • 10 tables with per-role RLS policies (Owner / Admin / Member / Viewer).
  • • Owner transitions gated by a database trigger — non-owners cannot promote to Owner.
  • • Cross-tenant isolation proven by 40+ Postgres integration tests.
  • • No service_role key ever loaded by the client.

Audit

Append-only, secret-redacted at insert.

Every sensitive action produces an audit event. The events table enforces immutability at the database — UPDATE and DELETE are blocked by trigger. Any field with a secret-shaped key is replaced with [REDACTED] before the row lands.

Logged actions

Sign-in success/failure, membership create/change, share/unshare, org changes, admin overrides.

Redacted keys

password · client_secret · access_token · refresh_token · private_key · service_account · assertion · authorization · saml_response · sshpass · cookie.

Read-only from UI

Admins can filter and export; nobody can rewrite history.

Want the full architecture?

The complete backend runbook lives with the source. Enterprise teams can request a review with our team before rolling out to production.