Repository navigation
Runlight 0.1.1
This release fixes the findings of a full security and code review of every implementation, plugin, and release workflow.
Security
- Sign-in no longer sends people to another site when the
nextpath resolves to one starting//, such as/..//example.com. - Sign-in limits and two-factor code limits hold under parallel requests in the PHP, Python, Ruby, Java, and .NET versions, which kept their counts in the database and could be raced past. One-time sign-in links, recovery codes, and two-factor codes are now single use across workers on SQLite too.
- Sign-in counts tries per address across accounts before checking a password, checks only a few passwords at once, and answers
busywith a 503 past that, so a flood of sign-ins cannot exhaust memory or CPU. - Counts kept per address no longer hold anything an IP address could be worked out from after a day: the key they are hashed with changes daily and old keys are deleted.
- Tries on a decoy two-factor step no longer count against the real account, so nobody can keep someone else locked out.
- Invites are deleted when the person who sent them is removed or demoted, and accepting one checks that its sender may still invite.
- OAuth codes and connect requests are taken atomically, so a code works once.
- The Elixir, Ruby, and .NET versions turn off MySQL's
NO_BACKSLASH_ESCAPESmode on every connection, as the TypeScript library does. On a server with that mode on, a tracking hit could otherwise have injected SQL. - Deeply nested JSON can no longer crash the .NET version, and a JSON object with many keys can no longer tie up the Rust version.
- The Rails engine checks a form posted with
_methodas the POST it is, so it cannot get past the JSON-only rule for changes. - The dashboard no longer opens to anyone because
MIX_ENVis unset (Elixir) or the token isNone(Python, which now usesrunlight.OPENfor an open dashboard). Ruby asks for a shared secret when accounts are on outside development. - Imported short links pass the same domain and destination checks as new ones, so an import cannot take over the site's own address.
- Visitor ids from a CSV import are keyed with a secret made for that import and never stored, so a visitor column holding IP addresses or emails cannot be recovered.
- Share links and embedded dashboards keep the number of people here now but no longer get the feed of single visits.
- The assistant's address and the SMTP server refuse link-local and cloud metadata addresses. Embedded dashboards need an https admin origin on its own port, except on localhost. Report links never use an address sent by the person setting them up.
- Outgoing requests are capped while they are read, drop credentials when a redirect changes origin, refuse https to http, and ignore proxy settings that would get around the check on where they connect.
- SQLite databases turn on
secure_delete, so deleted daily salts do not linger in the file. - The PHP drop-in refuses to start when its data folder could be downloaded, and its README shows how to deny it in nginx and Caddy. Files Runlight makes are readable by the server's user alone.
Fixed
- A session cookie that is not valid percent-encoding reads as signed out instead of failing with a 500.
- MCP batches hold at most 20 messages.
- The Go server can no longer crash when work arrives while it waits for background work, and it carries its own time zone data, as the .NET version now does.
- Python closes its database connections, and Java caps its connection pool at ten. A cancelled request in Rust no longer leaves a transaction or lock open in the pool.
- Ruby no longer freezes waiting for a database lock under Rails' query cache, and a Runlight failure no longer breaks the app's own pages.
- The assistant's limits are checked and counted together in the Go, Java, .NET, and Rust versions, and Rust counts them per person.
- The WordPress plugin reports AI agents after the page has been sent, with the right address on sites in a subfolder. Its settings page, and Drupal's, check the Runlight address after they load.
- The Next.js instructions in the dashboard show
middleware.tsfor Next.js 15 and earlier. - Releases check that the branch matches GitHub before tagging and push the branch and tag together. The Docker image installs exactly what the lockfile names and is started once before it is published.