Issue 193: mssql's variable substitution and shell commands, proven and fixed by mesh-catalog PR 210

This commit is contained in:
2026-10-02 00:25:48 +02:00
parent 696957aa5e
commit afbfd5f29d
@@ -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.