192: no provision says where the console is, its port was never assigned, and nothing owns a person's agent configuration since the predecessor left. 193: the query verb wraps the caller's text in a read-only transaction the text can end, and its rows come back keyed by BEGIN.
2.9 KiB
status, opened, located-in, fixed-by, amended-design
| status | opened | located-in | fixed-by | amended-design | |
|---|---|---|---|---|---|
| located | 2026-10-02 |
|
193 — The store seat's read-only query is read-only by convention, and its answer is unreadable
What was observed
Asking the store seat's query verb for a count through the console returned this. Rows are each
wrapped in an object under a key named BEGIN: the column name, then the value, then the word
ROLLBACK. A query returning nothing gave the column name and ROLLBACK alone. The answer to
select count(*) as n from <table> was:
rows: [ {BEGIN: "n"}, {BEGIN: "46"}, {BEGIN: "ROLLBACK"} ]
A reader can work it out. A program cannot, and a query with two columns loses which value belongs to which.
Why this is here
The cause is the same line that makes the query read-only. The holder's tool sends
BEGIN TRANSACTION READ ONLY; <the caller's statement>; ROLLBACK; to the command-line client as one
string. The client prints a command tag for each of the three statements, and the parser takes the
first line, BEGIN, as the header.
And it is not read-only. ADR 0159
decided the store seat's query verb is "one read-only statement against one database". The only
thing enforcing that is the wrapping transaction, and the caller's statement is pasted inside it as
text. A statement that begins by ending the transaction (a commit, then anything) runs whatever
follows it outside the read-only transaction, with the holder's own role, the administrative one that creates every
consumer's role and database. A rule stated in a decision and enforced by string concatenation is enforced by nothing.
This is read from the code, not tried against the live store, and it should not be tried there. The lab bed is where it gets proven.
Every caller with invokes on the store seat's query can do this. The console has invokes: ["*"],
so that includes anyone logged in on a machine running the console.
What a fix looks like
- One statement, refused otherwise. Send the caller's statement alone, through the client's single-statement path (the extended protocol takes one statement per call and refuses more). The read-only property then comes from the session, not from text around the statement.
- Read-only by role, not by transaction. Run the verb as a role that can only read, granted
pg_read_all_data, not as the administrative role. A statement that escapes every wrapper still cannot write. - Rows as rows. Parse the client's output with the column names it returns, or use a driver instead of the command-line client, so a row is an object keyed by its columns.
- The check 0159 lacks: a test that sends a commit followed by a write and asserts the write is refused and nothing changed. Another asserts a two-column row comes back keyed by both columns.