Skip to content

[ANCHOR-1400]: Validate the SEP-45 home domain with the shared matcher and restrict wildcard matching - #2087

Merged
ceciliaromao merged 15 commits into
developfrom
fix/anchor-1400
Nov 13, 2026
Merged

ceciliaromao merged 15 commits into
developfrom
fix/anchor-1400

Conversation

@ceciliaromao

@ceciliaromao ceciliaromao commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Description

  • SEP-45 now validates home_domain with the same matcher SEP-10 uses (ClientDomainHelper.isDomainNameMatch), in both getChallenge and validate, instead of comparing only URI.getAuthority(). A value that differs from a configured domain by a path, query, fragment, userinfo or backslash is rejected with 400 Invalid home domain.
  • getChallenge runs the check before it creates the nonce, simulates the transaction or fetches the client_domain, so nothing is built or server-signed for a rejected value.
  • Values are normalized before matching (lower-cased, one leading http:// or https:// removed), so clients that send a scheme or mixed case keep working. The SEP-45 JWT now carries the normalized domain (localhost:8080) in iss and home_domain; the challenge still echoes the value the client sent.
  • A wildcard * in home_domains now matches only letters, digits, -, _ and . (it used to be .*). This fixes wildcards never matching a real subdomain in SEP-45, and stops *.example.org from matching values like evil.example/x.example.org in SEP-45 and SEP-10.
  • validate rejects an empty (or scheme-only) home_domain and a missing one with 400 Invalid home domain (a missing one used to be an NPE).

Context

  • HackerOne report 4108576. Sep45Service accepted and signed a home_domain that SEP-10 rejects, and WebAuthJwtFilter accepts that token on the SEP-24 endpoints, where the home domain is copied into the interactive-URL token.
  • The shared matcher alone was not enough: its .* wildcard also let / ? # @ through, so the wildcard is tightened in the shared helper as part of the same change.

Testing

  • ./gradlew test
  • ./gradlew :core:test: Sep45ServiceTest (27), ClientDomainHelperTest (29), Sep10ServiceTest (73), 0 failures. New tests cover each rejected value, the wildcard accept/reject cases, scheme and case tolerance, the signed claims, and that a rejected challenge creates no nonce and runs no simulation.
  • ./gradlew :core:spotlessCheck :essential-tests:spotlessCheck
  • ./gradlew :essential-tests:testSep45 (6) and :essential-tests:testSep10 (21 cases) against the local stack: 0 failures. Sep45Tests now expects the bare domain in jwt.iss and jwt.homeDomain.

Documentation

N/A. No new configuration or API; the home_domains comment in anchor-config-default-values.yaml already describes the wildcard form.

Known limitations

  • SEP-45 iss and home_domain change from the value the client sent (e.g. http://localhost:8080) to the normalized domain (localhost:8080). Relying parties comparing against the scheme-prefixed form need to match on the bare domain.
  • A leading dot (.example.org) still matches *.example.org, as before, because * can match the empty string.
  • SEP-10 stays case-sensitive; only SEP-45 compares case-insensitively.
  • Sep24End2EndTests (SEP-45 wallet flow over Kafka events) was not run locally.

@ceciliaromao ceciliaromao self-assigned this Oct 28, 2026
amandagonsalves and others added 2 commits November 12, 2026 02:05
…ts and pin the SEP-10 PoC (#2088)

### Description

- Rejects a wildcard `home_domains` match whose host has a label longer
than 63 characters or is longer than 253 characters (the `:port` is not
counted). The check lives in `ClientDomainHelper.hasValidHostLabels`, so
SEP-10 and SEP-45 both get it. Exactly 63 and exactly 253 are still
accepted. The exact-match branch is unchanged.
- Adds regression tests for report #4108627 at the three SEP-10 call
sites of the matcher, using the reporter's fixture
(`*.wildcard.stellar.org`) and strings:
`evil.com/x.wildcard.stellar.org` and `a.wildcard.stellar.org/evil` are
rejected, `abc.def.wildcard.stellar.org` and `a.wildcard.stellar.org`
are accepted.
  - `createChallenge` (GET `/auth`)
  - `validateChallenge` (POST `/auth`, operation key name check)
  - `validateChallengeTransactionHomeDomain`

### Context

- Report #[4108627](https://hackerone.com/reports/4108627): SEP-10
signed a caller-chosen `home_domain` on a wildcard config because
`isDomainNameMatch` expanded `*` to `.*`, so
`evil.com/x.wildcard.stellar.org` matched `*.wildcard.stellar.org`.
- The fix for that claim (`*` limited to `[A-Za-z0-9_.-]`, plus
empty/hyphen-edged label rejection) is already in #2087 (ANCHOR-1400),
which this branch is stacked on. This PR does not touch it.
- What was left: (1) only `createChallenge` had a test with a slash
payload, and with another fixture; the POST `/auth` sites had none. (2)
The assessment promised to reject a `home_domain` that is not a
syntactically valid host, but a label over 63 characters or a host over
253 still matched under `*.sdp.stellar.org`, which no DNS host can have.

### Testing

- `./gradlew test`
- `./gradlew :core:test` passes in full (1168 tests); 14 tests added in
`ClientDomainHelperTest` (7), `Sep10ServiceTest` (6) and
`Sep45ServiceTest` (1).
- Length limits are tested at the limit and at limit+1 (63/64 label,
253/254 host, with and without a `:8080` port pattern).
- `./gradlew :core:spotlessCheck` passes.
- Independent verification: all 15 spec checks pass at HEAD; 3 mutations
(`>` to `>=` on each limit, dropping the port strip) and 15 more
throwaway ones (including `*` back to `.*`) were all caught by the
tests.

### Documentation

N/A: internal validation change, no feature or public API change.

### Known limitations

- Stacked on #2087 (`fix/anchor-1400`): retarget this PR to `develop`
once #2087 merges.
- The length limits apply only to wildcard matches. A configured exact
domain is compared by equality with no length rule, because that text
comes from operator config, not from the request.
- `_` is still accepted in wildcard labels (kept on purpose in
ANCHOR-1400).
- The reporter's PoC is covered at unit level only; there is no
integration test through the running `/auth` endpoints.
@ceciliaromao
ceciliaromao merged commit b7f2dd4 into develop Nov 13, 2026
34 of 38 checks passed
@ceciliaromao
ceciliaromao deleted the fix/anchor-1400 branch November 13, 2026 17:22
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.

2 participants