# 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. ## Next recommended task 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.