v0.13.0
## Added
- A working Users page. Accounts show as cards grouped by role, in the order ADMIN, BLACK,
WHITE, BLUE, with search and role and status filters. Create and edit open in a modal, and
each card has an activation switch. BLACK and up can change accounts. WHITE can only view
- Bulk account actions. "Add teams" creates a numbered range, such as team01 to team20, in
one request, up to 100 at a time. Selecting cards gives a bar to activate, deactivate, set
the role of, or delete every selected account. A bulk action only reaches selected accounts
the current filters still show
- `PATCH /api/users?user_id=1&user_id=2` sets `activated`, `role` or both on several accounts
at once
- A time window for the scoring page graph: the last 30 minutes (the default), 1, 2, 6 or 12
hours, or the whole competition. `GET /api/scores/history` takes an optional `window` in
seconds, at least 60. Each team's line starts at its value when the window opens, and the
x-axis always spans the full window
- Signed-in sessions now end when an account's role, activation or password changes, or the
account is deleted. The next request gets a 401 and the auth cookie is cleared. Triggers on
`Users` keep a `session_epoch` and announce changes with Postgres `LISTEN/NOTIFY`. Each API
process keeps the epochs in memory, so no request queries the database for this
- An `insecure` option on LDAP checks that skips LDAPS certificate verification. It is off by
default
- `just reset-swarm` returns every node to the state `just setup-node` left it in, using the
new `docker/scripts/reset_node.sh`. It removes leftover containers, volumes and orphaned
stack networks, and restarts dockerd when only a restart clears a network. `delete-swarm`
runs it first
- Integration tests against real services for HTTP (nginx), LDAP (OpenLDAP) and telnet
(busybox telnetd). The SSH and SMB tests gain stalled-server and oversized-reply cases
- `FuzzValidate` and `FuzzRunners` in `minion/fuzz_test.go`. They feed malformed configs to
the check schema and to every check runner
## Changed
- Minion networks use ipvlan (L2 mode) instead of macvlan. Each minion keeps its own IP but
shares the host NIC's MAC. The script is now `docker/scripts/create_docker_ipvlan_network.sh`
and refuses to run on a host that isn't an active swarm member
- `just setup-node <host> <ip-range>` joins the node to the swarm if it isn't already a
member. It stops if the node belongs to a different swarm. `show-swarm-join-info` is only
needed for joining a node by hand
- Swarm update and rollback `monitor` is 30s instead of 90s for the backend and minion
services
- Go 1.26 is the minimum. Every module's `go.mod` says `go 1.26.0`, and the CI jobs, the CI
tools image and the backend and minion Dockerfiles build on `golang:1.26-alpine3.23`.
Dependencies moved with it, including pgx 5.9.2, amqp091-go 1.13.0, x/crypto 0.56.0 and
go-ntlmssp v0.1.1
- The integration test jobs pull Docker Hub images, the dind service and testcontainers' own
Ryuk image included, through the group's GitLab Dependency Proxy. GitLab fetches each image
from Docker Hub once and serves its cached copy after that, so CI no longer runs into Docker
Hub's rate limit. The SSH tests pull `ghcr.io/linuxserver/openssh-server` directly
- The scoreboard lists services as rows and teams as columns
- User writes need BLACK or higher. Nobody can give a role above their own, change an
account above their own role, or change the role or activation of their own account, or
delete it
- `POST /api/users` takes a `users` field holding a JSON array of accounts and returns 201.
The whole set is validated before anything is written, and every bad entry is named. Taken
usernames are a 409 instead of a 200. `DELETE /api/users` takes `user_id` once per account
and returns 204. `PUT /api/users` refuses a missing field instead of saving a zero value
- Login refuses a deactivated account with a 403, checked only after the password matches.
An existing session no longer stops a new login
- Blue team numbers must be above 0 and unique among blue teams. `Users.team_number` and
`created_at` are NOT NULL
- Deleting a team also deletes its round results and score adjustments. A staff account that
other records still name as their author can't be deleted, and should be deactivated
instead
- Engine reset restarts the round, ledger and score ids at 1
- A list of accepted outputs can't contain a blank entry. Every string contains "", so a
blank entry used to make any "contains one of" check pass
- Checks stop reading a reply at 16 MiB and fail. This covers HTTP, SSH, SMB, FTP and telnet
- An SSH check gives the connection and the SSH handshake 5 seconds together. Before, only
the connection had a limit
- Existing databases need the schema changes above applied by hand: the cascades, the
`Users` constraints and `session_epoch` column, and the two session triggers. A fresh
database gets them from setup
## Fixed
- A chain check started after its deadline reported "all steps passed" without running a
step. It now fails. The minion also reported a pass or a timeout at random in that case,
and a late result could overwrite a timeout result
- A telnet check passed on the prompt or the command's own echo when the command printed
nothing. Reading large telnet output was slow enough to hit the deadline
- A team-set MSSQL password containing `;` could change where the check connected
- MSSQL checks starting at the same time could read a half-written Kerberos config
- LDAPS checks always verified the certificate, despite the `insecure=1` in their URL
- A MongoDB check passed without running a query when given a query type it doesn't handle
- Editing a user without a new password set that account's password to an empty string, and
a new password typed into the form was ignored
- A password over 72 bytes or a taken username returned a 500 instead of a clear error, and
deleting any team that had played a round failed
- A failed refresh no longer blanks the Users page. The list stays, with an alert
- The search box and the filters on the Users page are the same height
- TanStack Charts logged a key warning on the scoring page graph
- One blue team inject hook still used `fetch` instead of `apiFetch`
- With amqp091-go 1.13.0, which refuses to publish on an expired context, a minion dropped
the "check exceeded round deadline" result and requeued the check. The check then bounced
between minions until the next round. Results now publish on the minion's own context
- The Go unit test jobs could start before a rebuild of the CI tools image finished, and run
on the old image. They now wait for `build_ci_tools_image` whenever it's in the pipeline