Repository navigation
prefixd 0.20.0 makes rustbgpd the default BGP speaker. prefixd drives it over authenticated gRPC, and GoBGP stays maintained and selectable. Upgrading from v0.19.1 takes one maintenance step; read the upgrade notes below first.
- rustbgpd by default. The Compose file has
rustbgpdandgobgpprofiles that run the same controller image. CI qualifies rustbgpd 0.76.0 (minimum), 0.77.0 and 0.78.0, the Compose pin. - FlowSpec checked at a real router. A new interop workflow runs prefixd with each speaker against FRR 10.7.1 and asserts the exact rules FRR receives through announce, police, withdraw, TTL expiry and speaker restart. This is control-plane evidence, not router enforcement.
- Safer intent. Dry-run mitigations are never announced when a POP switches to enforced,
max_new_per_minuteis now enforced per POP, and every mitigation creation is written to the audit log. - Redesigned dashboard. One consistent design system, the prefixd green accent, and pages that put active mitigations, signal, enforcement evidence and expiry first.
- Release assets. Each release now ships a Linux amd64 tarball with
prefixdandprefixdctl, a CycloneDX SBOM, checksums, and digest-pinned images.
Upgrade notes
- Upgrading from v0.19.1 is one maintenance step: back up PostgreSQL, keep
pop,modeand the GoBGP endpoint unchanged, runprefixd --adopt-legacy-speakeronce, then start normally. Migrations 013–015 apply on first start. Follow v0.19.1 → 0.20.0, which includes a route that keeps the v0.19.1 Compose layout and pins the 0.20.0 images. - An unchanged v0.19.1
prefixd.yamlloads and stays on GoBGP. Its removed keys are ignored with one warning each; 0.21.0 rejects them, so delete them during the upgrade. See Removed below. - Switching a POP from
mode: dry-runtoenforcednow needsprefixd --retire-dry-run-intentfirst while unexpired dry-run mitigations remain. Enforced startup refuses to run until they are retired. - Review Breaking below before upgrading: API and detector clients see new
202,400,409and422responses, saved pagination cursors stop working, and Compose commands need a speaker profile. - Source builds need Rust 1.89 or later.
Known limitations
- A
201means prefixd saved intent and the speaker accepted the local request. It doesn't prove export or that a router installed the rule. GoBGP read-back is its accepted local RIB after import policy, not retained intent, and peer health alone doesn't prove a mitigation is installed. - TTL expiry needs the running controller. If prefixd stops while the speaker stays up, rules stay announced.
- Run one controller per POP and speaker target. The lifecycle lock doesn't fence controllers in other processes.
- Unsupported unrelated local rules on a GoBGP speaker fail the complete observation and block reconciliation until an operator removes them.
- GoBGP process metadata is unsupported in health detail.
- Vendor forwarding qualification and a desired/acknowledged announcement ledger remain open; write-only speaker backends, ExaBGP, an HTTP shim, exec transport and runtime plugins aren't supported.
- Downgrading to v0.19.1 removes dry-run origin protection and ignores pending withdrawal cleanup. Retire dry-run intent and drain cleanup first.
- Release binaries are Linux amd64 only.
Breaking
-
Make rustbgpd the default speaker.
bgp.backendselectsrustbgpdorgobgp;bgp.mode: sidecarremains a GoBGP spelling. Whenbgp.backendis unset, a configuration with onlybgp.gobgp_grpcresolves to GoBGP and warns, one with bothbgp.gobgp_grpcandbgp.rustbgpdfails to load untilbgp.backendis set, and one withoutbgp.gobgp_grpcresolves to rustbgpd. Shipped configurations setbgp.backendexplicitly. 0.21.0 will requirebgp.backendwheneverbgp.gobgp_grpcis set. -
Bind each POP to an immutable, credential-free speaker target before connecting. Migration 014 adds the targets and retains target IDs on withdrawal requests; lifecycle guards reject missing or mismatched ownership, and startup rejects a target that differs from the bound one. A database with v0.19.1 speaker history needs the one-shot
prefixd --adopt-legacy-speaker, which binds the configured target, audits the adoption and exits without connecting to the speaker. Credential rotation preserves the binding; changing speakers needs the drained target transfer in the upgrade guide. -
Refuse enforced startup while the POP has unexpired dry-run mitigations, and return
409without storing the event when a detector event would extend one in enforced mode. Retire them withprefixd --retire-dry-run-intent. See Security below. -
Reject ban events from
mode: corroboratingsources on/v1/events/batchand the Alertmanager, FastNetMon and generic webhook adapters, as/v1/eventsalready did. See Security below. -
Return accepted intent with a request-local
announcement_outcome: "unconfirmed"and mitigation ID after speaker errors, including batch and signal wrappers. Manual creation uses HTTP202for this case; ordinary success and dry-run keep201. The dashboard warns that announcement is unconfirmed and opens the saved intent. -
Enforce the documented
quotas.max_new_per_minutelimit, which was parsed but never applied. Admission now rejects a new mitigation with422when the POP already has that many mitigations created in the trailing 60 seconds, checked in the same serialised transaction as the active quotas, so controllers sharing the database cannot both take the last slot. A rejected request saves nothing, announces nothing and uses no quota slot, and incrementsprefixd_guardrail_rejections_total{reason="NewMitigationRateExceeded"}. Dry-run creations count; TTL extensions by repeat events do not.0admits no new mitigations, as for the active quotas. The shipped configs set 30, so a burst above 30 new mitigations a minute at one POP is now rejected; raise the limit before upgrading if you rely on larger bursts. -
Reject duplicate map keys in YAML configuration, which the previous parser could silently replace with the later value. Unknown keys in
observabilityandshutdownother than the removed keys listed below now fail loading. -
Return structured
400errors instead of silently dropping or ignoring input: malformed or empty date filters on event, audit, mitigation and signal-group listings; invalid mitigation and signal-group status tokens; empty or NUL POP filters; and malformed, oversized, wrong-endpoint or superseded pagination cursors. -
Reject
http.auth.mode: mtlsat startup without native TLS and nonempty server certificate, key and client-CA paths. See Security below. -
Replace the Compose layout.
docker-compose.ymlnow hasrustbgpdandgobgpprofiles in one controller image, named volumes seeded from image defaults, and prebuiltmainimages, in place ofbuild: .and the./configsbind mount. Every Compose command selects a profile. The controller runs as UID 999, and Prometheus and Grafana publish on host loopback only. -
Require Rust 1.89 or later to build.
-
Rust library users:
RustBgpdConfigliterals need the new optional identity fields or..Default::default(). Removed library items are listed under Removed.
Removed
-
Remove the ineffective configuration keys
escalation,http.auth.ldap,http.auth.radius,timers.correlation_window_seconds,observability.audit_log_path,observability.metrics_listenandshutdown.preserve_announcements. 0.20.0 still loads them, ignores them and logs one warning per key withrejected_in: "0.21.0"; 0.21.0 rejects them. Settings JSON dropsescalation,timers.correlation_window_secondsandshutdown.preserve_announcements, and the shutdown log drops its preserve-announcements attribute. Playbooks select their first step, credentials authentication uses PostgreSQL operators, the correlation engine usescorrelation.window_seconds, metrics stay on the private API listener and shutdown still leaves speaker rules in place. -
Remove timestamp-only pagination cursors. Mitigation listings use versioned opaque cursors ordered by creation time and mitigation ID; event, audit and signal-group listings use endpoint-specific
e1,a1ands1cursors with timestamp/UUID continuation, so timestamp ties no longer omit rows. Cursors saved from v0.19.1 return400; restart from the first page. Filters and response fields are unchanged. Undecodable stored rows fail the whole page, including lookahead, rather than silently truncating results or manufacturing audit actors or group dimensions; signal-group detail, open/expired reads and existing-group insert results use the same fallible conversion. -
Hide the escalated mitigation state and the
mitigation.escalatedalert event; no code path produces either. The dashboard drops the Escalated status filter, command-palette shortcut and alert/notification options, and the references stop listing them. Storedescalatedrows, thestatus=escalatedAPI filter and saved alert filters or notification mutes that namemitigation.escalatedstill parse. Dashboard toasts for TTL extensions (mitigation_updated) now follow themitigation.createdmute instead of the hidden escalated one. -
Retire dashboard mock mode, unused legacy toast and CSS files, unused UI scaffolding with its 23 exclusive direct dependencies, the stale npm lock and the obsolete
next lintcommand. Real API behavior and Sonner notifications are unchanged. -
Remove from the Rust library:
RepositoryTrait::find_active_by_victimand its PostgreSQL and mock implementations;Settings.escalation,AuthConfig.ldapandAuthConfig.radius; the typesEscalationConfig,EscalationEvaluator,EscalationDecision,LdapConfigandRadiusConfig;Playbooks::get_escalation_step; and the unused policy correlation prototype. Historical escalated state and the playbook step schema are retained.
Security
-
Keep dry-run mitigations local after a switch to
mode: enforced. Before this fix, the first enforced reconciliation after a restart announced every unexpired dry-run mitigation. Migration 015 records each mitigation's admitting mode. Reconciliation never announces a dry-run mitigation, and its withdrawal or TTL expiry never deletes speaker state. In enforced mode, a detector event no longer extends one; it returns409without storing the event. Enforced startup now refuses to run while the POP has unexpired dry-run mitigations. The new one-shotprefixd --retire-dry-run-intentwithdraws them locally, writes an audit entry for each and exits without contacting the speaker. On first start, live rows from before the migration take the configured mode, with one system audit entry, so upgrade without changingmode. Downgrading removes this protection; retire dry-run intent first. -
Reject ban events from
mode: corroboratingsources on/v1/events/batchand the Alertmanager, FastNetMon and generic webhook adapters, as single/v1/eventsalready did, so such a source can no longer create a mitigation alone. Batch rejects only the offending item; adapter unban and resolved alerts still reach withdrawal. -
Reject
http.auth.mode: mtlswithout native TLS and nonempty server certificate, key and client-CA paths before database, speaker or listener startup. Programmatic application-state construction enforces the same validation. Configure all three TLS paths, or use bearer/credentials authentication when a proxy forwards plaintext to prefixd; forwarded identity headers do not authenticate mTLS clients. -
Reject local and documentation IPv6 literals in alert destination URL edits, including IPv4-mapped forms of blocked IPv4 addresses, and reject absolute
localhost.names and their subdomains. Rejected saves preserve the running configuration and saved secrets; DNS and redirect targets are not checked by this literal-address validation. -
Return controlled database decode errors for PostgreSQL timestamps outside Chrono's supported finite range, including infinity, instead of unwinding application-owned reads. Preserve ordinary timestamp precision and nullability.
-
Reject non-finite or out-of-range signal confidence before primary event or corroborator ingestion changes state. Preserve optional confidence, inclusive zero/one boundaries and corrected retries with the same external event ID.
-
Preserve exact policing rates through both speaker encoders, including 1–7 bps instead of turning them into discard. Reject missing, zero or inexact police rates and rates on discard before saving or extending intent, and when loading or editing playbooks. Keep historical malformed actions withdrawable by their exact local match.
-
Hide interactive operator-password entry in
prefixdctland restore terminal echo after input. Fail explicitly when a controlling terminal is unavailable. -
Check backend dependency licenses, sources and advisories with cargo-deny in the existing security job. Reject unapproved licenses, sources and wildcard requirements while retaining duplicate warnings and the separate full-lock audit's explicit RSA exception.
-
Refresh the audit baseline:
rustls0.23.46,quinn-proto0.11.18,h20.4.19 and non-yankedchacha200.10.2 address current lockfile findings. Patch bothrandbranches to 0.8.6/0.9.3 and remove the now-obsolete RUSTSEC-2026-0097 exception. Replace unmaintainedrustls-pemfilewithrustls-pki-typesfor certificate, CA and private-key loading, removing its exception while preserving mandatory mTLS client verification. The maintained YAML parser (see Changed) removes the former YAML maintenance warning. The only remaining vulnerability exception isrsa(RUSTSEC-2023-0071), present through sqlx's optional, uncompiled MySQL driver with no patched release. -
Raise dashboard dependencies to Next.js 16.4.2 and Undici 8.12.2, and patch affected frontend transitive dependencies including sharp, source-map-js, js-yaml, Vitest and nanoid.
bun auditreports no vulnerabilities.
Added
-
Include the rustbgpd speaker adapter in normal builds through the default
bgp-rustbgpdfeature, with authenticated gRPC, local-rule read-back and IPv4/IPv6 discard and policing support. Connect through an owner-only Unix socket or verified native client-certificate mTLS; loopback bearer and verified HTTPS configurations remain supported. Unix reconnects validate ownership and permissions, and certificate and token rotation keep the same speaker target identity. Reject non-root HTTP endpoint paths, because gRPC does not use those prefixes to distinguish speaker targets. Bound initial connections and TLS handshakes to ten seconds. -
Add
rustbgpdandgobgpCompose profiles in one controller image, with private owner-authenticated speaker management and persistent writable configuration seeding that preserves operator edits. The quickstart leads with the prebuiltmaincontroller and dashboard images and keeps a local source-build option. Docker copy-up no longer resets private empty-volume ownership or modes, and unused GoBGP profiling and metrics HTTP listeners are disabled. -
Add
origin_mode(dry-run,enforcedornullwhen unknown) to mitigation API responses and WebSocket mitigation messages. The dashboard marks dry-run-origin mitigations with a neutral "Simulation" tag on the Overview, list and detail pages; the detail page shows "Origin unknown" for rows that predate origin tracking. -
Audit mitigation creation. Each admitted mitigation writes one
createaudit entry in the same transaction as its row, from manual creation, single and batch events and the Alertmanager, FastNetMon and generic webhook adapters. The entry records the operator, or the detector source and event ID, plus the POP, scope, action and parameters, TTL, origin mode and the mitigation ID astarget_id. TTL extensions by repeat events and rejected admissions write no entry. The audit log page and its action filter show the new action. -
Show authenticated rustbgpd daemon version, process uptime, readiness and observation time in health detail, CLI status and the existing speaker strip. Keep controller metadata separate and failed, stale or unsupported observations explicit; GoBGP process metadata remains unsupported.
-
Add an optional
expires_at_capto manual mitigation creation for maintenance recreation without extending the original deadline. Existing TTL guardrails apply to remaining lifetime. Expired intent is not announced after a database wait, including reconciliation's final pending-cleanup check. -
Split required speaker writes from optional rule and peer observation while retaining the selected-speaker facade. Health reports backend and availability; CLI and dashboard distinguish unsupported, failed and empty peer reads. All existing adapters retain both observations. Write-only reconciliation skips repairs while preserving desired counts, TTL expiry and pending cleanup; production write-only backends remain gated on a durable announcement ledger.
-
Add reconciliation repair outcome counters and operator recovery and alert guidance.
-
Add
prefixd --version./openapi.jsonnow reports the package version asinfo.versioninstead of a fixed1.0.0. -
Publish releases through a tag-triggered workflow: version-tagged controller and dashboard images, a Linux amd64 tarball with
prefixdandprefixdctl, a CycloneDX SBOM,SHA256SUMSand image digests in a draft GitHub Release, followed by an install-contract check of the published assets.ci.ymlno longer runs on version tags; it keeps publishingmain,latestand commit images. -
Qualify the speakers in CI with isolated, pinned fixtures: shared readable-speaker lifecycle checks across Mock, rustbgpd and GoBGP; digest-pinned rustbgpd 0.76.0 and 0.77.0 coverage for local lifecycle, TTL withdrawal and recovery through the same client after a speaker restart; GoBGP API, peer and restart qualification; and a rustbgpd↔GoBGP FlowSpec peer test for exact dual-family receipt, withdrawal and re-announcement and retained nonselected local intent. Backend fixture logs are retained.
-
Add an FRR FlowSpec interop workflow and
just interop-frr. Each leg drives prefixd's HTTP API with rustbgpd 0.78.0 or GoBGP 4.10.0 peered to FRR 10.7.1 in containerlab. It asserts the exact rules in FRR's received IPv4 and IPv6 FlowSpec tables through discard, police, detector, withdrawal, TTL expiry and speaker-restart steps, and uploads a dated receipt. This is control-plane evidence only. An SR Linux 25.10.1 probe found no FlowSpec support there.
Changed
-
Qualify rustbgpd 0.78.0: Compose and the interop lab pin it; CI tests 0.76.0 (minimum), 0.77.0 and 0.78.0.
-
rustbgpd 0.78.0 changes for operators:
bgp_outbound_route_drops_totalnow counts FlowSpec writer-channel deferrals, a newbgp_outbound_commit_refusals_totalmetric exists, theBgpOutboundRouteDropsalert drops from critical to warning, and rustls 0.23.46 security fixes cover its gRPC TLS listener. -
Put active mitigation intent, signal evidence, expiry and honest router-observation limits first in the operator workspace. Refresh the light/dark theme, Geist typography and navigation; preserve user zoom and remove decorative pulses, hardcoded quota caps and sampled action totals presented as global counts.
-
Give dashboard status its own colour scale, separate from the interaction accent: active mitigations and healthy services are green, pending and degraded states amber, and escalated or failed states red. Keep one dashboard shell mounted across navigation, tighten card spacing, lift dialogs and menus above page surfaces in dark mode, and match the browser theme colour to each theme. Bandwidth labels drop trailing zeros, such as "5 Mbps".
-
Adopt the prefixd brand palette: neutral grey surfaces and text with the brand green as the single accent for actions, focus and links, replacing the teal accent. Success states share the green hue; each theme keeps 4.5:1 text contrast for status colours and primary buttons.
-
Remove the Severity column from the mitigations list; it was derived from status and action and repeated them. Every dashboard page now has one titled header, shared tables, status pills and empty, loading and error states, with sentence-case labels, UTC timestamps and monospace only for identifiers. On narrow screens, mitigation and event rows stack so target, action, expiry and Withdraw stay visible. The audit log search placeholder is now "Search target or details…".
-
Chart the overview activity in UTC hours to match the top-bar clock, with UTC tooltips, 12px ticks and an inset last tick; the chart now stays inside its column. Mitigation, corroborator-cache and signal-group expiry show the absolute UTC deadline beside the countdown. Correlation source weights use a neutral ramp with a matching legend. Configuration editors, Administration and notification preferences use the shared empty, loading and error states, and each failed read offers Retry. The Configuration, Administration and Correlation tabs are kept in
?tab=, so links and refresh reopen the same tab. -
Replace unmaintained backend YAML parsing with
serde-saphyr2.0.0. Keep strict booleans, unexpanded merge keys and snippet-free parse errors; duplicate map keys are now rejected (see Breaking). -
Refresh the dashboard toolchain to Bun 1.4.3, native Node.js 26.12.0 tests, React 19.3.1 and current Radix and test dependencies, with frozen Bun lock installs in CI and containers. Enforce TypeScript errors in production dashboard builds and add a standalone type check. Update the playbook YAML editor to js-yaml 5 with bundled TypeScript declarations while preserving YAML merge keys.
-
Enforce the vendored anti-slop Oxlint rules on the dashboard:
bun run lintnow runs injust frontend, and so injust gateand the CI Frontend job. Dashboard tests drive components through a scripted controller behind the real fetch path instead of module mocks. The playbook YAML editor now reports an unknown step action as a parse error. -
prefixd_reconciliation_active_countnow carries the controller's configured POP as itspoplabel instead of a hard-codedpop="local". Update any queries that selectpop="local". -
Development and CI: add a sequential
just gateverification bar and focused recipes, with optional non-mutating commit and push hooks; check Rust 1.89 and locked Linux builds for production binaries with rustbgpd, all targets/features and no default features; forbid unsafe code in the library, both binaries and the integration-test crate, and require explained lint exemptions; isolate integration-test HMAC secrets in bounded child processes; run pull-request CI for stacked topic branches; build both Docker images on pull requests without publishing; tolerate Docker cache export failures; and refresh compatible Rust patches (futures 0.3.33, prost 0.14.4, serde_json 1.0.151, async-trait 0.1.92, base64 0.23.1 with only its standard-library feature).
Fixed
-
Admit new mitigations atomically against shared PostgreSQL customer, POP and global quotas. Competing controllers cannot consume the same last slot; quota rejection or a bounded database admission failure sends no announcement from that request. The 5-second admission deadline covers only the work before COMMIT, so a slow COMMIT can no longer return
500for a mitigation it stored. -
Persist new detector and manual mitigation intent before speaker announcement, preventing a failed insert or interrupted request from leaving an unowned rule. Retain committed intent after speaker errors so readable reconciliation and TTL/withdrawal cleanup can recover it.
-
Persist withdrawal intent before speaker RPCs (migration 013) so ambiguous replies and database failures cannot restore a requested deletion. Retry across controller restarts, preserve the original actor/reason in an atomic audit transaction, and reject foreign-POP, shared-owner and stale-operation deletions. New mutations conflicting with pending cleanup return
409. Drain cleanup before changing speaker targets or downgrading to a binary that ignores the queue. -
Keep expired mitigations eligible for withdrawal retry when the speaker call fails. Successful expiry state and notifications now require a confirmed withdrawal; other expired mitigations are still processed during a failure.
-
Detector unban and bulk withdrawal now report speaker failures and preserve active state instead of reporting successful removal. Operators can retry.
-
Preserve enforced-mode withdrawal requests across dry-run restarts. Reject canonical duplicate creation before RPC, extend a single canonical detector owner, and let expired legacy duplicate owners complete TTL cleanup once no live owner remains.
-
Repair discard/policing action drift as well as missing FlowSpec matches on exclusively administered speaker targets. Reject ambiguous owners and invalid observed inventories, preserve unknown rules, and recheck lifecycle state before each upsert. Converged polls do not repeat announcements; this adds no fence against another controller writing the same local match.
-
Recover every eligible desired rule when creation timestamps tie. Reconciliation reads a strict complete owner inventory scoped to the controller's POP; pending creation rows remain excluded from repairs and active-status gauges.
-
Isolate expiry inventory to the controller's POP before decoding and fail on corrupt owned rows instead of silently skipping them. Durable withdrawal retries and independent reconciliation stages continue after enumeration fails.
-
Failed announcement repairs now make reconciliation fail while other repairs continue. Continue independent reconciliation housekeeping and peer observations after mutation failures, and release the lifecycle lock between scheduler rules. Startup performs one immediate pass, then waits for the interval.
-
Require complete dual-family GoBGP local read-back. Received paths are excluded; malformed or unsupported local rules and stream failures fail observation. Bound full inventory and peer streams to 30 seconds, and reject lossy action decoding instead of inventing discard or truncating policing rates.
-
Bound complete GoBGP and rustbgpd mutation responses, including stalled bodies and trailers, to 30 seconds per attempt with at most three attempts.
-
Select the Rustls crypto provider before TLS startup.
-
Canonicalize prefix spelling when computing FlowSpec rule identity so equivalent IPv6 addresses do not trigger repeated reconciliation injections.
-
Keep pending or failed-expiry rows in status-active mitigation gauges. Use rustbgpd peer addresses for unique session names and skip unnecessary locked database reads for already-observed announcement candidates.
-
Grafana reconciliation runs and configuration reloads now group and label the actual
statusseries. -
Preserve host scope for automatic IPv6 mitigations with canonical
/128destinations, including when prefix guardrails are relaxed. Use ICMPv6 protocol 58 in IPv6 event metadata and policy intent; IPv4 retains/32and ICMP protocol 1, with TCP/UDP behavior unchanged. -
Accept explicit
icmpv6in manual mitigation requests as IPv6 protocol 58. Keepicmpas IPv4 protocol 1 and reject cross-family ICMP choices before saving intent or calling the speaker. -
Reject disjoint detector/service destination-port selections before they become protocol-wide FlowSpec rules or extend an existing wildcard owner. Intentional unconstrained port matches remain supported.
-
Apply concrete POP filters before mitigation pagination and match identifiers exactly, preserving omitted/
allglobal listings. -
Fail IP history and incident reports when a selected mitigation cannot be decoded, instead of returning successful partial history. Preserve existing IP filters and SQL limits.
-
Count successful entries in bulk acknowledgement responses so duplicate IDs agree with the ordered per-entry results while each stored row is updated once.
-
Saturate large timeseries day/hour conversions before applying the existing range maximum and bucket minimum, preserving invalid-input defaults.
-
Report string-length validation limits in bytes, matching the existing UTF-8 byte checks, including playbook names and alert destinations in both dashboard editors; document reason limits without changing accepted input lengths.
-
Preserve exact untransformed webhook
bpsandppsintegers and reject nonfinite or out-of-range floating-point results instead of rounding or saturating them. Fractional rates still truncate toward zero; floating-point endpoints are rejected because JSON parsing can lose the original range. -
Avoid overflow when classifying FastNetMon SYN percentages from large valid integer counters, preserving the existing integer threshold and protocol rules.
-
FastNetMon SYN floods labelled
udp_flood. Vector detection matched the first protocol word anywhere in the attack details, so an idle line such asoutgoing udp traffic: 0 mbpswon oversyn_flood. Zero-metric lines, including0 packets per second, are ignored; TCP flag vectors are matched beforeudp; and protocol token boundaries keepackout ofpacketsandAttack_type. -
Reject non-finite correlation confidence thresholds and source/default weights on YAML loading and API edits, including numeric overflow. Preserve the active correlation configuration after a rejected reload or edit.
-
Load saved standalone
correlation.yamlat startup so correlation editor changes survive restart. Reject invalid or unreadable files instead of silently reverting to inline settings. -
Keep disabled authentication consistently anonymous: reject named login with HTTP 403, ignore retained operator sessions before request tracing, and hide named dashboard controls. Logout still deletes retained sessions. Wait for a known authentication mode before presenting the login form.
-
Fix
prefixdctl operators listagainst the shipped operator schema while preserving its JSON fields, table output and exclusion of password hashes. -
Safelist removal from the dashboard returned 404. The Next.js API proxy joins percent-decoded route segments, so
DELETE /v1/safelist/8.9.8.9%2F32reached the backend as/v1/safelist/8.9.8.9/32and missed the single-segment route. The proxy now re-encodes each segment, and the route accepts a literal slash form ({*prefix}) for proxies that decode%2F. -
Dashboard had no sign-out (and no role-based permissions).
/v1/auth/loginand/v1/auth/mereturn a flat operator (operator_id), but the client readdata.operator, leavingoperatorasundefined. The user menu (and therefore sign-out) never rendered, and role checks denied admin actions to authenticated admins. Auth responses are now mapped to the clientOperatorshape andisAuthenticatedis a strict boolean. -
Copy buttons failed silently on plain-HTTP deployments.
navigator.clipboardonly exists in secure contexts, so the incident report, FlowSpec rule, and webhook endpoint copy actions threw and reported nothing when the dashboard was served over HTTP. The copy helper now falls back to a hidden textarea, checksclipboard-writepermission, and surfaces a "Copy failed — clipboard unavailable" toast instead of claiming success. Chrome ignores clipboard writes entirely for plain-HTTP origins (other thanlocalhost), so production dashboards need HTTPS for clipboard actions — documented indocs/how-to/deployment.md. -
Stop the Playbooks form showing "Unsaved changes" on load. The form dropped each playbook's
correlationfield, so it never matched the loaded playbooks, and saving from either playbook editor erased per-playbook correlation overrides. Both editors now keep them, and Correlation shows each override. -
Show a failed request as a retryable error, not as "not found" or as default values, on the mitigation and signal-group detail pages and in notification preferences, where saving the defaults would have overwritten stored settings.
-
Show a healthy controller as healthy on the Administration page. The daemon status pill treated the reported
healthyvalue as an error anddegradedas normal. -
Display confidence fractions consistently as percentages across event, mitigation and correlation views. Preserve zero, distinguish missing and invalid values, and avoid implying that confidence confirms mitigation or router installation.
-
Keep withdrawal confirmations open during requests and failures, preserve failed bulk selections and reasons for retry, and report local completion without claiming downstream router convergence.
-
Restore dashboard live cache refresh and notification muting by matching the server's WebSocket event names. Label ordinary updates and feed resynchronization accurately.
-
Restore immediate dashboard retries after failed GET requests while continuing to share requests that are still pending, and stop handled failed GET requests from also producing unhandled browser rejections during cache cleanup.
-
The command palette finds mitigations and events by ID, and rows that load while a search is already typed are no longer hidden.
Documentation
-
Add the v0.19.1 → 0.20.0 upgrade guide and a release checklist.
-
Publish the speaker profile qualification record for both Compose profiles, and refresh the operator guides and example dashboard image against it.
-
Rebuild the README around reader paths, the rustbgpd profile and the guardrails that run before any speaker call. Move setup next steps into the quickstart, add cookbook recipes for manual mitigation and dry-run rollout, and route the documentation index by task.
-
Organize guides, references, explanations and historical evidence with a documentation index and local-stack tutorial; retain ADR identifiers.
-
Replace the historical roadmap checklist with current remaining work and reconcile speaker/framework decisions with shipped defaults, transports and durable intent. Remove the unusable duplicate PostgreSQL example; use the maintained rustbgpd or GoBGP configuration instead.
-
Record the rustbgpd-first, pluggable-speaker rollout while retaining GoBGP. Correct the controller-crash claim: a separate BGP daemon can keep advertising while prefixd is down; TTL enforcement requires running reconciliation.
-
Refresh the security support policy and the authentication and audit guidance. Correct the documented history-pruning transaction to retire completed withdrawals safely and retain pending cleanup, recent history and referenced detector/correlation events; audit records are preserved and vacuum runs separately.
-
The local development guide notes that the dashboard dev server can't serve the live feed; use the nginx origin to test it.
-
prefixdctl mitigations listhelp and the CLI reference now say it lists every status, newest first, unless--statusis given. The dry-run cookbook covers both no-mitigation cases: an event for an address no customer owns returns202accepted_no_mitigation, and an owned, safelisted address returns422.
Artifacts
Container images, tagged 0.20.0 and 0.20:
ghcr.io/lance0/prefixd@sha256:c8f9adb20a8d426aae5a2b3c184223c5dc6d4271c51b6eb9ddb5f715151c9d58ghcr.io/lance0/prefixd-dashboard@sha256:51d5e5a7c550b715a32bab2244c89b525e6809c1b8231cf8ed869fdc13886f86
SHA256SUMS:
143e43e7a754760fb1a2c78fed9e35fcb30795497f6817011ea6d8b8f8d47019 prefixd-linux-amd64.tar.gz
54d5aed2a85a152bba80554fde25ea34b72148c74fd4100442732424523038d6 prefixd-sbom.cdx.json
Full changelog: v0.19.1...v0.20.0