Change Who Can Reach Your KDCube App Without Rebuilding
Declare stable API, MCP, and widget surfaces in code. Change availability, audience, authority, and browser request proof per deployment.
Your app keeps portable surface defaults in code, while each deployment can switch a resource off, narrow its audience, select an authority, or require one-time browser CSRF proof without rebuilding it.
KDCube calls the deployable unit an app in builder-facing prose. Literal SDK and descriptor contracts still say bundle in names such as @bundle_entrypoint, bundle_id, and bundles.yaml.
- an app with an API, MCP, or widget declaration
- its item in
bundles.yaml - the Control Plane Apps view or descriptor source
- one permitted and one denied test identity
- a real caller for the surface you change
10OPSeparate Existence From Policy
Keep five questions independent. One private switch cannot express all of them.
| Question | Owner | Result |
|---|---|---|
| Does it exist? | code decorator | Undeclared code is not remotely callable through that surface family. |
| Is it active here? | enabled.* | A disabled app or resource returns 404. |
| Who may reach it? | visibility | User-type and raw-role checks run before app code. |
| Which authority may call? | auth | Current or delegated authority and grants are checked. |
| Does a cookie mutation carry request proof? | API csrf | A one-time token proves intent for the exact operation POST. |
enabled is availability. visibility is audience policy. auth is authority policy. csrf is request proof, not another role or grant.
Name separately what would make one surface absent, hidden, unauthorized, or rejected for missing browser request proof.
20OPDeclare Portable API Defaults
Put the operation and its safe defaults on @api(...).
from typing import Any from kdcube_ai_app.infra.plugin.bundle_loader import api @api( method="POST", alias="report_publish", route="operations", user_types=("registered",), roles=("kdcube:role:editor",), csrf=True, ) async def report_publish(self, **payload: Any) -> dict[str, Any]: return await self.reports.publish(payload)
Both visibility dimensions apply when both are present. user_types use anonymous < registered < paid < privileged. roles compare raw role ids.
csrf=True is valid only for POST on operations. Public webhooks use provider proof or another configured guard.
App discovery lists report_publish [POST] operations with the decorator's effective audience and CSRF default.
30OPOverride The Exact API Method
Write only the deployment difference under the concrete route, alias, and method.
bundles:
items:
- id: reporting@1-0
config:
surfaces:
as_provider:
api:
operations:
report_publish:
POST:
visibility:
user_types: [registered]
roles: [kdcube:role:publisher]
auth:
authority_id: platform
grants: [reports:publish]
csrf: true
Resolution: method policy wins over alias fallback; alias fallback wins over the decorator default.
A missing value keeps the code default. An explicit empty visibility list removes that restriction for this deployment. Keep descriptors sparse so they do not pin every old code default.
The effective API descriptor reports the effective value, code default, exact descriptor path, and whether an override exists.
40OPSwitch One Resource Off
enabled:
bundle: true
api:
"operations.report_publish.POST": false
mcp:
reports: true
widget:
reporting: true
Missing values mean enabled. This is an override layer, not a second inventory of app resources.
Disable report_publish: it returns 404, while a sibling API, MCP server, or widget remains available.
50OPUse The Apps View
Open Apps, select the app, then select the concrete API, MCP endpoint, or widget. The editor writes the same descriptor-backed paths.
For an API POST on operations, Cookie request CSRF shows the effective value, decorator default, exact descriptor path, and explicit-override state. Save writes the method override. Reset writes a null marker there and restores the decorator default for that method.
The MCP editor has no CSRF switch. MCP uses explicit protocol credentials and its own auth contract, not ambient browser cookies.
Save one local override, reopen the resource, and see its marker. Reset it and see the decorator default return.
60OPTeach The Browser The CSRF Exchange
When the effective API policy has csrf: true, a cookie-authenticated browser obtains one token before the mutation.
GET .../operations/report_publish/csrf
-> {csrf_required: true, csrf_token: "...", expires_in: 600}
POST .../operations/report_publish
X-KDCube-CSRF-Token: <returned token>
The token binds subject, tenant, project, app, operation, and method, then is consumed once. Missing, expired, reused, or mismatched proof gets 403. Shared proof-state unavailability gets 503.
Explicit bearer and ID-token requests do not perform this cookie exchange. Their credential is non-ambient request proof; visibility, authority, grants, and product authorization still run.
POST without a token returns 403; a fresh token reaches the operation; token reuse returns 403.
70OPUse Each Surface's Vocabulary
| Surface | Deployment controls |
|---|---|
| API | enabled; user types; roles; authority/grants; operation-POST CSRF |
| MCP | enabled; transport; public, app-owned, or managed auth |
| Widget | enabled; user types; roles; optional authority/grants |
| App listing | enabled.bundle; app-level allowed_roles |
@mcp(...) intentionally has no proc-side user types, roles, or CSRF. Govern a protected MCP endpoint through its auth owner.
surfaces:
as_provider:
mcp:
reports:
auth:
mode: managed
authority_id: delegated_client
tools:
report_read:
grants: [reports:read]
report_publish:
grants: [reports:publish]
selected_tool_grants: true
Managed mode checks the delegated bearer, concrete resource, selected tool, and grants before app MCP code runs. Bundle mode leaves credential verification to the app.
Discover the MCP endpoint, then call one permitted and one denied tool through the selected auth mode.
80OPVerify The Effective Deployment
- Reload the app or wait for the live property update.
- Reopen Apps and inspect effective values and override markers.
- Test a permitted and denied user/role independently.
- Test authority/grant denial independently from visibility.
- Test CSRF missing, accepted, and reused states when enabled.
- Run MCP discovery and one real tool call.
- Disable one surface and verify siblings remain live.
- Reset one override and verify the code default returns.
For local source changes, refresh or reload the staged runtime copy before judging the result.
- Code declares every real surface and its portable defaults.
- Descriptors contain only intentional deployment differences.
- Availability, audience, authority, and request proof remain separate.
- API policy is route- and method-specific where needed.
- Protected cookie POSTs implement the token exchange.
- MCP uses MCP auth rather than API CSRF or role shortcuts.
- Reset restores the decorator default.
- The effective behavior passed a real transport test.