Files
server-configs/httpserver/AUDIT.md
2026-03-22 00:54:28 -07:00

7.5 KiB
Raw Permalink Blame History

Code Audit Report — httpserver stack

Date: 2026-03-05
Scope: /opt/stacks/httpserver (compose, data/, chat server, services loader, static assets)


Executive summary

  • Security: Several issues: sensitive file exposure, client-side XSS risk in chat, missing escaping in services HTML, no chat rate limiting or abuse controls.
  • Correctness: One protocol mismatch (gs.html chat history never populates).
  • Maintainability: Minor issues (comment vs script name, dependency pinning).

1. Security

1.1 Sensitive file served as static content (High)

File: data/banned.json
Issue: The entire data/ directory is mounted as the Apache document root (./data:/var/www/html). So banned.json is publicly reachable at /banned.json. It contains fail2ban-style jail names and IP addresses.

Impact: Information disclosure (internal IPs, jail names). Useful for reconnaissance.

Recommendation:

  • Move banned.json outside the web root (e.g. project root or a non-served volume) if it must live in the repo, or
  • Exclude it from the volume used as document root (e.g. serve only a whitelisted subdirectory), or
  • Stop serving it over HTTP (e.g. use it only in a backend/script that doesnt expose it).

1.2 Chat client uses innerHTML for message text (Medium)

File: data/chat.js (around line 102)

// Comment says "use textContent for safety" but code uses innerHTML:
textSpan.innerHTML = msg.text;

Issue: The server sanitizes only < and > in chat-server.js. Using innerHTML with server output is fragile: if sanitization is ever relaxed or bypassed, or if the client receives data from another path, this becomes an XSS sink.

Recommendation: Use textContent for the message body so the DOM is not parsed as HTML. If you need newlines, use textContent and style with white-space: pre-wrap (or similar).

1.3 Services HTML built from JSON without escaping (Medium)

File: data/services-loader.js
Issue: Service name, url, category, and meta[].label / meta[].url are interpolated into HTML strings (e.g. href="${meta.url}", ${service.name}) without encoding. If services-data.json is ever edited incorrectly, compromised, or merged with untrusted data, a value containing " or > could break attributes or inject script.

Recommendation: Add a small escapeHtml (and optionally escapeAttr) helper and use it for every value that is inserted into HTML or attributes. Keep treating services-data.json as trusted input, but defense-in-depth avoids mistakes and future data sources.

1.4 Chat server: no rate limiting or abuse controls (Medium)

File: data/chat-server.js
Issue: Any client can connect and send unlimited messages. There is no per-IP or per-connection rate limit, no use of banned.json, and no max message size beyond the 200-character sanitizer slice.

Impact: DoS via message flood; spam; possible memory pressure from a very large history if HISTORY_MAX is raised.

Recommendation: Add rate limiting (e.g. per connection: max N messages per minute). Optionally enforce max message size and consider using banned.json (or a similar list) to reject connections from banned IPs if the proxy passes client IP (e.g. via X-Forwarded-For and a trusted proxy config).

1.5 Chat sanitization is minimal (Low)

File: data/chat-server.jssanitize()
Issue: Only < and > are escaped. For plain text in a <div> (and if the client uses textContent as recommended), this is enough for basic XSS prevention. It does not normalize Unicode or protect against other edge cases (e.g. if the same string were ever used in an attribute).

Recommendation: Keep server-side sanitization and switch the client to textContent. If you later use the same string in attributes or other contexts, add encoding appropriate to that context (e.g. attribute encoding).


2. Correctness

2.1 Chat history protocol mismatch in gs.html (Bug)

Files: data/chat-server.js vs data/gs.html
Issue: The server sends history as:

{ type: 'history', messages: history }

gs.html expects:

if (Array.isArray(msg.history)) { ... }

So msg.history is always undefined and chat history is never shown on the GS page.

Recommendation: In gs.html, use msg.messages when data.type === 'history' (and optionally check Array.isArray(msg.messages)), consistent with chat.js.


3. Configuration & deployment

3.1 Apache proxy config duplication

File: data/chat-proxy.conf
Issue: Both RewriteRule and ProxyPass/ProxyPassReverse are used for /chat → WebSocket. This can be redundant or confusing; one consistent mechanism (e.g. mod_rewrite with [P] or ProxyPass) is easier to reason about.

Recommendation: Prefer one approach (e.g. RewriteRule with [P,L] for WebSocket upgrade, and ensure no double proxy). Document which Apache modules are required.

3.2 Chat server port exposure

File: compose.yaml
Issue: The chat service publishes 8098:8081. So the WebSocket server is reachable on host port 8098 without going through Apache. If Apache is the intended single entry point for the site, consider not publishing the chat port on the host (only expose it on internal-net so Apache can proxy to it).

Recommendation: Remove the ports: mapping for the chat service if all access should be via Apaches /chat; otherwise document that 8098 is intentionally public.

3.3 Dependency pinning

File: data/package.json
Issue: "ws": "^8.18.0" allows minor/patch updates. Rebuilds can pull different versions.

Recommendation: Use exact versions (e.g. "ws": "8.18.0") or lock with package-lock.json committed and npm ci in the image for reproducible builds.


4. Maintainability

4.1 Outdated comment / missing script

File: data/services-loader.js (header comment)
Issue: Historical note: the loader previously had an embedded-data workflow and referenced a script for syncing/embedding.

Recommendation: Keep services-data.json as the single source of truth and load it at runtime (no embed/sync step).


5. Positive notes

  • Chat server sanitizes nickname and message length and strips </>.
  • gs.html uses an escapeHtml helper for chat nick and text when building HTML.
  • main.js respects prefers-reduced-motion and limits star count on small viewports.
  • Compose resource limits and logging options are set; networks are isolated.
  • .env in project root is not under the web root, so it is not served.

Priority Item Status
High Stop serving banned.json Done: Apache <Files "banned.json"> Require all denied in chat-proxy.conf
Medium In chat.js, use textContent for message text Done
Medium In services-loader.js, escape all dynamic values Done: escapeHtml / escapeAttr added and used
Medium Add rate limiting to chat server Done: 30 msg/min per connection (configurable via CHAT_RATE_LIMIT)
Medium Fix gs.html to use msg.messages for history Done
Low Simplify Apache proxy config and document Done: comment + deny for banned.json
Low Do not publish chat port; use Apache only Done: port removed; gs.html uses location.host + '/chat'
Low Pin ws; fix services-loader.js comment Done: "ws": "8.18.0"; loader now reads services-data.json directly (no status/embed script).