Skip to content
flowplanDocumentationCommunityMarketplaceStart page

Storage and operations

Security and attack detection

Which security events Flowplan records, how it detects attacks and how to pass them on to logs, OpenTelemetry, fail2ban or CrowdSec.

6 min read

On this page
  1. Security
  2. What is recorded
  3. Administration → Threats & incidents
  4. Detection rules
  5. Logs (stdout, JSON)
  6. OpenTelemetry
  7. Prometheus
  8. fail2ban
  9. CrowdSec
  10. More variables

Security

Flowplan records security-relevant events, detects typical attacks in them and hands both to your own tools. It all works without setup; the variables below only connect it to the outside.

What is recorded

  • Sign-in: successful, failed, refused (limit or block), from a new device
  • Account: password changed or reset, passkey added or removed, two-factor on or off, API token created or revoked, sessions ended, account deleted
  • Rights: admin rights granted or removed (also through SSO or LDAP groups at sign-in), roles, invitations, accounts disabled or reset by admins
  • Sharing and data: share links, published pages, all exports and backups
  • Refused: requests from a foreign origin, internal addresses (SSRF), scripts in submitted content, many refused API requests from one address
  • Administration: LDAP, SCIM, restore, extensions, domains, AI provider, alerts and other security-relevant settings

Privacy: for an address, the database keeps only its network (IPv4 /24, IPv6 /48) and a hash keyed with a secret of the instance – enough to group and block, not the address itself. Accounts of failed sign-ins are only stored masked (ma***@example.com). Events are deleted after 90 days (FLOWPLAN_SECURITY_RETENTION_DAYS).

Administration → Threats & incidents

The page /#admin/threats (group Security) sums it up and refreshes every 30 seconds:

  • State: open incidents (critical ones in red), hints on missing connections, numbers of the last 24 hours and sign-ins per hour
  • Incidents of the detection rules, with actions right next to them: resolved, block the address or network for 24 hours, end all sessions of the person, disable the account. Details & next steps opens an incident's own page (/#admin/threats/incident/<id>): on the left what happened – why the rule fired (with its thresholds), the period, the origin and whether it is already blocked, the accounts tried, successful sign-ins in the same period (red when an account may have been taken over afterwards), networks, countries, devices and the timeline of all its events; for probing also the requested paths with answer and count (IDs and tokens replaced by :id, :n and :token). On the right what the incident means, when it is harmless and what to do step by step – with the matching action (block the address, end sessions, disable the account, resolve) or the link to it right at the step.
  • Addresses with failures, events with filters (severity, kind) and export as CSV or JSON
  • Signed-in people (admins first), open ways in (API tokens, share links, public pages of the whole instance) to revoke
  • Blocks of addresses and networks, for a time or indefinitely. Blocked addresses only get 403 – signed-in people in them too. Your own address and network cannot be blocked.

New incidents go to all instance admins as notifications of the kind Security incidents of the instance. Each admin chooses how under Settings → Notifications: the channels (by default inbox, push, e-mail and chat apps) and whether all incidents or critical only arrive; the notification leads straight to the incident's page, and the top of the incident list says how you are notified right now. When a webhook is set up under Monitoring → Alerts by webhook, every incident also goes there right away (event ops.security with rule and severity). At most 30 reports an hour, so an attack floods nobody.

Whoever signs in with a device not seen for 90 days gets a security notice themselves – a taken-over account is often noticed by its owner first.

Detection rules

RuleTriggerSeverity
Password guessing on an account10 failures on one account in 15 minuteswarning
Many failures from one address20 failures from one address in 15 minuteswarning
Many accounts from one address5 different accounts from one address in 15 minutes (credential stuffing)critical
Sign-in after many failuressuccessful sign-in after 5 failures on the same accountcritical
Admin from a new devicean admin signs in with a device not seen for 90 dayswarning
Two countries in a short timesign-ins from two countries within 2 hours (needs FLOWPLAN_COUNTRY_HEADER)critical
Rights changedadmin rights, LDAP/SCIM, restore, extensions, domainscritical
Many exportsmore than 20 exports by one person in an hour (and far above their usual)warning
Probing60 refused API requests (401/403/404) from one address in 10 minuteswarning
Attacks through content5 refused scripts or internal addresses in an hourwarning

The same thing within a day counts on the open incident instead of opening a new one.

Logs (stdout, JSON)

Every event is a JSON line on stdout – Docker, Loki, Elastic, Graylog or Datadog pick them up as they are:

{"ts":"2026-10-30T19:11:27.536Z","level":"warn","logger":"flowplan.security","event":"login.failure","severity":"warn","ip":"203.0.113.7","network":"203.0.113.0/24","subject":"ma***@example.com","device":"Chrome · macOS","detail":"password"}

More fields depending on the event: user (account id), country. The full address (ip) is only in this line – for fail2ban and CrowdSec. FLOWPLAN_SECURITY_LOG=anonymous leaves it out, =off writes no lines.

OpenTelemetry

With OTEL_EXPORTER_OTLP_ENDPOINT (or OTEL_EXPORTER_OTLP_LOGS_ENDPOINT) Flowplan sends the events as OTLP logs (HTTP/JSON, /v1/logs) to your collector, e.g. Grafana Alloy, the OpenTelemetry Collector, Grafana Cloud, Honeycomb or Datadog. Credentials go in OTEL_EXPORTER_OTLP_HEADERS (api-key=…,x-scope-orgid=…), the service name in OTEL_SERVICE_NAME (default flowplan). The fields of the log line arrive as attributes flowplan.*.

Prometheus

/api/metrics also has flowplan_security_events_total{event,severity}, flowplan_security_incidents_open{rule,severity} and flowplan_security_blocks. An alert in Prometheus or Grafana:

- alert: FlowplanSecurityIncident
  expr: sum(flowplan_security_incidents_open{severity="critical"}) > 0
  for: 1m

fail2ban

With the journald logging driver (logging: { driver: journald } in the Compose service) fail2ban reads the lines directly:

# /etc/fail2ban/filter.d/flowplan.conf
[Definition]
failregex = "logger":"flowplan\.security","event":"login\.(failure|refused)","severity":"[a-z]+","ip":"<HOST>"
            "logger":"flowplan\.security","event":"request\.probing","severity":"[a-z]+","ip":"<HOST>"

# /etc/fail2ban/jail.d/flowplan.conf
[flowplan]
enabled = true
backend = systemd
journalmatch = CONTAINER_NAME=flowplan
filter = flowplan
maxretry = 10
findtime = 10m
bantime = 1h

When fail2ban blocks in the firewall in front of the reverse proxy, the request never arrives. The blocks in Flowplan itself also work behind Cloudflare or a proxy fail2ban does not control.

CrowdSec

A parser for the log lines and a scenario for password guessing (example; the acquisition with labels: { type: flowplan }):

# parsers/s01-parse/flowplan.yaml
name: flowplan/flowplan-logs
description: "Flowplan security events"
filter: "evt.Line.Labels.type == 'flowplan'"
onsuccess: next_stage
nodes:
  - filter: "UnmarshalJSON(evt.Line.Raw, evt.Unmarshaled, 'flowplan') in ['', nil] && evt.Unmarshaled.flowplan.logger == 'flowplan.security'"
    statics:
      - meta: log_type
        expression: "'flowplan_' + replace(evt.Unmarshaled.flowplan.event, '.', '_')"
      - meta: source_ip
        expression: evt.Unmarshaled.flowplan.ip

# scenarios/flowplan-bf.yaml
type: leaky
name: flowplan/login-bf
description: "Flowplan password guessing"
filter: "evt.Meta.log_type in ['flowplan_login_failure', 'flowplan_login_refused']"
groupby: evt.Meta.source_ip
capacity: 10
leakspeed: 30s
blackhole: 5m
labels:
  remediation: true

More variables

VariableDefaultEffect
FLOWPLAN_SECURITY_LOGonon (with address), anonymous (without), off (no log lines)
FLOWPLAN_SECURITY_RETENTION_DAYS90How long events stay in the database (at least 7)
FLOWPLAN_COUNTRY_HEADERemptyThe proxy's header with the country of the request, e.g. CF-IPCountry behind Cloudflare – turns on the rule "two countries". Only set it when the proxy sets the header itself.
OTEL_EXPORTER_OTLP_ENDPOINTemptyOTLP collector for the events (see above)
OTEL_EXPORTER_OTLP_LOGS_HEADERSOTEL_EXPORTER_OTLP_HEADERSHeaders for the collector, for the logs only: api-key=abc,x-scope=flowplan (comma-separated, values URL-encoded). Without it, OTEL_EXPORTER_OTLP_HEADERS applies.

Everything with addresses needs the reverse proxy to set X-Forwarded-For and a correct FLOWPLAN_TRUSTED_PROXIES (Configuration). When no address arrives, the page shows a hint.

Was this helpful?
YesThanks!
No – tell us what's missing

Found a mistake or something missing?Edit on GitHub