fix(api): conceal cross-tenant resource denials on internal routes - #6586
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryHigh Risk Overview Adds Workspace-level list/create and a few intentionally asymmetric surfaces are left unchanged. Reviewed by Cursor Bugbot for commit c96b6b0. Configure here. |
f67ef7f to
aa0bba5
Compare
|
@cursor review |
Greptile SummaryThe PR extends the existing cross-tenant resource-concealment behavior from v2 endpoints to corresponding internal workflow, file, table, and knowledge routes.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| apps/sim/lib/api/server/routes/resource-concealment.ts | Introduces shared internal and raw-handler concealment while preserving the existing v2 classification behavior. |
| apps/sim/lib/workflows/api/route-policies.ts | Applies workflow-not-found concealment to internal single-workflow policies without changing same-workspace denials. |
| apps/sim/lib/knowledge/api/route-policies.ts | Conceals knowledge-base-scoped operations while deliberately retaining existing behavior for workspace-level list and create policies. |
| apps/sim/lib/table/api/route-policies.ts | Adds internal table, group, import, and export concealment policies while retaining the table-lock response branch. |
| apps/sim/lib/workspace-files/api/internal-error-policies.ts | Adds concealment variants for single-file and content operations while preserving specialized file errors. |
| apps/sim/app/api/workflows/[id]/route.ts | Wires declarative and raw workflow handlers through the new concealment behavior. |
| apps/sim/app/api/workflows/[id]/deployments/[version]/route.ts | Conceals cross-tenant failures in the raw deployment-version mutation before existing error classification. |
| apps/sim/app/api/table/table-transfer-routes.test.ts | Verifies all internal table-transfer routes select the appropriate resource-specific concealment policy. |
| apps/sim/lib/api/server/routes/resource-concealment.test.ts | Covers internal and v2 concealment, preserved same-workspace denials, specialized policy branches, and unconcealed list/create behavior. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
Request[Internal or v2 resource request] --> UseCase[Authorized application use case]
UseCase --> Result{Outcome}
Result -->|Allowed| Success[Existing success response]
Result -->|Cross-tenant denial| Conceal[Resource concealment policy]
Conceal --> NotFound[Domain-specific 404 response]
Result -->|Same-workspace denial| Forbidden[Existing 403 response]
Result -->|Other domain error| BasePolicy[Existing domain error policy]
BasePolicy --> ExistingShape[Existing route-specific error response]
Reviews (3): Last reviewed commit: "fix(api): conceal cross-tenant resource ..." | Re-trigger Greptile
The v2 routes rewrite DelegatedWorkspaceAuthorizationError, NoWorkspaceAccessError, and WorkspaceApiKeyScopeAuthorizationError to a 404 so a caller with no reach into a workspace cannot confirm a resource exists. The internal routes reach the same application use cases and still answered 403, so the same probe worked from the other surface. Same-workspace role denials stay 403 on both.
aa0bba5 to
c96b6b0
Compare
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit c96b6b0. Configure here.
Summary
createV2ResourceConcealmentPolicyrewritesDelegatedWorkspaceAuthorizationError,NoWorkspaceAccessError, andWorkspaceApiKeyScopeAuthorizationErrorto a404on v2 single-resource routes. The internal routes reach the same application use cases and still answered403, so the existence probe the v2 policy closes still worked from the other surface.createInternalResourceConcealmentPolicy(andconcealCrossTenantResourceErrorfor the rawwithRouteHandlerroutes) beside the v2 factory, and renamedv2-resource-concealment.ts→resource-concealment.tssince it now owns both surfaces. The concealed failure is re-projected through each domain's existing base policy as anot_foundorchestration error, so every body shape (success: false, the legacy workflowcodefield, the table423lock branch) is exactly what that route's real 404 already returns.InsufficientWorkspacePermissionsError,PersonalApiKeysDisabledError,WorkspaceApiKeyAuthorizationError, andPrincipalKindAuthorizationErrorall stay403on both surfaces, and genericOrchestrationError('forbidden', …)is never classified by message.Routes whose status changed (cross-tenant denial only:
403→404)GET/PUT/DELETE /api/workflows/[id]GET/POST/PATCH/DELETE /api/workflows/[id]/deployGET /api/workflows/[id]/deploymentsGET/PATCH /api/workflows/[id]/deployments/[version]PATCH/DELETE /api/workspaces/[id]/files/[fileId]PUT /api/workspaces/[id]/files/[fileId]/contentGET/PUT /api/workspaces/[id]/files/[fileId]/sharePOST /api/table/imports,POST /api/table/[tableId]/exportsPOST/PATCH/DELETE /api/table/[tableId]/groupsGET/DELETE /api/table/imports/[importId],POST .../parts,POST .../completeGET/DELETE /api/table/exports/[exportId],GET .../download/api/knowledge/[id]/**(read/update/delete/restore, documents, chunks, tags, connectors, uploads, upsert) andPOST /api/knowledge/searchDeliberately left at 403
GET/POST /api/knowledgename no resource, so a403there betrays nothing. Concealing them would turn a denied workspace into a phantom empty list./api/mcp/**,/api/skills/**,/api/tools/custom,/api/logs/[id], workflow runs/execute/resume — the v2 routes for these use v2-only use cases with no internal counterpart sharing the symbol, so there is no asymmetry to close. Several of them are also where a flip would regress:hooks/queries/mcp.ts:92,127andhooks/queries/workflow-mcp-servers.ts:60,130swallow404into[], so a denied user would see a "no servers configured" empty state instead of an error, anddeploy-modal/components/mcp/mcp.tsx:400auto-recreates a tool on404.GET /api/workspaces/[id]/host-context— untouched.workspace-host-provider.tsx:29is the one surface that renders a dedicated<WorkspaceAccessDenied />on403; flipping it would drop a revoked user into a stale workspace tree.UI impact of what did change
I swept
hooks/**andapp/**for403/404branching on the affected families. Nothing structural flips:knowledge/[id]/base.tsx:1233already renders "Knowledge base not found" for both statuses.kb/knowledge.ts:113rewrites404to "Document not found" — message-only change, same error layout.tables/[tableId]/error.tsxcopy already hedges "deleted or you may not have permission".error.messageverbatim, so a denied write now reads "Table not found" instead of "Insufficient workspace permissions".logs.ts:208skips retry on404— one fewer retry before the same error state.Type of Change
Testing
bun run type-check,bunx vitest run app/api lib/api(342 files / 3257 tests),bun run lint,bun run check:api-validation— all green.Tests pin the concealed status per policy and per route:
resource-concealment.test.tsnow covers every internal policy alongside the v2 ones, and route-level tests drive the real handlers for file rename/share/content, workflowPUT, and deployment-versionPATCH. Table/group route tests assert the wiring. Verified they can fail — stubbingconcealCrossTenantResourceErrorto a passthrough turns 60 tests red, and un-wiring a single table route fails its wiring assertion withexpected { kind: 'plain' } to be { kind: 'conceal-import' }.Checklist