# Security upgrade notes

This update tightens identity checks and retained-site quotas. Review
existing identities before restarting the gateway. Back up the gateway
database and stop gateway writes while performing any identity repair. Keep
backups private.

The WordPress identity binding and the wp-cli command policy that were part
of this update went away with the WordPress lane in 0.10.0; the
`wp_identities` table and the `users.wp_login` column stay in older
databases, unread.

## GitHub identities

Accounts are matched by positive numeric GitHub ID. A username change preserves
the local account when the new username is free; a conflicting username is
refused. A different GitHub ID cannot take over an existing local username. Existing
accounts cannot receive another invitation; obsolete invitations for the old
and current usernames are cleared during verified login/rename.

Legacy accounts created by offline bootstrap may have no GitHub ID. Their
existing tokens continue to work, but GitHub login is refused until the operator
explicitly binds the account after independently verifying its owner. Matching
a current username alone is not enough. Never clear an ID to make login succeed.
After stopping the gateway and taking a consistent SQLite backup (including any
WAL state), use bound SQL parameters for the verified positive ID and local row:

```sql
BEGIN IMMEDIATE;
UPDATE users SET github_id = :verified_id
WHERE id = :local_id AND COALESCE(github_id, 0) = 0
  AND :verified_id > 0
  AND NOT EXISTS (SELECT 1 FROM users WHERE github_id = :verified_id);
SELECT changes();
```

Commit only if exactly one row changed and the read-back identity is correct;
otherwise roll back. Do not substitute a guessed ID or infer ownership from a
username. If earlier token issuance is suspect, revoke the affected tokens
before resuming access.

The configured bootstrap username can create the first owner only when no local
owner exists; it does not bind or promote an existing account.

Startup adds a unique index for positive GitHub IDs. If old records contain
duplicates, startup fails instead of choosing an account. Preserve the database,
verify which local account owns each identity and its sites, revoke any tokens
issued to a wrongly linked account, and reconcile the conflicting records
before restart. To quarantine conflicting rows, disable every affected account
and clear its provider binding to zero while retaining its local ID, role,
sites, and history. Rebind only independently verified, distinct provider IDs
with the conditional transaction above. Leave unverified rows disabled and
unbound. Do not pick a winner by row order, delete accounts, or transfer sites
merely to make the index succeed. Re-enable a quarantined account only after
verification and token review.

## Operation changes

- Deleted or archived sites count toward the quota while their data is retained.
  An owner purge releases the slot; a soft delete does not. Creation reserves
  capacity atomically, including concurrent requests.
- Owner mutations require a token with `publish` scope. Read-only owner tokens
  retain read access and can revoke themselves by logging out.
- Artisan commands on the Laravel lane are audited with passwords and secrets
  in flags redacted. Older audit records are not scrubbed by this update; if
  earlier records contain credentials, restrict access and rotate those
  credentials using the relevant account's controls.

## Installer verification

The installer verifies the exact platform binary against `SHA256SUMS` before
replacing an installed executable. `RAPADURA_SHA256` optionally pins a digest
verified through an independent channel. Binary and manifest downloads require
HTTPS. A manifest served by the same host detects mismatched downloads but does
not establish authenticity against compromise of that host or installer.

## Regression checks

```sh
go test ./...
go test -race ./...
go vet ./...
sh -n deploy/install.sh
bash -n deploy/rapadura-artisan
bash -n deploy/install-vm.sh
```

Tests use temporary SQLite databases, dry-run hosting, a fake artisan runner
and controlled installer downloads. They do not reproduce the production
systemd/sudo environment; verify that and operator identity migrations in
staging before a production rollout.
