fix: refuse the sandbox rather than quietly running it as the superuser
Both reviews landed on the same line independently. _as_role swaps the credentials in the DSN with a regex, and when the pattern does not match it returned the string unchanged. Two shapes miss it: no inline credentials, and a unix socket URL. Either one is a legal DSN. What that costs is not a broken test. The sandbox builds, every assertion still passes, and bare_dsn is now the admin connection, so the worst-case case runs the real script with --apply as a superuser against the shared table. The verifier ran that command as a dry run to see what it would have done: target public.llm_calls, 11 rows to delete. The case would still have gone red on the exit code, after the rows were gone. It raises now. There is also a second check that connects and compares current_user, because a successful string substitution is not the same as connecting as that role -- PGUSER and friends still override. The whole design rests on that connection having no grant on the shared table; a string comparison is too thin a thing to rest it on. That check has to stay inside the try. Past it the cleanup statements have already been merged into the fixture-level stack, and unwinding again runs DROP OWNED BY twice, which has no IF EXISTS. The catalog probe took any SQL and ran it on the admin connection. The design claims withholding the DSN makes the boundary structural; that was only true of the connection string, not of the capability. It takes SELECT now. --table's schema half is restricted to plain identifiers. Not a security fix, since the name goes through a parameter and _quote: the help text says complex identifiers are unsupported and the code was accepting them anyway.
This commit is contained in:
@@ -360,6 +360,59 @@ class TestUsageErrors:
|
||||
assert "llm_calls" in result.stderr
|
||||
|
||||
|
||||
class TestTableIdentifierWhitelist:
|
||||
"""schema 段只收普通标识符: 让 `--help` 说的"不支持复杂标识符"成为事实。
|
||||
|
||||
这不是安全边界(`to_regclass($1)` 参数化 + `_quote` 转义,注入面本就不存在),
|
||||
是**契约边界**: 帮助文本写着不支持,实现却照单全收,受害的是照文档做判断的人。
|
||||
"""
|
||||
|
||||
def test_schema_with_a_space_exits_one(self):
|
||||
result = _run(
|
||||
"--backend",
|
||||
"postgres",
|
||||
"--dsn",
|
||||
"postgresql://x/y",
|
||||
"--older-than-days",
|
||||
"7",
|
||||
"--table",
|
||||
"bad schema.llm_calls",
|
||||
)
|
||||
|
||||
assert result.returncode == 1
|
||||
assert "--table" in result.stderr
|
||||
|
||||
def test_schema_with_a_semicolon_exits_one(self):
|
||||
result = _run(
|
||||
"--backend",
|
||||
"postgres",
|
||||
"--dsn",
|
||||
"postgresql://x/y",
|
||||
"--older-than-days",
|
||||
"7",
|
||||
"--table",
|
||||
"a;b.llm_calls",
|
||||
)
|
||||
|
||||
assert result.returncode == 1
|
||||
assert "--table" in result.stderr
|
||||
|
||||
def test_a_plain_identifier_with_underscores_and_digits_is_accepted(self):
|
||||
"""收紧不得误伤正常名字: 这条走到连接阶段才失败(退出 2),说明校验放行了。"""
|
||||
result = _run(
|
||||
"--backend",
|
||||
"postgres",
|
||||
"--dsn",
|
||||
"postgresql://127.0.0.1:1/nope",
|
||||
"--older-than-days",
|
||||
"7",
|
||||
"--table",
|
||||
"pgw_s_a1b2c3.llm_calls",
|
||||
)
|
||||
|
||||
assert result.returncode == 2
|
||||
|
||||
|
||||
class TestHelp:
|
||||
def test_help_names_the_maintenance_role_and_the_recommended_path(self):
|
||||
"""帮助文本是运维唯一会读的文档,权限口径与"推荐不是 DELETE"必须在里面。"""
|
||||
|
||||
Reference in New Issue
Block a user