Skip to content

fix(lightning): resolve Lightning Addresses case-insensitively (211 users still 404ing) - #533

Merged
ralyodio merged 1 commit into
masterfrom
fix/lnurlp-case-insensitive
Aug 16, 2026
Merged

fix(lightning): resolve Lightning Addresses case-insensitively (211 users still 404ing)#533
ralyodio merged 1 commit into
masterfrom
fix/lnurlp-case-insensitive

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Follow-up to #532. That PR was squash-merged before this commit was pushed, so master has the wallet/pay-link fix but not this one, and 211 users are still broken in production right now:

$ curl https://ugig.net/.well-known/lnurlp/Riot_SecOps
{"status":"ERROR","reason":"Unknown user: Riot_SecOps"}   # HTTP 404

The bug

/.well-known/lnurlp/<username> matched profiles with:

.eq('username', username.toLowerCase())

That is a case-sensitive equality against a column that stores the username in its original case. So a profile stored as Riot_SecOps can never be found — /Riot_SecOps lowercases the input to riot_secops and misses, and /riot_secops misses too. Every mixed-case username 404s Unknown user in both case forms, regardless of whether its wallet has a pay link.

211 of 687 wallet holders have a mixed-case username, and all 211 advertise an ln_address. The #532 backfill gave them working pay links — this is the only thing still standing between them and receiving payments.

The fix

Match case-insensitively. ilike is used only as a prefilter and its wildcards are escaped; the authoritative check is a strict toLowerCase() comparison in JS, so an over-broad pattern can never resolve to the wrong payee. This matters here because usernames contain _, which is a single-character wildcard in SQL LIKE.

Three usernames collide case-insensitively — AgentHansaHelper/agenthansahelper, AidenLy/aidenly, Vault9000/vault9000. In each pair exactly one profile has a wallet, so the lookup prefers an exact-case match and then the candidate that actually has a wallet, which disambiguates all three.

Verification

  • npx tsc --noEmit clean, eslint clean.
  • Backfill from fix(lightning): stop orphaning LNbits wallets and advertising dead Lightning Addresses #532 is done and confirmed: a full rescan of all 687 wallets shows 687 with pay links, 0 remaining, 0 errors.
  • Lowercase users already resolve in prod today (davidanup, aks_escrow, weruh, sunsonai, ratamaha, jonyd, voltron_agent, vianerds_scoutworkshop — all HTTP 200, each with its own distinct callback).
  • After this deploys, Riot_SecOps and the other 210 should return 200 rather than Unknown user.

🤖 Generated with Claude Code

The route matched profiles with .eq('username', username.toLowerCase()), a
case-sensitive comparison against a column that stores the original case. Any
mixed-case username therefore 404'd with "Unknown user" in BOTH case forms —
including its own — no matter whether the wallet had a pay link. 211 of 687
wallet holders have a mixed-case username and all of them advertise an address.

Match case-insensitively instead. ilike is only a prefilter (wildcards escaped);
the strict lowercase comparison in JS is what decides the match, so an over-broad
pattern can never resolve to the wrong person. Three usernames differ only by
case, so prefer an exact-case match and then the candidate that actually has a
wallet, which disambiguates all three.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

46 finding(s)

HIGH/CRITICAL: 2 | MEDIUM: 25 | LOW: 19

Severity Rule Location
HIGH secret-generic-api-key docs/agents/integration-guide.md:893
HIGH js-ssrf-outbound-request scripts/scan-all-skills.ts:38
MEDIUM manifest-install-lifecycle-script package.json:18
MEDIUM js-open-redirect src/app/agent-login/AgentLoginForm.tsx:38
MEDIUM js-dynamic-code-execution src/app/api/skills/[slug]/scan/route.test.ts:212
MEDIUM js-dynamic-code-execution src/app/api/skills/[slug]/scan/route.test.ts:223
MEDIUM js-dynamic-code-execution src/app/api/skills/[slug]/scan/route.test.ts:239
MEDIUM js-unescaped-html-sink src/app/blog/[slug]/page.tsx:79
MEDIUM js-open-redirect src/app/dashboard/subscription/page.tsx:90
MEDIUM js-open-redirect src/app/dashboard/subscription/page.tsx:113
MEDIUM js-open-redirect src/app/dashboard/subscription/page.tsx:144
MEDIUM js-open-redirect src/components/funding/FundingClient.tsx:137
MEDIUM js-dynamic-code-execution src/lib/skills/composite-scanner.test.ts:106
MEDIUM js-dynamic-code-execution src/lib/skills/metadata-extract.ts:300
MEDIUM js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:36
MEDIUM js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:44
MEDIUM js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:66
MEDIUM js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:81
MEDIUM js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:94
MEDIUM js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:103
MEDIUM js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:118
MEDIUM js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:144
MEDIUM js-dynamic-code-execution src/lib/skills/secureclaw-scanner.test.ts:162
MEDIUM js-dynamic-code-execution src/lib/skills/security-scan.test.ts:28
MEDIUM js-dynamic-code-execution src/lib/skills/security-scan.ts:48
MEDIUM js-dynamic-code-execution src/lib/skills/url-import.test.ts:178
MEDIUM js-dynamic-code-execution src/lib/skills/url-import.test.ts:191
LOW secret-generic-credential cli/src/commands/auth.test.ts:66
LOW secret-generic-credential cli/src/commands/auth.test.ts:85
LOW secret-generic-credential src/app/api/auth/login/route.test.ts:53
LOW secret-generic-credential src/app/api/auth/login/route.test.ts:68
LOW secret-generic-credential src/app/api/auth/login/route.test.ts:87
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:158
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:182
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:193
LOW secret-generic-credential src/app/api/auth/signup/route.test.ts:232
LOW secret-generic-credential src/lib/api.test.ts:126
LOW secret-generic-credential src/lib/api.test.ts:131
LOW secret-generic-credential src/lib/validations.test.ts:148
LOW secret-generic-credential src/lib/validations.test.ts:512
LOW secret-generic-credential src/lib/validations.test.ts:523
LOW secret-generic-credential src/lib/validations.test.ts:538
LOW secret-generic-credential src/lib/validations.test.ts:548
LOW secret-generic-credential src/lib/validations.test.ts:557
LOW secret-generic-credential src/lib/validations.test.ts:567
LOW secret-generic-credential src/lib/validations.test.ts:582

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 94f518c into master Aug 16, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant