7.8 KiB
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.serviceis enabled and active (exited). This verifies that UFW is installed/enabled, not which allow/deny rules are effective; unprivilegedufw status,iptables, andnftrule reads were denied and no elevation was attempted.- The backend container is
emi-backend-app-1(Compose projectemi-backend, serviceapp) with restart policyalways. - Direct, unauthenticated backend root requests at
http://127.0.0.1:3001/returned401 Unauthorizedfor bothGETandHEAD; neither response carried a redirect location. TheHEADprobe'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) returned204 No Content,Vary: Origin, Access-Control-Request-Headers,Access-Control-Allow-Credentials: true, andAccess-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, andhttps://invalid.examplereturned noAccess-Control-Allow-Origin. Thus none of those tested browser origins was granted cross-origin read access by the direct backend response. api.emmint.comresolved to76.108.184.110. Its unauthenticated HTTPS root behavior was302 FoundwithLocation: /loginfor bothGETandHEAD; its preflight was204 No Content. The public responses identify an upstream Caddy (Via: 1.1 Caddy) and Express application; noAccess-Control-Allow-Originwas observed on the tested public preflight.
Listener, container, network, and proxy facts
emi-backend-app-1publishes3001/tcpas0.0.0.0:3001and[::]: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 isinternal=false,attachable=false, and IPv6 disabled. The container has a read-write bind mount from/home/aeroreyna/EMI-Backendto/app, a read-write node-modules volume, andReadonlyRootfs=false. - Other all-interface TCP listeners observed on
.119were 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 on127.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.comis therefore not verified as a local.119service/container; its upstream placement and forwarding policy remain unresolved. - Documented frontend hostnames resolve away from the
.119LAN address:emmint.comandwww.emmint.comto198.175.150.40, andflorida.emmint.comto35.164.64.246. Their unauthenticated HTTPS root behavior was consistent with external WordPress/LiteSpeed (emmint.com:GET/HEAD301 towww;OPTIONS200),www.emmint.com(GET/HEAD/OPTIONS200), and an external Caddy-backed site forflorida.emmint.com(GET/HEAD/OPTIONS200). They are not verified.119reverse-proxy frontends.
Unresolved exposure and CORS findings
- 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.
- Public API proxy topology is unresolved.
api.emmint.compresents Caddy/Express behavior and redirects unauthenticatedGET/HEADto/login, but its public address does not identify the local.119LAN 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. - CORS is deny-by-observation, not intent-verified. Every tested direct preflight, including
https://emmint.com, lackedAccess-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: trueis present without a tested allowed origin. - Published public websites are not evidence of an API frontend. The documented
emmint.com,www.emmint.com, andflorida.emmint.comhostnames resolve to third-party/external web endpoints rather than verified.119proxy 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:
- Proxy-only API: bind Docker
3001to 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. - 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.
- 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.