Add a dedicated `eirescope` user (UID 1000) and chown /app to it so
the image no longer runs as root.
Drop flask and sqlalchemy from requirements.txt — neither is imported
anywhere; the server uses stdlib http.server and raw sqlite3. Add
jinja2 explicitly (it was previously a transitive of Flask) and pin
upper bounds on every dep so builds are at least somewhat reproducible.
`run.py` and the server defaults exposed the dashboard on every
interface with no authentication. Default to 127.0.0.1; the Docker
entrypoint still passes --host 0.0.0.0 explicitly, so containers are
unaffected. Document the override in the --host help text.
Also switch from HTTPServer to ThreadingHTTPServer so one slow
investigation (subdomain enumeration / whois / ~200 social URL
probes) no longer blocks every other request.
Both search handlers trusted the client-supplied Content-Length and
passed it straight to `rfile.read()`, so a single request with a
forged `Content-Length: 1073741824` would allocate gigabytes. Clamp
to 64 KiB, well above any legitimate search payload.
`{{ json_data|safe }}` injected the output of `json.dumps(summary)` raw
into a <script> block. Python's json.dumps does not escape </script>,
so any attacker-controlled string in the summary (the query itself
when entity_type is company/person, scraped WHOIS, breach names, etc.)
could close the script tag and execute arbitrary JS in viewers'
browsers.
Use Jinja2's `tojson` filter, which produces HTML-safe JSON (escapes
<, >, &, ' to \uXXXX). Drop the server-side json.dumps argument and
the unused `json` import in report_generator.
`_serve_static` joined the URL-supplied filepath directly into STATIC_DIR
without normalising `..` segments or rejecting absolute paths, allowing
arbitrary file read on the host. Resolve both sides to realpaths and
require the result to live under STATIC_DIR.