fix(seed): run psql with -X so psqlrc cannot retarget after the local guard

psql -c no longer implies -X, so ~/.psqlrc or PSQLRC (exportable from
.env) ran after the guard and could \connect to another host. Pass -X
on both the local and Docker psql paths and assert the control
invocation carries it.

Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
This commit is contained in:
Elrond
2026-10-16 10:02:38 -04:00
parent 9c7bd0f61b
commit 2f337aae90
2 changed files with 20 additions and 10 deletions

View File

@@ -2323,7 +2323,11 @@ fn admin_seed_refuses_remote_database_before_invoking_psql() {
)
.expect("copy script without developer .env");
let psql = root.join("psql");
std::fs::write(&psql, "#!/bin/sh\ntouch \"$SEED_PSQL_WITNESS\"\nexit 1\n").expect("stub psql");
std::fs::write(
&psql,
"#!/bin/sh\nprintf '%s\\n' \"$@\" > \"$SEED_PSQL_WITNESS\"\nexit 1\n",
)
.expect("stub psql");
std::fs::set_permissions(&psql, std::fs::Permissions::from_mode(0o755))
.expect("executable stub");
let witness = root.join("psql-was-invoked");
@@ -2338,15 +2342,16 @@ fn admin_seed_refuses_remote_database_before_invoking_psql() {
.env("SEED_PSQL_WITNESS", &witness)
.output()
.expect("run local seed guard");
let invoked = witness.exists();
if invoked {
let args = std::fs::read_to_string(&witness).ok();
if args.is_some() {
std::fs::remove_file(&witness).expect("clear witness");
}
(output, invoked)
(output, args)
};
// Control: a local host with a plain database name reaches psql, so the
// refusals below come from the guard rather than an unrunnable fixture.
let (_, control_invoked) = run("localhost", "", "", "buzz");
// It must also pass -X so a psqlrc cannot \connect after the guard.
let (_, control_args) = run("localhost", "", "", "buzz");
let cases = [
("remote host", "shared-db.example.invalid", "", "", ""),
("address override", "localhost", "192.0.2.1", "", ""),
@@ -2402,11 +2407,15 @@ fn admin_seed_refuses_remote_database_before_invoking_psql() {
),
];
let results = cases.map(|(name, host, address, service, database)| {
let (output, invoked) = run(host, address, service, database);
(name, output, invoked)
let (output, args) = run(host, address, service, database);
(name, output, args.is_some())
});
std::fs::remove_dir_all(root).expect("remove scratch fixture");
assert!(control_invoked, "plain local target did not reach psql");
let control_args = control_args.expect("plain local target did not reach psql");
assert!(
control_args.lines().any(|arg| arg == "-X"),
"psql invoked without -X: {control_args:?}"
);
for (name, output, invoked) in results {
assert!(!invoked, "{name} reached psql");
assert!(!output.status.success(), "{name} accepted");

View File

@@ -38,15 +38,16 @@ if [[ ! "${PGDATABASE}" =~ ^[A-Za-z0-9_-]+$ ]]; then
exit 1
fi
# -X skips psqlrc, which runs after this guard and could \connect elsewhere.
if command -v psql >/dev/null 2>&1; then
run_psql() {
PGPASSWORD="${PGPASSWORD}" psql -h "${PGHOST}" -p "${PGPORT}" \
PGPASSWORD="${PGPASSWORD}" psql -X -h "${PGHOST}" -p "${PGPORT}" \
-U "${PGUSER}" -d "${PGDATABASE}" "$@"
}
elif docker exec buzz-postgres psql --version >/dev/null 2>&1; then
run_psql() {
docker exec -i -e PGPASSWORD="${PGPASSWORD}" buzz-postgres \
psql -U "${PGUSER}" -d "${PGDATABASE}" "$@"
psql -X -U "${PGUSER}" -d "${PGDATABASE}" "$@"
}
else
echo "error: neither psql nor buzz-postgres docker psql is available" >&2