Skip to content

fix(api): preserve raw colons in role-ID path segment (DPoP htu mismatch)#43

Merged
sashyo merged 1 commit into
mainfrom
fix/dpop-htu-role-encoding
May 20, 2026
Merged

fix(api): preserve raw colons in role-ID path segment (DPoP htu mismatch)#43
sashyo merged 1 commit into
mainfrom
fix/dpop-htu-role-encoding

Conversation

@sashyo
Copy link
Copy Markdown
Owner

@sashyo sashyo commented May 20, 2026

Summary

After #42 the SSH connect path correctly builds ssh:<gw>:<srv>:<user> role IDs and fetches via getByRole(...), but the request now fails with 401 "DPoP proof invalid: htu mismatch":

got      = https://demo.keylessh.com/api/ssh-policies/committed/ssh%3ATide-GW%3AMy%20Server%3Ademo
expected = https://demo.keylessh.com/api/ssh-policies/committed/ssh:Tide-GW:My%20Server:demo

encodeURIComponent encodes : as %3A, but the DPoP htu canonicalization on this server keeps colons raw. The wire URL contains %3A while the signed htu claim contains : → server rejects with 401 before our route ever runs.

RFC 3986 permits : in path segments as pchar. Restoring raw colons after encodeURIComponent is safe and makes the wire URL match what DPoP canonicalizes to.

Change

client/src/lib/api.tsgetByRole:

// Before:
`/api/ssh-policies/committed/${encodeURIComponent(roleId)}`
// After:
`/api/ssh-policies/committed/${encodeURIComponent(roleId).replace(/%3A/g, ":")}`

Only one call site uses this URL pattern with a role-ID containing :; audited via grep.

Test plan

…tu mismatch

encodeURIComponent encodes the colons in 'ssh:Tide-GW:My Server:demo' as
%3A, but the DPoP htu canonicalization on this server keeps them raw.
Result: the URL on the wire is …/ssh%3ATide-GW%3A…/ but the signed htu
claim is …/ssh:Tide-GW:…/, so the server rejects the request with 401
"DPoP proof invalid: htu mismatch".

RFC 3986 allows ':' in path segments as pchar, so restoring the raw colons
after encodeURIComponent is safe and makes the wire URL match what DPoP
canonicalizes to.
@sashyo sashyo merged commit aa756b8 into main May 20, 2026
2 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