# Anti-spam

## The model

Quaeriva scores content instead of matching a single rule. One signal never decides an outcome on its own, because every single signal has legitimate traffic behind it.

Signals contributing to the score:

- account trust level and account age
- whether the email address is verified
- a normalised near-duplicate fingerprint of the content
- number of external links
- repeated linked domains
- posting burst rate per account and per address
- honeypot field hit
- implausibly short form completion time

The assessment returns a score, the reasons behind it, and one disposition:

| Disposition | Effect |
|---|---|
| `allow` | Published according to the normal moderation policy |
| `moderate` | Held for a moderator |
| `reject` | Refused |

## Thresholds

`community.spam_moderate_score` and `community.spam_reject_score` are configurable in **Settings → Community**.

Defaults favour **moderation over rejection**. A new low-trust account writing risky-looking content is usually a real person writing their first post badly, not a bot. Holding it costs a moderator a click; rejecting it costs you a member.

## Content fingerprinting

Fingerprints are computed after Unicode normalisation, whitespace collapsing and punctuation stripping, so cosmetic mutation of the same spam text still collides. This works for Arabic and English alike; normal prose in either language does not collide by accident.

## Honeypot and form age

Forms carry a hidden field that a human never fills and a session-bound signed timestamp.

- The honeypot field is **not** named after a common autofill field, so password managers do not trip it.
- The timestamp is signed and session-bound, so a client cannot simply forge an older value.
- The honeypot is hidden visually but excluded accessibly, so screen-reader users are not asked to fill it.

Either signal feeds the score. Neither is a permanent ban by itself.

## Optional challenge

Turnstile is off by default. When enabled, use it for registration, account recovery and risk-triggered posting. Do not challenge every ordinary comment. See [Security](SECURITY.md).

## Rate limits

Per-account and per-address limits apply to posting, reporting and voting. Limits throttle; they do not ban.

## Extending

The `spam.assessment` filter lets a plugin adjust the score after core scoring. A plugin **cannot** bypass a security check by returning `true` from a generic filter.

## Tuning advice

- Start with the defaults and read the moderation queue for a week.
- If real posts are being held, raise the moderate threshold before touching the reject threshold.
- Never lower the reject threshold to the point where a single signal reaches it.
- Address-based blocking alone is the classic way to ban a whole office, university or mobile carrier. Do not rely on it.
