Documentation Index
Fetch the complete documentation index at: https://docs.crewship.ai/llms.txt
Use this file to discover all available pages before exploring further.
What “instance scope” means
An instance-scope backup bundles every workspace on a Crewship host plus the cross-workspace surfaces that make the install usable:- All workspace, crew, and agent rows (same as
--scope=workspaceper workspace) - The credstore — encrypted credential rows from every workspace, exported without decrypting so the bundle carries two layers of protection: the credstore’s own AES-256-GCM plus the outer AGE seal.
- The auth signing secret (
NEXTAUTH_SECRET) so sessions survive a restore onto the same host. - The instance identity (
instance_config.hostname) so the target can detect whether a restore is a same-host roll-back or a cross-host DR operation.
Access control
- Server-level OWNER is distinct from workspace OWNER. Instance
backup is gated by
CREWSHIP_OWNER_EMAIL(env var). Any other workspace role — including OWNER or ADMIN of a single workspace — is refused with HTTP 403. The env is intentionally not stored in the database; rotating it is a file edit + restart, never a SQL UPDATE that a compromised DB session could perform. - Rate limited to 1 instance backup per user per hour. A runaway
script cannot DoS the host by thrashing the endpoint. Workspace-
and crew-scope backups are still governed by
backup_locksonly. - Encryption is recipient-only. Instance scope refuses
--passphrase-file. The surface is too broad (every workspace’s secrets in one blob) to trust a brute-forceable scrypt passphrase. Callers must supply an AGEage1…X25519 public key and hold the matching private key offline.
Crypto chain
- Bundle leak alone (attacker has
.tar.zst): outer AGE recipient still encrypts the payload — attacker must also have the recipient’s X25519 private key. - Bundle + AGE key: payload decrypts, but
encrypted_valuerows are still AES-256-GCM underENCRYPTION_KEY. Attacker needs the source host’s master key env too. - Bundle + AGE key + source ENCRYPTION_KEY: full compromise. This is
the same blast radius as losing a live backup bundle of the raw
credentials table, and is why offsite storage of the AGE private
key belongs on a separate device from the one that stores
ENCRYPTION_KEY.
Cross-host restore (disaster recovery)
Each bundle records the source hostname in the manifest. On restore the runner compares it toinstance_config.hostname of the TARGET:
- Same host: no rotation. Sessions and credentials resume seamlessly.
- Different host OR unknown source host: the manifest’s source
hostname differs from the target’s
instance_config.hostname. The helperbackup.ShouldRotateAuthKeysOnRestore(ininternal/backup/authkeys.go) detects this condition.
Auth key rotation on cross-host restore is planned but not
currently enforced.
internal/backup/authkeys.go defines
RotateAuthKeys, but it is not wired into the crewship backup restore CLI flow — the restore command’s flag set is
--as-workspace, --as-crew, --passphrase-file, --use-keyring,
and --dry-run (see cmd/crewship/cmd_backup_lifecycle.go).
Operators performing a cross-host DR restore should manually rotate
NEXTAUTH_SECRET before the next crewshipd start to invalidate
any session tokens from the source host.Credstore master-key continuity
encrypted_value is written unmodified at backup time and
reinserted unmodified at restore time. The target instance must
share the source’s ENCRYPTION_KEY (or a compatible key version
from ENCRYPTION_KEY_vN) to actually decrypt on next keeper.Reload.
When keys differ:
- Reload logs a “decrypt failed” warning for each affected row.
- The credential remains in the DB but is flagged (via the existing keeper error path) and must be re-entered by the workspace owner.
- No silent data loss — the row is retained so manual recovery is possible once the correct key is located.
ENCRYPTION_KEY across hosts. The operator must coordinate master
keys out of band.
Rotation policy
- AGE recipient: rotate whenever a holder of the private key leaves the organisation. Old bundles encrypted to the retired key should be re-encrypted (unseal with old key, reseal with new) or retired.
ENCRYPTION_KEY: rotate at least yearly, or immediately on any suspected host compromise. UseENCRYPTION_KEY_vNversioned keys so ciphertext-at-rest remains decryptable during transition.NEXTAUTH_SECRET: rotate manually on any cross-host restore (automatic rotation in the CLI is planned but not yet wired — see the cross-host restore note above). Also rotate if an operator session token leaks (every session expires next restart).
Incident response
If a bundle is known or suspected to have leaked:- Rotate the AGE recipient holding the matching private key
immediately; revoke the pubkey from
manifest.encryption.recipientsso future backups do not re-seal to it. - Rotate
ENCRYPTION_KEYon the source host; keeper.Reload will re-encrypt rows using the new key on its first successful cycle. - Force-logout every session by rotating
NEXTAUTH_SECRET. - Audit-log review: every backup create/restore emits a row in
audit_logswith entity_typebackup; instance scope carries a metadata object including crypto chain, scope, andcross_instanceflag. Filter the log for the incident window.
Known limitations
- Instance backup does NOT protect against a live compromise prior
to
crewship backup create. An attacker with root on the source is inside the trust boundary and can capture keys directly. - Instance backup does NOT encrypt the manifest (
MANIFEST.jsonis stored plaintext inside the outer tar.zst soinspectworks without a key). Manifest carries no secrets — only metadata — but hostnames, user emails, and workspace slugs are visible. --scope=instance --passphrase-fileis refused by design. Deployments that cannot manage AGE keys should use workspace scope and accept the operational cost of restoring each workspace individually.- Instance restore into a non-empty installation is refused. Merge strategies (conflict resolution across workspaces, keeper crew selection) were intentionally scoped out — restore onto a clean install and let the operator migrate data manually if needed.