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:fix/mcpd-instance-health-and-retry
michal:feat/skills-7-cutover
michal:feat/skills-6-ui
michal:feat/skills-5-sync
michal:feat/skills-4-reviewer
michal:feat/skills-3-skill-resource
michal:feat/skills-2-revisions
michal:feat/skills-1-schema
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
3 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2b2444a2c5 |
docs+smoke(v7): visibility section in virtual-llms.md + register/list smoke
Some checks failed
CI/CD / typecheck (pull_request) Successful in 55s
CI/CD / test (pull_request) Successful in 1m11s
CI/CD / lint (pull_request) Successful in 2m49s
CI/CD / smoke (pull_request) Failing after 1m42s
CI/CD / build (pull_request) Successful in 5m37s
CI/CD / publish (pull_request) Has been skipped
Wraps up v7 Stage 3: - docs/virtual-llms.md gains a "Visibility scope (v7)" section that explains public-vs-private semantics, who skips the filter (owner + `*` admin), how to grant single-row exceptions via name-scoped RBAC, per-row override syntax in mcplocal config, the `--visibility` flag on `mcpctl create llm`/`create agent`, and YAML round-trip behavior. - New smoke (virtual-llm-visibility.smoke.test.ts) publishes one public + one private virtual Llm via the registrar against the live mcpd and asserts the GET /llms response carries visibility + a non-empty ownerId for both, and that GET /llms/<name> returns the private row to its owner without 404. Cross-user filtering is covered by mcpd's visibility-filter unit tests; smoke proves the fields make the round-trip end-to-end. Will pass once mcpd is rebuilt + deployed via fulldeploy.sh on this branch (current main is v6, doesn't yet serialize visibility). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
2c98a21323 |
feat(mcpd+cli+mcplocal): wire visibility filter through routes, CLI, registrar (v7 Stage 2)
Stage 1 added the schema + service predicate. This stage threads the filter through every surface that lists or fetches Llms/Agents: - mcpd routes: viewerFromRequest helper builds a Viewer from the request's RBAC scope. List endpoints rely on the existing preSerialization hook (now two-phase: name-scope first, visibility second). get-by-id/get-by-name routes pass the viewer to the service which 404s on hidden rows. - RBAC: AllowedScope gains `isAdmin` to distinguish a `*` cross-resource grant (admins skip visibility) from a plain `view:llms` grant (wildcard for RBAC, but visibility still applies). FastifyRequest augmentation updated. - VirtualLlmService.register accepts ownerId and stamps it on freshly created virtual rows; defaults visibility to 'private' on first create, leaves existing rows untouched on sticky reconnect. - AgentService.registerVirtualAgents mirrors the same defaults. - mcplocal: LlmProviderFileEntry / AgentFileEntry / RegistrarPublishedX carry visibility through to the register payload (default 'private'). - CLI: VISIBILITY column on `mcpctl get llm` and `mcpctl get agent`, `--visibility` flag on `mcpctl create llm` / `create agent`. YAML round-trip works because visibility passes through stripInternalFields unchanged (ownerId is already stripped). Completions regenerated. Tests: mcpd 908/908, mcplocal 731/731, cli 437/437. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
21f8bede2e |
feat(mcpd+db): visibility scope + ownership for Llms and Agents (v7 Stage 1)
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.
|