hashicorp / hashicorp/vault-plugin-auth-jwt

Unicode NFD/NFC Identity Collision in JWT Auth

Open
#377 0 comments 0 reactions 0 assignees View on GitHub
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.