From 0cbcbf3efd4ca16a502c6ecc217919e1d97261f0 Mon Sep 17 00:00:00 2001
From: ghost <49853598+JSONbored@users.noreply.github.com>
Date: Wed, 1 Jul 2026 21:41:26 -0700
Subject: [PATCH 1/2] fix(selfhost): avoid postgres url argv leaks
---
.../src/routes/docs.self-hosting-backup-scaling.tsx | 5 +++--
scripts/export-grafana-reporting-db.sh | 4 ++--
scripts/migrate-selfhost-sqlite-to-postgres.ts | 7 ++++---
test/unit/selfhost-grafana-reporting.test.ts | 10 ++++++++++
4 files changed, 19 insertions(+), 7 deletions(-)
diff --git a/apps/gittensory-ui/src/routes/docs.self-hosting-backup-scaling.tsx b/apps/gittensory-ui/src/routes/docs.self-hosting-backup-scaling.tsx
index a52017cdb0..09ea2b6a5f 100644
--- a/apps/gittensory-ui/src/routes/docs.self-hosting-backup-scaling.tsx
+++ b/apps/gittensory-ui/src/routes/docs.self-hosting-backup-scaling.tsx
@@ -96,8 +96,9 @@ QDRANT_URL=http://qdrant:6333`}
@pgbouncer:5432/gittensory
+npm run selfhost:postgres:migrate -- --sqlite /data/gittensory.sqlite
+npm run selfhost:postgres:migrate -- --sqlite /data/gittensory.sqlite --execute`}
/>
Restore checks
diff --git a/scripts/export-grafana-reporting-db.sh b/scripts/export-grafana-reporting-db.sh
index 52cbd0c809..7d0ced17b6 100644
--- a/scripts/export-grafana-reporting-db.sh
+++ b/scripts/export-grafana-reporting-db.sh
@@ -41,7 +41,7 @@ pg_enabled() {
}
pg_scalar() {
- psql "$PG_DB" -X -q -t -A -v ON_ERROR_STOP=1 -c "$1"
+ PGDATABASE="$PG_DB" psql -X -q -t -A -v ON_ERROR_STOP=1 -c "$1"
}
pg_table_exists() {
@@ -63,7 +63,7 @@ pg_column_exists() {
pg_copy_csv() {
query="$1"
out="$2"
- psql "$PG_DB" -X -q -v ON_ERROR_STOP=1 -c "COPY ($query) TO STDOUT WITH CSV" >"$out"
+ PGDATABASE="$PG_DB" psql -X -q -v ON_ERROR_STOP=1 -c "COPY ($query) TO STDOUT WITH CSV" >"$out"
}
sqlite_import_csv() {
diff --git a/scripts/migrate-selfhost-sqlite-to-postgres.ts b/scripts/migrate-selfhost-sqlite-to-postgres.ts
index 13a400fc59..8e4653f8d2 100644
--- a/scripts/migrate-selfhost-sqlite-to-postgres.ts
+++ b/scripts/migrate-selfhost-sqlite-to-postgres.ts
@@ -36,14 +36,15 @@ const TABLES_ALLOWED_AFTER_SCHEMA_INIT = new Set(["global_agent_controls", "glob
const POSTGRES_TEXT_NUL_REPLACEMENT = "\uFFFD";
function usage(): string {
- return `Usage: npm run selfhost:postgres:migrate -- --sqlite --postgres-url [--execute]
+ return `Usage: DATABASE_URL= npm run selfhost:postgres:migrate -- --sqlite [--execute]
Copies a self-host SQLite database into an empty Postgres backend. The default is a transactionally
-rolled-back dry run. Pass --execute to commit the copy.
+rolled-back dry run. Pass --execute to commit the copy. Prefer DATABASE_URL over --postgres-url so
+the Postgres credential is not exposed through process command lines.
Options:
--sqlite SQLite source file. Defaults to DATABASE_PATH or /data/gittensory.sqlite.
- --postgres-url Postgres target URL. Defaults to DATABASE_URL.
+ --postgres-url Postgres target URL. Defaults to DATABASE_URL; avoid this on shared hosts.
--migrations-dir Migration directory. Defaults to migrations.
--execute Commit the copy. Omit for a rollback dry run.
--allow-non-empty Allow non-empty target tables only when overlapping primary keys are identical.
diff --git a/test/unit/selfhost-grafana-reporting.test.ts b/test/unit/selfhost-grafana-reporting.test.ts
index a0289c582b..96298d3695 100644
--- a/test/unit/selfhost-grafana-reporting.test.ts
+++ b/test/unit/selfhost-grafana-reporting.test.ts
@@ -50,6 +50,16 @@ function fakePsql(root: string): string {
psql,
`#!/bin/sh
args="$*"
+case " $args " in
+ *" postgres://"*|*" postgresql://"*)
+ echo 'psql command line leaked postgres URL' >&2
+ exit 8
+ ;;
+esac
+if [ "\${PGDATABASE:-}" != "postgres://gittensory:pw@postgres:5432/gittensory" ]; then
+ echo 'psql did not receive postgres URL through PGDATABASE' >&2
+ exit 8
+fi
case "$args" in
*\\\\copy*)
echo 'unexpected psql meta-command copy' >&2
From 6511de402211716943010d8801f43d8c0e3637cf Mon Sep 17 00:00:00 2001
From: JSONbored <49853598+JSONbored@users.noreply.github.com>
Date: Wed, 1 Jul 2026 23:22:14 -0700
Subject: [PATCH 2/2] fix(docs): export DATABASE_URL in the postgres migrator
doc snippet
The postgres migrator now reads DATABASE_URL from the environment
instead of taking --postgres-url on argv (avoiding a credential leak
through process listings), and the docs were updated to match. But
the snippet set DATABASE_URL as a plain (non-exported) shell
assignment, so a user pasting the block verbatim would run the two
following npm run selfhost:postgres:migrate commands without the
variable in their environment, failing with "--postgres-url or
DATABASE_URL must be a postgres:// URL".
---
.../src/routes/docs.self-hosting-backup-scaling.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/gittensory-ui/src/routes/docs.self-hosting-backup-scaling.tsx b/apps/gittensory-ui/src/routes/docs.self-hosting-backup-scaling.tsx
index 09ea2b6a5f..ed012253fb 100644
--- a/apps/gittensory-ui/src/routes/docs.self-hosting-backup-scaling.tsx
+++ b/apps/gittensory-ui/src/routes/docs.self-hosting-backup-scaling.tsx
@@ -96,7 +96,7 @@ QDRANT_URL=http://qdrant:6333`}
@pgbouncer:5432/gittensory
+ code={`export DATABASE_URL=postgres://gittensory:@pgbouncer:5432/gittensory
npm run selfhost:postgres:migrate -- --sqlite /data/gittensory.sqlite
npm run selfhost:postgres:migrate -- --sqlite /data/gittensory.sqlite --execute`}
/>