chore: close 1.9.0, with dependencies and the SonarCloud findings (#161)
CHANGELOG [Unreleased] becomes [1.9.0] - 2026-10-07; the bug report
placeholder and the build docs (en/es), both version overrides, follow.
Dependencies: Go 1.27.1, every direct module, the pinned Go tools, the
Docker base images and the Actions pins were current; the Go closure moves
mattn/go-colorable to 0.1.16, pnpm 12.9.1 -> 12.10.1, and the docs site
takes astro 7.3.6, @astrojs/markdown-remark 7.3.2 and typescript-eslint
8.71.1 (TypeScript stays on 6 for @astrojs/check). Three build-time
advisories pnpm audit reported are overridden with their patched releases
(smol-toml 1.9.0, katex 0.18, postcss-selector-parser 7.1.6); the built
site is byte-identical. braces still has no patched release and reaches
only markdownlint-cli2.
The 15 SonarCloud code smells on main are cleared: Pacer.observe,
Pool.AddAddresses and refreshAddressCache drop under the cognitive
complexity limit, empty functions say why, the reject-with values both
families take are constants, and seven test conditions lose a variable
they never used. GitHub code scanning, Dependabot and secret scanning
have no open alert.
fix: an unreachable LAPI at startup no longer empties the router's lists, and close 1.7.2 (#147)
An unreachable CrowdSec LAPI at startup emptied the router's address
lists: with retry_initial_connect (the default) go-cs-bouncer retries it
silently, the startup collection ended after 10 s with no decisions, and
the first reconciliation removed every entry the bouncer owned. The
stream now closes Synced once the LAPI's first answer, empty or not, has
been forwarded; the first reconciliation waits for it, logging a warning
every 30 s and leaving the lists as they are, then drains what the
stream queued. Reproduced in a virtual RouterOS 7.24.4: 1.7.1 emptied a
40-entry list 10 s after starting with the LAPI down; with the fix the
list stayed for 70 s and the pass added 5 when the LAPI came back.
go-cs-bouncer's logrus lines, including the one naming why the LAPI
refuses the bouncer, now go to the bouncer's own log instead of stderr.
The release-notes Docker line named untagged images (1.7.1 instead of
v1.7.1); the GoReleaser footer is fixed, and the notes of the 17 releases
that carried it were corrected. The docs give the address cache's real
type and describe the startup wait and retry_initial_connect.
Closes 1.7.2: CHANGELOG [1.7.2] - 2026-10-06, the bug report placeholder
and the build-from-source commands.
chore(deps): update every dependency, 2026-10-22 (#171)
## Description
The dependency round of 2026-10-22: every Go module, pinned Go tool,
documentation site package, GitHub Action and Docker base image at its
newest release, after #168 moved Go to 1.27.3 and Vite to 8.3.5. It also
clears the documentation site's `pnpm audit` findings that have a fix.
One finding remains, `braces` (GHSA-vfj7-8cjw-p6xm), because no patched
release of `braces` exists; it is explained under "What remains" below.
Supersedes the open Dependabot pull requests #162 (go-minor-patch) and
#169 (npm-minor-patch); Dependabot closes them once `main` has these
versions.
## Type of change
- [ ] 🐛 Bug fix (non-breaking change that fixes an issue)
- [ ] ✨ New feature (non-breaking change that adds functionality)
- [ ] 💥 Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] 📖 Documentation update
- [x] 🔒 Security / dependency update
- [x] 🧪 Tests / tooling / CI
- [ ] 🧹 Refactoring / code cleanup
- [ ] ⚡ Performance improvement
## Changes
### Go
- **Toolchain**: Go 1.27.3 is still the newest release (go.dev lists
1.27.3 and 1.26.10), so the `go` directive, `GO_VERSION`/`GOTOOLCHAIN`
in `ci.yml` and `codeql.yml`, and `golang:1.27.3-alpine` stay. No
`toolchain` line.
- **Modules** (`go get -u -t ./...`, `go mod tidy`):
`prometheus/client_golang` 1.24.1 → 1.25.0. Its breaking changes are in
`api/prometheus/v1` (the Prometheus HTTP API client), which the bouncer
does not import; it uses `prometheus`, `promauto`, `promhttp` and
`testutil`, and the metrics tests pass unchanged. It now needs Go ≥
1.26. Indirect: `prometheus/common` 0.72.1, `golang.org/x/net` 0.61.0,
`golang.org/x/sys` 0.49.0, `golang.org/x/text` 0.43.0,
`go-openapi/strfmt` 0.27.4, `go-openapi/validate` 1.0.1,
`ebitengine/purego` 0.11.2, `stretchr/objx` 0.8.0. `go list -m -u` shows
no newer version for any module `go.mod` lists.
- **Pinned tools** (`Makefile`): `golangci-lint` v2.14.0 → v2.15.0,
`govulncheck` v1.8.0 → v1.9.0, `golang.org/x/tools` v0.51.0 → v0.52.0
(goimports, modernize). `gosec` v2.29.0, `actionlint` v1.7.12 and
`staticcheck` v0.8.2 (2026.2.2) are already the newest releases.
- **errcheck finding from golangci-lint 2.15.0**
(`internal/manager/loop.go:163`, `<-l.pass.done`): golangci-lint 2.15.0
moves errcheck from 1.20.0 to 1.30.0, whose release notes add "report
unchecked errors from channel receive statements". So it is a new check,
not a misfire: the receive does drop an `error`. The drop is deliberate.
`stopPass` runs on shutdown and on a CrowdSec stream error, right after
it cancels the pass; it only waits for the pass goroutine to return, the
error is the cancellation (or a failure the returning loop no longer
acts on), and the loop returns. errcheck's own way to mark a deliberate
drop, `_ = <-l.pass.done`, is rejected by staticcheck as S1005
("unnecessary assignment to the blank identifier"), which I checked with
both linters. So the line keeps the plain receive with a comment and
`//nolint:errcheck // deliberate discard, see above`, the form
`nolintlint` (require-specific, require-explanation) accepts and the one
`cmd/cs-routeros-bouncer/main.go` already uses. No behavior change.
### Documentation site (`docs/`)
- **pnpm** 12.10.1 → 12.15.0 via `corepack use pnpm@12.15.0` (rewrites
the `packageManager` hash CI reads).
- **Packages** (`pnpm update --latest '!typescript'`, then `pnpm
update`): `astro` 7.3.6 → 7.4.2, `@astrojs/starlight` 0.42.5 → 0.42.6,
`@astrojs/markdown-remark` 7.3.2 → 7.3.4, `@astrojs/check` 0.9.10 →
0.9.12, `eslint` 10.12.0 → 10.13.0, `html-validate` 11.16.2 → 11.17.0,
`markdownlint-cli2` 0.23.3 → 0.24.0, `playwright` 1.63.0 → 1.64.1,
`prettier` 3.9.9 → 3.9.10, `prettier-plugin-astro` 1.1.0 → 1.1.1,
`typescript-eslint` 8.71.1 → 8.72.0. The rest (`@astrojs/sitemap`,
`rehype-mermaid`, `sharp`, `@eslint/js`, `eslint-plugin-astro`,
`globals`, `mdast-util-*`, `micromark-extension-mdxjs`, `yaml`) were
current. The resulting lockfile resolves exactly the same 722 package
versions as a resolution from scratch with no lockfile, and `pnpm dedupe
--check` passes.
- **TypeScript stays at 6.0.3** (7.0.2 is out): `@astrojs/check` 0.9.12
still declares `typescript: ^5.0.0 || ^6.0.0` and `typescript-eslint`
8.72.0 `>=4.8.4 <6.1.0`. This is the hold the `dependabot.yml` comment
already describes.
- **Overrides** in `pnpm-workspace.yaml`, checked by hand: `smol-toml`
removed (`markdownlint-cli2` 0.24.0 pins 1.9.1, patched; the old `1.9.0`
override would have pinned it back down); `postcss-selector-parser`
removed (`@expressive-code/core` 0.44.3 moved to `postcss-nested`
^7.0.2, which takes `postcss-selector-parser` ^7.0.0 and resolves 7.1.7,
patched; `eslint-plugin-astro` shares that copy); `katex` kept, because
`mermaid` 11.17.2 (the newest 11.x, which `mermaid-isomorphic` 3.1.0
asks for) still asks for `^0.16.47` and `micromark-extension-math` 3.1.0
for `^0.16.0`, both vulnerable to GHSA-238p-pmpm-9mq7. It resolves to
0.18.10: 0.18.11 is deprecated on npm ("Accidentally published with
breaking changes. Use 0.19.0 instead"), and I did not push the override
to 0.19, since it only exists to clear the advisory and no diagram here
uses math. The comment above the override is rewritten for what is left.
- **`allowBuilds`**: `esbuild` removed. Astro 7.4 replaced its esbuild
use with Vite/Rolldown APIs, so esbuild is no longer installed (it is
only an optional peer of Vite now).
- **Advisories cleared**: `markdown-it` 15.0.1 → 15.0.3 under
`markdownlint-cli2` 0.24.0 (GHSA-r7fv-28h4-cvq7, GHSA-gfr2-rv44-r23c,
GHSA-vfgh-jcmw-g37q), by updating the parent, no override. `dompurify`
3.4.16 → 3.4.17 under `mermaid` (GHSA-cg2g-xh64-v4q7 critical,
GHSA-mrv2-3pqr-j5jr high, GHSA-c6gr-qvv4-5v3f low, all published today),
by the lockfile refresh within `mermaid`'s `^3.3.3`, no override.
### GitHub Actions and Docker
- **`pnpm/action-setup` `v6` → `v6.1.0`** in `ci.yml` (3 uses) and
`docs.yml` (1). The repo pins floating majors where the major tag tracks
the newest release and exact tags where it does not (as with
`hadolint-action`, `cosign-installer`, `sbom-action`). pnpm's `v6` tag
still points at v6.0.10, while the newest published release is v6.1.0
(2026-09-05, "support pnpm v12"), so it gets an exact tag.
- Every other `uses:` already resolves to its newest published release;
I compared each floating major tag with the commit of the newest
release: `actions/cache@v6` (6.1.0), `checkout@v7` (7.0.1),
`setup-go@v7` (7.0.0), `setup-node@v7` (7.1.0), `upload-artifact@v7`
(7.0.2), `download-artifact@v8` (8.0.2), `upload-pages-artifact@v5`
(5.0.0), `deploy-pages@v5` (5.0.1), `docker/login-action@v4` (4.6.0),
`setup-buildx-action@v4` (4.4.1), `setup-qemu-action@v4` (4.4.0),
`github/codeql-action/*@v4` (4.39.1), `goreleaser-action@v7` (7.3.0),
`sonarqube-scan-action@v8` (8.3.0); exact pins `hadolint-action@v3.5.0`,
`action-shellcheck@2.0.0`, `sbom-action/download-syft@v0.24.3` and
`cosign-installer@v4.1.2` are their newest tags. `gitlab-mirror.yml` is
untouched (#170 replaces it).
- **Docker base images**: unchanged and current. `golang:1.27.3-alpine`
is the newest Go; `alpine:3.24` is the newest Alpine series (it pulled
3.24.3); `busybox:1.38-uclibc` is the newest BusyBox (1.38.0).
### Changelog
- `CHANGELOG.md` under `[Unreleased]`: a `### Changed` entry for this
round and a `### Security` entry for the markdown-it and DOMPurify
advisories. No version bump, no tag.
## What remains, and why
- **`braces` ≤ 3.0.3, GHSA-vfj7-8cjw-p6xm (high, stack exhaustion on
deeply nested brace patterns)**: `pnpm audit --audit-level=high` in
`docs/` still exits 1 on this one finding and nothing else. There is no
patched release: the advisory lists no fixed version, npm's newest
`braces` is 3.0.3 (2024-05-21), the upstream issue (number 70 in the
micromatch/braces repository, open since 2026-09-17) has fix pull
requests open or closed unmerged, and the default branch has had no
commit since 2025-01-19. No parent update removes it: the only path is
`markdownlint-cli2` 0.24.0 (newest, published 2026-10-21) → `micromatch`
4.0.8 (newest, depends on `braces` ^3.0.3), directly and through
`globby` 16.2.4 → `fast-glob`. An override cannot help because there is
no fixed version to point it at, and I did not add an audit ignore, so
the finding stays visible. Exposure: `braces` only expands the glob
patterns `markdownlint-cli2` gets from this repo (`"../**/*.md"` and
`.markdownlint-cli2.jsonc`), in a lint step that never ships with the
bouncer or in the site. It goes away when `braces` publishes a fix (or
`micromatch` drops it) and `markdownlint-cli2` picks it up.
- **TypeScript 7** and **katex 0.19**: held on purpose, as above.
- **Not part of this pull request**: the Astro deprecation warning about
`markdown.rehypePlugins` (asking for `unified({...})` from
`@astrojs/markdown-remark`) and the two `[content]` warnings print on
`main` too (Astro 7.3.6 already emits the deprecation), so they are not
new here.
## Testing
All with Go 1.27.3 (`GOTOOLCHAIN=go1.27.3`), the tools installed by
`make tools` at the new pins, and pnpm 12.15.0 through corepack. The
docs site was built from a copy outside `/root` after a clean `pnpm
install --frozen-lockfile --ignore-scripts`, as CI installs it.
- [x] Unit tests pass (`go test -race -count=1 -coverprofile ./...`, as
CI's Tests job: every package `ok`)
- [x] Static analysis passes: `golangci-lint config verify` and `run`
(on `./cmd/... ./internal/...` and on `./...`), `gofmt -l -s`,
`goimports-check`, `golangci-lint fmt --diff`, `go vet ./...`,
`modernize`, `gosec`, `staticcheck`, `govulncheck` 1.9.0 ("No
vulnerabilities found."), `actionlint` (covers the workflow edits),
`markdownlint-cli2` 0.24.0 over `../**/*.md` (and a probe file confirmed
it really lints). `go mod verify` and `go mod tidy -diff` are clean; `go
build` for linux/amd64 and linux/arm64 with CI's flags.
- [x] Documentation checks pass: `pnpm analyze` in `docs/` (config,
brand, chart, i18n and contrast checks, `astro check` 0
errors/warnings/hints, ESLint, Prettier, `astro build` with 59 pages,
html-validate, social cards). `pnpm dedupe --check` passes. I also built
`main` the same way and compared the two `dist/` trees: the same 251
files; the HTML differs only in the Astro/Starlight `generator` tags,
scoped `astro-*` class hashes and Starlight 0.42.6's own component CSS,
and all 12 Mermaid diagrams are byte-identical in both renders (24
images, light and dark).
- [x] `pnpm audit` in `docs/`: before, 7 advisories (1 critical, 4 high,
2 low) in `braces`, `markdown-it` and `dompurify`; after, 1 (`braces`,
high, no fix; see above).
- [ ] Tested against a real MikroTik router (no behavior change)
- [x] Docker image builds (`docker build --pull -f docker/Dockerfile` as
in CI; the image runs `--help` and reports Alpine 3.24.3)
## RouterOS compatibility
- Tested RouterOS version(s): not applicable (no RouterOS-facing change)
- Tested router model(s): not applicable
## Checklist
- [x] My code follows the project style guidelines
- [ ] I have added tests for my changes (if applicable)
- [ ] I have updated the documentation (if applicable)
- [x] I have updated CHANGELOG.md (if applicable)
- [x] I have updated release/configuration files when behavior changed
- [x] My changes do not introduce new warnings
feat: slow down while the router CPU is busy, mikrotik.cpu_limit (#159)
The bouncer now slows its reconciliation work down while the router's
CPU is busy, as promised on the MikroTik forum. mikrotik.cpu_limit
(default 80, 0 turns it off) is compared with RouterOS' cpu-load, the
average of all cores over the last second, read at most once a second
and only while there is bulk work.
Two readings in a row at or above the limit start it: a pause before
each block of work (a script of 100 adds, 100 single adds or removals, a
duplicate lookup), 200 ms doubling to 5 s while the load stays high and
halving back to full speed once it is 10 points under, and pool work one
entry at a time, the pause holding every worker. A periodic pass or a
retry waits up to a minute for the load to drop before it starts; the
first pass and the one after a reboot do not. Live bans and unbans are
never paced, nor is a shutdown, but as before those that arrive during a
pass are applied when it ends. A pass that ends slowed down ends the
slowdown with it. crowdsec_bouncer_routeros_throttled and
crowdsec_bouncer_routeros_throttle_seconds_total report it, and the log
says when it starts and ends.
On a virtual RouterOS 7.24.4 held at 50% with cpu_limit 55, 30,000
entries took 60-84 s instead of 6-12 s and kept the router at or above
the limit 3-4% of the pass instead of 73-88%; through the pool the pace
at the 5 s pause is 19.8 entries a second. On an RB5009 the default 80
never slowed a 20,000-entry load (17 s at 30% of the CPU, as 1.8.0), and
cpu_limit 20 held it at 14% over 48 s.
fix: end the address-list read on shutdown, and bound silences, not replies (#155)
The read of the bouncer's own list at the start of a reconciliation
ignored the context, so a shutdown during it waited for the whole list.
rosapi.RunArgsContext now watches its context: once it is done, a
blocked write or read ends at once, and routeros.Client.RunContext
closes the connection the command was cut short on instead of retrying
it. ListAddresses takes the reconcile context, and a read a shutdown
ended is logged as one Info line.
command_timeout covered a whole reply with one deadline, so a read that
took longer than the timeout failed every time, its retry included. The
deadline now starts again with every sentence of the reply.
A command whose retry failed on the transport left half a reply on its
connection, and the next command read the rest; that connection is now
closed.
On a virtual RouterOS 7.24.4 with 30,000 entries: SIGTERM during the
read exits in 0.26 s with the rules removed (killed at 10 s with 4 rules
left before); a read that takes ~30 s goes through with
command_timeout: 5s; a full pass costs the same as before.
chore: close 1.9.0, with dependencies and the SonarCloud findings (#161)
CHANGELOG [Unreleased] becomes [1.9.0] - 2026-10-07; the bug report
placeholder and the build docs (en/es), both version overrides, follow.
Dependencies: Go 1.27.1, every direct module, the pinned Go tools, the
Docker base images and the Actions pins were current; the Go closure moves
mattn/go-colorable to 0.1.16, pnpm 12.9.1 -> 12.10.1, and the docs site
takes astro 7.3.6, @astrojs/markdown-remark 7.3.2 and typescript-eslint
8.71.1 (TypeScript stays on 6 for @astrojs/check). Three build-time
advisories pnpm audit reported are overridden with their patched releases
(smol-toml 1.9.0, katex 0.18, postcss-selector-parser 7.1.6); the built
site is byte-identical. braces still has no patched release and reaches
only markdownlint-cli2.
The 15 SonarCloud code smells on main are cleared: Pacer.observe,
Pool.AddAddresses and refreshAddressCache drop under the cognitive
complexity limit, empty functions say why, the reject-with values both
families take are constants, and seven test conditions lose a variable
they never used. GitHub code scanning, Dependabot and secret scanning
have no open alert.