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

6.4 KiB

EMI production security audit — 2026-08-18

Scope, safety boundary, and verdict

Read-only audit of aeroreyna@192.168.68.119, ~/EMI-Backend, and the running emi-backend-app-1 container. No services, containers, checkout files, Git state, firewall/proxy configuration, credentials, environment values, compose diff content, or Mac-mini WIP were changed. No secret values were read or recorded.

Verdict: conditionally acceptable to remain running, but not production-security clean. The live application source files that control the observed server behavior match the .119 checkout's HEAD commit 92320c9c8993308172282d6ac4437e19c752650a (Add monthly MongoDB backup script, 2026-05-29). The server should not be recreated or redeployed without human review because its local compose file is modified relative to that commit.

The main .119 server may remain on its current deployed commit while the separate Mac-mini backend WIP remains uncommitted. This audit made no change to that WIP. The conclusion is limited to keeping the existing .119 container running; it is not approval to rebuild, recreate, deploy, merge, or commit anything.

Evidence and runtime identity

  • Checkout HEAD: 92320c9c8993308172282d6ac4437e19c752650a.
  • Checkout status: one tracked modified file and one untracked file. The tracked docker-compose.yml differs from HEAD; its diff content was neither read nor displayed.
  • Running container: emi-backend-app-1; image emi-backend-app; image ID sha256:0f245f652484e3704723cb93717b9ca867c38e74d8eb05eab7b123fa638eef44.
  • Image/container creation: 2026-05-29; current container start: 2026-08-17. Restart policy: always.
  • The container bind-mounts /home/aeroreyna/EMI-Backend read-write at /app. It therefore executes checkout-mounted application files rather than an isolated immutable copy of those files.
  • SHA-256 comparisons (without exposing file content) show the running /app/index.js, /app/package.json, /app/config/corsOptions.js, and /app/config/cookiesOptions.js each match the corresponding HEAD file. The checkout versions of those same files and Dockerfile also match HEAD.

Verified-good controls

  • The root request on 127.0.0.1:3001 returned 401 Unauthorized, rather than an unauthenticated success response. No Location redirect header was observed.
  • Preflight requests returned 204 No Content, with Vary: Origin, Access-Control-Request-Headers; ordinary requests returned Vary: Origin. This prevents a shared cache from treating all origin variants as identical.
  • Both tested origins (https://emmint.com and an intentionally invalid external origin) received no Access-Control-Allow-Origin header. In a browser, neither tested origin is granted cross-origin read access by that response.
  • The server returned Access-Control-Allow-Credentials: true, so the observed CORS behavior is configured for credentialed requests only when an origin is explicitly permitted.
  • The committed cookie-options source contains an HttpOnly control marker and that file is byte-identical in the running container. This establishes that the committed runtime file includes that control, not that every authentication flow actually emits a correctly flagged cookie.

Risks and findings

  1. Public API port exposure — high-priority review. Docker publishes 3001/tcp on 0.0.0.0:3001 and [::]:3001, not loopback only. The container is on a non-internal Docker bridge (emi-backend_default, internal=false). Network/firewall enforcement outside Docker was not changed or assumed. If the intended posture is proxy-only API access, this is broader exposure than intended.

  2. Mutable production code path — high-priority operational risk. The production container bind-mounts the checkout read-write at /app, and the container root filesystem is not read-only. A checkout edit can affect the running process or subsequent restarts outside an immutable-image release boundary.

  3. Dirty compose checkout — release-integrity blocker. The only tracked modification is represented by the dirty docker-compose.yml check; its contents were deliberately not inspected. Current process-critical application files match HEAD, but a future recreate/restart through Compose may use uncommitted configuration. Do not treat a recreate as reproducing committed production source until reviewed in an isolated worktree.

  4. CORS intent is not fully proven. Both tested origins lacked Access-Control-Allow-Origin, including https://emmint.com. This is safe against those specific browser origins, but it may also mean the intended production frontend origin was not tested, is unavailable under that exact hostname, or is misconfigured. The audit cannot claim that the intended frontend works, nor that every unwanted origin is rejected.

  5. Cookie flags are not runtime-verified. No non-mutating endpoint in this audit emitted a Set-Cookie header, so Secure, SameSite, domain/path scope, and actual issuance behavior remain unverified. No authentication request was made.

Unknowns

  • Which exact public frontend origins are intended and whether each should be allowed with credentials.
  • The effective firewall, upstream reverse-proxy, TLS termination, and Internet/LAN reachability policy for published port 3001.
  • Whether the untracked file or modified compose file affects a future Compose lifecycle action; neither content was read.
  • The running Node process's precise listener address inside the container, health endpoint, authentication-cookie issuance path, and behavior through the public proxy/TLS route.
  • Whether image dependencies in /app/node_modules correspond to the committed lockfile; the named volume was not inspected.

Required human approval before remediation

Any remediation requires explicit owner approval before: reviewing the dirty compose diff; creating an isolated worktree; modifying Docker/Compose, CORS, cookies, listener binding, reverse proxy, firewall, or image build; rebuilding/recreating/restarting the container; committing/merging; or deploying. The Mac-mini backend WIP must remain uncommitted and untouched unless separately approved.

Approved read-only follow-up: identify the intended EMI public frontend origin(s) and probe only their localhost-proxied, non-authenticated GET/OPTIONS responses for CORS headers; then separately inventory the reverse-proxy and firewall exposure without changing either.