# Hardening

A node on a laptop and a node your organization depends on are the same binary with different settings. This page is the difference, in the order the settings matter.

## The vault key

The one secret the deployment cannot run without, and the one whose loss cannot be recovered from. Three rules.

Hand it to the process from your secret manager rather than from a file beside the data directory. Never store it in the backup bucket, because sealing the credentials before they reach a database is what makes the backup objects worthless to whoever holds the bucket alone. And back it up on its own path with its own access list, because a data backup without the key restores nothing readable.

Rotation is not built. The sealed format is ready for it and the re-sealing pass is not written, so the control you have today is who can read the key rather than how often it changes.

## What the node listens on

`-addr` defaults to loopback, and that default is correct until you mean otherwise. When the node does face a network, put it behind something that terminates TLS and forwards to it, rather than exposing the port directly.

The node serves the authenticated API, the MCP endpoint, the sign-in callback, and inbound webhook messages on that one port. The webhook path sits outside the authenticated API by necessity, because the app sending a message holds no key of yours. Everything on it is verified by signature instead, so it is not an unauthenticated hole, but it is the path most exposed to the internet and worth naming in whatever review you run.

## Who may create an account

`-signup` takes `open`, `invite`, or `closed`, and the default is `closed`. A node reachable from a wide network with open signup is a node anybody who reaches it can create a project on.

`invite` is the setting most organizations want: accounts exist because an administrator made them exist, and the console renders the door accordingly rather than showing a signup form that fails.

## Which browsers may call it

`-browser-origins` names the origins allowed to call the API from a browser. Name exactly the origins you mean. A wildcard on a credentialed API is a hole rather than a convenience, and a node serving its own console needs no entry here at all, because same-origin calls are not cross-origin calls.

`-console-cookie-domain` stays empty for a node serving its own console, which keeps the session cookie to that one host. Set it only when the console and the API are two hosts under one domain.

## Email that arrives

`-mail-driver` defaults to `log`, which prints sign-in links into the node's own log instead of sending them. That is correct for one person on a laptop and wrong the moment a second person needs to sign in. Configure a real driver before you invite anybody, and treat the node's log as a secret until you have, because for as long as the driver is `log` that log contains working sign-in links.

## Outbound traffic

The node refuses to send a request to a host that is not on the allowlist for the project making the call, and refuses to reach loopback, link-local, and private ranges at all unless a deployment explicitly allows it. That second rule is what stops a provider hostname that resolves inward from turning a tool call into a request against your own infrastructure.

Both the refusals and the hosts actually reached are recorded, and both are readable without the server running:

```bash
automaton egress denials -data /var/lib/automaton -project acme -since 168h
automaton egress hosts   -data /var/lib/automaton -project acme -since 168h
```

Read the denials list after any rollout. A denial is usually either a connector reaching a host you have not allowed, which is a decision to make, or a misconfiguration, which is a bug to fix. Read the hosts list when somebody asks where the data went, because it is the answer.

An internal system with no public address is not a case for widening this. It is what the [relay](./the-relay.md) is for.

## Keys and separation of duty

Mint the narrowest role that does the job. An assistant holds `agent`. A person answering approvals holds `approver`. A dashboard holds `viewer`. Administration holds `admin`, and `admin` does not bypass separation of duty: an admin who submitted a job still cannot approve it with the same key.

Give keys an expiry when you mint them. Revoke rather than rotating in place, because a revoked key id stays in the audit record and a reused one confuses it.

## Retention and erasure

Set retention per project to whatever your records policy says, and run the sweep on that schedule. When somebody has to be erased, erase the collection rather than deleting rows: destroying the key that opens a collection leaves the accounting behind, which is what lets you prove the erase happened. Both are on the [backups page](./backups.md).

## Before you call it done

- The vault key is in the secret manager, backed up separately, and not in the bucket.
- The backup names every database, and you have restored from it onto a scratch machine and read a real row out.
- `-signup` is `invite` or `closed`, and `-mail-driver` is a real driver.
- `-browser-origins` names exact origins, or is empty because the node serves its own console.
- The node is behind TLS termination and is not exposing its port directly.
- Somebody who is not the person who installed it has read the egress denials list.
- Every key that exists has the narrowest role that works, and you can say who holds it.
