Skip to content

Hard-migrate secrets from app scope to package scope - #694

Merged
kody-bot merged 9 commits into
mainfrom
cursor/package-scoped-secrets-fe1f
Jul 9, 2026
Merged

kody-bot merged 9 commits into
mainfrom
cursor/package-scoped-secrets-fe1f

Conversation

@kentcdodds

@kentcdodds kentcdodds commented Jul 9, 2026 •

Copy link
Copy Markdown
Owner

Hard-migrates secret ownership from app scope to package scope with no compatibility alias. Existing app buckets and legacy package caller contexts are rewritten by migration; package identity is propagated through apps, jobs, services, invocations, inline workflows, and OpenAPI operations. User secrets require package approval across mounts, fetch placeholders, secret-aware capabilities, and atomic secret mutations. Package deletion removes owned secrets and stale grants even when the package projection is already missing.

Walkthrough

package-scoped-secret-clean-demo.mp4

Package-owned secret detail

User-secret package access grants

Package scope works for headless packages; only user secrets expose package grants.

System recap — extends existing primitives (medium risk)

Mode: recap · Base: main @ 6e5e315 · Head: c88f148

Classification: extends — changes the secret ownership and authorization contract without adding a new system primitive.

Primitives touched

Primitive Group Impact
secrets assistant extends — package ownership, atomic updates, and uniform package authorization
saved-packages assistant extends — package deletion owns idempotent secret cleanup
package-runtime runtime extends — propagates first-class package secret identity
capabilities-execute runtime extends — package-aware fetch and capability secret resolution
openapi-bindings assistant extends — preserves package identity and app-origin approvals through provider operations
workflows assistant extends — inline runs require retained package security context
jobs assistant extends — legacy and resynced package contexts are refreshed
app-ui surfaces extends — package scope replaces app scope
d1-app-db storage extends — hard schema and data migration

System map

Package identity flows from saved packages through every runtime into secret resolution and D1 ownership; account UI manages package buckets and user-secret grants.

Legend: green = composes (wiring only) · amber = extended by this PR · red = new primitive · gray = context (unchanged, included only when an edge crosses it).

flowchart LR
	savedPackages["saved-packages<br/>Saved packages"]:::extended
	packageRuntime["package-runtime<br/>Package runtime"]:::extended
	executeRuntime["capabilities-execute<br/>Capabilities execute runtime"]:::extended
	openApi["openapi-bindings<br/>OpenAPI provider bindings"]:::extended
	secrets["secrets<br/>Secret references"]:::extended
	appUi["app-ui<br/>Browser app"]:::extended
	d1["d1-app-db<br/>D1 app database"]:::extended
	savedPackages -->|"packageId runtime identity + delete cleanup"| packageRuntime
	packageRuntime -->|"package caller context"| executeRuntime
	openApi -->|"package-aware operation + token refresh"| secrets
	executeRuntime -->|"mounts, fetch, capability inputs"| secrets
	appUi -->|"package scope routes and user grants"| secrets
	secrets -->|"package buckets + conditional updates"| d1
	classDef touched fill:#1a7f37,color:#fff
	classDef extended fill:#9a6700,color:#fff
	classDef added fill:#cf222e,color:#fff
	classDef untouched fill:#57606a,color:#fff
Loading

Before / after

Before After
session | app | user secret scopes session | package | user only
package grants enforced only by mounts grants enforced on every user-secret package path
app packages selectable as owners every saved package selectable as owner
legacy jobs/workflows could lose package identity migration/validation/resync fail closed with package identity
package updates used read-then-upsert approved user-secret updates are conditional and atomic

Invariants

  • per-user-isolation remains mandatory on every bucket, package lookup, migration, and cleanup query.
  • no-secrets-in-chat remains unchanged; plaintext stays inside server-side resolution.
Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features
    • Secrets now support package scope in addition to user and session.
    • Added package-scoped secret pages, approvals, and updated routing/URL/query contracts for creating and viewing package secrets.
    • Workflow and runtime execution now carry package identifiers for package-aware secret handling.
  • Bug Fixes
    • Strengthened secret access enforcement: user-scoped secrets require package approval, while package-owned secrets are restricted to their owning package.
    • Updated secret listing/metadata and filtering to use package-scoped association rules.
  • Documentation
    • Updated guides and architecture docs to reflect package-scoped terminology, placeholder behavior, and storage/schema contracts.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR replaces app-scoped secrets with package-scoped secrets across storage, routing, account management, runtime context, package workflows, authorization, migrations, tests, and documentation. User-secret approvals remain package-specific, while package-owned secrets use their package bucket binding.

Changes

Package-scoped secret contracts and storage

Layer / File(s) Summary
Scope contracts and persistence migration
packages/shared/src/account-secret-route.ts, packages/worker/src/mcp/secrets/*, packages/worker/migrations/0057-package-scoped-secrets.sql
Secret scopes and metadata use package/packageId; buckets, entries, approval lists, indexes, and persisted job contexts are migrated accordingly.
Package authorization and secret operations
packages/worker/src/mcp/secrets/package-access.ts, packages/worker/src/mcp/capabilities/secrets/*, packages/worker/src/mcp/fetch-gateway.ts, packages/worker/src/mcp/run-kody-registry.ts
Resolved user secrets require package approval across mounts, placeholders, capability inputs, and secret capabilities; package-owned secrets use their owning package binding.

Account and runtime integration

Layer / File(s) Summary
Account secret routes and editor
packages/worker/client/routes/*, packages/worker/src/app/*, packages/shared/src/account-secret-route.node.test.ts
Account routes, loaders, filters, editor state, package selection, save validation, and approval handling use package identifiers and package-specific paths.
Runtime context and workflows
packages/worker/src/jobs/*, packages/worker/src/mcp/*, packages/worker/src/package-*/*
storageContext.packageId is propagated through jobs, MCP execution, OpenAPI requests, package invocation, retrievers, package workers, and version-3 inline workflow payloads.

Validation and documentation

Layer / File(s) Summary
Tests and documentation
docs/*, packages/worker/src/**/*.node.test.ts, packages/shared/src/*.node.test.ts
Documentation describes package ownership and approval semantics, while tests cover migration behavior, route parsing, package authorization, account flows, cleanup, and runtime context propagation.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PackageRuntime
  participant SecretResolver
  participant PackageAccess
  participant AccountSecrets
  PackageRuntime->>SecretResolver: Resolve secret with packageId
  SecretResolver->>PackageAccess: Validate resolved secret access
  PackageAccess->>AccountSecrets: Build approval URL when user approval is missing
  PackageAccess-->>PackageRuntime: Allow package-owned or approved user secret
Loading

Possibly related PRs

🚥 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
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.
Title check ✅ Passed The title clearly and concisely summarizes the main change: migrating secrets from app scope to package scope.
✨ 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/package-scoped-secrets-fe1f

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.

@kody-bot
kody-bot marked this pull request as ready for review July 9, 2026 21:55
@github-actions

github-actions Bot commented Jul 9, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Preview deployed: https://kody-pr-694.kody-a99.workers.dev

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

Mocks:

@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/src/mcp/capabilities/secrets/secret-set.ts (1)

49-86: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Make package-authorized user-secret updates atomic.

resolveSecret validates existence and approval, but saveSecret later performs an upsert. A concurrent account deletion can therefore let the package recreate a user secret despite the explicit no-create rule.

Use a shared conditional-update service that verifies allowed_packages and updates an existing row atomically; reuse it for the OpenAPI refresh path as well.

🤖 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/worker/src/mcp/capabilities/secrets/secret-set.ts` around lines 49 -
86, Package-authorized user-secret updates are currently non-atomic because
secret-set resolves and authorizes the secret first, then saveSecret can still
upsert a deleted row. Update the secret-set flow to use a shared conditional
update path that checks allowed_packages and only updates an existing
user-scoped secret atomically, reusing the same service in both the package
secret update path and the OpenAPI refresh path. Keep resolveSecret and
assertPackageCanAccessResolvedSecret for validation, but replace the final
saveSecret upsert with the atomic conditional-update helper.
🧹 Nitpick comments (1)
packages/worker/client/routes/account-secrets.tsx (1)

284-319: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the legacy secret-id fallback The parseAccountSecretId branch in getSelectionState is unreachable now that the account secrets route table only registers /new, /approve, /user/:secretName, /package/:packageId/:secretName, and /session/:sessionId/:secretName.

🤖 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/worker/client/routes/account-secrets.tsx` around lines 284 - 319,
The legacy secret-id fallback in getSelectionState is now dead code because the
route table only matches the explicit secrets paths. Remove the
url.pathname.startsWith(`${secretsBasePath}/`) branch and the associated
parseAccountSecretId fallback logic, leaving getSelectionState to handle only
/new, /approve, and the parsed AccountSecretPath cases.
🤖 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/worker/src/mcp/capabilities/openapi-provider/operation-request.ts`:
- Around line 451-477: The package approval URL is being built from the OAuth
token URL instead of the app origin, which can send denial links to the external
provider and leak metadata. Update `executeOpenApiOperationRequest` and
`tryRefreshIntegrationAccessToken` to thread `ctx.callerContext.baseUrl` through
the approval flow, and use that value in the `assertPackageCanUpdateUserSecret`
calls instead of `input.integration.tokenUrl`.

In `@packages/worker/src/mcp/secrets/service.node.test.ts`:
- Around line 8-10: The mock in service.node.test.ts only handles the user-scope
secret_entries query, so package-scope listing is never exercised. Update the
`all()` handler in the test fixture to add a branch for
`listPackageScopeSecretMetadata`/the package-scope `secret_entries` query and
return the expected package-scoped rows, while keeping the existing user-scope
branch intact so `listPackageSecretsByPackageIds` hits the intended path.

In `@packages/worker/src/package-registry/service.ts`:
- Around line 381-390: The secret cleanup is currently gated by the
saved-package check, which leaves orphaned package secrets and stale approvals
when the saved-package row is missing. Update the package deletion flow in the
service handling the savedPackage branch so deleteAllPackageScopedSecrets and
removeAllSecretApprovalsForPackage always run for the given
env/userId/packageId, even when savedPackage is absent, while keeping any
saved-package-specific logic separate.

---

Outside diff comments:
In `@packages/worker/src/mcp/capabilities/secrets/secret-set.ts`:
- Around line 49-86: Package-authorized user-secret updates are currently
non-atomic because secret-set resolves and authorizes the secret first, then
saveSecret can still upsert a deleted row. Update the secret-set flow to use a
shared conditional update path that checks allowed_packages and only updates an
existing user-scoped secret atomically, reusing the same service in both the
package secret update path and the OpenAPI refresh path. Keep resolveSecret and
assertPackageCanAccessResolvedSecret for validation, but replace the final
saveSecret upsert with the atomic conditional-update helper.

---

Nitpick comments:
In `@packages/worker/client/routes/account-secrets.tsx`:
- Around line 284-319: The legacy secret-id fallback in getSelectionState is now
dead code because the route table only matches the explicit secrets paths.
Remove the url.pathname.startsWith(`${secretsBasePath}/`) branch and the
associated parseAccountSecretId fallback logic, leaving getSelectionState to
handle only /new, /approve, and the parsed AccountSecretPath cases.
🪄 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: ab5072cd-b06e-442d-97b4-be8e4ca304fd

📥 Commits

Reviewing files that changed from the base of the PR and between 8a68e67 and 53ea3f9.

📒 Files selected for processing (61)
  • docs/contributing/architecture/data-storage.md
  • docs/contributing/architecture/primitives.yaml
  • docs/contributing/skill-patterns/cloudflare-api-v4.md
  • docs/guides/account-secret-setup.md
  • docs/use/secrets-and-values.md
  • packages/shared/src/account-secret-route.node.test.ts
  • packages/shared/src/account-secret-route.ts
  • packages/shared/src/chat.ts
  • packages/worker/client/routes/account-approval-shared.ts
  • packages/worker/client/routes/account-secrets.tsx
  • packages/worker/client/routes/index.tsx
  • packages/worker/migrations/0057-package-scoped-secrets.sql
  • packages/worker/src/app/account-secrets-data.ts
  • packages/worker/src/app/handlers/account-secrets.node.test.ts
  • packages/worker/src/app/handlers/account-secrets.ts
  • packages/worker/src/app/loader-data.ts
  • packages/worker/src/app/router.ts
  • packages/worker/src/app/routes.ts
  • packages/worker/src/jobs/service.node.test.ts
  • packages/worker/src/jobs/service.ts
  • packages/worker/src/mcp/capabilities/meta/execute.ts
  • packages/worker/src/mcp/capabilities/meta/search-and-execute.node.test.ts
  • packages/worker/src/mcp/capabilities/openapi-provider/index.ts
  • packages/worker/src/mcp/capabilities/openapi-provider/operation-request.node.test.ts
  • packages/worker/src/mcp/capabilities/openapi-provider/operation-request.ts
  • packages/worker/src/mcp/capabilities/secrets/jwt-sign.ts
  • packages/worker/src/mcp/capabilities/secrets/secret-delete.ts
  • packages/worker/src/mcp/capabilities/secrets/secret-list.ts
  • packages/worker/src/mcp/capabilities/secrets/secret-set.ts
  • packages/worker/src/mcp/capabilities/secrets/shared.ts
  • packages/worker/src/mcp/execute-modules/kody-runtime-utils.ts
  • packages/worker/src/mcp/fetch-gateway.node.test.ts
  • packages/worker/src/mcp/fetch-gateway.ts
  • packages/worker/src/mcp/run-kody-registry.ts
  • packages/worker/src/mcp/secrets/capability-approval-url.ts
  • packages/worker/src/mcp/secrets/host-approval.ts
  • packages/worker/src/mcp/secrets/package-access.node.test.ts
  • packages/worker/src/mcp/secrets/package-access.ts
  • packages/worker/src/mcp/secrets/package-approval-url.ts
  • packages/worker/src/mcp/secrets/package-scope-migration.node.test.ts
  • packages/worker/src/mcp/secrets/placeholders.ts
  • packages/worker/src/mcp/secrets/repo.ts
  • packages/worker/src/mcp/secrets/secret-bindings.ts
  • packages/worker/src/mcp/secrets/service.node.test.ts
  • packages/worker/src/mcp/secrets/service.ts
  • packages/worker/src/mcp/secrets/types.ts
  • packages/worker/src/mcp/storage.ts
  • packages/worker/src/mcp/tools/execute.node.test.ts
  • packages/worker/src/mcp/tools/execute.ts
  • packages/worker/src/package-invocations/service.node.test.ts
  • packages/worker/src/package-invocations/service.ts
  • packages/worker/src/package-registry/service.node.test.ts
  • packages/worker/src/package-registry/service.ts
  • packages/worker/src/package-registry/types.ts
  • packages/worker/src/package-retrievers/service.ts
  • packages/worker/src/package-runtime/package-app.ts
  • packages/worker/src/package-runtime/package-service.ts
  • packages/worker/src/package-runtime/package-workflows.node.test.ts
  • packages/worker/src/package-runtime/package-workflows.ts
  • packages/worker/src/package-runtime/realtime-session.ts
  • packages/worker/src/repo/checks.ts
💤 Files with no reviewable changes (2)
  • packages/worker/src/app/router.ts
  • packages/worker/src/app/routes.ts

Comment thread packages/worker/src/mcp/capabilities/openapi-provider/operation-request.ts Outdated
Comment thread packages/worker/src/mcp/secrets/service.node.test.ts
Comment thread packages/worker/src/package-registry/service.ts Outdated

@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 using default effort and found 2 potential issues.

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 3130513. Configure here.

Comment thread packages/worker/src/jobs/service.ts
SELECT json_group_array(value)
FROM json_each(e.allowed_packages)
WHERE value <> ?
),

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.

Approval list becomes null JSON

Low Severity

When the last package id is removed from a user secret’s allowed_packages, removePackageFromSecretApprovals sets the column via json_group_array with no rows, which SQLite stores as SQL NULL instead of []. Downstream updates that require json_valid(allowed_packages) can then skip those rows.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3130513. Configure here.

@kody-bot
kody-bot merged commit 5fe4a1c into main Jul 9, 2026
5 checks passed
@kody-bot
kody-bot deleted the cursor/package-scoped-secrets-fe1f branch July 9, 2026 22:43
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.

3 participants