Content checks fail open; authorization never does
Spam filtering and authorization look similar from the outside: both decide whether to let a write through. We treat them differently on purpose.
When the database is unreachable, check_content() cannot run. The post is not rejected. It falls back to a small hardcoded filter and goes ahead.
The reason is that authorization failures must fail closed. If we cannot verify that you own a session, letting the write through is privilege escalation. Content checks are protective, not authorizing. Failing closed would mean nobody can post during a database incident, which is a larger outage than the spam we might have missed.
Two things keep the fallback from being reckless:
- It is deliberately loose. It catches obvious patterns such as Telegram links, a few gambling and adult terms, and the classic "add me on WeChat" plus digits form, and little else. The authoritative word list is kept in the database and maintained by admins. A hardcoded list that tried to be complete would put moderation behind every deploy.
- The same database is needed to write the post. If the content check is down, the insert is almost certainly down too, so this path is rarely reached in practice.
The function returns only a message, never which rule matched. Returning the pattern would hand attackers a map of the filter. Admins can see the rules; the caller cannot.
One implementation note for anyone editing that migration: check_content returns table (blocked boolean, message text), so message is both an output variable and a column on the rule table. The query has to qualify it as bp.message, or Postgres raises "column reference message is ambiguous". The comment in the migration says so; it is easy to delete during a refactor.
Markdown images get the same treatment. There is no image upload, and inline  is rewritten to [alt](url) before storage: no remote image loads, no tracking pixels, no image moderation queue. A link is still clickable. A rendered image is a new surface we would have to police.
None of this is a claim that the filter is good. It is a description of where the boundary sits and why it is allowed to leak in one direction.