Security model

Adjoint's security story rests on one architectural decision: your data never enters our infrastructure. The control plane orchestrates; the data lives, moves, and is queried exclusively inside your AWS account. This page lays out precisely what each side holds, how credentials move, and — just as important — what Adjoint does not do yet.

What the control plane stores

Adjoint's control plane database contains metadata only:

Other than that state blob, no table stores a database password, a source password, or your data. The only other secrets at rest on our side are one-way hashes.

How database credentials move

adj creds <instance> is a live broker read, not a vault lookup:

  1. The control plane checks that you are the instance's owner — even an org admin gets a 403 for someone else's instance.
  2. It relays the request over the agent's outbound bridge connection into your cluster.
  3. The agent reads the instance's credentials Secret (generated by CloudNativePG inside your cluster when the instance was created) and returns it in-memory.
  4. The CLI prints it and forgets it — credentials are never written to the CLI config, the control-plane database, or any log. The audit trail records that a read happened, never the value.

The password itself only ever exists in your cluster (as a Kubernetes Secret) and on your screen. Credential TTL/rotation is not implemented yet — a credential stays valid for the life of the instance; destroying or resetting the instance retires it.

Accounts and sessions

Audit trail

Every privileged action lands in a per-org, SHA-256 hash-chained audit log, written in the same database transaction as the action itself. Each event's hash covers the previous event's hash, so retroactive edits break the chain. adj org audit --verify re-derives the entire chain and fails loudly if any link is broken. See Usage and audit.

Isolation between developers

Each developer's instances run in their own Kubernetes namespace (ns-<user>), and a policy pack enforces the walls:

These policies are continuously re-generated — hand-deleting a NetworkPolicy in the cluster gets it re-created. The policy pack ships with an offline test suite; a live network penetration pass against a running cluster is on our bench plan but has not been performed yet — we say so here so you can scope your own testing (we'll gladly support a pentest against your deployment).

Source credentials (imports)

The username/password you provide at adj source create are applied directly to your data-plane cluster as a Kubernetes Secret in the adjoint-import namespace — they never transit or persist in Adjoint's control plane. At-rest protection for that Secret is your cluster's etcd/EBS encryption (EKS encrypts by default); Adjoint adds no application-layer encryption on top. Import connections to your source honor the --sslmode you register (default require).

Connections and TLS — current reality

By default, instances are reachable only inside the data-plane VPC (the endpoint is an in-cluster service name) — nothing is exposed to the internet, and the laptop-to-database path is whatever your team's VPC access is (VPN, bastion, or kubectl port-forward via adj kubeconfig).

Orgs can additionally opt in to public, TLS-required endpoints: an admin runs adj org policy set public-endpoints on (default off, and the change is audited). With it on, newly launched instances get a crypto-random capability hostname (e-<instance-token>.c-<cluster-token>.db.<domain>) reachable from the internet on port 5432. The router that fronts it refuses any non-TLS connection outright and never terminates or inspects the encrypted session — it reads only the TLS SNI to route, passthrough-style; the TLS session itself terminates at the CloudNativePG instance's own certificate, signed by a private CA generated per cluster. adj creds returns a sslmode=verify-full URI and saves that CA locally so the client validates the server's identity, not just encrypts the wire. Password auth is unchanged and still brokered live, never stored. The router also rate-limits connections per source IP and globally.

Full details, the hostname/token model, rotation (adj rotate-endpoint), and the in-cluster fallback: Connectivity.

What Adjoint does not have today

Stated plainly so you can evaluate against your requirements:

Source and org deletion are both self-service (adj source rm, adj org delete) — see Uninstall for the full walkthrough, including the zero-cluster precondition and the one-time final audit export.

Questions, disclosure reports, or security review requests: [email protected].