# FIPS 140-3 Images

Every Hatchet server image is also published with a `-fips` suffix. Tags match the regular images.

## What a `-fips` image guarantees

- Every Go binary is built with `GOFIPS140=v1.0.0`, which links the validated Go Cryptographic Module (CMVP certificate #4985) and bakes `fips140=on` into the binary. The module runs its integrity self-test at startup and TLS is restricted to approved cipher suites and curves. You can verify a binary with `go version -m <binary>`: it reports `GOFIPS140=v1.0.0-c2097c7c` and `DefaultGODEBUG=fips140=on`.
- A binary built with `GOFIPS140` refuses to start if `GODEBUG=fips140=off` is set in its environment.
- On top of the module, the server refuses non-approved choices: bcrypt password hashes, an AES-GCM master keyset, HMAC-MD5 and HMAC-SHA1 inbound webhooks, and HMAC keys shorter than 112 bits (session cookie secrets, webhook signing secrets).
- FIPS images do not ship the `openssl`, `curl` or `psql` command-line tools. `hatchet-admin quickstart` refuses the certificate stage on a FIPS build so you need to provide your own certificates or pass `--skip certs`.

`libcrypto3` and `libssl3` are present in the image because the Alpine base depends on them. Nothing in Hatchet loads them: all binaries are built with `CGO_ENABLED=0` and use only the Go module. Security scanners may still list them.

## Requirements

- Postgres must use `scram-sha-256` password authentication. `md5` is not approved. Postgres 14 and later default to SCRAM.
- Cookie secrets, inbound webhook signing secrets and any other HMAC secret must be at least 14 bytes.
- Keysets must be generated by a FIPS build of `hatchet-admin`, which produces AES-CTR-HMAC-SHA256 keys. Regular builds generate AES-GCM keys, which a FIPS build refuses.

## Moving between image kinds

The two directions are not symmetric.

- **Regular to FIPS** needs the steps below. A FIPS image refuses an AES-GCM master key and bcrypt password hashes, so keysets must be regenerated, encrypted secrets re-created, and passwords reset.
- **FIPS to regular** needs nothing. A regular image reads a CTR-HMAC master key, keeps using CTR-HMAC data keys for it, decrypts every secret the FIPS image wrote, and verifies PBKDF2 passwords. API tokens and sessions stay valid.

One thing to watch after a rollback: users created or password-reset while on the regular image get bcrypt hashes. If you later return to the FIPS image, those users are refused until reset with `hatchet-admin user set-password`. Everything else written by the regular image remains readable by the FIPS image.

## Switching an existing deployment to `-fips`

If you must move an existing database, expect these three steps. None of them is automatic.

1. **Regenerate the keysets.** The server refuses to start on an AES-GCM master key. Run `hatchet-admin keyset create-local-keys` from a FIPS build and install the new master, private JWT and public JWT keysets together. The JWT keysets are stored encrypted under the master key, so they cannot be reused.
2. **Re-create encrypted secrets and reissue tokens.** Anything encrypted under the old master key is unreadable afterwards: OAuth tokens, Slack integrations and inbound webhook credentials. Every API token was signed with the old JWT keyset, so workers need new tokens.
3. **Reset passwords.** Users with bcrypt hashes get `invalid credentials` on login and the server logs a warning naming the fix. Set a new password with:

```sh
printf '%s\n' "$NEW_PASSWORD" | hatchet-admin user set-password --config ./config --email user@example.com --password-stdin
```

This writes a PBKDF2-HMAC-SHA256 hash and revokes the user's existing sessions.
