# PLAI Chain Public API Hardening

## Enforced candidate-network boundary

The public Express service is a candidate/testnet control plane, not a public mainnet RPC.

- Express binds to `127.0.0.1`; Nginx is the only public ingress.
- Public chain hosts redirect HTTP requests to HTTPS.
- Every `/api/**` request except CORS preflight requires authenticated HMAC v2.
- `/health` remains the only unsigned machine-readable health endpoint.
- API requests, admin login, and wallet mutation paths are rate limited.
- Candidate EVM external transfers remain locked.
- Legacy mock mining, validator assignment, voting, and finalization remain locked.
- Besu QBFT is the only canonical candidate ledger; mock blocks are exposed only as
  a legacy read-only archive.
- Secure cookies and baseline browser security headers are enabled.

`AISFOR_CHAIN_SECURITY_ENFORCED=1` makes the service fail closed at startup if the
loopback bind, public API lock, service authentication, HMAC v2, secure-cookie, or transfer-lock
requirements drift from their safe values.

## HMAC v2 request requirements

Authenticated service requests must include:

- `X-AISFOR-SERVICE`
- `X-AISFOR-SIGNATURE`
- `X-AISFOR-TIMESTAMP`
- `X-AISFOR-NONCE`

The signature covers service, timestamp, nonce, method, request path, and normalized
body. API-key-only and legacy signatures are rejected while
`AISFOR_CHAIN_REQUIRE_HMAC_V2=1`.

`X-AISFOR-API-KEY` is accepted only for compatibility with existing signed
clients. It is not sufficient authentication and new clients should omit it.

## Operational verification

After a deployment or restart, verify:

1. `/health` returns 200 through localhost and HTTPS without service credentials.
2. a signed `/api/evm/status` request reports `externalTransfersEnabled: false`.
3. unsigned GET and POST requests below `/api` return 401 before route handling.
4. a correctly signed request to a non-existent API path passes authentication and returns 404.
5. port 4317 listens only on loopback.
6. HTTP requests for the public chain hosts return 308 to HTTPS.
7. Mongo observation/live writers complete without overlapping full-sync jobs.
8. Mongo writers upsert the current stable-ID generation before pruning stale rows;
   they never delete an entire live collection before replacement.

The in-process sync queue is process-local. Stop `aisfor-chain` before running a
manual full-sync CLI so a separate CLI process cannot overlap the live writer;
start the service again immediately after sync and comparison complete.

Do not disable the startup security gate to work around a failed deployment. Correct
the failing setting and restart the service.
