uptrakit

Shared Surface Security

Shared Surface Security

Overview

Shared surfaces let plugins and connected services project UI capabilities through the controller. Security is enforced by a fail-closed contract admission model plus per-request authorization and transport controls.

Permission model

Permissions are evaluated at two levels:

  • descriptor-level: SurfaceDescriptor.required_permission
  • interaction-level: InteractionDescriptor.required_permission

Enforcement points:

  • GET /api/v1/surfaces — only tenant-visible surfaces are listed
  • GET /api/v1/surfaces/{surface_id}/read — descriptor permission check
  • POST /api/v1/surfaces/{surface_id}/interactions/{interaction_id} — descriptor and interaction permission checks

Frontend filtering is convenience only; server checks are authoritative.

Provider-origin invocation

Provider-origin (service-initiated) calls carry no user; they are gated by tenant scope plus the provider-permission gate: an interaction with required_permission is denied to CallerOrigin::Provider unless it sets provider_invocable.

provider_invocable = true means any same-tenant provider with the UiSurfaces capability may invoke the interaction, subject only to tenant scope and the standard idempotency/timeout/rate controls. It is a deliberate privilege expansion, accepted because tenant services are co-trusted (enrolled by the tenant admin), writes are tenant-scoped and recoverable, and every provider-origin invocation is audit-attributed to the calling service.

Registration admission rejects the flag on permissioned interactions from Service-kind providers (Plugin/BuiltIn-owned interactions only); per-caller narrowing is deliberately outside the flag — a future optional allowlist field composes with it.

Handlers of flagged interactions must not treat provider origin as privileged beyond tenant membership.

Registration admission controls

Service and plugin providers are admitted through SurfaceRegistry with strict validation:

  • framework generation compatibility (supported_generation)
  • required capability coverage
  • slot ID validation against central slot registry
  • provider-kind/transport compatibility
  • tenant-binding correctness for authenticated service context
  • allowlist checks for controller queries, SSE topics, and direct built-in operations
  • contract shape and depth limits
  • payload and interaction count limits

Invalid registrations are rejected with structured rejection reasons.

Targeting and tenancy controls

Targeted surfaces require explicit target_provider_id at invocation time. Provider resolution is tenant-aware; cross-tenant providers are excluded from availability and dispatch.

For service providers:

  • tenant services must register tenant-scoped bindings matching authenticated tenant
  • system services must register global scope with no tenant binding

Sensitive parameter handling

Sensitive interaction values are not sent in plaintext params. Clients send encrypted_sensitive_params (ECIES P-256 metadata + ciphertext payload).

Controller behavior:

  • validates presence/shape requirements
  • forwards ciphertext opaquely to provider
  • never decrypts sensitive payloads

Provider behavior:

  • publishes encryption metadata in provider info
  • decrypts payload locally using provider private key

Action execution controls

Invocation path is mediated by SurfaceProxy:

  • idempotency key handling (duplicate_request protection)
  • per-request timeout handling
  • provider disconnect and unavailability handling
  • typed error-code mapping for caller-safe failure semantics
  • in-flight budget/idempotency cancellation-safety: if a caller disconnects mid-request, the proxy releases the provider/tenant in-flight budget and the idempotency reservation on future-drop (RAII guards) plus a deadline-keyed backstop sweep, so a disconnecting client cannot permanently exhaust a provider's or tenant's in-flight budget or wedge an idempotency key

Capability gating

UI/surface runtime participation is gated on protocol capability negotiation. Incompatible providers are excluded from the active surface catalog; their registrations are rejected at admission time rather than silently degrading runtime behavior.

Trust boundaries

  • Browser never talks directly to services for surface actions.
  • Service-to-controller calls are authenticated over mTLS WebSocket.
  • Controller mediates all provider selection and dispatch.
  • NATS is not used for session-targeted surface action payload delivery.

Key files

FilePurpose
crates/shared/surfaces/src/Shared surface contract and validation policy types
crates/shared/wire/src/wire_validate_impls.rsWire-level payload validation
crates/ui/web-api/src/surface_registry.rsRegistration admission and tenant/provider indexing
crates/ui/surface-proxy/src/proxy.rsInvocation correlation, idempotency, timeout, and routing
crates/ui/web-api/src/routes/surfaces.rsAuthz enforcement and API error mapping
crates/ui/web-api/src/routes/service_ws/handler/mod.rsService message handling for surface registration and actions

See also