11 Commits
Author SHA1 Message Date
dependabot[bot] 50600a0269 Bump python from 3.12-slim to 3.14-slim in /docker
Bumps python from 3.12-slim to 3.14-slim.

---
updated-dependencies:
- dependency-name: python
  dependency-version: 3.14-slim
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-07-13 08:53:48 +00:00
Bentlybro 9564b63bc2 Pin both Dockerfiles to python:3.12-slim
docker/Dockerfile used 3.14-slim and deploy/Dockerfile used 3.11-slim.
Unify on 3.12 — the version the CI matrix tests against and the local
venv runs — so the image matches what's actually validated. Update the
docs that referenced 3.11 to match.
2026-07-06 13:31:04 +01:00
Bentlybro a38762dd05 Switch gunicorn worker from eventlet to gthread
Cloud Run was 500-ing every async route on the eventlet worker:

  RuntimeError: asyncio.run() cannot be called from a running event loop
  RuntimeError: You cannot use AsyncToSync in the same thread as an
                async event loop - just await the async function directly.

Root cause: standard Flask decorators (@login_required from Flask-Login,
@limiter.limit from Flask-Limiter, and Flask's own async-view wrapper)
call ``flask.current_app.ensure_sync(obj)(*a, **k)``, which uses
``asgiref.AsyncToSync``, which calls ``asyncio.run()``. Under eventlet's
greenlet hub ``asyncio.run()`` raises because the hub *is* the running
loop. Every ``async def`` route wrapped by any of those decorators (i.e.
all of /api/moderate, /auth/*, /dashboard/*) dies at the decorator
boundary before the view body ever executes. Sentry AUTOMOD-3B/3C/3D.

The dev server didn't show this because werkzeug gives each request a
fresh OS thread with no existing event loop, so asgiref can spin up its
own loop fine.

gthread worker + SOCKETIO_ASYNC_MODE=threading:
- Real OS threads for each request → asgiref's AsyncToSync works.
- Flask-SocketIO threading mode supports WebSockets for our
  dashboard-scale realtime traffic.
- Dev (run.py) and prod now use the same async mode, so behaviour
  matches end-to-end.
- 50 threads/worker = plenty of concurrency for HTTP + long-lived
  WebSocket connections.

Dropped eventlet and psycogreen from requirements.txt — they were pinned
specifically for the eventlet worker, neither is used any more.
2026-04-22 14:22:26 +01:00
Bentlybro 478ca18ad8 Phase 0 hardening + batched AI moderation + websocket/perf fixes
Security / correctness
- Privilege escalation: rule/api-key/project-update routes now gate on
  can_manage_members instead of is_member; plain members could previously
  rewrite or disable moderation rules and delete API keys.
- Project.is_owner() AttributeError on non-admin API-user deletion (route
  + template) replaced with user_id comparison.
- Flask-Limiter on /auth/login, /auth/register, /auth/change-password,
  and /api/moderate (per API key id).
- Remove silent SECRET_KEY fallback: docker-compose fails hard if unset;
  production config validator refuses to boot without SECRET_KEY and
  OPENAI_API_KEY.
- Require verified email in Google OAuth auto-link flow.
- User-supplied regex now evaluated via the regex package with a 1s
  timeout, closing the ReDoS vector.
- Cap batched AI chunk count (max 40) and concurrent workers (200 -> 10).
- openai>=2.32 (from 1.55.3 so reasoning_effort kwarg works); add regex,
  Flask-Limiter, gunicorn, eventlet, psycogreen; drop the stdlib-shadowing
  pypi asyncio package.

Production runtime
- New wsgi.py: eventlet.monkey_patch + psycogreen.patch_psycopg so DB
  queries cooperate with the event loop; sets SOCKETIO_ASYNC_MODE=eventlet
  for app startup.
- Dockerfiles run as non-root app user and CMD gunicorn with
  --worker-class eventlet. run.py refuses FLASK_CONFIG=production so a
  misconfigured deploy fails loudly.
- cloudbuild.yaml stops managing env vars/secrets on deploy — it was
  about to clobber secret-backed env vars with plain ones. Runtime config
  is now owned by the Cloud Run service.

SocketIO async-mode fix (was causing full site lockup during API calls)
- async_mode picked via SOCKETIO_ASYNC_MODE env var, default "threading".
  The previous sys.modules auto-detect false-positived because
  flask_socketio / python-engineio import eventlet on load whenever it is
  installed, pinning a non-monkey-patched eventlet server in front of
  blocking stdlib I/O.

Real-time dashboard updates
- New content_received WebSocket event fires when the API accepts a
  submission; UI renders a "Processing…" row immediately and updates it
  in place when moderation_update arrives.
- Fix stale updated_at timestamp in moderation_update payloads.

Batched AI rule evaluation
- Replace the per-rule parallel fan-out (N OpenAI calls) with a single
  batched call evaluating all AI-prompt rules at once. On a 6-rule
  default project this drops safe-content cost from 5-12s to ~1-4s.
- response_format=json_object + max_completion_tokens=4000 so the model
  emits strict JSON and stops at the close-brace.
- OPENAI_REASONING_EFFORT config: minimal/low/medium/high for gpt-5 /
  o-series; accepts "none" on gpt-5.4. Works around the gpt-5-nano case
  where reasoning tokens ate the full output budget and returned empty
  responses.

Client errors
- Malformed / non-UTF-8 JSON bodies now return 400 with a clear message
  about UTF-8 encoding instead of a generic 500.

Logging
- Single INFO line per moderation: the existing
  "Content <id>: decision in Zs" summary.
- Per-stage timing, websocket scheduling, and batched-AI call details
  moved to DEBUG so they are available when diagnosing latency without
  cluttering steady-state logs.
2026-04-21 16:01:18 +01:00
dependabot[bot] 57f3cd4bbc Bump python from 3.13-slim to 3.14-slim in /docker (#33)
Bumps python from 3.13-slim to 3.14-slim.

---
updated-dependencies:
- dependency-name: python
  dependency-version: 3.14-slim
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-17 10:49:42 +00:00
dependabot[bot] c2d7a776c6 Bump python from 3.11-slim to 3.13-slim in /docker
Bumps python from 3.11-slim to 3.13-slim.

---
updated-dependencies:
- dependency-name: python
  dependency-version: 3.13-slim
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
2025-10-07 15:10:09 +00:00
Bentlybro c6d1944bff Delete .dockerignore 2025-09-25 13:29:24 +01:00
Bentlybro bc370f2cc1 Remove default admin credentials from config and compose
Eliminates hardcoded default values for ADMIN_EMAIL and ADMIN_PASSWORD in both config.py and docker-compose.yml, requiring explicit environment variable configuration for improved security.
2025-09-11 09:35:49 +01:00
Bently 76916b0559 Update README.md 2025-08-20 19:09:39 +01:00
Bently 59dc4e33e0 Rename .env.docker to .env.docker.example 2025-08-20 19:09:21 +01:00
Bentlybro 70bf7526fc Add Docker setup for AutoModerate
Introduces Docker configuration files including Dockerfile, docker-compose.yml, environment template, and documentation to enable containerized deployment of the AutoModerate application with PostgreSQL. This setup streamlines local development and production deployment using Docker.
2025-08-20 19:08:31 +01:00