diff --git a/04-ISSUES/193-the-store-seats-read-only-query-is-read-only-by-convention/00-report.md b/04-ISSUES/193-the-store-seats-read-only-query-is-read-only-by-convention/00-report.md index 4b8afbb..70e6b2a 100644 --- a/04-ISSUES/193-the-store-seats-read-only-query-is-read-only-by-convention/00-report.md +++ b/04-ISSUES/193-the-store-seats-read-only-query-is-read-only-by-convention/00-report.md @@ -2,7 +2,7 @@ status: located opened: 2026-10-02 located-in: [mesh-catalog modules/postgres/client.ts (readOnlyQuery)] -fixed-by: [mesh-catalog PR 209 (postgres; open)] +fixed-by: [mesh-catalog PR 209 (postgres; open), mesh-catalog PR 210 (mssql; open)] amended-design: --- @@ -73,8 +73,27 @@ deleting — got past the first layer and was stopped by the second, which is wh in one message, so several statements still arrive together. They are harmless as the reader, and refusing them is left to whoever moves the module to a driver. -## The same hole, elsewhere +## The same hole, elsewhere — and two worse ones -The `mssql` module wraps a caller's statement the same way (`BEGIN TRANSACTION; … ROLLBACK;` as its -administrator) for its own `mssql_query` tool. It holds no seat, but the console may call it. It -needs the same change: a login that can only read, and no wrapper as text. Open. +The `mssql` module wrapped a caller's statement the same way (`BEGIN TRANSACTION; … ROLLBACK;` as its +administrator) for its `mssql_query` tool. Its command-line client added two holes of its own. Both +were proven on a throwaway server, running the client the way the module ran it: + +- **It substitutes `$(NAME)` from its environment into the caller's text**, and the administrator's + password is in that environment. Selecting it as a string returned the password. +- **It reads a line beginning `:!!` as a command that starts a program**, in the container that holds + the administrator's password and the module's bus credentials. Its switch for refusing such commands + makes the shipped version ignore the statement entirely, so the switch cannot be the guard. + +None of it was reachable on the live mesh, for a reason that is a defect of its own: the runtime image +never installed the client, so every mssql tool failed (`spawn sqlcmd ENOENT`). The fix (mesh-catalog +PR 210) installs the client at a pinned digest and runs the caller's statement as a login that can +connect and read and do nothing else. Substitution is off. The statement must be one line, placed after +the module's own text, so no line of it can begin a command; a line break is refused before the client +starts. On the throwaway server, writes, `xp_cmdshell`, impersonating the administrator, and joining +the administrators' role were all refused, and the variable came back as the literal text. + +**The general lesson**, worth more than either module: *a command-line client is an interpreter with +its own syntax, and a caller's text handed to it is a program in that syntax as well as in SQL.* A +module that passes a caller's text to a client has two languages to defend, and a transaction drawn +around the text defends neither.