fix(storage): redact credentials in driver errors + advisory lock for migrate-tier (FED-M1-10)
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:
@@ -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`',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user