hashicorp / hashicorp/vault-plugin-auth-jwt
Unicode NFD/NFC Identity Collision in JWT Auth
- Dominant language
- Go
- Stars
- 107
- Forks
- 73
- Avg merge
- 13h 49m
- Merged PRs (30d)
- 7
Description
## Describe the bug
The JWT auth method does not apply Unicode normalization when creating identity aliases from JWT claims. Two JWTs with the same visual `sub` value in different Unicode representations (NFC vs NFD) create **two distinct Vault entities**, splitting a single logical user's identity.
In `vault-plugin-auth-jwt`, `path_login.go:248-271`, `createIdentity()` uses the `user_claim` string directly as the alias name:
```go
userName, ok := userClaimRaw.(string)
// ...
alias := &logical.Alias{
Name: userName, // no normalization
Metadata: metadata,
}
```
No `unicode/norm` package is imported anywhere in the plugin. This exists in vault-plugin-auth-jwt v0.25.0 (bundled in Vault 1.21.4) and persists through v0.26.1.
## To Reproduce
1. Enable JWT auth and configure a role with `user_claim="sub"`.
2. Login with a JWT where `sub` = `"caféuser"` (NFC form: é as single codepoint `U+00E9`):
```bash
vault write auth/jwt/login jwt="$TOKEN_NFC" role="test-role"
# → Creates entity aaa-aaa
```
3. Login with a JWT where `sub` = `"caféuser"` (NFD form: `e` + combining acute accent `U+0301`):
```bash
vault write auth/jwt/login jwt="$TOKEN_NFD" role="test-role"
# → Creates entity bbb-bbb (DIFFERENT entity)
```
4. Both strings render identically as "caféuser" and are semantically the same user per the Unicode specification (`unicodedata.normalize('NFC', nfd) == nfc`).
## Expected behavior
Both Unicode forms should resolve to the **same** Vault entity. The alias name should be normalized (preferably to NFC per [RFC 8264 PRECIS](https://www.rfc-editor.org/rfc/rfc8264)) before identity lookup/creation.
## Environment
- **Vault Server Version**: 1.21.4
- **vault-plugin-auth-jwt Version**: v0.25.0 (bundled), also verified in v0.26.1
- **Server Operating System/Architecture**: linux/amd64
## Additional context
**Impact**: The same logical user ends up with two distinct Vault entities, each with separate policies, audit trails, and secret access. If an admin sets policies on entity `aaa`, the user logging in via the NFD form (`bbb`) won't have those policies. The mismatch is invisible in UIs since both forms render identically.
**Suggested fix** — normalize to NFC in `createIdentity()`:
```go
import "golang.org/x/text/unicode/norm"
userName = norm.NFC.String(userName)
```
This is primarily a concern when IdPs allow non-ASCII characters in usernames or display names used as the `sub` claim.
[report.md](https://github.com/user-attachments/files/26163280/report.md)
[poc.py](https://github.com/user-attachments/files/26163279/poc.py)
[docker-compose.yml](https://github.com/user-attachments/files/26163278/docker-compose.yml)
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in path_login.go:248-271 and inspect createIdentity(), where the user_claim string becomes the logical alias name. Verify the NFC and NFD reproduction cases, then ensure equivalent forms resolve to one Vault entity and add or update coverage for both representations.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- authentication, security
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100