Skip to main content

Fleet Bans

One agent blocking an address is an observation. Forty agents blocking the same address is a different kind of fact — and the second one is invisible to any agent looking only at its own traffic.

Fleet bans close that gap. Each agent reports what it decided to block, the platform corroborates across the deployment, and every agent pulls back the aggregated result.

Advisory by default

Out of the box a fleet ban does not install a block. The set is consulted, the outcome counted and logged, and traffic is unaffected.

That is deliberate. Acting on what other machines concluded is a large step, and it should be justified by the false-positive rate that advisory mode exists to measure. Run it, read the numbers, then turn on enforcement for the agents where they hold up.

Two halves of one loop​

DirectionConfigScope
Ban ledgerUpload — what this agent decided to blockplatform.ban_ledgerfleet_bans:write
Fleet bansDownload — what the fleet corroboratedplatform.fleet_bansfleet_bans:read

They are separate stores with opposite provenance, and kept separate on purpose. The ledger holds first-party evidence — decisions this agent made from its own events. The fleet set holds bans this agent has no local evidence for. Merging them is how a fleet ban gets re-reported upward as fresh evidence and corroboration becomes an echo of itself.

What a fleet ban carries​

Field
agentsDistinct agents that reported banning this address
agent_count_totalLive agents in the workspace — the denominator
confidencePlatform confidence, 0–100
scorePlatform score, 0–99, directly comparable to a threat-MMDB score
first_banned_unixWhen the fleet first banned this address
expires_unixWhen the ban lapses; absent means no expiry
layersBitmask of the enforcing layers that reported it

agent_count_total is the field that makes the rest readable. agents: 3 is unanimity in a three-agent fleet and noise in a three-thousand-agent one. Any threshold you write should be a ratio, not a count.

The layer bitmask records what kind of agreement it is — access_rules, smart_firewall, ids, waf, threat_intel, captcha, rate_limit. Three agents agreeing via three different layers is stronger corroboration than three agreeing via the same one, which is usually the same rule firing three times.

The platform is not trusted​

Both local safety predicates are re-applied to everything the platform serves, and anything failing either is dropped regardless of what arrived:

PredicateDrops
Ban-safeAnything not unambiguously global-unicast. Fails closed.
Never-banYour own infrastructure — HOME_NET, and the node's own IPs

The never-ban check is the one that matters most in practice. A public load balancer VIP is global-unicast, so the first predicate would permit it; banning it blackholes every client behind it. It is protected through HOME_NET: in Kubernetes the Synapse operator puts the VIPs there, and elsewhere you list them in ids.address_vars.HOME_NET yourself.

The asymmetry is intentional: the agent is permissive about what it uploads — an unsafe ban is worth reporting so the fleet can see it — and strict about what it accepts.

Rejections are counted separately as rejected_unsafe, rejected_protected and rejected_malformed, so a set that is arriving but being discarded is visible rather than looking like an empty fleet. A malformed record is skipped and counted rather than failing the whole payload.

Configuration​

platform:
ban_ledger:
enabled: true
interval_secs: 60

fleet_bans:
enabled: true
refresh_secs: 60
advisory: true
advisory_dedup_secs: 300
enforce: false

Both inherit platform.base_url, so neither normally states a URL.

interval_secs governs how fast a ban propagates, not how much traffic this generates: a tick whose snapshot has not changed sends nothing at all, and a forced resend happens only every twelfth suppressed tick. refresh_secs is a cheap version check — the artifact is downloaded only when the version changes.

The shipped configuration sets both to 60 seconds (left out, each defaults to 300), so the loop is symmetric rather than having a slow leg. Raise refresh_secs if a large fleet makes the version endpoint hot.

Enforcing the set​

With enforce: true an agent stops only advising and blocks what the fleet corroborated:

  • In the kernel (Linux, unless firewall.mode: none). Each fleet ban goes into the same IP filter local bans use, and comes out again when its deadline passes, when the fleet withdraws it, when your own configuration starts to allow the address, or when it no longer ranks within the fleet's share. While the agent runs, a new set reaches the kernel as soon as it is downloaded, and a ban whose deadline passes comes out within about half a minute.
  • At the proxy. Behind a load balancer the kernel sees only the balancer's address, so a kernel ban on the client never matches. With proxy.protocol.enabled: true and a balancer that sends PROXY protocol headers, the proxy knows the real client and refuses a fleet-banned one with 403 and X-Block-Reason: fleet_ban. It does not use X-Forwarded-For for this. The proxy refuses every enforceable fleet ban, whether or not the kernel holds it.

What is never enforced, whatever the set says:

  • An address you allow. access_rules.allow beats a fleet ban, including when the allowed entry is a wider range than the banned address.
  • Your own infrastructure. The same never-ban checks as above — HOME_NET, which is where load-balancer VIPs belong, and the node's own IPs — and, on Unix, any address the host holds on any interface.
  • An address your configuration already blocks. A configured access_rules.block entry covering it drops it anyway. On nftables the fleet's entry would even keep that range from being installed, so a held fleet ban a configured range comes to cover is lifted for it.
  • A ban without a deadline. Every fleet ban the platform publishes expires unless agents keep reporting it; a ban that does not is never installed.
  • More than its share, in the kernel. Fleet bans take at most a quarter of each address family's IP filter (firewall.max_ip_filter_entries_v4 / _v6), leaving at least three quarters for this agent's own bans and rules. When the set is larger, the most widely corroborated bans win the kernel's quarter, and the proxy still refuses the rest. The quarter is of the filter the kernel actually has: a resized filter takes effect only once the agent's pinned BPF state is removed.

A fleet ban and a local ban on the same address do not interfere: lifting either leaves the other in place.

An enforced fleet ban is never reported as this agent's own. The ban ledger leaves it out, so it is never uploaded, and the platform ignores it when corroborating. Without that, one agent's ban would come back as every other agent's evidence and never expire.

Enforcement is Linux-only in the kernel; the proxy refusal works wherever the proxy runs. Each install is a security event with source fleet_ban and rule id fleet_bans.enforce, and so is an address's first refusal at the proxy in each advisory_dedup_secs window (at most 50 such events a second); every refusal is counted. Each lift is a synapse.fleet_ban.lifted OpenTelemetry log record, which your own collector keeps (the platform stores only block events). The synapse.fleet_bans.* metrics report the set's size and age, what is enforced, and what was skipped and why.

Limits worth knowing​

  • Advisory means advisory. Consulted, counted, logged. No traffic is affected until enforcement is turned on.
  • An unloaded set is not an empty one. A miss before the first set has been published means we do not know yet, not not banned. Enforcement never lifts a ban for that reason: until a set arrives, only bans past their own deadline come out.
  • A stopped agent lifts nothing. While it is down, what it installed stays in the filter, past its deadline if need be; only nftables expires an entry by itself, within the hour. The agent records what it installed, and on the next start it lifts whatever the fleet withdrew or what expired in the meantime.
  • Turning enforcement off takes a restart. enforce is read at startup. The first start with it off lifts everything the agent installed.
  • A ban the kernel will not take waits. An address the IP filter refuses (a full filter, for one) is tried again after five minutes. After three refusals in one address family, the rest of that family waits for the next pass; bans already held, and the other family, are unaffected.
  • Corroboration is not proof. Agents in one deployment often run one policy, so agreement can mean the same rule fired everywhere rather than independent confirmation. Read the layer mask before treating a count as consensus.
  • It needs both scopes. fleet_bans:write to contribute, fleet_bans:read to receive. An agent with only read is a passenger.

Use cases​

  • Measure before enforcing — run advisory for a period and read the false-positive rate against your own traffic, then enforce where it holds up.
  • Close the window on a scanner — once enforcing, an address the fleet agrees on is blocked on agents it has not reached yet.
  • Spread a detection across a fleet — one agent seeing a scanner means every agent knows about it within about a minute.
  • Corroborate a marginal signal — a source your local threshold nearly caught is easier to judge when forty other agents already did.
  • Spot a bad rule — a sudden spike in reported bans across one layer is usually a rule, not an attack.

See also​