Skip to content

fix(httpapi): strip relayauth token-class prefix before parsing bearer as JWT#205

Merged
khaliqgant merged 1 commit into
mainfrom
fix/strip-relayauth-token-prefix
May 22, 2026
Merged

fix(httpapi): strip relayauth token-class prefix before parsing bearer as JWT#205
khaliqgant merged 1 commit into
mainfrom
fix/strip-relayauth-token-prefix

Conversation

@khaliqgant
Copy link
Copy Markdown
Member

Summary

Relayauth's /v1/tokens/path and /v1/tokens/workspace mint endpoints wrap the access token they issue with a class prefix: relay_pa_<jwt>, relay_ws_<jwt>, relay_id_<jwt>. The prefix encodes the token class for clients that route by type; it is NOT part of the RS256 JWS that follows.

parseBearer in internal/httpapi/auth.go was splitting the raw bearer on . without first stripping the prefix. For a relay_pa_eyJ.eyJ.<sig> token, parts[0] ended up as relay_pa_eyJ<rest_of_header_b64>. The underscore is valid in base64url, so base64.RawURLEncoding.DecodeString sometimes succeeded; the subsequent JSON unmarshal then failed because the decoded bytes weren't valid JSON. Net effect: every request bearing a relayauth-wrapped token returned 401 "invalid jwt header" (or, depending on how dots fell in the prefix, "invalid jwt format").

Production impact

The cloud repo's proactive runtime (agentworkforce/cloud) starts a relayfile-mount daemon inside per-fire Daytona sandboxes so handler-side writebacks (ctx.github.comment etc.) flush to relayfile cloud. Mint succeeded, daemon launched, and then:

2026/05/22 20:47:09 mount sync cycle failed: http 401 unauthorized: invalid jwt header
2026/05/22 20:47:09 mount sync cycle failed: http 401 unauthorized: invalid jwt header
2026/05/22 20:47:09 mount sync cycle failed: http 401 unauthorized: invalid jwt header

Verified end-to-end against agentworkforce/proactive-agents#105/#106:

  • Handler ran successfully (runner.handler.ok durationMs=3)
  • Draft comment files appeared at /workspace/github/repos/AgentWorkforce/proactive-agents/issues/106/comments/create comment <uuid>.json
  • Daemon hit 401 on every poll cycle; nothing reached relayfile cloud
  • runScript eventually 524'd from Daytona's proxy after the cleanup trap's --once flush also hammered 401s

Same flow underpins cloud-agent boxes (packages/web/.../box/box-manager.ts), which use the same mint helper.

Fix

+var relayauthTokenPrefixes = []string{
+	"relay_pa_",
+	"relay_ws_",
+	"relay_id_",
+}
+
+func stripRelayauthTokenPrefix(raw string) string {
+	for _, prefix := range relayauthTokenPrefixes {
+		if strings.HasPrefix(raw, prefix) {
+			return strings.TrimPrefix(raw, prefix)
+		}
+	}
+	return raw
+}
+
 func parseBearer(authHeader string, verifier *bearerVerifier, now time.Time) (tokenClaims, *authError) {
 	...
 	raw := strings.TrimSpace(strings.TrimPrefix(authHeader, "Bearer "))
+	raw = stripRelayauthTokenPrefix(raw)
 	parts := strings.Split(raw, ".")

Falls back to the raw input if no known prefix is present, so bare JWTs minted via /v1/tokens (no class wrapper) continue to parse unchanged.

Test plan

  • TestParseBearerStripsRelayauthTokenPrefixes — exercises all three prefixes (relay_pa_, relay_ws_, relay_id_) against a real RS256 JWT, plus a bare-JWT sanity case + a relay_pa_not-a-jwt rejection case (header decode must still fail AFTER the strip)
  • All existing TestParseBearer* cases pass unchanged
  • go test ./... clean (8 packages, no regressions in mountsync/mountfuse/writeback/digest/schema)
  • Post-merge + relayfile-cloud deploy: trigger an issues.opened event in AgentWorkforce/proactive-agents. Expected: mount daemon polls successfully (200 from /v1/workspaces/.../fs/events), ctx.github.comment draft file pushes to relayfile cloud, adapter writeback POSTs comment to GitHub, comment lands on the issue.

Future-proofing

If relayauth adds new token classes (relay_<class>_*), append the prefix to relayauthTokenPrefixes. The list lives in internal/httpapi/auth.go next to its sole consumer.

Possible follow-up

Independent investigation (cloud-side) surfaced a second potential issue: scopeMatches returns a 403 "missing required scope: fs:read" when checking a token with path-scoped grants (relayfile:fs:read:/github/*) against a coarse fs:read requirement. The Go-side code actually handles this correctly (path is the OPTIONAL 4th segment, plane/res/act match), so I believe the 403 came from a different curl test against a path-required endpoint via scopeMatchesPath. Confirming this after the prefix-strip is merged + deployed; will file a separate PR if a second issue surfaces.

🤖 Generated with Claude Code

…r as JWT

Relayauth's `/v1/tokens/path` and `/v1/tokens/workspace` mint endpoints
wrap the access token they issue with a class prefix
(`relay_pa_<jwt>`, `relay_ws_<jwt>`, `relay_id_<jwt>`). The prefix
encodes the token class for clients that route by token type, but is
NOT part of the RS256 JWS string that follows.

`parseBearer` was splitting the raw bearer on `.` without first
stripping the prefix. For a `relay_pa_eyJ.eyJ.<sig>` token,
`parts[0]` ended up as `relay_pa_eyJ<rest_of_header_b64>`. The
underscore character is valid in base64url so `base64.RawURLEncoding.
DecodeString` sometimes succeeded; the subsequent JSON unmarshal then
failed because the decoded bytes were not valid JSON. Net effect:
every request bearing a relayauth-wrapped token returned a 401
"invalid jwt header" (or, depending on how the dots fell in the
prefix, "invalid jwt format").

This silently broke the relayfile-mount daemon used by cloud's
proactive runtime (agentworkforce/cloud) and the cloud-agent box
flow: the mount started, hit 401 on every sync cycle, and writeback
drafts that handlers wrote to local disk never reached relayfile
cloud or the upstream provider. Verified end-to-end against issue
agentworkforce/proactive-agents#105/#106 — handler ran fine, draft
files written to local mirror, daemon log filled with
`mount sync cycle failed: http 401 unauthorized: invalid jwt header`.

Fix: introduce `stripRelayauthTokenPrefix` and call it inside
`parseBearer` immediately after the `Bearer ` strip and before the
dot-split. Falls back to the raw input if no known prefix is present,
so bare JWTs minted via `/v1/tokens` (with no class wrapper) continue
to parse unchanged.

Tests: new `TestParseBearerStripsRelayauthTokenPrefixes` exercises all
three known prefixes (`relay_pa_`, `relay_ws_`, `relay_id_`) against
a real RS256 JWT, plus a bare-JWT sanity case and a
`relay_pa_not-a-jwt` rejection case. Existing `TestParseBearer*` cases
all still pass. Full `go test ./...` clean (8 packages, no
regressions in mountsync/mountfuse/writeback/digest/relayfile/schema).

If relayauth adds new token classes in the future, append the prefix
to `relayauthTokenPrefixes` in `internal/httpapi/auth.go`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

Warning

Rate limit exceeded

@khaliqgant has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 20 minutes and 3 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4ec0d53c-21ef-4069-958b-0bd1815ca974

📥 Commits

Reviewing files that changed from the base of the PR and between f3a178c and 8bb2b70.

📒 Files selected for processing (2)
  • internal/httpapi/auth.go
  • internal/httpapi/auth_test.go
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/strip-relayauth-token-prefix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

Relayfile Eval Review

Run: .relayfile/evals/runs/2026-05-22T21-02-16-306Z-HEAD-provider
Mode: provider
Git SHA: 1e74d76

Passed: 4 | Needs human: 0 | Reviewable: 0 | Missing output: 0 | Failed: 0 | Skipped: 0

Human Review Cases

No reviewable human-review cases captured Relayfile output.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 2 files

Re-trigger cubic

@khaliqgant khaliqgant merged commit a6b2ad5 into main May 22, 2026
9 checks passed
@khaliqgant khaliqgant deleted the fix/strip-relayauth-token-prefix branch May 22, 2026 21:10
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