Skip to content

v3.0.0

Choose a tag to compare

@github-actions github-actions released this 14 Oct 14:49
· 6 commits to main since this release
edaa64b

Changed

  • Plain-Django UI replaces the React SPA as the served UI. New apps/mvp
    (vendored Bulma + vendored HTMX, Django session auth) at /: dashboard, domains,
    scan start (2×2 modes + custom workflow, unchanged passive/active authorization
    gate), scan detail (HTMX progress every 3s, findings filters, CSV link), history,
    login/logout and a forced change-password middleware. Views call the engine service
    layer directly. The SPA spa view + catch-all were removed from openeasd/urls.py
    (unknown paths redirect to the dashboard). Why: a much simpler MVP surface —
    no Node toolchain, no JWT/API round-trip, one process to serve it.
  • Django-Ninja REST API + JWT REMOVED; the React SPA deleted. /api/, the
    apps/core/console/api/ package and per-app api.py routers are gone. Why: the
    server-rendered UI uses session auth and the service layer directly.
  • dbos pinned >=2.31.1,<3. 2.31.0 guarded module-level Queue() construction.

Removed

  • API teardown completed. JWT token-blacklist app (ninja_jwt.token_blacklist), the
    purge_expired_blacklisted_tokens function + scheduled_token_purge DBOS cron, and the
    django-ninja, django-ninja-jwt, pyjwt dependencies. New data migration
    scans/0018_remove_token_purge_job deletes the seeded token_purge ScheduledJob
    row. pydantic is now a direct dependency (the AI layer used it; it was transitive via
    django-ninja). Why: no JWT tokens exist any more, so nothing to blacklist or purge.
  • PDF reports (WeasyPrint, the /reports/<uuid>/pdf/ route, the PDF template and
    its tests). CSV export is kept (/reports/<uuid>/csv/, now session-auth only).
  • The React SPA source (frontend/ — React/Vite/Vitest), the CI frontend job, and the
    Dockerfile node build stage, plus the dependabot npm entry + dead frontend/WeasyPrint install
    steps. Why: the server-rendered apps/mvp UI replaces it; no Node toolchain remains.

Added

  • Single Docker image deploy (--target singleimage): embedded PostgreSQL +
    gunicorn + DBOS worker via honcho in one container, data on the mandatory /data
    volume (docker/entrypoint-singleimage.sh). The primary MVP deploy; the 3-tier
    compose/k8s path remains as the privilege-separated / scalable alternative.
    Why: one-container run for a small trusted single-user instance.

Fixed

  • cloud_enum timeout now degrades the scan to partial instead of wedging it.
    apps/cloud_assets/collector.py ran cloud_enum via subprocess.run(capture_output=True, timeout=1800). cloud_enum forks a worker process per cloud provider, and a
    surviving grandchild holding the captured stdout pipe can make subprocess.run
    block past its own timeout — TimeoutExpired fires internally but
    communicate() never returns, so the scan step wedges and never surfaces the
    timeout. The scan then sits running until the no-progress watchdog reaps it
    (and, in the MSSP wrapper, the poller gave up first and recorded failed).
    Fix: route cloud_enum through the existing run_capped helper (temp-file
    redirect + process-group SIGKILL + wait() on the direct child — the same
    anti-hang fix nuclei/nuclei_network already use), so the 30-min cap reliably
    raises ToolTimeout. The runner records that as a failed step and the scan
    finalizes partial with a report rather than hanging. Why: five
    Passive·Deep scans had failed for days on cloud_enum timed out after 1800s;
    a single passive tool timing out must not fail the whole scan.
  • Postgres OOM + worker connection resilience (preprod outage). A batch of
    Passive·Deep scans failed because openeasd-postgres was OOM-killed under
    concurrent scan load (1Gi limit, exit 137, killed 8×); each kill dropped every
    worker DB connection, and with persistent connections but no health checks the
    worker kept reusing the dead ones ("the connection is closed") for ~18h until
    it was manually restarted. Three-part fix: (1) k8s/postgres.yaml memory
    limit 1Gi→4Gi (request 256Mi→1Gi) so a batch of concurrent scans doesn't
    OOM the DB; (2) CONN_HEALTH_CHECKS=True on the Django DB config so a reused
    connection is verified + transparently reconnected after a server restart,
    instead of raising until it ages out; (3) a worker liveness probe (exec DB
    round-trip; no HTTP listener on the worker) that restarts the pod after ~2 min
    of Postgres unreachability so it re-establishes fresh connections. The
    OpenEASD-MSSP wrapper reported these faithfully — it was not the cause.