feat: v7 RBAC visibility scope for Llms and Agents #72
Open
michal
wants to merge 3 commits from
feat/v7-rbac-visibility into main
pull from: feat/v7-rbac-visibility
merge into: michal:main
michal:main
michal:feat/provider-disable
michal:feat/provider-lifecycle-cli
michal:feat/v6-polish
michal:feat/inference-task-queue
michal:perf/vitest-threads-and-docker-pnpm-cache
michal:feat/virtual-llm-v2-wake
michal:fix/llm-yaml-roundtrip-strip-lifecycle
michal:feat/virtual-llm-v1
michal:fix/status-probe-reasoning-content
michal:feat/status-llm-say-hi
michal:feat/status-server-llms-and-spa-fix
michal:fix/vitest-workspace-projects
michal:feat/web-prompt-editor-personalities
michal:feat/agents-and-chat-ux
michal:feat/openbao-wizard
michal:feat/project-llm-ref
michal:feat/llm-failover
michal:feat/llm-infer
michal:feat/llm
michal:feat/secretbackend
michal:feat/mcpagent
michal:feat/mcptoken
michal:feat/mcp-console
michal:feat/gated-prompt-intelligence
michal:fix/per-provider-health-check
michal:feat/tiered-llm-providers
michal:feat/per-project-llm-pagination-tests
michal:feat/completions-llm-flags-promptrequest
michal:feat/gemini-acp-provider
michal:feat/llm-config-and-secrets
michal:feat/response-pagination
michal:fix/stdio-flush-and-notifications
michal:feat/prompt-resources-and-proxy-transport
michal:feat/mcp-stdio-bridge
michal:fix/delete-content-type-and-project-servers
michal:fix/completion-no-repeat-server-arg
michal:fix/completion-instances-attach-detach
michal:fix/completion-jq-wrapped-json
michal:fix/completion-nested-names
michal:feat/completions-stale-erase-and-tests
michal:feat/completions-project-scope-dynamic
michal:feat/project-scoped-get
michal:feat/tests-sh-and-project-routes-tests
michal:fix/project-list-rbac
michal:feat/project-improvements
michal:fix/update-shell-completions
michal:fix/migrate-legacy-admin-role
michal:fix/build-type-errors
michal:feat/projects-rbac-users-groups
michal:fix/db-tests-passwordhash
michal:feat/health-probe-runner
michal:fix/stdin-describe-instance
michal:feat/container-liveness-sync
michal:fix/logs-resolve-and-tests
michal:fix/instance-ux
michal:feat/node-runner-registry-pull
michal:feat/node-runner-base-image
michal:fix/create-error-handling
michal:feat/healthcheck-probes
michal:feat/mcp-templates
michal:feat/replace-profiles-with-secrets
michal:feat/create-edit-commands
michal:feat/docker-container-management
michal:feat/database-schema
michal:feat/mcp-registry-client
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "feat/v7-rbac-visibility"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Adds a per-row visibility scope (
public|private) toLlmandAgentso users on a shared mcpd cluster can publish private virtualLlms without exposing them to everyone with
view:llms.21f8bed) — schema column,ownerId,Viewerpredicate,unit tests (13/13 passing).
2c98a21) — route layer + RBACisAdminsplit + mcplocaldefaults to
private+ CLI--visibilityflag +VISIBILITYcolumnon
get llm/get agent+ completions regen.2b2444a) —docs/virtual-llms.mdvisibility section +live smoke (
virtual-llm-visibility.smoke.test.ts) covering theregister → list → get-by-name round-trip.
Visibility model:
public— anyone withview:llmssees it. Default for hand-created.private— only owner + name-scoped grant +*admin. Default fornewly-published virtual rows from mcplocal.
A plain
view:llmsresource grant is not admin: the v7 split ensuresthat grant only widens RBAC name-scoping but the visibility filter
still applies on top. Cross-resource
*grant is the explicit adminescape hatch.
Legacy rows (pre-v7) backfill
visibility=public, ownerId=NULL— fullybackwards compatible.
Test plan
908/908)731/731)437/437)match generator outputtest passesbash fulldeploy.shto roll out mcpd + mcplocal RPMpnpm test:smokeincludes the newvirtual-llm-visibility.smoke.test.ts)alice-vllm-localprivate,bob with only
view:llmsdoesn't see it;mcpctl create rbac bob view:llms --name alice-vllm-localmakes it visible🤖 Generated with Claude Code
Adds the schema + service-layer machinery for per-user RBAC scoping of virtual Llms and Agents. Without this, anyone with `view:llms` sees every other user's published model — fine for a single-user homelab, wrong for org use where workstation-published models or paid keys aren't meant to be broadcast. Schema: - Llm: new `ownerId String?` + `visibility String @default("public")`. NULL ownerId on legacy rows is treated as public for back-compat. - Agent: `visibility String @default("public")` (Agent already has `ownerId`, required). - Composite index `(visibility, ownerId)` on both tables for the list-filter hot path. - Migration backfills both columns to 'public' so pre-v7 setups behave identically post-deploy. Service layer: - New `Viewer` / `AgentViewer` shape: `{ userId, wildcard, allowedNames }`. The route layer computes this from `request.userId` + `RbacService.getAllowedScope` and passes it down. NULL viewer = skip the filter (internal callers — cron sweeps, audit, tests). - `isLlmVisibleTo` / `isAgentVisibleTo` pure predicates encode the decision tree: visibility=public → visible (RBAC layer above already passed) viewer=null OR wildcard → visible ownerId === viewer.userId → visible row.name in viewer.allowedNames → visible else → hidden - LlmService.list/getById/getByName + AgentService equivalents accept an optional Viewer arg and apply the predicate. Get-style methods 404 (not 403) on hidden rows so name enumeration via differential status is impossible. Repositories: CreateInput/UpdateInput types gained `ownerId`/ `visibility` (Llm) and `visibility` (Agent). Update is in place; ownerId is set-once at create time. Tests: - 13 unit tests on the predicate covering every branch (null viewer, public, wildcard, owner, name-scoped grant, foreign private, legacy null-ownerId). - mcpd 908/908 (was 893; +15 across the merge windows + this PR). Stage 2 (next): route plumbing — every list/get endpoint needs to build the Viewer from the request and pass it through. mcplocal virtuals default to visibility=private on register. CLI adds a VISIBILITY column and a --visibility flag. yaml round-trip preserves the field.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.