## Summary - harden Provider Connection authority so workspace scope comes only from explicit workspace context and record ownership - require explicit `environment_id` for Provider Connection create flows and remove remembered-environment or Filament-tenant fallback authority - keep legacy query aliases such as `tenant`, `tenant_id`, and `managed_environment_id` inert for Provider Connection access - add targeted Spec 339 feature coverage for create authority, workspace authority, and wrong-workspace / legacy-query denial behavior - include Spec 339 artifacts (`spec.md`, `plan.md`, `tasks.md`) for the hardening slice ## Validation - `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/ProviderConnections --filter=ScopeHardening` ## Notes - no new uncommitted workspace changes were present to commit in this turn; the branch already contained the feature commits - Livewire v4 compliance unchanged - Filament provider registration remains in `bootstrap/providers.php` - no migrations, new assets, or route-family restructures Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #410
16 KiB
Feature Specification: Spec 339 - Provider Connection Scope Hardening
Feature Branch: 339-provider-connection-scope-hardening
Created: 2026-05-31
Status: Draft
Input: Candidate provider-connection-scope-hardening from docs/product/spec-candidates.md + user-provided draft + repo inspection of Provider Connection scope/authority seams.
Spec Candidate Check (mandatory — SPEC-GATE-001)
- Problem: Provider Connections are credential-adjacent. Their scope and authorization authority must not come from hidden UI/session state (remembered environment) or Filament tenant fallbacks.
- Today's failure:
- Provider Connection create authorization can still fall back to remembered environment (
WorkspaceContext::lastEnvironmentId) or Filament tenant (Filament::getTenant()), allowing “implicit target environment” whenenvironment_idis missing. - Workspace authority can be inferred via Filament tenant when workspace context is missing, which is unsafe for a credential-adjacent surface.
- Legacy query keys (
tenant,tenant_id,managed_environment_id) still exist in the ecosystem and must not regain authority via accidental parsing.
- Provider Connection create authorization can still fall back to remembered environment (
- User-visible improvement: Operators can trust that Provider Connection list/view/create/edit/actions are scoped only by:
- explicit workspace context (required), plus
- explicit
environment_id(filter/create), or - record ownership (
ProviderConnection.workspace_id+ProviderConnection.managed_environment_id) for view/edit/actions.
- Smallest enterprise-capable version:
- define and enforce an explicit authority-source contract (allowed vs forbidden),
- remove hidden fallbacks in
ProviderConnectionPolicyand Provider Connection resource query scoping, - add targeted tests proving 404 vs 403 semantics and that legacy query aliases cannot grant authority.
- Explicit non-goals:
- no provider-neutral target-scope refactor (Spec 281 family),
- no provider registry / OAuth redesign,
- no schema changes,
- no broad link/query cleanup beyond Provider Connections (defer to
canonical-link-query-cleanupcandidate).
- Permanent complexity imported: a small set of contract tests + tightened policy/resource guardrails; no new persisted truth or abstraction layer.
- Why now: Spec 338 tightened the workspace/environment scope contract. Provider Connections remain the main credential-adjacent hub; harden scope authority before adding more provider operations or onboarding variants.
- Why not local: UI-only fixes do not guarantee consistent authority across create/view/actions. This must be enforced at the policy/resource boundary and guarded by tests.
- Approval class: Core Enterprise (security / authorization hardening).
- Red flags triggered: credential-adjacent surface + authorization semantics + cross-surface context drift. Defense: narrow scope, reuse existing
WorkspaceContext+ capabilities, and forbid new abstraction/persistence. - Score: Nutzen: 2 | Dringlichkeit: 2 | Scope: 1 | Komplexität: 1 | Produktnähe: 2 | Wiederverwendung: 2 | Gesamt: 10/12
- Decision: approve.
Summary
Provider Connections sit at the provider boundary and are credential-adjacent. This spec hardens their authority model so that:
- Create requires an explicit
environment_id(no remembered-environment or Filament-tenant fallback). - View/Edit/Actions derive scope from the
ProviderConnectionrecord (workspace + managed environment ownership). - List filtering uses explicit
environment_idonly, validated against the current workspace and access scope. - Hidden context (topbar remembered environment, Filament tenant context, legacy query aliases) must not grant authority.
This is a narrow P1 security hardening slice following the Spec 338 contract work, with tests that make regressions obvious.
Spec Scope Fields (mandatory)
- Scope: workspace (workspace-owned hub with optional environment filter)
- Primary Routes (representative):
/admin/provider-connections/admin/provider-connections?environment_id=<id>/admin/provider-connections/create?environment_id=<id>/admin/provider-connections/{record}(view)/admin/provider-connections/{record}/edit
- Data Ownership:
ProviderConnectionis workspace-owned and is linked to oneManagedEnvironmentviamanaged_environment_id. No schema changes. - RBAC:
- Non-member / out-of-workspace access: 404 (deny-as-not-found)
- Member but missing capability: 403
- Capabilities used by this surface:
Capabilities::PROVIDER_VIEW,Capabilities::PROVIDER_MANAGE, and existing dedicated-credential capability variants.
Authority / Scope Contract (Spec 339)
A. Allowed authority sources (canonical)
Create:
- explicit
environment_idquery parameter (validated and workspace-owned) - explicit environment selection in form state (only if a picker is introduced later)
- workspace context from
WorkspaceContext(required)
View/Edit/Actions:
ProviderConnection.workspace_id+ProviderConnection.managed_environment_id- record → tenant (
ManagedEnvironment) →workspace_id(derived) - workspace context from
WorkspaceContextfor deny-as-not-found semantics and list scoping
B. Forbidden authority sources (must never grant permission)
- remembered environment (
WorkspaceContext::lastEnvironmentIdand related helpers) - Filament tenant fallback (
Filament::getTenant()) as a source of workspace/environment authority - legacy query aliases (
tenant,tenant_id,managed_environment_id) as authority inputs - Filament internal state/query keys as authority (
tableFilters[...], etc.)
C. 404 vs 403 semantics (authorization UX contract)
- Non-member / no workspace context / wrong workspace / out-of-scope record: 404 (deny-as-not-found)
- Member but missing capability: 403
Required Runtime Decisions
D1 — Create requires explicit environment_id (no hidden fallback)
Decision:
- Provider Connection create is only valid with explicit
environment_idthat belongs to the current workspace. - Remembered environment and Filament tenant must never substitute for missing
environment_id.
Acceptance:
/admin/provider-connections/createwithoutenvironment_idcannot be used to create a connection.- Wrong-workspace
environment_iddenies as 404. - Member without
PROVIDER_MANAGEdenies as 403.
D2 — Workspace authority requires explicit workspace context (no Filament fallback)
Decision:
- Workspace authority for Provider Connections must come from
WorkspaceContext(session workspace), not from Filament tenant fallback.
Acceptance:
- If workspace context is missing, Provider Connections deny as 404 even if Filament tenant is set.
D3 — Environment filtering is explicit and inert
Decision:
environment_idmay filter list results but must be validated against current workspace and the user’s environment access scope.- Legacy query keys do not grant access and are not treated as authority.
Acceptance:
?managed_environment_id=...,?tenant=...,?tenant_id=...do not grant access or widen results.
UI Surface Impact (mandatory — UI-COV-001)
- No UI surface impact
- Existing page changed
- New page/route added
- Navigation changed
- Filament panel/provider surface changed
- New modal/drawer/wizard/action added
- New table/form/state added
- Customer-facing surface changed
- Dangerous action changed
- Status/evidence/review presentation changed
- Workspace/environment context presentation changed
UI/Productization Coverage (UI-COV-001)
- Route/page/surface: Provider Connections (list, view, create, edit) + credential-adjacent actions (dedicated credential management and provider operations) in
ProviderConnectionResource. - Current page archetype: workspace admin “Integration/Settings” hub resource (credential-adjacent).
- Design depth: Domain Pattern Surface (security/authority hardening; minimal UX change expected).
- Repo-truth level: repo-verified (
ProviderConnectionResource,ProviderConnectionPolicy,WorkspaceContext,WorkspaceHubEnvironmentFilter+ existing ProviderConnections feature tests). - Existing pattern reused: Spec 338 scope contract + existing deny-as-not-found semantics,
WorkspaceContext, capabilities, and existing ProviderConnections test families. - New pattern required: none (tighten existing seams; avoid new abstraction/persistence).
- Screenshot required: no (unless implementation changes visible CTA/empty-state messaging; then add one targeted screenshot in the implementation PR).
- Page audit required: no (existing archetype).
- Customer-safe review required: no (operator-only surface).
- Dangerous-action review required: yes (credential-adjacent actions must remain confirmation/authorization/audit-safe and scoped to record ownership).
- Coverage files to update (in implementation PR):
docs/ui-ux-enterprise-audit/route-inventory.md(only if navigation entries/routes change; not expected)docs/ui-ux-enterprise-audit/design-coverage-matrix.md(no new surface; expectedno)N/A - no new reachable UI surface added; authority hardening only
Cross-Cutting / Shared Pattern Reuse (mandatory)
- Cross-cutting feature?: yes
- Interaction class(es): navigation + scope presentation, authorization semantics, credential-adjacent action gating.
- Systems touched (expected):
apps/platform/app/Filament/Resources/ProviderConnectionResource.phpapps/platform/app/Policies/ProviderConnectionPolicy.phpapps/platform/app/Support/Workspaces/WorkspaceContext.phpapps/platform/app/Support/Navigation/WorkspaceHubEnvironmentFilter.phpapps/platform/tests/Feature/ProviderConnections/*
- Existing pattern(s) to extend: Spec 338 scope/authority contract (explicit
environment_idfilter, remembered environment is navigation-only, deny-as-not-found for out-of-scope). - Allowed deviation and why: none (tighten existing seams; no new surface frameworking).
- Consistency impact: authority sources must match across list/create/policy/actions; tests must enforce that legacy query aliases and hidden context do not widen authority.
- Review focus: “credential-adjacent authority comes from explicit input or record ownership only.”
OperationRun UX Impact
- Touches OperationRun start/completion/link UX?: no
- N/A: This spec hardens authority sources and authorization semantics. Provider operations that already run via
OperationRunmust remain scoped to the record’s owning environment/workspace; no newOperationRuntypes or start UX changes are introduced.
Provider Boundary / Platform Core Check
- Shared provider/platform boundary touched?: yes
- Boundary classification: platform-core authority (workspace/environment scope) + provider-owned identity (provider scope identifiers) must remain separated.
- Seams affected: Provider Connection authority sources (workspace/environment), policy decisions, and filter/query semantics.
- Why this does not deepen provider coupling accidentally: The contract uses neutral platform terms (
workspace,environment_id, record ownership) and explicitly forbids conflating provider “tenant” identifiers with platform scope authority. - Follow-up path: none (follow-up work belongs in
canonical-link-query-cleanupand/or Spec 281 family, not in this slice).
Testing / Lane / Runtime Impact (mandatory for runtime behavior changes)
- Test purpose / classification: Feature (authorization/scope contract).
- Validation lane(s): fast-feedback (Feature). Browser smoke is optional and only justified if visible UX changes occur.
- Why this classification and these lanes are sufficient: The risk is scope/authorization drift, which is best proven by targeted policy/resource tests (positive + negative cases) without introducing heavy or browser-wide cost by default.
- Planned validation commands:
cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/ProviderConnections --filter=ScopeHardeningcd apps/platform && ./vendor/bin/sail pint --dirty --format agent
Acceptance Criteria
- AC1: Provider Connection create requires explicit
environment_id; remembered environment and Filament tenant cannot substitute. - AC2: Provider Connection list/view/edit/actions derive scope only from current workspace context + explicit
environment_id(filter/create) or from record ownership (view/edit/actions). - AC3: Legacy query aliases (
tenant,tenant_id,managed_environment_id) do not grant access or widen results. - AC4: 404 vs 403 semantics match the repo rules: out-of-scope/non-member is 404; missing capability is 403.
- AC5: Targeted feature tests are green and make authority regressions obvious.
User Scenarios & Testing (mandatory)
User Story 1 — View Provider Connections safely (Priority: P1)
As an operator in a workspace, I can list and view Provider Connections for environments I am entitled to, and I can optionally filter the list by an explicit environment.
Independent Test: A user with workspace membership and PROVIDER_VIEW can list and view records in-scope; a non-member or wrong-workspace access is 404; a member without capability is 403; legacy query aliases do not widen access.
Acceptance Scenarios:
- Given a workspace member with
PROVIDER_VIEWon Environment A, When they visit/admin/provider-connections, Then the list contains only connections in environments they can access in the current workspace. - Given the same user, When they visit
/admin/provider-connections?environment_id=<A>, Then the list is filtered to Environment A and remains scope-safe. - Given a user without workspace membership, When they hit Provider Connections routes, Then the response is deny-as-not-found (404).
- Given a member without
PROVIDER_VIEW, When they attempt to list/view, Then the response is 403. - Given any user, When they add
tenant,tenant_id, ormanaged_environment_idquery keys, Then those keys do not grant access or change authority behavior.
User Story 2 — Create Provider Connection only with explicit target environment (Priority: P1)
As an operator, I can create a Provider Connection only when I explicitly choose the target environment (by passing environment_id) and I am authorized for that environment.
Independent Test: Create is impossible without explicit environment_id, even if a remembered environment or Filament tenant exists.
Acceptance Scenarios:
- Given a member with
PROVIDER_MANAGEon Environment A, When they open/admin/provider-connections/create?environment_id=<A>, Then create is allowed and the record is owned by Environment A + the current workspace. - Given a request to
/admin/provider-connections/createwithoutenvironment_id, When the user has a remembered environment, Then create is still denied (no implicit fallback). - Given an
environment_idfrom another workspace, When the user attempts create, Then the response is 404. - Given a member without
PROVIDER_MANAGE, When they attempt create for an in-scope environment, Then the response is 403.
User Story 3 — Perform credential-adjacent actions with record-derived authority (Priority: P2)
As an operator, credential-adjacent actions (dedicated credential management, provider operations) execute only when I am authorized for the record’s owning environment/workspace, independent of remembered environment or Filament tenant state.
Independent Test: A record-bound action uses the record’s ownership and denies 404/403 correctly.
Acceptance Scenarios:
- Given a Provider Connection record owned by Environment A, When a user with access to Environment A triggers an action, Then the action is authorized using record ownership and capability checks.
- Given the same record, When a user’s remembered environment is Environment B, Then authorization still uses record ownership (A), not hidden context.
- Given a member without the dedicated credential capability, When they attempt dedicated credential actions, Then the response is 403.
Follow-up spec candidates
canonical-link-query-cleanup(broader link/query inventory + alias cleanup after scope contract)environment-resource-context-follow-through(reduce hidden Filament context reliance inside selected environment resources)