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

7.8 KiB
Raw Blame History

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.