Nostr WoT
NostrSignerNIP-46Clients

Amber 6.6.6 fixes a nostrconnect parser that mangled NIP-46 secrets

A Kotlin default separator turned a padded base64 secret into a comma-separated fragment, and NIP-46 requires the client to reject exactly that.

Nostr WoT Newsroom

Story4 min read

Assembled by the Nostr WoT Newsroom from the cited primary sources, and published automatically without individual human review.

Amber 6.6.6 fixes a nostrconnect parser that mangled NIP-46 secrets

Amber 6.6.6 was published on 28 September 2026. Its first changelog entry repairs a parsing bug in the signer's handling of nostrconnect:// connection tokens: a connection secret containing an = character came back altered in the connect response, and the requesting client refused the connection.

Issue #526, opened on 23 September, gives the concrete case. A client offers a token of the form nostrconnect://715ad7d1...?relay=wss%3A%2F%2Frelay.primal.net&secret=XQUdha4rjh_gjAgHN8X5yg==&perms=.... The secret is base64 with its padding intact. Amber returned XQUdha4rjh_gjAgHN8X5yg, , instead. The issue reports that applications built on NDK, naming Nmail as an example, generated secrets in this form.

The spec makes the failure total, not cosmetic

NIP-46 describes secret as a required query parameter, "a short random string that the remote-signer should return as the result field of its response". It then states that the value "MUST be provided to avoid connection spoofing" and that the client "MUST validate the secret returned by connect response".

That requirement is why a single mangled character sequence ends the handshake. The client is not permitted to accept a near match or to fall back to trusting the responding key. It compares what it generated against what it received, sees a different string, and stops. The pairing never completes, and from the user's side the signer simply does not connect.

The specification puts no restriction on the alphabet of the secret. "A short random string" admits base64, and base64 padding is =. A client producing padded base64 was within the spec, and the signer was the side that had to accept it.

A default separator, twice

The cause is narrow and documented in the diff. Kotlin's joinToString uses ", " as its default separator. The previous code split each query parameter on every =, discarded the first element, then rejoined the remainder without passing a separator:

kotlin
val internalSplit = it.split("=")
val paramName = internalSplit.first()
val json = internalSplit.mapIndexedNotNull { index, s ->
    if (index == 0) null else s
}.joinToString { data -> data }

Applied to secret=XQUdha4rjh_gjAgHN8X5yg==, the split produces four elements, the last two empty. Rejoining the final three with ", " yields XQUdha4rjh_gjAgHN8X5yg, , . Two defects compound in one line: the padding characters are consumed as delimiters, and a separator nobody intended is inserted in their place.

Version 6.6.6 replaces the whole block with a named helper that splits once:

kotlin
fun splitParam(param: String): Pair<String, String> =
    param.substringBefore("=") to param.substringAfter("=", "")

The same release corrects the identical mistake at a second call site. The token is split on ? to separate the client public key from the query string, and the remaining fragments were rejoined with split.drop(1).joinToString { it }. That now reads joinToString("") { it }. The first site broke every padded secret; the second only bites a token whose query string carries a literal ?, but it is the same default and the same class of error.

A new test file, NostrConnectUtilsTest.kt, adds seven cases. Four cover values holding =, including the padded secret from the issue, a value with = in the middle, a percent-encoded relay URL and a perms list. Three pin the degenerate inputs so the repair does not change them: a parameter with no =, one with an empty value, and the empty string.

The rest of the release

Six commits and sixteen files separate 6.6.5 from 6.6.6. The remainder is presentation and routing rather than protocol.

Three changelog entries concern contrast. White text and icons on light amber surfaces in dark theme are corrected across the Edit Relays floating action button, the add-relay icon buttons and the selected filter chips on the feedback screen. The Tor and proxy status icon in the top app bar, described as nearly invisible in light theme, now uses theme-aware colors in both themes, and the relay reconnect icon is matched to the adjacent relay count.

The last entry changes where feedback goes. Feedback issues are published to the relays advertised on Amber's repository announcement rather than to a hardcoded relay, with a fallback to the previous behavior when the announcement cannot be fetched. Timeouts over Tor were lengthened so a slow relay can still confirm the publish.

What the artifacts establish

Version 6.6.6 is a published GitHub release with Android assets and a signed checksum manifest, tagged at commit fc2e0de5. The parser change, the helper, the second call site and the seven test cases are all visible in the comparison against 6.6.5, so the changelog and the diff agree on this release.

That establishes what the code now does. It does not establish that any particular installation has updated, and store and repository channels move at their own pace. A user whose client kept failing its secret check should confirm the version actually running on the device.

Sources

Every claim in this piece links to a primary source.

  1. Amber v6.6.6 release — greenart7c3/Amber (September 28, 2026)
  2. Issue #526: nostrconnect:// parser corrupts param values that contain = — greenart7c3/Amber (September 23, 2026)
  3. Comparison v6.6.5...v6.6.6 — greenart7c3/Amber (September 28, 2026)
  4. NIP-46 Nostr Remote Signing — nostr-protocol/nips (September 28, 2026)

Stay Updated

Get news about published Nostr WoT releases, new features, and integrations.

You will receive the newsletter in English.

We store your email address and preferred language to send you the newsletter.

Newsletters