CI & Build / Build now, or wait for Android? (push) Successful in 2s
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python tests (push) Successful in 9s
CI & Build / integration (push) Successful in 12s
CI & Build / Build & push image (push) Successful in 32s
Operator, before exposing the instance: *"I'd expect that we should have a proxy hops setting for how many proxy hops we should trust a shared real-ip at… and is there any session logging."* Neither existed, and the first one was a real hole. **The address was forgeable.** `client_address()` read the LEFTMOST `X-Forwarded-For` entry — nominally "the original client", and precisely the one a caller controls, because anything they send arrives before what proxies append. So `curl -H "X-Forwarded-For: 1.2.3.4"`, rotated per request, minted a fresh rate-limit bucket every time. Concretely: stuffing ONE account stayed limited (the account key is unforgeable and that is why it exists), but spraying MANY accounts from one source was not — each account got its own budget, and the per-address cap meant to bound the total was defeated by a header. On a LAN that is nothing. It is not nothing on a public host. Now it counts in from the RIGHT by `THOUGHTSYNC_TRUSTED_PROXY_HOPS`, default 1. Each hop appends what it saw, so the rightmost entries are the ones our own infrastructure wrote and a forged prefix lands to the left of them where it can never be selected — proven for the honest, forged, padded, CDN and shorter-than-configured cases. 0 ignores the header entirely; 2 is Cloudflare in front of a proxy. Too high is the dangerous direction, so a header shorter than configured falls back to the socket address rather than reaching further left. `X-Forwarded-Proto` had the same bug and now shares the same rule. Both live in a new `proxy.py` rather than being written twice — two places holding one decision is how issue 2183 happened, and this is the same decision. Env rather than the Settings UI, against rule 25's usual pull: it is deployment topology rather than preference, and the limiter consults it BEFORE opening a database connection, which is the entire point of checking a throttle before doing expensive work. Easy to move if that reads wrong. **And there was no logging at all** — `auth.py` had no logger, and the only record of anything was `device_tokens.last_used_at`. Sign-ins, failures, throttle trips, new accounts and device-token issuance now all log, with the attempted email and the trusted address. Deliberately including the email: it is the operator's own server, and "somebody failed a login" without saying against which account is not actionable. `basicConfig` at INFO in `create_app`, because hypercorn configures its own loggers and leaves the root at WARNING — without it every line above would have gone nowhere, which is a worse failure than not writing them. This is the app log, not an audit table. Not queryable, not retained past log rotation. The table is task 2939; this is what makes the next few days observable.
73 lines
3.2 KiB
Bash
73 lines
3.2 KiB
Bash
# ThoughtSync production settings. Copy to `.env` and edit:
|
|
#
|
|
# cp .env.example .env
|
|
#
|
|
# Only POSTGRES_PASSWORD has no default — compose refuses to start without it.
|
|
# Everything else here is optional. Anything NOT in this file (site name, signups,
|
|
# attachment limits, trash retention, link previews) is configured in the admin
|
|
# Settings UI and stored in the database, not here.
|
|
|
|
# --- required ---------------------------------------------------------------
|
|
|
|
# Generate one and keep it: changing it later means also changing it inside the
|
|
# database, or Postgres will reject the app's connection.
|
|
#
|
|
# openssl rand -base64 24 | tr -d '/+=' | head -c 32
|
|
#
|
|
# Stick to letters and digits. This value goes into a connection URL, so a `@`,
|
|
# `/`, `:` or `#` in it will be misparsed as URL structure rather than password.
|
|
POSTGRES_PASSWORD=
|
|
|
|
# --- optional ---------------------------------------------------------------
|
|
|
|
# Which build to run.
|
|
#
|
|
# latest tracks the `main` branch — the production line (default)
|
|
# dev tracks the `dev` branch — newer, less settled
|
|
# <commit sha> pins one exact build; every push publishes one, and this is
|
|
# the rollback lever when an upgrade misbehaves
|
|
#
|
|
# NOTE: `main` can sit well behind `dev`. If a feature you expect is missing,
|
|
# check which branch it actually landed on before assuming a bug.
|
|
#THOUGHTSYNC_TAG=latest
|
|
|
|
# The host port the app is published on.
|
|
#THOUGHTSYNC_PORT=5000
|
|
|
|
# Which interface to bind. The default (all interfaces) is what lets desktop
|
|
# clients on your network reach the server. Behind a reverse proxy, set this to
|
|
# 127.0.0.1 so only the proxy can talk to it.
|
|
#THOUGHTSYNC_BIND=0.0.0.0
|
|
|
|
# How many proxies sit in front of this app. This is a SECURITY setting, not a
|
|
# preference: it decides which entry of X-Forwarded-For is believed, and therefore
|
|
# whether a caller can forge their own address and slip the per-address rate limit.
|
|
#
|
|
# 0 nothing in front — the app is directly exposed
|
|
# 1 one reverse proxy terminating TLS (the default, and the usual case)
|
|
# 2 a CDN in front of that proxy, e.g. Cloudflare
|
|
#
|
|
# Set it to the number you actually run. Too HIGH is the dangerous direction — it
|
|
# starts trusting entries no proxy of yours wrote. Too low just means callers share
|
|
# a rate-limit bucket.
|
|
#THOUGHTSYNC_TRUSTED_PROXY_HOPS=1
|
|
|
|
# How much the app says. Credential events (sign-ins, failures, throttles, new
|
|
# accounts, device tokens issued) are logged at INFO and read with
|
|
# `docker compose logs app`.
|
|
#THOUGHTSYNC_LOG_LEVEL=INFO
|
|
|
|
# Database identity. Changing these AFTER the first start does not rename anything
|
|
# that already exists — the volume keeps whatever the first run created.
|
|
#POSTGRES_USER=thoughtsync
|
|
#POSTGRES_DB=thoughtsync
|
|
|
|
# --- a note on HTTPS --------------------------------------------------------
|
|
#
|
|
# The app marks its session cookie Secure automatically when a request arrives over
|
|
# HTTPS, directly or via a proxy setting X-Forwarded-Proto — no setting needed.
|
|
#
|
|
# Worth knowing if you use the desktop app: typing a bare hostname there defaults to
|
|
# https://, deliberately, so a device token never crosses the wire in cleartext by
|
|
# accident. Serving over plain HTTP means typing the `http://` yourself.
|