Skip to content

Fix v0.2 audit findings across security, data safety, and UX - #4

Merged
saime428 merged 1 commit into
masterfrom
fix/v02-audit
Jul 26, 2026
Merged

Fix v0.2 audit findings across security, data safety, and UX#4
saime428 merged 1 commit into
masterfrom
fix/v02-audit

Conversation

@saime428

Copy link
Copy Markdown
Owner

Summary

A six-dimension audit of v0.2.0 (architecture, code quality, security, frontend, tests, docs) produced 31 findings. Every finding acted on here was independently re-verified against the code first, which corrected three overstatements and reversed one recommendation outright (see Review notes).

Highest-severity issue: the web app had no authentication and leaned on a spoofable Host header, while the shipped Dockerfile and compose file hardcode --host 0.0.0.0. A published port therefore let anyone on the local network read every dream and repoint the AI provider to exfiltrate future analyses.

Changes

Security

  • Non-loopback binds enable token auth (cookie / X-DreamLoop-Token / ?token=); the access URL is printed at startup and persisted in .dreamloop/web_token, or pinned via DREAMLOOP_TOKEN.
  • Header-less writes are refused from non-loopback clients unless authenticated. Explicit --no-auth / DREAMLOOP_NO_AUTH=1 deliberately keeps them open — that mode has no token to offer a scripted client, so rejecting them would leave the JSON API unusable with no way to recover.
  • Tokens compare as bytes: compare_digest raises TypeError on non-ASCII input, turning a bad token into a 500 at the auth boundary.
  • ?token= is handed to a cookie and redirected out of page URLs, and redacted from uvicorn's access log (which records full query strings). API paths keep ?token= working — curl has no cookie jar.
  • 401s on /api/ paths return JSON, not HTML.

Data safety

  • migrate_analysis_table dropped the renamed old table unconditionally: an analysis table missing a required column lost every stored analysis silently. Unmigratable data is now preserved in a backup table, and the migration paths (previously 0% covered) have tests.
  • A corrupt config.json returned 500 from every page, including the settings page needed to fix it. Both config writers now replace the file atomically through one shared helper.
  • Invalid model JSON is wrapped as AnalysisIncomplete; a truncated or fenced local-model response used to surface as a 500.

Diagnosability

  • The three swallowed exceptions in web.py are now logged (the project had no logging at all), a missing dream no longer reports as a provider error, and image-generation failures are visible to the user.
  • Import errors map to 400/404 instead of 500.

Analysis flow

  • POST /drafts/analyze stores the draft and redirects, so refreshing no longer re-runs a slow LLM call and the result survives navigation.

Frontend

  • Dream text in cards is clamped; long entries turned cards into walls of text.
  • Heat-cell contrast now meets WCAG AA.
  • Skip link, loading-button reset on bfcache restore, bundled-font preload.

Performance

  • init() is idempotent — every DreamLoop method re-ran the full migration.
  • feedback_summary uses one join instead of a query per feedback row (measured 1757 ms at 800 rows before).

Tests and docs

  • Byte-exact source-string assertions replaced with format-insensitive checks; added coverage for the image generator, the endpoints that had none, and the CLI auth wiring.
  • Refreshed PROJECT_CONTEXT.md and the superseded frontend plan, documented that .dreamloop/ is created relative to the working directory, added SECURITY.md plus issue and PR templates.

Verification

  • pytest — 212 passed, 0 failed (was 157); coverage 87% → 91%
  • Real-uvicorn smoke test of the token flow — 10 checks passed. This caught a leak the TestClient-based tests structurally cannot see: the redirect strips the token from browser history, but the first request was still written verbatim to the access log. Fixed with a redaction filter and re-verified.
  • git diff --check — passed

Review notes

  • Verification reversed one finding. The audit asked for dark ink on heat levels 2–4; measured against the OKLCH ramp, dark text tops out near 2.1:1 there and cannot reach 4.5:1. Only the brightest step inverts; the mid steps use lighter text instead. Meeting the original request literally would have required brightening the ramp and destroying the density gradient.
  • One commit, not several. web.py carries token auth, the PRG rework, and error handling; database.py carries the migration fix and the drafts table. Splitting along those seams would produce commits that do not pass tests.
  • SECURITY.md intentionally lists only GitHub Security Advisories — no maintainer email is published. Add one if you want that channel.
  • CONTRIBUTING.md advertises "Good First Issues" but the repository has zero open issues and nothing under that label; that needs work on GitHub, not in code.
  • Deliberately not addressed: the 18.8 MB full-cmap fonts (preload added, unicode-range sharding left for later), and web.py / core.py remaining large modules.

A six-dimension audit of v0.2.0 (architecture, code quality, security,
frontend, tests, docs) produced 31 findings; every one acted on here was
independently verified against the code before being fixed.

Security (the only high-severity finding)
- Binding a non-loopback host now enables token auth: the access URL is
  printed at startup, and cookie/header/query credentials are accepted.
  Previously the app had no authentication at all and relied on a
  spoofable Host header, so a published Docker port exposed every dream
  to the local network and let anyone repoint the AI provider.
- Header-less writes are refused from non-loopback clients unless they
  authenticate. Explicit --no-auth / DREAMLOOP_NO_AUTH=1 keeps them open:
  that mode has no token to offer a scripted client, so rejecting them
  would leave the JSON API unusable with no way to recover.
- Compare tokens as bytes; compare_digest raises TypeError on non-ASCII
  input, which turned a bad token into a 500 at the auth boundary.
- Hand ?token= to a cookie and redirect it out of page URLs, and redact
  it from uvicorn's access log, which records full query strings.
- Return JSON rather than HTML for 401s on /api/ paths.

Data safety
- migrate_analysis_table dropped the renamed old table unconditionally,
  so an analysis table missing a required column lost every stored
  analysis silently. Unmigratable data is now preserved in a backup
  table, and the previously untested migration paths have coverage.
- Recover from a corrupt config.json instead of returning 500 from every
  page, including the settings page needed to fix it. Both config writers
  now replace the file atomically through one shared helper.
- Wrap invalid model JSON as AnalysisIncomplete; a truncated or fenced
  response from a local model surfaced as a 500.

Diagnosability
- Log the three swallowed exceptions in web.py (the project had no
  logging at all), stop reporting a missing dream as a provider error,
  and surface image-generation failures to the user.
- Map import errors to 400/404 instead of 500.

Analysis flow
- POST /drafts/analyze now stores the draft and redirects, so a refresh
  no longer re-runs a slow LLM call and the URL survives navigation.

Frontend
- Clamp dream text in cards, which long entries turned into walls of text.
- Fix heat-cell contrast to meet WCAG AA. Dark ink cannot reach 4.5:1 on
  the mid ramp steps, so only the brightest step inverts.
- Add a skip link, reset loading buttons on bfcache restore, and preload
  the bundled fonts.

Performance
- init() is idempotent; every DreamLoop method re-ran the full migration.
- feedback_summary uses one join instead of a query per feedback row.

Tests and docs
- Replace byte-exact source-string assertions with format-insensitive
  checks, and cover the image generator, the endpoints that had none, and
  the CLI auth wiring. 212 tests pass; coverage 87% -> 91%.
- Refresh PROJECT_CONTEXT.md and the superseded frontend plan, document
  that .dreamloop/ is created relative to the working directory, and add
  SECURITY.md with issue and PR templates.
@saime428
saime428 merged commit 2361915 into master Jul 26, 2026
1 check passed
@saime428 saime428 mentioned this pull request Jul 27, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant