← Data Console

Browse SQLite, with a read-only mode that holds

SQLite is a file, which makes it the easiest database to share and the easiest to change by accident. Opening it read-only is a property of how it is opened rather than of a user account, and that distinction is the whole of the safety story here.

Create a read-only SQLite user

Verified: opened with mode=ro, SELECT returned rows while INSERT and DROP both raised. Note that mode=ro still requires the process to be able to READ the file, and a plain path without the file: URI silently opens read-write.

-- SQLite has no users and no GRANT. Read-only is the OPEN MODE:
--   file:/path/to/app.db?mode=ro

-- Python, verified
import sqlite3
conn = sqlite3.connect("file:/path/to/app.db?mode=ro", uri=True)

-- Belt and braces: make the file itself unwritable for the process
chmod 444 /path/to/app.db

What browsing SQLite looks like

  • Declared types are shown alongside what the values actually are, which matters because SQLite does not enforce the declaration.
  • Dates arrive as text, integers or floats depending on what wrote them, and the console shows which.
  • Foreign keys are displayed where declared — whether they were ever enforced is a separate question.
  • The whole database is one file, so what you are looking at is unambiguous.

What catches people out on SQLite

The declared type is advisory

A column declared INTEGER will happily hold the string 'banana', because SQLite uses type affinity rather than enforcement. If a column looks wrong, the data may genuinely be wrong rather than misrendered.

Foreign keys are off unless switched on per connection

Every connection must issue PRAGMA foreign_keys = ON. A schema full of relationships may contain orphans that nothing ever rejected.

There is no date type

Dates are text, Unix integers or Julian floats, decided by whatever wrote them. Sorting and comparing only behave if the format sorts lexicographically, which is why ISO-8601 is the one to use.

A read-only login still shows everything

The user above stops writes. It does not stop someone reading every email address, card number and salary in the database, and it does not record who did. The console adds the other half: sensitive columns are detected and masked before the rows leave the server, unmasking is granted per column to named people, and every access is logged. Access Review answers "who can see what" across every source in one report, which is the question that is very hard to answer from a pile of grants.

Questions

How do I open a SQLite database read-only?

Open it through a file: URI with mode=ro. Verified: SELECT worked while INSERT and DROP were refused. A plain filesystem path opens read-write, so the URI form is the part that matters. Setting the file permissions to read-only is a useful second layer.

Why does a column contain the wrong type?

Because SQLite does not enforce declared types. The declaration is an affinity, and any value can be stored in any column, so what you are seeing is probably what is really there.

More on SQLite

Other databases