fix(storage): redact credentials in driver errors + advisory lock for migrate-tier (FED-M1-10)
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful

Independent two-round security review surfaced credential leak vectors and
a concurrency footgun in the federation tier code. All findings addressed:

Credential redaction (HIGH):
- New packages/storage/src/redact-error.ts strips user:password from
  postgres://, postgresql://, redis://, rediss:// URLs (case-insensitive,
  global). Internal — not exported from package index.
- Applied to: migrate-tier inner catch, tier-detection postgres+pgvector+
  valkey probe error fields, cli.ts storage status + migrate-tier outer
  catch. The TierHealthReport JSON emitted by `mosaic gateway doctor --json`
  no longer leaks DSNs to monitoring pipelines.
- 10 unit tests covering both schemes, multi-URL, no-creds, case variants.

Advisory lock for migrate-tier (LOW-MEDIUM):
- PostgresMigrationTarget gains tryAcquireAdvisoryLock /
  releaseAdvisoryLock using session-scoped pg_try_advisory_lock with key
  hashtext('mosaic-migrate-tier'). Non-blocking — fails fast with a clear
  message if another invocation is in progress. Released in finally; PG
  releases automatically on session end. Dry-run skips lock acquisition.

SKIP_TABLES rationale (advisory):
- Comment expanded to document why sessions/verifications/admin_tokens
  are skipped AND why accounts/provider_credentials are intentionally
  migrated (durable user-bound credentials). Operators migrating to a
  shared/multi-tenant federated tier should review whether to wipe these
  manually post-migration.

Tests: 95 storage tests pass + 1 integration test skipped (FEDERATED_INTEGRATION).

Refs #460
This commit is contained in:
Jarvis
2026-04-19 21:01:22 -05:00
parent 1e2b8ac8de
commit a2277c91cb
6 changed files with 261 additions and 70 deletions

View File

@@ -12,6 +12,7 @@
import postgres from 'postgres';
import { Redis } from 'ioredis';
import { redactErrMsg } from './redact-error.js';
/* ------------------------------------------------------------------ */
/* Local structural type — avoids circular dependency */
@@ -159,7 +160,7 @@ async function probePostgresMeasured(url: string): Promise<ProbeResult> {
port,
durationMs: Date.now() - start,
error: {
message: cause instanceof Error ? cause.message : String(cause),
message: redactErrMsg(cause instanceof Error ? cause.message : String(cause)),
remediation:
'Start Postgres: `docker compose -f docker-compose.federated.yml --profile federated up -d postgres-federated`',
},
@@ -231,7 +232,10 @@ async function probePgvectorMeasured(url: string): Promise<ProbeResult> {
host,
port,
durationMs: Date.now() - start,
error: { message: cause instanceof Error ? cause.message : String(cause), remediation },
error: {
message: redactErrMsg(cause instanceof Error ? cause.message : String(cause)),
remediation,
},
};
} finally {
if (sql) {
@@ -299,7 +303,7 @@ async function probeValkeyMeasured(url: string): Promise<ProbeResult> {
port,
durationMs: Date.now() - start,
error: {
message: cause instanceof Error ? cause.message : String(cause),
message: redactErrMsg(cause instanceof Error ? cause.message : String(cause)),
remediation:
'Start Valkey: `docker compose -f docker-compose.federated.yml --profile federated up -d valkey-federated`',
},