Files
FamReynaBrain/projects/factory/emi-production-perimeter-audit-2026-08-18.md
T
2026-09-14 22:38:47 -04:00

47 lines
7.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# EMI production perimeter audit — 2026-08-18
## Scope and verdict
Read-only perimeter follow-up on `aeroreyna@192.168.68.119` (`emiserver`). Inspected only non-secret listener/firewall service facts, Docker published-port/network/container metadata, detected proxy metadata, DNS, and unauthenticated HTTP `GET`, `HEAD`, and `OPTIONS` behavior. No authentication, form submission, secret/environment/configuration reading, dirty Compose diff inspection, source modification, service/container/proxy/firewall change, or Mac-mini backend WIP interaction occurred.
**Verdict: perimeter is not production-clean.** Authentication controls prevent unauthenticated root access to the backend, and the tested origins were not granted browser CORS read access. However, the EMI backend remains directly published on every IPv4 and IPv6 interface at TCP 3001; the effective UFW rule policy could not be read without elevation; and the intended public frontend/API origin and proxy path remain unproven. Do not infer a proxy-only or firewall-restricted production posture.
## Verified exposure controls
- `ufw.service` is enabled and active (exited). This verifies that UFW is installed/enabled, not which allow/deny rules are effective; unprivileged `ufw status`, `iptables`, and `nft` rule reads were denied and no elevation was attempted.
- The backend container is `emi-backend-app-1` (Compose project `emi-backend`, service `app`) with restart policy `always`.
- Direct, unauthenticated backend root requests at `http://127.0.0.1:3001/` returned `401 Unauthorized` for both `GET` and `HEAD`; neither response carried a redirect location. The `HEAD` probe's status/header result is valid, although curl reported a transfer-length warning after the server advertised a body.
- The same direct backend preflight (`OPTIONS`, `Origin: https://emmint.com`, `Access-Control-Request-Method: GET`) returned `204 No Content`, `Vary: Origin, Access-Control-Request-Headers`, `Access-Control-Allow-Credentials: true`, and `Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE`.
- Direct preflights for `https://emmint.com`, `https://www.emmint.com`, `https://florida.emmint.com`, `https://api.emmint.com`, and `https://invalid.example` returned no `Access-Control-Allow-Origin`. Thus none of those tested browser origins was granted cross-origin read access by the direct backend response.
- `api.emmint.com` resolved to `76.108.184.110`. Its unauthenticated HTTPS root behavior was `302 Found` with `Location: /login` for both `GET` and `HEAD`; its preflight was `204 No Content`. The public responses identify an upstream Caddy (`Via: 1.1 Caddy`) and Express application; no `Access-Control-Allow-Origin` was observed on the tested public preflight.
## Listener, container, network, and proxy facts
- `emi-backend-app-1` publishes `3001/tcp` as `0.0.0.0:3001` and `[::]:3001`, confirmed both by Docker and the host listener table. It is not loopback-only.
- The backend is attached to Docker bridge `emi-backend_default`, whose metadata is `internal=false`, `attachable=false`, and IPv6 disabled. The container has a read-write bind mount from `/home/aeroreyna/EMI-Backend` to `/app`, a read-write node-modules volume, and `ReadonlyRootfs=false`.
- Other all-interface TCP listeners observed on `.119` were 22, 222, 2283, 3000, 8085, 9000, 9001, 9443, and 18789–18790. Docker metadata attributes these to Immich, Gitea, Nextcloud, Authentik, Portainer Agent, and OpenClaw as applicable. This audit makes no claim that each is reachable outside the host/LAN because effective firewall and upstream NAT policy were not readable.
- The Paseo daemon listens only on `127.0.0.1:6767`; an OpenCode process listens only on `127.0.0.1:45243`.
- No Caddy, Nginx, Traefik, HAProxy, or Apache systemd unit was found. Apache processes were present, but no port 80/443 listener was present in the host listener table. No Docker reverse-proxy container was present. The Caddy observed at public `api.emmint.com` is therefore not verified as a local `.119` service/container; its upstream placement and forwarding policy remain unresolved.
- Documented frontend hostnames resolve away from the `.119` LAN address: `emmint.com` and `www.emmint.com` to `198.175.150.40`, and `florida.emmint.com` to `35.164.64.246`. Their unauthenticated HTTPS root behavior was consistent with external WordPress/LiteSpeed (`emmint.com`: `GET`/`HEAD` 301 to `www`; `OPTIONS` 200), `www.emmint.com` (`GET`/`HEAD`/`OPTIONS` 200), and an external Caddy-backed site for `florida.emmint.com` (`GET`/`HEAD`/`OPTIONS` 200). They are not verified `.119` reverse-proxy frontends.
## Unresolved exposure and CORS findings
1. **Direct API exposure remains the primary perimeter finding.** TCP 3001 is published to all host interfaces and the Docker network is non-internal. UFW's active rule set and the router/NAT policy were intentionally not elevated/read, so this audit cannot establish whether LAN or Internet clients can reach the direct port.
2. **Public API proxy topology is unresolved.** `api.emmint.com` presents Caddy/Express behavior and redirects unauthenticated `GET`/`HEAD` to `/login`, but its public address does not identify the local `.119` LAN endpoint and no local Caddy listener/container was found. It is not proven whether the proxy protects all API paths, which upstream it uses, or whether direct 3001 bypasses it.
3. **CORS is deny-by-observation, not intent-verified.** Every tested direct preflight, including `https://emmint.com`, lacked `Access-Control-Allow-Origin`; the tested public API preflight also lacked it. This blocks browser cross-origin reads for those requests, but it does not establish the intended credentialed frontend origin or prove that a production frontend can call the API successfully. `Access-Control-Allow-Credentials: true` is present without a tested allowed origin.
4. **Published public websites are not evidence of an API frontend.** The documented `emmint.com`, `www.emmint.com`, and `florida.emmint.com` hostnames resolve to third-party/external web endpoints rather than verified `.119` proxy listeners. No documented production frontend hostname was shown to be an allowed CORS origin for this API.
## Context received after audit
**Authoritative infrastructure context:** `api.emmint.com` is served by Caddy on FamReynaServer `.110`, which routes to the EMI backend on `.119`. That shared Caddy/Authentik/DNS/firewall layer is outside the EMI project's ownership. This report's observation that the proxy placement was unresolved is therefore superseded as a project-boundary question: the route may be documented and health-verified by EMI work, but must not be modified by an EMI task. Any proxy remediation needs a separately scoped shared-infrastructure task and explicit approval.
## Minimal remediation options — require explicit human approval
Choose only after confirming the intended API hostname, frontend origin(s), required credential mode, and LAN/Internet exposure policy:
1. **Proxy-only API:** bind Docker `3001` to loopback only (or remove the host publication), then explicitly proxy the required HTTPS API hostname through the approved reverse proxy. Review the dirty Compose change in an isolated worktree before any lifecycle action.
2. **Firewall-restricted direct API:** retain the published port only if necessary, and approve explicit UFW/router rules limiting 3001 to the named trusted proxy/LAN sources. Verify from an authorized external and LAN vantage point after the approved change.
3. **Explicit CORS allowlist:** configure only the confirmed HTTPS frontend origin(s), preserve `Vary: Origin`, and test allowed and denied preflights plus an unauthenticated request through the real proxy. Do not use a wildcard origin with credentialed requests.
Any option requires separate approval for configuration review/change, rebuild/recreate/restart, deployment, or firewall/proxy modification. The Mac-mini backend CORS WIP remains untouched and uncommitted.