Skip to content

feat(relayauth): self-sufficient workspace-path mint#48

Merged
khaliqgant merged 1 commit into
mainfrom
codex/workspace-path-token
Jun 3, 2026
Merged

feat(relayauth): self-sufficient workspace-path mint#48
khaliqgant merged 1 commit into
mainfrom
codex/workspace-path-token

Conversation

@khaliqgant
Copy link
Copy Markdown
Member

@khaliqgant khaliqgant commented Jun 3, 2026

User description

Summary

  • Remove the workspaces lookup from POST /v1/tokens/workspace-path and mint directly from the org API-key claim plus caller-supplied workspaceId.
  • Keep the route path-token-only, with the existing scope containment and path-validation guards intact.
  • Add direct-mint coverage for no seeded workspace row, org stamping, missing workspaceId, scope containment, and traversal rejection.
  • Keep the branch on the pre-release version; .github/workflows/publish.yml owns the version bump during the sanctioned publish flow.

Validation

  • npm run -w @relayauth/server typecheck
  • node --test --import tsx packages/server/src/__tests__/tokens-route.test.ts

Notes

  • This is the RelayAuth-side Option A fix approved for the Cloud consumer path.
  • Publish will happen through workflow_dispatch after merge, using the workflow-managed version bump and provenance publish.

CodeAnt-AI Description

Add direct workspace path token minting

What Changed

  • Added a new token endpoint that issues a short-lived relay_pa_* token directly from an org API key and workspaceId, without needing a pre-existing workspace token
  • The new flow keeps the authenticated org as the source of truth, blocks invalid or traversal paths, and rejects requested scopes outside the API key grant
  • Exposed the new request and response shapes in the TypeScript SDK and documented the new API in the README
  • Added coverage for missing workspaceId, direct minting without a seeded workspace row, org stamping, TTL limits, scope checks, and invalid paths

Impact

✅ Shorter path-token setup
✅ Fewer workspace-token dependencies
✅ Clearer direct-mint API usage

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

@codeant-ai
Copy link
Copy Markdown

codeant-ai Bot commented Jun 3, 2026

CodeAnt AI is reviewing your PR.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 3, 2026

Review Change Stack

Warning

Review limit reached

@khaliqgant, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 46 minutes and 7 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, 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 include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b07ec948-d79d-4a86-8592-9d1e0cbdc469

📥 Commits

Reviewing files that changed from the base of the PR and between dfbf601 and 88b0fae.

📒 Files selected for processing (2)
  • packages/server/src/__tests__/tokens-route.test.ts
  • packages/server/src/routes/tokens.ts
📝 Walkthrough

Walkthrough

Adds a new POST /v1/tokens/workspace-path endpoint and SDK method for direct issuance of short-lived path-scoped tokens (relay_pa_*) using org API keys. Callers supply an explicit workspaceId, paths, scopes, and optional TTL; the server validates org ownership, enforces scope grants, caps TTL at 1h, and returns a token pair without creating a workspace token.

Changes

Workspace-path token issuance feature

Layer / File(s) Summary
Type contracts and API documentation
packages/types/src/token.ts, README.md
New WorkspacePathTokenIssueRequest and WorkspacePathTokenPair type aliases allow direct path-token minting with required workspaceId and configurable ttlSeconds. API documentation describes the new endpoint and type shapes.
TypeScript SDK client method and tests
packages/sdk/typescript/src/client.ts, packages/sdk/typescript/src/index.ts, packages/sdk/typescript/src/__tests__/client-tokens.test.ts
RelayAuthClient.issueWorkspacePathToken sends POST to /v1/tokens/workspace-path with request body and API key header. SDK test verifies correct endpoint routing, request serialization, header attachment, and response structure.
Server route handler, validation, and test suite
packages/server/src/routes/tokens.ts, packages/server/src/__tests__/tokens-route.test.ts
Route handler validates workspaceId ownership using org API key boundary, normalizes paths, enforces scope grants, caps TTL at 1h, issues token pair with tokenClass: "relay_pa". Test suite covers validation errors, successful direct minting, org-boundary rejection, TTL capping, scope enforcement, and path degeneration rejection.
Trajectory execution records
.trajectories/completed/2026-06/traj_1774612574920_f3fdc1c5.json, .trajectories/completed/2026-06/traj_1774612574920_f3fdc1c5.md, .trajectories/index.json
PR #48 review workflow marked completed; merge-findings decision records rejection of cross-org workspace-path minting; trajectories index updated with workspace-relative paths.

Sequence Diagram

sequenceDiagram
  participant Caller as Org with API Key
  participant SDK as RelayAuthClient
  participant Handler as POST /v1/tokens/workspace-path
  participant TokenSvc as Token Service
  Caller->>SDK: issueWorkspacePathToken({workspaceId, paths, scopes, ttlSeconds})
  SDK->>Handler: POST with x-api-key header
  Handler->>Handler: validate org API key grant<br/>validate workspaceId ownership
  Handler->>Handler: normalize paths<br/>derive path scopes
  Handler->>Handler: enforce requested scopes ⊆ granted scopes
  Handler->>Handler: cap TTL at 1h
  Handler->>TokenSvc: issueTokenPair(path metadata)
  TokenSvc-->>Handler: {accessToken, refreshToken}
  Handler-->>SDK: WorkspacePathTokenPair<br/>(tokenClass: "relay_pa")
  SDK-->>Caller: return token pair with paths
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • AgentWorkforce/relayauth#42: Both PRs update path-scoped token issuance surface by modifying shared SDK/types/tests and implementing route logic in packages/server/src/routes/tokens.ts; this PR adds workspace-direct minting (/v1/tokens/workspace-path) complementing the prior path endpoint.

Poem

🐇 A rabbit hops through workspace paths so fair,
Direct tokens minting in the air,
Org boundaries guarded, scopes held tight,
Short-lived relay tokens, shining bright!
No walls between, just paths to share.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(relayauth): self-sufficient workspace-path mint' directly describes the main change: adding direct (self-sufficient) workspace-path token minting without pre-existing workspace tokens.
Description check ✅ Passed The description clearly explains the workspace-path token minting feature, its implementation details, validation approach, and testing coverage, all of which align with the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/workspace-path-token

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.

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Jun 3, 2026
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new endpoint POST /v1/tokens/workspace-path to directly mint short-lived path-scoped tokens using an organization API key and a workspace ID, along with corresponding SDK client updates, type definitions, and tests. A critical issue was identified in the route handler where auth.claims.sponsorId and auth.claims.sponsorChain can be undefined when using an Org API Key, which could lead to a runtime TypeError (500 error) inside createPathTokenIdentity. A defensive fallback has been suggested to resolve this.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +469 to +477
const identity = createPathTokenIdentity({
agentId,
agentName,
orgId: auth.claims.org,
workspaceId,
sponsorId: auth.claims.sponsorId,
sponsorChain: auth.claims.sponsorChain,
scopes: auth.claims.scopes,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

When minting a token via /workspace-path using an Org API Key, the auth.claims object represents the API key itself rather than a delegated user session. Consequently, auth.claims.sponsorId and auth.claims.sponsorChain will be undefined.

Passing these directly to createPathTokenIdentity will cause a runtime TypeError when it attempts to read options.sponsorChain.length, crashing the request with a 500 error.

We should defensively default sponsorId to agentId and sponsorChain to an empty array [] to ensure safe fallback behavior.

Suggested change
const identity = createPathTokenIdentity({
agentId,
agentName,
orgId: auth.claims.org,
workspaceId,
sponsorId: auth.claims.sponsorId,
sponsorChain: auth.claims.sponsorChain,
scopes: auth.claims.scopes,
});
const identity = createPathTokenIdentity({
agentId,
agentName,
orgId: auth.claims.org,
workspaceId,
sponsorId: auth.claims.sponsorId ?? agentId,
sponsorChain: auth.claims.sponsorChain ?? [],
scopes: auth.claims.scopes,
});

@codeant-ai
Copy link
Copy Markdown

codeant-ai Bot commented Jun 3, 2026

CodeAnt AI finished reviewing your PR.

agent-relay-code Bot added a commit that referenced this pull request Jun 3, 2026
@agent-relay-code
Copy link
Copy Markdown

Implemented fixes for PR #48:

  • Added a guard in tokens.ts so direct workspace-path minting rejects a workspaceId that local context already associates with a different org.
  • Updated the route test to cover that cross-org rejection in tokens-route.test.ts.
  • Updated README request shape to include expiresIn.
  • Removed the generated packages/types/tsconfig.tsbuildinfo artifact from the PR surface.

Verification run locally:

  • npm run typecheck passed.
  • npm --workspace @relayauth/server test passed.
  • npm --workspace @relayauth/sdk test passed.
  • node --test --import tsx packages/server/src/__tests__/tokens-route.test.ts passed.
  • npx turbo test --concurrency=1 passed.
  • node --import tsx --test tests/agent-token.test.ts passed.

Note: plain npm test hit a local parallel-build resource kill (exit 137); rerunning the same Turbo test phase with --concurrency=1 passed.

@agent-relay-code
Copy link
Copy Markdown

pr-reviewer applied fixes — committed and pushed dfbf601 to this PR. The notes below describe what changed.

Implemented fixes for PR #48:

  • Added a guard in tokens.ts so direct workspace-path minting rejects a workspaceId that local context already associates with a different org.
  • Updated the route test to cover that cross-org rejection in tokens-route.test.ts.
  • Updated README request shape to include expiresIn.
  • Removed the generated packages/types/tsconfig.tsbuildinfo artifact from the PR surface.

Verification run locally:

  • npm run typecheck passed.
  • npm --workspace @relayauth/server test passed.
  • npm --workspace @relayauth/sdk test passed.
  • node --test --import tsx packages/server/src/__tests__/tokens-route.test.ts passed.
  • npx turbo test --concurrency=1 passed.
  • node --import tsx --test tests/agent-token.test.ts passed.

Note: plain npm test hit a local parallel-build resource kill (exit 137); rerunning the same Turbo test phase with --concurrency=1 passed.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/server/src/routes/tokens.ts`:
- Around line 424-454: The handler registered in tokens.post("/workspace-path")
currently trusts any auth with relayauth:api-key:manage:* and uses
body.workspaceId (workspaceId) without checking workspace-token scope; call
resolveWorkspaceToken(...) after authenticateAndAuthorizeFromContext to detect
workspace tokens and then either (A) reject workspace tokens by returning a 403
when resolveWorkspaceToken(...) yields a workspace token, or (B) enforce the
workspace boundary by comparing workspaceId ===
resolvedWorkspaceToken.workspaceId and returning 403/400 if they differ; update
the code around authenticateAndAuthorizeFromContext, resolveWorkspaceToken, and
the workspaceId check to implement one of these two behaviors.
- Around line 474-499: The route issues a path token without checking
MAX_SPONSOR_CHAIN_DEPTH even though createPathTokenIdentity appends agentId to
auth.claims.sponsorChain, which can produce an overlong sponsorChain that later
fails refresh; before calling issueTokenPair in this handler, replicate the same
depth guard used in /v1/tokens and /v1/tokens/agent: compute newDepth =
(auth.claims.sponsorChain?.length || 0) + 1 (or equivalent), compare against
MAX_SPONSOR_CHAIN_DEPTH, and return the appropriate error/abort if the limit
would be exceeded so issueTokenPair is only called when the sponsorChain remains
within the allowed depth.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3b511fc3-a33c-41f7-a12c-77943ec77cf9

📥 Commits

Reviewing files that changed from the base of the PR and between 2fd956c and dfbf601.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (11)
  • .trajectories/completed/2026-06/traj_1774612574920_f3fdc1c5.json
  • .trajectories/completed/2026-06/traj_1774612574920_f3fdc1c5.md
  • .trajectories/index.json
  • README.md
  • packages/sdk/typescript/src/__tests__/client-tokens.test.ts
  • packages/sdk/typescript/src/client.ts
  • packages/sdk/typescript/src/index.ts
  • packages/server/src/__tests__/tokens-route.test.ts
  • packages/server/src/routes/tokens.ts
  • packages/types/src/token.ts
  • packages/types/tsconfig.tsbuildinfo
💤 Files with no reviewable changes (1)
  • packages/types/tsconfig.tsbuildinfo

Comment thread packages/server/src/routes/tokens.ts Outdated
Comment on lines +424 to +454
tokens.post("/workspace-path", async (c) => {
const auth = await authenticateAndAuthorizeFromContext(
c,
"relayauth:api-key:manage:*",
matchScope,
);
if (!auth.ok) {
return c.json({ error: auth.error, code: auth.code }, auth.status);
}

const body = await parseJsonObjectBody<PathTokenRequest>(c.req.raw);
if (!body) {
return c.json({ error: "Invalid JSON body" }, 400);
}

const workspaceId = normalizeOptionalString(body.workspaceId);
if (!workspaceId) {
return c.json({ error: "workspaceId is required", code: "workspaceId_required" }, 400);
}

const storage = getSqlStorage(c.get("storage"));
// Direct workspace-path minting is intentionally equivalent to:
// POST /v1/tokens/workspace (org API key + caller-supplied workspaceId)
// followed by /v1/tokens/path.
// The org API key grant is the authorization boundary here; the workspace
// row is not an auth source and must not be required on this hot path. If
// local context does know the workspace, it must still belong to this org.
const workspaceContext = await storage.contexts.getWorkspace(workspaceId);
if (workspaceContext && workspaceContext.orgId !== auth.claims.org) {
return c.json({ error: "workspace_not_found", code: "workspace_not_found" }, 404);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject workspace tokens here or pin them to their own workspace.

This route only checks for relayauth:api-key:manage:*, but unlike /v1/tokens/path it never calls resolveWorkspaceToken(...). Since /v1/tokens/workspace can mint workspace tokens with caller-granted scopes, a workspace token carrying relayauth:api-key:manage:* can reach this endpoint and mint path tokens for any same-org or unseeded workspaceId, bypassing the workspace boundary.

Please either reject workspace tokens on this route or enforce requestedWorkspaceId === resolvedWorkspaceToken.workspaceId before minting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/server/src/routes/tokens.ts` around lines 424 - 454, The handler
registered in tokens.post("/workspace-path") currently trusts any auth with
relayauth:api-key:manage:* and uses body.workspaceId (workspaceId) without
checking workspace-token scope; call resolveWorkspaceToken(...) after
authenticateAndAuthorizeFromContext to detect workspace tokens and then either
(A) reject workspace tokens by returning a 403 when resolveWorkspaceToken(...)
yields a workspace token, or (B) enforce the workspace boundary by comparing
workspaceId === resolvedWorkspaceToken.workspaceId and returning 403/400 if they
differ; update the code around authenticateAndAuthorizeFromContext,
resolveWorkspaceToken, and the workspaceId check to implement one of these two
behaviors.

Comment on lines +474 to +499
const identity = createPathTokenIdentity({
agentId,
agentName,
orgId: auth.claims.org,
workspaceId,
sponsorId: auth.claims.sponsorId,
sponsorChain: auth.claims.sponsorChain,
scopes: auth.claims.scopes,
});

const tokenPair = await issueTokenPair(storage, c.env, identity, {
accessScopes: accessScopes.scopes,
accessAudience,
accessExpiresIn,
action: "token.issued",
meta: {
tokenClass: "path",
agentName,
paths: JSON.stringify(paths.paths),
accessScopes: JSON.stringify(accessScopes.scopes),
accessAudience: JSON.stringify(accessAudience),
},
wrapAccessToken: true,
wrapRefreshToken: true,
tokenIdPrefix: RELAY_PATH_TOKEN_PREFIX,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Enforce the sponsor-chain depth limit before issuing direct path tokens.

createPathTokenIdentity(...) appends the derived agentId to auth.claims.sponsorChain, but this route never re-checks MAX_SPONSOR_CHAIN_DEPTH. If the caller is already at depth 10, the new token is issued with an invalid chain and will later fail refresh in /v1/tokens/refresh, which does enforce that limit.

Add the same depth guard used in /v1/tokens and /v1/tokens/agent before issueTokenPair(...).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/server/src/routes/tokens.ts` around lines 474 - 499, The route
issues a path token without checking MAX_SPONSOR_CHAIN_DEPTH even though
createPathTokenIdentity appends agentId to auth.claims.sponsorChain, which can
produce an overlong sponsorChain that later fails refresh; before calling
issueTokenPair in this handler, replicate the same depth guard used in
/v1/tokens and /v1/tokens/agent: compute newDepth =
(auth.claims.sponsorChain?.length || 0) + 1 (or equivalent), compare against
MAX_SPONSOR_CHAIN_DEPTH, and return the appropriate error/abort if the limit
would be exceeded so issueTokenPair is only called when the sponsorChain remains
within the allowed depth.

@khaliqgant khaliqgant force-pushed the codex/workspace-path-token branch from dfbf601 to a3d1d42 Compare June 3, 2026 12:14
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.

4 issues found across 8 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="README.md">

<violation number="1" location="README.md:56">
P3: README omits the supported `expiresIn` field from `WorkspacePathTokenIssueRequest`, so the direct-mint contract is documented too narrowly.</violation>
</file>

<file name="packages/server/src/routes/tokens.ts">

<violation number="1" location="packages/server/src/routes/tokens.ts:434">
P1: `/workspace-path` does not enforce org-API-key callers; bearer/other key kinds can mint direct workspace path tokens for arbitrary `workspaceId`.</violation>

<violation number="2" location="packages/server/src/routes/tokens.ts:473">
P0: Do not trust the requested `workspaceId` for workspace-token callers. Pin this field to the caller’s workspace claim (or reject workspace tokens here), otherwise a workspace token with `relayauth:api-key:manage:*` can mint path tokens outside its workspace boundary.</violation>

<violation number="3" location="packages/server/src/routes/tokens.ts:479">
P2: Add a sponsor-chain depth check before issuing the token pair. Without this guard, this endpoint can mint tokens that already exceed `MAX_SPONSOR_CHAIN_DEPTH` and fail on later refresh.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

agentId,
agentName,
orgId: auth.claims.org,
workspaceId,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P0: Do not trust the requested workspaceId for workspace-token callers. Pin this field to the caller’s workspace claim (or reject workspace tokens here), otherwise a workspace token with relayauth:api-key:manage:* can mint path tokens outside its workspace boundary.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/server/src/routes/tokens.ts, line 473:

<comment>Do not trust the requested `workspaceId` for workspace-token callers. Pin this field to the caller’s workspace claim (or reject workspace tokens here), otherwise a workspace token with `relayauth:api-key:manage:*` can mint path tokens outside its workspace boundary.</comment>

<file context>
@@ -413,6 +421,88 @@ tokens.post("/path", async (c) => {
+    agentId,
+    agentName,
+    orgId: auth.claims.org,
+    workspaceId,
+    sponsorId: auth.claims.sponsorId,
+    sponsorChain: auth.claims.sponsorChain,
</file context>

return c.json({ error: auth.error, code: auth.code }, auth.status);
}

const body = await parseJsonObjectBody<PathTokenRequest>(c.req.raw);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: /workspace-path does not enforce org-API-key callers; bearer/other key kinds can mint direct workspace path tokens for arbitrary workspaceId.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/server/src/routes/tokens.ts, line 434:

<comment>`/workspace-path` does not enforce org-API-key callers; bearer/other key kinds can mint direct workspace path tokens for arbitrary `workspaceId`.</comment>

<file context>
@@ -413,6 +421,88 @@ tokens.post("/path", async (c) => {
+    return c.json({ error: auth.error, code: auth.code }, auth.status);
+  }
+
+  const body = await parseJsonObjectBody<PathTokenRequest>(c.req.raw);
+  if (!body) {
+    return c.json({ error: "Invalid JSON body" }, 400);
</file context>

scopes: auth.claims.scopes,
});

const tokenPair = await issueTokenPair(storage, c.env, identity, {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Add a sponsor-chain depth check before issuing the token pair. Without this guard, this endpoint can mint tokens that already exceed MAX_SPONSOR_CHAIN_DEPTH and fail on later refresh.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/server/src/routes/tokens.ts, line 479:

<comment>Add a sponsor-chain depth check before issuing the token pair. Without this guard, this endpoint can mint tokens that already exceed `MAX_SPONSOR_CHAIN_DEPTH` and fail on later refresh.</comment>

<file context>
@@ -413,6 +421,88 @@ tokens.post("/path", async (c) => {
+    scopes: auth.claims.scopes,
+  });
+
+  const tokenPair = await issueTokenPair(storage, c.env, identity, {
+    accessScopes: accessScopes.scopes,
+    accessAudience,
</file context>
Suggested change
const tokenPair = await issueTokenPair(storage, c.env, identity, {
if ((auth.claims.sponsorChain?.length ?? 0) >= MAX_SPONSOR_CHAIN_DEPTH) {
return c.json({ error: "sponsor_chain_too_deep", code: "sponsor_chain_too_deep" }, 400);
}
const tokenPair = await issueTokenPair(storage, c.env, identity, {

Comment thread README.md
scopes?: string[];
audience?: string[];
expiresIn?: number;
ttlSeconds?: number; // capped to 3600s server-side
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: README omits the supported expiresIn field from WorkspacePathTokenIssueRequest, so the direct-mint contract is documented too narrowly.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At README.md, line 56:

<comment>README omits the supported `expiresIn` field from `WorkspacePathTokenIssueRequest`, so the direct-mint contract is documented too narrowly.</comment>

<file context>
@@ -48,16 +48,29 @@ type AgentTokenIssueRequest = {
   scopes?: string[];
   audience?: string[];
   expiresIn?: number;
+  ttlSeconds?: number; // capped to 3600s server-side
+};
+
</file context>

@khaliqgant khaliqgant force-pushed the codex/workspace-path-token branch from a3d1d42 to 88b0fae Compare June 3, 2026 12:29
@agent-relay-code
Copy link
Copy Markdown

⚠️ pr-reviewer did not push — the PR branch advanced during the review, so fixes were withheld to avoid overwriting newer commits. Re-trigger the review once the branch settles. The notes below are advisory and were not pushed.

Reviewed and fixed PR #48.

Changes made:

  • Added server regression coverage for refreshing a direct workspace-path token without workspace-token lineage.
  • Removed the generated packages/types/tsconfig.tsbuildinfo artifact from the PR.

Verification run:

  • node --test --import tsx packages/server/src/__tests__/tokens-route.test.ts passed.
  • npx turbo test --concurrency=1 passed.
  • node --import tsx --test tests/agent-token.test.ts passed.
  • Direct workspace typechecks passed for server, sdk, types, migrate, core, ai, cli, and observer.
  • Top-level Turbo typecheck hit local exit 137 during SDK build dependency, so I verified typechecks directly per workspace instead.

@khaliqgant khaliqgant merged commit b8bc849 into main Jun 3, 2026
3 checks passed
@khaliqgant khaliqgant deleted the codex/workspace-path-token branch June 3, 2026 12:39
@agent-relay-code
Copy link
Copy Markdown

⚠️ pr-reviewer did not push — the PR branch advanced during the review, so fixes were withheld to avoid overwriting newer commits. Re-trigger the review once the branch settles. The notes below are advisory and were not pushed.

Reviewed PR #48 against .workforce/pr.diff, changed files, and context. I did not find a PR-caused breakage that needed source edits; the workspace-path change is covered by the added tests and remains consistent with the public SDK types/docs.

Local checks run:

  • node --test --import tsx packages/server/src/__tests__/tokens-route.test.ts --test-name-pattern='workspace-path|tokens/path|tokens/refresh'
  • npm --workspace @relayauth/server test
  • npm --workspace @relayauth/server run typecheck
  • npm --workspace @relayauth/server run build
  • npx turbo typecheck --concurrency=1 --filter=@relayauth/server...
  • npx turbo build --concurrency=1

Note: I had to run npm install and build workspace deps locally because the checkout initially lacked linked/built packages.

@agent-relay-code
Copy link
Copy Markdown

Reviewed PR #48 against the provided diff and traced the route, tests, SDK/types, and CI workflow impact. I did not find any code changes needed.

Verification run locally:

  • node --test --import tsx packages/server/src/__tests__/tokens-route.test.ts
  • npm --workspace @relayauth/server test
  • npx turbo build --filter='!@relayauth/landing' --concurrency=1
  • npx turbo typecheck --filter='!@relayauth/landing' --concurrency=1
  • npx turbo test --filter='!@relayauth/landing' --force --concurrency=1

Note: the default parallel Turbo build hit local exit 137 once, consistent with memory pressure; the same CI build passed serially.

@agent-relay-code
Copy link
Copy Markdown

⚠️ pr-reviewer did not push — the PR branch advanced during the review, so fixes were withheld to avoid overwriting newer commits. Re-trigger the review once the branch settles. The notes below are advisory and were not pushed.

Reviewed PR #48 against the provided diff and traced the route, tests, SDK/types, and CI workflow impact. I did not find any code changes needed.

Verification run locally:

  • node --test --import tsx packages/server/src/__tests__/tokens-route.test.ts
  • npm --workspace @relayauth/server test
  • npx turbo build --filter='!@relayauth/landing' --concurrency=1
  • npx turbo typecheck --filter='!@relayauth/landing' --concurrency=1
  • npx turbo test --filter='!@relayauth/landing' --force --concurrency=1

Note: the default parallel Turbo build hit local exit 137 once, consistent with memory pressure; the same CI build passed serially.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant