From ed5975a1cc8bddadcd462d8e22837a323b866c98 Mon Sep 17 00:00:00 2001 From: Letdown2491 Date: Sun, 14 Jun 2026 21:34:26 -0600 Subject: [PATCH] fix(config): fail gracefully on malformed startup config Replace raw panics in the env-parsing helpers and nPubToPubkey with log.Fatalf, so a mistyped config value exits 1 with an actionable, key-named message instead of a status-2 panic crash-loop under a supervisor (StartOS/systemd/Docker). Also reject non-npub bech32 in npub fields (e.g. a pasted nsec) and redact malformed secret-looking values from logs. --- blossomMigration.go | 2 +- config.go | 35 +++++++++++++++++++++++++---------- init.go | 16 ++++++++-------- 3 files changed, 34 insertions(+), 19 deletions(-) diff --git a/blossomMigration.go b/blossomMigration.go index f321be5..c9faf43 100644 --- a/blossomMigration.go +++ b/blossomMigration.go @@ -12,7 +12,7 @@ func migrateBlossomMetadata(ctx context.Context, bl *blossom.BlossomServer) { outboxDBWrapper := blossom.EventStoreBlobIndexWrapper{Store: outboxDB, ServiceURL: getHTTPScheme(config.RelayURL) + config.RelayURL} // List all BlobDescriptor for the relay owner pubkey - ownerPubkey := nPubToPubkey(config.OwnerNpub) + ownerPubkey := nPubToPubkey("OWNER_NPUB", config.OwnerNpub) blobsChan, err := outboxDBWrapper.List(ctx, ownerPubkey) if err != nil { slog.Error("🚫 Failed to list blobs", "error", err) diff --git a/config.go b/config.go index 0a05c9f..10de3bb 100644 --- a/config.go +++ b/config.go @@ -76,7 +76,7 @@ func loadConfig() Config { cfg := Config{ OwnerNpub: getEnv("OWNER_NPUB"), - OwnerPubKey: nPubToPubkey(getEnv("OWNER_NPUB")), + OwnerPubKey: nPubToPubkey("OWNER_NPUB", getEnv("OWNER_NPUB")), DBEngine: getEnvString("DB_ENGINE", "lmdb"), LmdbMapSize: getEnvInt64("LMDB_MAPSIZE", 0), BlossomPath: getEnvString("BLOSSOM_PATH", "blossom"), @@ -191,7 +191,7 @@ func getNpubsFromFile(filePath string) map[string]struct{} { for _, npub := range npubs { npub = strings.TrimSpace(npub) - pubKeys[nPubToPubkey(npub)] = struct{}{} + pubKeys[nPubToPubkey(filePath, npub)] = struct{}{} } return pubKeys } @@ -215,7 +215,7 @@ func getEnvInt(key string, defaultValue int) int { if value, ok := os.LookupEnv(key); ok { intValue, err := strconv.Atoi(value) if err != nil { - panic(err) + log.Fatalf("invalid value for %s: %q is not an integer (%v)", key, value, err) } return intValue } @@ -226,7 +226,7 @@ func getEnvInt64(key string, defaultValue int64) int64 { if value, ok := os.LookupEnv(key); ok { intValue, err := strconv.ParseInt(value, 10, 64) if err != nil { - panic(err) + log.Fatalf("invalid value for %s: %q is not an integer (%v)", key, value, err) } return intValue } @@ -237,7 +237,7 @@ func getEnvBool(key string, defaultValue bool) bool { if value, ok := os.LookupEnv(key); ok { boolValue, err := strconv.ParseBool(value) if err != nil { - panic(err) + log.Fatalf("invalid value for %s: %q is not a boolean (%v)", key, value, err) } return boolValue } @@ -248,19 +248,34 @@ func getEnvDuration(key string, defaultValue time.Duration) time.Duration { if value, ok := os.LookupEnv(key); ok { durationValue, err := time.ParseDuration(value) if err != nil { - panic(err) + log.Fatalf("invalid value for %s: %q is not a duration (%v)", key, value, err) } return durationValue } return defaultValue } -func nPubToPubkey(nPub string) string { - _, v, err := nip19.Decode(nPub) +// nPubToPubkey decodes a bech32 npub into its hex public key. label identifies +// the source of the value (an env var name or file path) so a mistyped npub +// produces an actionable error instead of a status-2 panic crash-loop. +func nPubToPubkey(label, nPub string) string { + prefix, v, err := nip19.Decode(nPub) if err != nil { - panic(err) + // Only echo the raw value when it looks like an npub; a malformed + // non-npub (e.g. a mistyped nsec) must not be leaked into logs. + if strings.HasPrefix(nPub, "npub1") { + log.Fatalf("invalid npub for %s: %q could not be decoded (%v)", label, nPub, err) + } + log.Fatalf("invalid npub for %s: value could not be decoded as an npub (%v)", label, err) + } + if prefix != "npub" { + log.Fatalf("invalid npub for %s: expected an npub, got a %q", label, prefix) + } + pubkey, ok := v.(string) + if !ok { + log.Fatalf("invalid npub for %s: %q did not decode to a public key", label, nPub) } - return v.(string) + return pubkey } var art = ` diff --git a/init.go b/init.go index 4675fc1..17fbccc 100644 --- a/init.go +++ b/init.go @@ -125,7 +125,7 @@ func initRelays(ctx context.Context) { initRelayLimits() privateRelay.Info.Name = config.PrivateRelayName - privateRelay.Info.PubKey = nPubToPubkey(config.PrivateRelayNpub) + privateRelay.Info.PubKey = nPubToPubkey("PRIVATE_RELAY_NPUB", config.PrivateRelayNpub) privateRelay.Info.Description = config.PrivateRelayDescription privateRelay.Info.Icon = config.PrivateRelayIcon privateRelay.Info.Version = config.RelayVersion @@ -177,7 +177,7 @@ func initRelays(ctx context.Context) { RelayURL string }{ RelayName: config.PrivateRelayName, - RelayPubkey: nPubToPubkey(config.PrivateRelayNpub), + RelayPubkey: nPubToPubkey("PRIVATE_RELAY_NPUB", config.PrivateRelayNpub), RelayDescription: config.PrivateRelayDescription, RelayURL: getWSScheme(config.RelayURL) + config.RelayURL + "/private", } @@ -188,7 +188,7 @@ func initRelays(ctx context.Context) { }) chatRelay.Info.Name = config.ChatRelayName - chatRelay.Info.PubKey = nPubToPubkey(config.ChatRelayNpub) + chatRelay.Info.PubKey = nPubToPubkey("CHAT_RELAY_NPUB", config.ChatRelayNpub) chatRelay.Info.Description = config.ChatRelayDescription chatRelay.Info.Icon = config.ChatRelayIcon chatRelay.Info.Version = config.RelayVersion @@ -242,7 +242,7 @@ func initRelays(ctx context.Context) { RelayURL string }{ RelayName: config.ChatRelayName, - RelayPubkey: nPubToPubkey(config.ChatRelayNpub), + RelayPubkey: nPubToPubkey("CHAT_RELAY_NPUB", config.ChatRelayNpub), RelayDescription: config.ChatRelayDescription, RelayURL: getWSScheme(config.RelayURL) + config.RelayURL + "/chat", } @@ -253,7 +253,7 @@ func initRelays(ctx context.Context) { }) outboxRelay.Info.Name = config.OutboxRelayName - outboxRelay.Info.PubKey = nPubToPubkey(config.OutboxRelayNpub) + outboxRelay.Info.PubKey = nPubToPubkey("OUTBOX_RELAY_NPUB", config.OutboxRelayNpub) outboxRelay.Info.Description = config.OutboxRelayDescription outboxRelay.Info.Icon = config.OutboxRelayIcon outboxRelay.Info.Version = config.RelayVersion @@ -306,7 +306,7 @@ func initRelays(ctx context.Context) { RelayURL string }{ RelayName: config.OutboxRelayName, - RelayPubkey: nPubToPubkey(config.OutboxRelayNpub), + RelayPubkey: nPubToPubkey("OUTBOX_RELAY_NPUB", config.OutboxRelayNpub), RelayDescription: config.OutboxRelayDescription, RelayURL: getWSScheme(config.RelayURL) + config.RelayURL + "/outbox", } @@ -347,7 +347,7 @@ func initRelays(ctx context.Context) { migrateBlossomMetadata(ctx, bl) inboxRelay.Info.Name = config.InboxRelayName - inboxRelay.Info.PubKey = nPubToPubkey(config.InboxRelayNpub) + inboxRelay.Info.PubKey = nPubToPubkey("INBOX_RELAY_NPUB", config.InboxRelayNpub) inboxRelay.Info.Description = config.InboxRelayDescription inboxRelay.Info.Icon = config.InboxRelayIcon inboxRelay.Info.Version = config.RelayVersion @@ -399,7 +399,7 @@ func initRelays(ctx context.Context) { RelayURL string }{ RelayName: config.InboxRelayName, - RelayPubkey: nPubToPubkey(config.InboxRelayNpub), + RelayPubkey: nPubToPubkey("INBOX_RELAY_NPUB", config.InboxRelayNpub), RelayDescription: config.InboxRelayDescription, RelayURL: getWSScheme(config.RelayURL) + config.RelayURL + "/inbox", }