Skip to content

Remove secret approval request tokens - #278

Merged
kentcdodds merged 3 commits into
mainfrom
cursor/host-approval-token-removal-04c3
Apr 28, 2026
Merged

kentcdodds merged 3 commits into
mainfrom
cursor/host-approval-token-removal-04c3

Conversation

@kentcdodds

@kentcdodds kentcdodds commented Apr 28, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Remove signed/encrypted host and package approval request token generation/verification.
  • Build approval URLs from the account secret route plus allowed-host or package_id only.
  • Update account approval actions and client submission to derive approval context from the current URL.
  • Preserve approval query params when submitting approve/reject actions and reject ambiguous host+package approval targets.
  • Remove the stale /account approval card now that token-free approvals require a selected secret route.
  • Refresh focused tests and E2E expectations for token-free approval URLs.

Validation

  • npx vitest run "packages/worker/src/app/handlers/account-secrets.node.test.ts" "packages/worker/src/mcp/fetch-gateway.node.test.ts" "packages/worker/src/mcp/secrets/errors.node.test.ts" "packages/worker/src/mcp/executor.node.test.ts"
  • npm run typecheck
Open in Web Open in Cursor 

Summary by CodeRabbit

  • Refactor

    • Approval flow now relies on explicit query parameters (e.g., allowed-host, package_id) instead of encrypted request tokens
    • Generated approval URLs no longer include token parameters; submission and navigation preserve the page query string for approval context
    • Error messaging adjusted for unreadable/malformed approval data
  • Tests

    • Updated fixtures and expectations to use token-less approval URLs
    • Removed token generation/verification tests and related mocks

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@coderabbitai

coderabbitai Bot commented Apr 28, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR removes encrypted token-based host and package approval flows, deleting token generation/verification and replacing them with deterministic approval derivation from URL query parameters (e.g., allowed-host, package_id). Client, handler, gateway, utilities, and tests are updated accordingly.

Changes

Cohort / File(s) Summary
Client-side approval UI & tests
e2e/account-secrets.spec.ts, packages/worker/client/routes/account-approval-shared.ts, packages/worker/client/routes/account-secrets.tsx, packages/worker/client/routes/account.tsx
Removed token from approval view and request flow; submitApprovalRequest no longer accepts a request token. Calls and tests now derive request URL/query params from current URL and use only allowed-host, capability, package_id.
Server handlers & tests
packages/worker/src/app/handlers/account-secrets.ts, packages/worker/src/app/handlers/account-secrets.node.test.ts
Deleted token creation/verification usage; approval view and resolution derive approval context from query params (selected secret + allowed-host/package_id). Tests updated to assert deterministic query-param flows and removed token-related mocks.
Host approval utilities & tests
packages/worker/src/mcp/secrets/host-approval.ts, packages/worker/src/mcp/secrets/host-approval.node.test.ts
Removed token prefix, token create/verify functions, and crypto/encryption logic. buildSecretHostApprovalUrl now only emits allowed-host query param; related tests removed/adjusted.
Package approval utilities
packages/worker/src/mcp/secrets/package-approval.ts, packages/worker/src/mcp/secrets/package-approval-url.ts
Deleted package approval token creation/verification and token-prefixed URL behavior. buildSecretPackageApprovalUrl signature no longer accepts/uses a token.
Fetch gateway & MCP tests
packages/worker/src/mcp/fetch-gateway.ts, packages/worker/src/mcp/fetch-gateway.node.test.ts, packages/worker/src/mcp/executor.node.test.ts, packages/worker/src/mcp/secrets/errors.node.test.ts
Stopped generating host-approval tokens in gateway flows; updated tests and fixtures to expect approval URLs that include only allowed-host (no request token param).
Misc tests updated
packages/worker/src/mcp/secrets/host-approval.node.test.ts, various node.test updates
Removed or simplified tests that validated token encryption/verification; updated fixtures/expectations to the new query-param-only approval format.

Sequence Diagram

sequenceDiagram
    actor User
    participant Browser as Client Browser
    participant Handler as Account Handler
    participant Storage as Secret Storage

    User->>Browser: Click approval link (e.g. ?allowed-host=example.com & selected=<id>)
    Browser->>Handler: GET /account?allowed-host=example.com&selected=<id>
    Handler->>Storage: Read secret + query params -> build ApprovalView
    Handler-->>Browser: Render approval view (no token)

    User->>Browser: Click "Approve"
    Browser->>Handler: POST /account/approve?action=approve (URL carries query params)
    Handler->>Storage: Derive approval from query params + selected secret
    Handler->>Storage: Persist allowed host / package id
    Handler-->>Browser: Success response
    Browser->>Browser: Update history / clear query params
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I hopped through code with a curious twitch,
Tokens unspun, now queries do switch,
Allowed-hosts shine in a simpler light,
No cipher, no cookie — the path feels right. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.56% 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 'Remove secret approval request tokens' is directly and specifically related to the main change: removing signed/encrypted host and package approval tokens throughout the codebase.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/host-approval-token-removal-04c3

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.

@kentcdodds
kentcdodds marked this pull request as ready for review April 28, 2026 15:30
@github-actions

github-actions Bot commented Apr 28, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Preview deployed: https://kody-pr-278.kentcdodds.workers.dev

Worker: kody-pr-278
D1: kody-pr-278-db
KV: kody-pr-278-oauth-kv

Mocks:

Comment thread packages/worker/client/routes/account-secrets.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/worker/client/routes/account-secrets.tsx (1)

630-638: ⚠️ Potential issue | 🔴 Critical

Approval POST drops required URL context (allowed-host / package_id).

submitApproval builds requestUrl from accountSecretsApiPath but does not carry over the current search params. After token removal, the server resolves approval context from URL query params, so this causes runtime failures (e.g., “Approval request is missing a host or package.”).

🐛 Proposed fix
 	try {
-		const selection = getSelectionState(getCurrentHref())
-		const requestUrl = new URL(accountSecretsApiPath, getCurrentHref())
+		const currentHref = getCurrentHref()
+		const selection = getSelectionState(currentHref)
+		const requestUrl = new URL(accountSecretsApiPath, currentHref)
+		requestUrl.search = new URL(currentHref).search
 		if (selection.selectedSecretId) {
 			requestUrl.searchParams.set('selected', selection.selectedSecretId)
 		}
 		const payload = await submitApprovalRequest<
 			AccountSecretsPayload & { error?: string; ok?: boolean }
 		>(action, requestUrl.toString())
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/client/routes/account-secrets.tsx` around lines 630 - 638,
The approval POST loses existing query params because requestUrl is constructed
from accountSecretsApiPath without copying the current page's search params;
update the code around getSelectionState/getCurrentHref/submitApprovalRequest so
you first parse the current href (const current = new URL(getCurrentHref())),
then merge or assign its search params into requestUrl (the URL created with
accountSecretsApiPath) before setting selection.selectedSecretId and calling
submitApprovalRequest, ensuring existing keys like allowed-host or package_id
are preserved in the request URL.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/worker/client/routes/account.tsx`:
- Around line 77-80: The approval POST drops current query params so
host/package context is lost; update the request URL you pass into
submitApprovalRequest (the variable/action used with AccountSecretsPayload and
payload handling) to include the current location's query string (e.g., merge
window.location.search or router location.search into the action URL) before
calling submitApprovalRequest so query-derived approval context is preserved.

In `@packages/worker/src/app/handlers/account-secrets.ts`:
- Around line 736-740: The approval path currently accepts secret IDs with scope
'session'; after calling parseAccountSecretId(input.secretId) (and before
calling getSecretContextForAccountSecret), add the same guard used in
handleDeleteAction to reject session-scoped secrets—i.e., if parsed.scope ===
'session' then throw an error (e.g., 'Invalid approval request.'); ensure you
reference parseAccountSecretId, parsed.scope, and
getSecretContextForAccountSecret when making this check so session secrets are
blocked here too.
- Around line 741-763: The approval handler currently prefers package when both
input.requestedPackageId and input.requestedHost are present, risking wrong
approvals; update the logic in the handler handling input.requestedPackageId /
input.requestedHost to explicitly detect if both are provided and throw an error
(e.g., "Approval request contains both host and package") instead of proceeding,
and keep the existing branches that return the package object (with packageId
and storageContext) or the host object (after normalizeAllowedHosts and
requestedHost validation) untouched otherwise; reference the symbols
input.requestedPackageId, input.requestedHost, normalizeAllowedHosts, and the
returned objects with kind:'package' and kind:'host' to locate and change the
code.

---

Outside diff comments:
In `@packages/worker/client/routes/account-secrets.tsx`:
- Around line 630-638: The approval POST loses existing query params because
requestUrl is constructed from accountSecretsApiPath without copying the current
page's search params; update the code around
getSelectionState/getCurrentHref/submitApprovalRequest so you first parse the
current href (const current = new URL(getCurrentHref())), then merge or assign
its search params into requestUrl (the URL created with accountSecretsApiPath)
before setting selection.selectedSecretId and calling submitApprovalRequest,
ensuring existing keys like allowed-host or package_id are preserved in the
request URL.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 935bc554-03a1-4659-9ea4-ff78943f924f

📥 Commits

Reviewing files that changed from the base of the PR and between 2266d3c and f707e3f.

📒 Files selected for processing (14)
  • e2e/account-secrets.spec.ts
  • packages/worker/client/routes/account-approval-shared.ts
  • packages/worker/client/routes/account-secrets.tsx
  • packages/worker/client/routes/account.tsx
  • packages/worker/src/app/handlers/account-secrets.node.test.ts
  • packages/worker/src/app/handlers/account-secrets.ts
  • packages/worker/src/mcp/executor.node.test.ts
  • packages/worker/src/mcp/fetch-gateway.node.test.ts
  • packages/worker/src/mcp/fetch-gateway.ts
  • packages/worker/src/mcp/secrets/errors.node.test.ts
  • packages/worker/src/mcp/secrets/host-approval.node.test.ts
  • packages/worker/src/mcp/secrets/host-approval.ts
  • packages/worker/src/mcp/secrets/package-approval-url.ts
  • packages/worker/src/mcp/secrets/package-approval.ts
💤 Files with no reviewable changes (4)
  • packages/worker/src/mcp/secrets/package-approval-url.ts
  • packages/worker/src/mcp/secrets/package-approval.ts
  • packages/worker/src/mcp/secrets/host-approval.ts
  • packages/worker/src/mcp/secrets/host-approval.node.test.ts

Comment thread packages/worker/client/routes/account.tsx Outdated
Comment on lines +736 to +740
const parsed = input.secretId ? parseAccountSecretId(input.secretId) : null
if (!parsed) {
throw new Error('Invalid approval request.')
}
if (token.startsWith(secretPackageApprovalTokenPrefix)) {
return await verifySecretPackageApprovalToken(env, token)
const storageContext = getSecretContextForAccountSecret(parsed)

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

Block session secret ids here as well.

parseAccountSecretId can return scope === 'session'—you already guard that in handleDeleteAction—but this path currently accepts it and feeds that scope into the approval mutators. That lets the account approval endpoint operate on a secret class the account page is supposed to reject.

🛡️ Suggested fix
 	const parsed = input.secretId ? parseAccountSecretId(input.secretId) : null
-	if (!parsed) {
+	if (!parsed || parsed.scope === 'session') {
 		throw new Error('Invalid approval request.')
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const parsed = input.secretId ? parseAccountSecretId(input.secretId) : null
if (!parsed) {
throw new Error('Invalid approval request.')
}
if (token.startsWith(secretPackageApprovalTokenPrefix)) {
return await verifySecretPackageApprovalToken(env, token)
const storageContext = getSecretContextForAccountSecret(parsed)
const parsed = input.secretId ? parseAccountSecretId(input.secretId) : null
if (!parsed || parsed.scope === 'session') {
throw new Error('Invalid approval request.')
}
const storageContext = getSecretContextForAccountSecret(parsed)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/src/app/handlers/account-secrets.ts` around lines 736 - 740,
The approval path currently accepts secret IDs with scope 'session'; after
calling parseAccountSecretId(input.secretId) (and before calling
getSecretContextForAccountSecret), add the same guard used in handleDeleteAction
to reject session-scoped secrets—i.e., if parsed.scope === 'session' then throw
an error (e.g., 'Invalid approval request.'); ensure you reference
parseAccountSecretId, parsed.scope, and getSecretContextForAccountSecret when
making this check so session secrets are blocked here too.

Comment thread packages/worker/src/app/handlers/account-secrets.ts
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 7cda239. Configure here.

Comment thread packages/worker/client/routes/account.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
packages/worker/src/app/handlers/account-secrets.ts (1)

736-743: ⚠️ Potential issue | 🟠 Major

Reject session secret ids in approval requests too.

This path still accepts parseAccountSecretId(...).scope === 'session' and then feeds it into getSecretContextForAccountSecret and the approval mutators. That re-enables account approvals for a secret class this file otherwise blocks from account-page operations.

Suggested fix
 	const parsed = input.secretId ? parseAccountSecretId(input.secretId) : null
-	if (!parsed) {
+	if (!parsed || parsed.scope === 'session') {
 		throw new Error('Invalid approval request.')
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/src/app/handlers/account-secrets.ts` around lines 736 - 743,
The handler currently allows parsed secret IDs with scope 'session' to proceed;
update the validation after parseAccountSecretId to reject session-scoped
secrets by checking parsed.scope === 'session' and throwing an error (same as
other invalid requests) before calling getSecretContextForAccountSecret and
performing approval mutators; ensure this check is added alongside the existing
parsed null check and the requestedHost/requestedPackageId mutual exclusion
check so session secrets cannot reach getSecretContextForAccountSecret or the
approval logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/worker/src/app/handlers/account-secrets.ts`:
- Around line 736-743: The handler currently allows parsed secret IDs with scope
'session' to proceed; update the validation after parseAccountSecretId to reject
session-scoped secrets by checking parsed.scope === 'session' and throwing an
error (same as other invalid requests) before calling
getSecretContextForAccountSecret and performing approval mutators; ensure this
check is added alongside the existing parsed null check and the
requestedHost/requestedPackageId mutual exclusion check so session secrets
cannot reach getSecretContextForAccountSecret or the approval logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4ab8218a-c00b-46c9-aacb-e60e772c33f6

📥 Commits

Reviewing files that changed from the base of the PR and between f707e3f and 7cda239.

📒 Files selected for processing (4)
  • packages/worker/client/routes/account-secrets.tsx
  • packages/worker/client/routes/account.tsx
  • packages/worker/src/app/handlers/account-secrets.node.test.ts
  • packages/worker/src/app/handlers/account-secrets.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/worker/client/routes/account.tsx
  • packages/worker/client/routes/account-secrets.tsx
  • packages/worker/src/app/handlers/account-secrets.node.test.ts

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@kentcdodds
kentcdodds merged commit 0742e15 into main Apr 28, 2026
8 checks passed
@kentcdodds
kentcdodds deleted the cursor/host-approval-token-removal-04c3 branch April 28, 2026 16:34
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.

2 participants