Skip to content

Security

2 September 2026

57 changes:

  • A caller of OutboundUrl::fetch() could not tell a 404 from a 200 — and the framework's own caller was storing the placeholder
  • The tokenactions self-repair could not run on MySQL
  • What the email second factor does when the store or the mailer fails
  • The class added this morning was the day's biggest coverage gap — and addRemoteImage() with it
  • Who counts as signed in for the administration area, and what a missing page answers with
  • A grouped page could not be asked for at all
  • Where the write spool buffers, and how long it keeps trying
  • Re-running create:model destroyed the model
  • The Redis operations that only Redis has
  • …and the same reconnect on the path most statements take
  • The Select2 branch of the CRUD generator
  • Revoking one device, and the cascade that is MySQL-only
  • What each DevPanel card shows when the thing behind it is missing
  • A security-notification class at 6% covered
  • The per-address rate limit had never run
  • The session upsert had never been issued against PostgreSQL
  • What a client is told about every MCP tool before it calls one
  • A round of 69 tests that moved coverage by one statement
  • route-list was tested through its parser and never through itself
  • The two scheduled commands whose execute() had no test at all
  • The scaffolders' refusals, and two things that cannot be tested the obvious way
  • MediaObject's error arms, and a dead-code finding that was not one
  • An application's own User class was never once returned in a test
  • Losing Redis would have taken the application down, not the cache
  • The Memcached counter had never run, and clear() empties more than you think
  • What the human check does when it breaks, and the top three targets that cannot be reached
  • The Greek in the search box had never been tested
  • The URL cache that keeps a worker's memory bounded
  • ST_MakePoint() takes longitude first, and nothing had ever checked
  • The deadlock retry, which had never retried anything
  • The privilege boundary in the admin area, and a green run that proved nothing
  • A better index: 48 methods with no covered line at all
  • TOTP replay protection, which had never run and stands down three ways
  • The webhook credential check, and the fourth test that replaced what it tested
  • The second leg of an API login had never been taken
  • The account a client-credentials token hangs on, and a one-character invariant
  • --spa-components and the two conditions that silently decide nothing happens
  • 453 statements were being excluded from measurement by accident
  • Three small classes off the never-run list, and a test that told me it was empty
  • What auth:twofactor-cleanup actually sweeps, and the assertion total I made unreadable
  • A test called "the omnibox limit is capped" that would pass on an uncapped omnibox
  • Configuring a local asset list crashed every page build
  • An addon setting called 2fa_enabled is not called that
  • The query behind hasIndex(), and the assertion that PostgreSQL does not inherit it
  • Three counters, sessions and headers — and a setter that means the opposite of its name
  • Seven more off the never-run list, in one pass
  • Three more, and a state leak I caused and had already written the guide entry for
  • Three small ones to close the gap
  • The machine account, an MCP call's identity, and a helper the framework ships untested
  • /adminer refuses with a 404, and why that is the security decision
  • The reconnect that must forget, and the first sign-in that is not a new device
  • The middleware list, and the bracket somebody will forget
  • can() and cannot() — the pair every guard clause is written with
  • The last three: two seams and the accessor every closing test reads
  • The two gaps that were in the environment, not the tests

31 August 2026

34 changes:

  • The half of OAuth discovery that was missing
  • Three components that were nearly accessible
  • The Model Context Protocol, over HTTP, for somebody else's assistant
  • Offering an MCP capability without writing a class
  • A page head that stops claiming things the page never said
  • What the site tells a machine that arrives uninvited
  • A keyboard can get past the navigation, and the consent screen stops phoning out
  • A token outlived the client that issued it, and kept working
  • An error log that was mostly not errors
  • A client secret nobody had to present
  • The SMTP password was readable to anyone who could read the database
  • The webhook signing key and the TOTP seed, likewise
  • A tenant scope that held until you asked for page 1
  • A grant on one record opened the whole collection
  • The session cookie lost its secure flag behind every TLS-terminating proxy
  • The legacy CSRF check compared tokens with ===
  • A LIMIT built by string concatenation
  • addslashes() as an escaping fallback, and as a PHP-literal encoder
  • The README described a framework this is not
  • The schema promised tenant isolation nobody implements
  • Joins on two columns, and aliases that survive resolution
  • Permissions can now be resolved within one organisation
  • Roles had two tables, a permissions screen, and no way to make one
  • The client secret is hashed, and the realtime key moves out of its way
  • A public client can say it is one
  • The roles screen had a menu entry and no address
  • A guard that read a variable that did not exist (FW-047)
  • A pager that could not be told to stop counting (FW-048)
  • Two components no theme could style
  • SchemaBuilder::hasIndex()
  • Tokens are encrypted at rest, and matched on a digest

29 August 2026

27 changes:

  • Two invisible columns, and a row of chips that ran off the panel
  • Reading a sent message back: what it carried, and what came of it
  • Web push: a notification on a device whose browser is closed
  • The coverage gate reported 100% on a change that added an untested package
  • Sending one account a message, on the channels it can actually receive
  • Mass messages: an audience worth choosing, and push that reaches people
  • The line the inbox shows, dark mode, and what DNS says about your mail
  • Three MCP tools for the questions that were being answered by failing
  • A log viewer in the DevPanel, at the address that used to 400
  • The mail log grows without limit, and deleting it is the wrong fix
  • The drift check's own false alarms, and the panel that had never worked
  • The email preview's findings were taking a third of the width from the message
  • Push had five parts and shipped four of them
  • Every browser restart minted a session, and none of the old ones ever ended
  • Nothing knew what kinds of mail an application sends
  • Choosing who a mass message goes to, and seeing who that is
  • The soft prompt was rendered on every page and never shown
  • Nothing recorded a sent push
  • The DevPanel had a second log viewer because of one hard-coded URL
  • The database tab could not answer a developer's question about a database
  • An unread message was something you had to go looking for
  • Two development query logs that grew until the process died
  • The database tab, again: chunks, and the half of the admin screen it still lacked
  • Twenty-five forked PHP processes, for a constant
  • The database tab, third time: copied rather than rewritten
  • An unsubscribe is two records, and the second had nowhere to go
  • The coverage gate, and the two bugs it found

28 August 2026

66 changes:

  • A sign-in is questioned when it looks wrong, not when the browser is new
  • One TOTP code, one login
  • A password change that cannot be the same password
  • The bundled sign-in forms can price automation
  • The mail an account was sent is on the account's screen
  • Themed email: the column that had never been read
  • An email is written in the recipient's language
  • A datatable over an authserver.* table read as empty on MySQL
  • Authorization is three layers, and the guide said two
  • Two reported bugs: a date of zero, and a cropped PNG's black corners
  • Something runs the second-factor cleanups
  • The services screen says whether anything is listening to its buttons
  • A message to many accounts, composed and sent from a screen
  • Three test classes were spending their time emptying the cache
  • Requiring a second factor, and requiring a real one
  • The debug bar says where the second factor stands — and stops forgetting your tab
  • Three dead ends on the administration screens
  • A settings row no longer opens the debug toolbar
  • Nothing on the settings screen opens the DevPanel any more
  • A scaffolded application comes with something that runs its background work
  • /admin/Services says how to create the supervisor
  • New-sign-in alerts can be on unless turned off
  • The DevPanel's Back button goes where you came from
  • Session can write, not only read
  • One apostrophe no longer destroys a page's breadcrumb structured data
  • Html\Date renders the time and the dropdowns it always claimed to accept
  • The queue worker runs under a supervisor
  • /admin/Services can see a supervisor in another container
  • An unsubscribe link, and the two headers Gmail actually reads
  • Addon::trigerAddon() refuses a nameless addon too
  • The default language is a list, not ten characters of free text
  • Adminer, at /adminer, behind the application's own gate
  • /messages — the inbox those internal messages were going into
  • Any browser with JavaScript can solve the human check — and a test proves it
  • A CSP-blocked redirect, and a script with two nonces
  • Adminer signs itself in
  • Html\Date reads the properties it declares
  • An idle connection is not a query running for three hours
  • Html\Date's field is validated by the browser again
  • A date is written the way the language writes dates
  • Two of the widest columns on the process list said the same thing four times
  • Two empty boxes where the log charts should be
  • The log dashboard's figures, asked for by something that is not a screen
  • The components guide listed Seo and then never mentioned it again
  • mcp:serve had its own copy of the tool catalogue, and it was stale
  • The most frequent error in the log was the framework asking a question
  • Every log entry was dated the moment you looked at it
  • mcp:serve is not something a person could debug
  • An MCP tab in the DevPanel: the schema as a form, the answer on the page
  • find-symbol: the question grep cannot answer
  • The DevPanel's MCP tab shipped with a JavaScript syntax error
  • route-list executed the views, and then said there were no routes
  • A link in the DevPanel is styled wherever it is
  • Two more MCP tools: what the CLI can do, and what the theme is made of
  • Three tests that were a copy of the tool catalogue
  • api-docs and find-tests: the other two of the four
  • Two rules that could not be checked, and now can
  • changelog-add: the one tool that writes
  • The plain-text part of an email was the CSS, with the links removed
  • Four headers that decide what happens to a message
  • Gmail actions: a button in the message list, and the reason yours is not showing
  • A ViewAction never needed a handler — the password-reset mail has one now
  • One-click mail actions, and the handler a "this wasn't me" button needs
  • The unsubscribe page was 181 KB, and 180 of them were the website
  • A session count that was not a number, and four tables called sessions
  • Email tracking that works, and only for mail somebody agreed to receive

16 August 2026

15 changes:

  • Wrote 1 path(s), and every word of it was true
  • The JSON renderer decided every response was fine
  • A failed list query killed the request
  • The controller a model needs costs 1.5 microseconds
  • The one place a shared layout could not be
  • A comparison table is a claim too
  • The redirect the guide promised
  • The cache category was accepted and discarded
  • A marker nothing flipped
  • The HTML document could not say what page it was
  • getData() and the columns it never returned
  • An alarm that stays rare
  • A column that described data nobody wrote
  • Fourteen tabs do not fit
  • Two things that did not complain

14 August 2026

39 changes:

  • The server now says where its own time went
  • An API request is not a browser session
  • auth:unlock — lifting a lockout you gave yourself
  • The toolbar answers "who am I, and until when"
  • A parent class for services, and a tab that admits they exist
  • An Errors tab, for what the browser threw
  • A Client tab: what the browser thinks the world is
  • The page outranks the toolbar
  • An API playground in the toolbar — and a doubled slash it found
  • A WebSocket daemon that can read a Redis stream
  • The body of a DELETE request
  • A ? in a raw fragment now binds where it was written
  • init will not scaffold over your application
  • Debug data for an application that does not use the API layer
  • The other way to lose a page
  • The toolbar injects through the response, not an output buffer
  • Two build settings that failed quietly
  • Three dead stubs, and four corrections to older posts
  • scaffold:spa — a front end for an application that already exists
  • The MCP server says which project it is
  • Typed endpoints, generated from the document
  • Where the suite's fifteen minutes actually go
  • A shared document, seven eight-second tests, and a ? in the bar
  • A scaffolded application was set up to learn it the hard way
  • The suite was running composer update sixty-one times
  • The test database was afraid of losing data
  • TRUNCATE is slower than dropping the table
  • The comment said // product 1 = Apple
  • The same change made PostgreSQL slower
  • A suite that only passed in one order
  • The guide described an API nobody had built
  • The widget area that rendered nothing
  • maxRuntime was a range, and it read like a number
  • An empty ban list is still a ban list
  • The ingest dropped the id it had just read
  • Which rule said no
  • Four corrections from the other side of the boundary
  • "Minor variable name changes"
  • A blank page is not an error

14 August 2026

39 changes:

  • The server now says where its own time went
  • An API request is not a browser session
  • auth:unlock — lifting a lockout you gave yourself
  • The toolbar answers "who am I, and until when"
  • A parent class for services, and a tab that admits they exist
  • An Errors tab, for what the browser threw
  • A Client tab: what the browser thinks the world is
  • The page outranks the toolbar
  • An API playground in the toolbar — and a doubled slash it found
  • A WebSocket daemon that can read a Redis stream
  • The body of a DELETE request
  • A ? in a raw fragment now binds where it was written
  • init will not scaffold over your application
  • Debug data for an application that does not use the API layer
  • The other way to lose a page
  • The toolbar injects through the response, not an output buffer
  • Two build settings that failed quietly
  • Three dead stubs, and four corrections to older posts
  • scaffold:spa — a front end for an application that already exists
  • The MCP server says which project it is
  • Typed endpoints, generated from the document
  • Where the suite's fifteen minutes actually go
  • A shared document, seven eight-second tests, and a ? in the bar
  • A scaffolded application was set up to learn it the hard way
  • The suite was running composer update sixty-one times
  • The test database was afraid of losing data
  • TRUNCATE is slower than dropping the table
  • The comment said // product 1 = Apple
  • The same change made PostgreSQL slower
  • A suite that only passed in one order
  • The guide described an API nobody had built
  • The widget area that rendered nothing
  • maxRuntime was a range, and it read like a number
  • An empty ban list is still a ban list
  • The ingest dropped the id it had just read
  • Which rule said no
  • Four corrections from the other side of the boundary
  • "Minor variable name changes"
  • A blank page is not an error

12 August 2026

14 changes:

  • Late writes into compressed chunks no longer have to be lost
  • print replaces the pdf document type, which had not worked for years
  • MariaDB stops pretending to be MySQL
  • The toolbar now sees what a page does after it renders, and can be opened on a live server
  • Nine queries per request, most of them avoidable
  • The SPA debug panel now says it exists
  • The docs now say when you would need them
  • The toolbar's hide button now hides the toolbar
  • spa:dev / spa:build, and a service container that actually exists
  • One toolbar, delivered two ways — and the SPA panel gains every tab
  • A name for every request, and the log lines it wrote
  • The server-rendered toolbar now uses the one renderer too
  • Settings read every key one at a time on PostgreSQL, and said nothing
  • SSE events published during a reconnect are no longer lost

11 August 2026

6 changes:

  • Five rolled-up views were frozen on every database without TimescaleDB
  • A model may give load() whatever parameters it needs
  • Five developer panels had been empty for years, and nothing said why
  • The rate limiter counted the proxy, and counted it badly
  • A human check that prices spam instead of pretending to detect it
  • getInstance() is a factory, and two call sites forgot

10 August 2026

16 changes:

  • pramnos init checks both Docker ports before proposing one
  • init scaffolds a working SPA — Svelte + daisyUI, Vite, Vitest and Docker
  • Files the container writes now belong to you, not to root
  • SPA follow-ups: the dev server, the site root, and the docs generator
  • The FK migration stops assuming the schema of tables it does not own
  • The scaffolded SPA now actually talks to the API
  • create:crud builds the SPA half too
  • A debug bar a SPA can actually use — and a guide for testing the front end
  • Generated CRUD gets real authorisation — and delete gets any at all
  • A SPA project gets an administration screen
  • cache:clear no longer wipes every installation sharing the backend
  • Signing in works, screens have URLs, and the SPA wears the project's colours
  • Permissions answers from whichever store the installation has
  • One permission system, available to every project with users
  • timescale:ensure — repairing hypertables on a database that got TimescaleDB late
  • The GDPR endpoints queried a table that does not exist