September 11, 2026·5 min read·engineeringsqlitesecuritydatasette

Datasette 0.65.4 closes gaps around private database responses

Datasette's security release aligns permission checks with SQLite naming, protects intermediate tables, and changes caching for private responses. The fixes concern the web application's authority over the database.

A database publishing tool has to preserve access restrictions through more than its final SQL query. Table names can have equivalent spellings, filters can consult another table, and a response can outlive the request that authorized it. Datasette 0.65.4 addresses failures in those surrounding decisions as part of a coordinated security release.

The GitHub release was published September 11. Its tagged changelog dates 0.65.4 September 10. The project also published its security release account on September 11, explaining the audit and the parallel updates to the stable and alpha branches.

This is an ecosystem security event, not a new SQLite engine release. Datasette mediates access to SQLite data through a web application. Its permissions and response behavior must agree with the database operations it permits, particularly when public and private data share one instance.

Names need the database's interpretation

One fix makes table and view permission checks account for SQLite's case-insensitive names. A permission decision based on spelling can be wrong if the database resolves another spelling to the same object. The security requirement applies to the table the database actually accesses, not merely the text that appeared in a request.

The release note is specific about that mismatch. It does not announce a change to SQLite's naming behavior. Datasette changes its own checks to account for behavior already present in the underlying database. That distinction matters when deciding where the repair belongs.

The same release repairs identifier escaping for primary-key column names taken from untrusted database schemas. The affected uses include row lookups and pagination. A schema may be valid database input while still requiring correct escaping when application code incorporates its names into newly constructed SQL.

This is a separate trust boundary from the values supplied in a filter. A primary-key name describes SQL structure. The application must preserve it as an identifier when it constructs a query. The release does not suggest that trusting a database file's values automatically makes every schema name safe to interpolate.

Full-text search index detection also changes. It now uses parameterized SQL and treats wildcard characters in table names literally. A table name used during discovery should identify that table, even when its characters have another meaning in a pattern. The fix aligns discovery behavior with that expectation.

I value SQLite as a file-oriented SQL engine with a small operational footprint, and this site runs on it. That simplicity does not remove the permissions implemented by applications around it. A publishing application has to assign and preserve authority explicitly because it is the component exposing database operations to web users.

A filter can depend on a protected table

Datasette's ?_through= filters now require permission to view the intermediate table. A filter can reveal information through its selection of results even when it does not display the intermediate table directly. Checking only the final table would leave an incomplete account of the data used to answer the request.

The important change is the permission requirement on that intermediate dependency. The release note does not say the filter feature has been removed. It says access to the table used by the filter must be authorized. The application continues to offer the operation under a stricter and more complete condition.

This is why the set of tables visible in the final response is insufficient as the entire permission model. An operation may consult other objects to determine what appears. The release identifies one such operation and adds the missing check at the point where the intermediate table matters.

The project's security account says most of the repaired issues affect public-internet instances that provide authenticated users access to private data. It particularly urges upgrades for installations using authentication plugins to protect that data. Those conditions describe the intended audience more accurately than treating every local, public-only database browser as having the same exposure.

Private responses now prohibit storage

Private and personalized dynamic responses now carry Cache-Control: private, no-store. The release also makes anonymous dynamic responses vary by Cookie and Authorization. Those headers express that response handling depends on the authentication context, even when the URL alone looks reusable.

The distinction between private and anonymous responses is deliberate. A personalized response should not be stored for later reuse. An anonymous response still needs the cache to distinguish requests carrying the headers that can change identity. Permission enforcement at request time cannot protect an answer that a cache later reuses under the wrong conditions.

These are response-policy changes, separate from SQL construction. A query can respect database permissions and still produce an unsafe publication result if its output is cached across identities. The release addresses both stages because the user's access restriction needs to survive through delivery of the response.

There is a useful limit on what can be concluded from the release note. It identifies the new header behavior but does not provide a universal claim about every external cache deployment. The concrete project change is how Datasette labels the dynamic responses it generates and which request headers distinguish anonymous ones.

Startup extension loading no longer stays enabled

Datasette also disables SQLite extension loading after loading extensions specified through --load-extension. This preserves the intended startup operation while restricting the capability afterward. The release note describes a lifecycle boundary for loading executable functionality into the database process.

That change fits the rest of the release. Several fixes narrow authority after an application has interpreted a request or completed setup. A server can need an extension during initialization without needing to leave extension loading enabled for subsequent work. The update makes that distinction explicit in the connection's behavior.

My preference is for the database publishing process to possess only the capabilities needed for the requested publication. The named startup extensions remain an administrator's deliberate selection. Disabling further loading afterward reduces the set of operations available during ordinary query handling.

The stable branch receives selected audited fixes

Simon Willison's release account describes reports from Sevban Dönmez followed by a broader security audit. Willison and Alex Garcia worked in a shared private repository, with one person writing reproducing tests and the other implementing fixes for most issues. The project then backported selected fixes to 0.65.x while releasing 1.0a39 for the alpha series.

The account says some automated tests remain private temporarily to allow upgrades before revealing the vulnerability details they encode. That limits how much exploit detail a reader can infer from public release bullets. It does not weaken the explicit upgrade recommendation or justify inventing attack sequences absent from the published material.

The substantive benefit of 0.65.4 is a more consistent access boundary across database names, query dependencies and delivered responses. Public-facing instances with private data need the stable update or the corresponding alpha security release. The project supplies both so that adopting these fixes does not require a stable-branch installation to move onto the alpha line.