The companion to #209, for the same class of hole in mssql_query (hq issue 193).
Live today, mssql's tools are dead. The runtime image never installed sqlcmd, so mssql_list_databases fails with spawn sqlcmd ENOENT. The holes below were therefore not reachable live, and this PR makes the tools work for the first time. That is why both changes go in one PR.
What was proven on a throwaway SQL Server 2022 (default bridge, no published port), with sqlcmd run the way the old code ran it:
SELECT '$(SQLCMDPASSWORD)' returned the sa password. sqlcmd substitutes $(NAME) from the environment, and the administrator's password is in it.
A line beginning :!! echo …ran a program in the tools container, which holds the sa and bus credentials.
go-sqlcmd v1.10's -X (disable commands) makes it ignore -Q and go interactive, so that flag cannot be the guard.
The fix:
The statement runs as mesh_mssql_reader, with CONNECT ANY DATABASE and SELECT ALL USER SECURABLES and nothing else. Every start takes it out of sysadmin and re-sets its password. The password is a new own-secret, reader. Without it, the call is refused and never falls back to sa.
Caller text runs with -x (no variable substitution). It follows the module's own text on the first line, and any CR or LF is refused before sqlcmd starts. sqlcmd only treats :/!! as commands at the start of a line: tried mid-line, after a lone CR, and indented, each was a syntax error.
No BEGIN TRANSACTION … ROLLBACK text. The old wrapper also double-wrapped FOR JSON.
go-sqlcmd v1.10.0 is installed at a pinned sha256. The stage was built on its own and runs.
Proof with the fix, same throwaway server:
SELECT returns rows, and $(SQLCMDPASSWORD) comes back literally.
Refused: COMMIT; DROP, DELETE, xp_cmdshell, EXECUTE AS LOGIN = 'sa', adding itself to sysadmin, CREATE TABLE, and a line-break :!! (no file created).
IS_SRVROLEMEMBER('sysadmin') = 0, as mesh_mssql_reader.
Tests.test/reader.test.ts (4, fake sqlcmd). Added build/test scripts to package.json.
Rollout. After build, the next push mints reader on mssql's node and recreates mssql's runtime container. The server container is untouched. Verify with mssql_list_databases (works at all) and mssql_query → SELECT SUSER_NAME() = mesh_mssql_reader.
The companion to #209, for the same class of hole in `mssql_query` (hq issue 193).
**Live today, mssql's tools are dead.** The runtime image never installed `sqlcmd`, so `mssql_list_databases` fails with `spawn sqlcmd ENOENT`. The holes below were therefore not reachable live, and this PR makes the tools work for the first time. That is why both changes go in one PR.
**What was proven on a throwaway SQL Server 2022 (default bridge, no published port), with sqlcmd run the way the old code ran it:**
- `SELECT '$(SQLCMDPASSWORD)'` returned **the sa password**. sqlcmd substitutes `$(NAME)` from the environment, and the administrator's password is in it.
- A line beginning `:!! echo …` **ran a program in the tools container**, which holds the sa and bus credentials.
- go-sqlcmd v1.10's `-X` (disable commands) makes it ignore `-Q` and go interactive, so that flag cannot be the guard.
**The fix:**
- The statement runs as `mesh_mssql_reader`, with `CONNECT ANY DATABASE` and `SELECT ALL USER SECURABLES` and nothing else. Every start takes it out of sysadmin and re-sets its password. The password is a new own-secret, `reader`. Without it, the call is refused and never falls back to sa.
- Caller text runs with `-x` (no variable substitution). It follows the module's own text on the first line, and any CR or LF is refused before sqlcmd starts. sqlcmd only treats `:`/`!!` as commands at the start of a line: tried mid-line, after a lone CR, and indented, each was a syntax error.
- No `BEGIN TRANSACTION … ROLLBACK` text. The old wrapper also double-wrapped `FOR JSON`.
- go-sqlcmd v1.10.0 is installed at a pinned sha256. The stage was built on its own and runs.
**Proof with the fix, same throwaway server:**
- `SELECT` returns rows, and `$(SQLCMDPASSWORD)` comes back literally.
- Refused: `COMMIT; DROP`, `DELETE`, `xp_cmdshell`, `EXECUTE AS LOGIN = 'sa'`, adding itself to sysadmin, `CREATE TABLE`, and a line-break `:!!` (no file created).
- `IS_SRVROLEMEMBER('sysadmin')` = 0, as `mesh_mssql_reader`.
**Tests.** `test/reader.test.ts` (4, fake sqlcmd). Added `build`/`test` scripts to package.json.
**Rollout.** After build, the next push mints `reader` on mssql's node and recreates mssql's runtime container. The server container is untouched. Verify with `mssql_list_databases` (works at all) and `mssql_query` → `SELECT SUSER_NAME()` = `mesh_mssql_reader`.
Proven on a throwaway server: as the administrator a caller's $(SQLCMDPASSWORD) returned
the sa password, and a line beginning ':!!' ran a program in the tools container. The
statement now runs as mesh_mssql_reader (CONNECT ANY DATABASE, SELECT ALL USER SECURABLES),
with substitution off (-x), after the module's own text on the first line, and a line break
is refused. go-sqlcmd v1.10.0 is installed at a pinned digest: the image never had sqlcmd,
so every mssql tool failed with spawn sqlcmd ENOENT.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The companion to #209, for the same class of hole in
mssql_query(hq issue 193).Live today, mssql's tools are dead. The runtime image never installed
sqlcmd, somssql_list_databasesfails withspawn sqlcmd ENOENT. The holes below were therefore not reachable live, and this PR makes the tools work for the first time. That is why both changes go in one PR.What was proven on a throwaway SQL Server 2022 (default bridge, no published port), with sqlcmd run the way the old code ran it:
SELECT '$(SQLCMDPASSWORD)'returned the sa password. sqlcmd substitutes$(NAME)from the environment, and the administrator's password is in it.:!! echo …ran a program in the tools container, which holds the sa and bus credentials.-X(disable commands) makes it ignore-Qand go interactive, so that flag cannot be the guard.The fix:
mesh_mssql_reader, withCONNECT ANY DATABASEandSELECT ALL USER SECURABLESand nothing else. Every start takes it out of sysadmin and re-sets its password. The password is a new own-secret,reader. Without it, the call is refused and never falls back to sa.-x(no variable substitution). It follows the module's own text on the first line, and any CR or LF is refused before sqlcmd starts. sqlcmd only treats:/!!as commands at the start of a line: tried mid-line, after a lone CR, and indented, each was a syntax error.BEGIN TRANSACTION … ROLLBACKtext. The old wrapper also double-wrappedFOR JSON.Proof with the fix, same throwaway server:
SELECTreturns rows, and$(SQLCMDPASSWORD)comes back literally.COMMIT; DROP,DELETE,xp_cmdshell,EXECUTE AS LOGIN = 'sa', adding itself to sysadmin,CREATE TABLE, and a line-break:!!(no file created).IS_SRVROLEMEMBER('sysadmin')= 0, asmesh_mssql_reader.Tests.
test/reader.test.ts(4, fake sqlcmd). Addedbuild/testscripts to package.json.Rollout. After build, the next push mints
readeron mssql's node and recreates mssql's runtime container. The server container is untouched. Verify withmssql_list_databases(works at all) andmssql_query→SELECT SUSER_NAME()=mesh_mssql_reader.