Skip to content

feat(abac): user-scoped private Tools/Agents/Skills via allowedPrincipals (#149) - #193

Open
k5s-bot[bot] wants to merge 1 commit into
mainfrom
feat/user-scoped-abac-149
Open

feat(abac): user-scoped private Tools/Agents/Skills via allowedPrincipals (#149)#193
k5s-bot[bot] wants to merge 1 commit into
mainfrom
feat/user-scoped-abac-149

Conversation

@k5s-bot

@k5s-bot k5s-bot Bot commented Aug 2, 2026

Copy link
Copy Markdown

Resolves #149 ("User Scoped Skills, Agents and Tools").

What & why

The issue asks for a way to mark a Skill, Agent, or Tool as privately scoped
to specific users
, in addition to the existing RBAC controls. RBAC today
(allowedRoles, ADR 0004) gates by role — a class of caller — which can't
express "only this person (or this handful) may see and use this resource"
without minting a throwaway one-person role and attaching it everywhere.

This adds an optional allowedPrincipals to the Tool, LocalTool,
Agent, and Skill CRDs — an ABAC (attribute-based access control) layer the
orchestrator enforces on top of RBAC. The design is written up in
ADR 0036.

Semantics

  • Empty / unset = public — RBAC alone gates the resource. Every existing CR
    keeps working unchanged.
  • Non-empty = private — a caller is a retrieval/selection candidate only
    when both hold: RBAC passes (allowedRoles ∩ callerRoles) and the
    caller's resolved principal (ADR 0030 §6 — identity.principal, falling back
    to subject; e.g. github:<login>) is one of allowedPrincipals.
  • The two axes are ANDed — private-scoping only narrows, never grants.
  • Skills carry no roles of their own (ADR 0011); they may carry their own
    allowedPrincipals, and their effective private set is the intersection of
    that with the allowedPrincipals of every referenced tool/agent that is
    itself private — so a skill can never widen a private tool's audience.
  • Fail-closed throughout, matching ADR 0004's discipline.

Changes

core-controller (Go):

  • AllowedPrincipals []string (optional) on ToolSpec, LocalToolSpec,
    AgentSpec, SkillSpec, with doc comments.
  • Regenerated zz_generated.deepcopy.go and the CRD manifests under
    config/crd/bases via make generate manifests (chart crds/ copies are
    gitignored build artifacts, synced by the same target).

agent-orchestrator (TS):

  • allowedPrincipals on ToolDescriptor/AgentDescriptor/SkillDescriptor,
    read from the CRDs by every registry.
  • callerPrincipal added to the tool/agent/skill query filters and enforced in
    all three Qdrant stores (query + getByIds) via a shared
    vector-store/qdrant-abac.ts helper (payload gains allowedPrincipals + a
    denormalized private flag; the filter ANDs a public OR names-me clause).
  • derive-access.ts extended to derive the skill's effective principal set.
  • graph.ts threads the principal through one callerFilter(identity) used at
    every store query/getByIds site — so ABAC is enforced identically on RAG
    retrieval, the active-skill/active-agent re-checks, IntegrationRoute forced
    targets, and a skill's tool resolution. A private resource never enters the
    candidate/skill-tool set, so it can never be planned or launched.

Docs/tests: ADR 0036 (+ index), orchestrator README, and unit tests across
the stores, registries, and skill-access derivation (incl. inheritance,
intersection, disjoint/unreachable, and private-respond-only cases).

Verification

  • apps/agent-orchestrator: tsc --noEmit clean; full vitest suite 592
    passed
    .
  • controllers/core-controller: go build ./... and go vet ./api/... clean;
    make generate manifests regenerates cleanly.

Notes / out of scope

  • allowedPrincipals gates discovery & selection; it's orthogonal to
    identityProviders (which governs whose credential a launch runs with).
  • Not addressed here (deliberately): wildcard/group principals, auto-adding the
    creating user as an owner, and any management UI — listing principals on the
    CR is the v1 scope; groups can layer on later without changing the filter.

Maintainer: apply the ai-review label to this PR to request an automated
code review, or the ai-triage label to have review feedback addressed and
the branch brought back in sync with its base. (The automation can't label its
own PR, so this needs a human.)

…pals

Resolves #149. Adds an optional `allowedPrincipals` to the Tool, LocalTool,
Agent, and Skill CRDs — an ABAC private-scoping layer enforced by the
orchestrator ON TOP of the existing RBAC `allowedRoles` filter (ADR 0036).

Empty/unset = public (unchanged). Non-empty = private: a caller is a retrieval
candidate only if RBAC passes AND their resolved principal (ADR 0030 §6,
`identity.principal ?? subject`) is listed. Skills derive their effective
private set by intersecting their own list with any private tool/agent they
reference, so a skill can never widen a private tool's audience.

- core-controller: `AllowedPrincipals` on Tool/LocalTool/Agent/Skill specs;
  regenerated deepcopy + CRD manifests.
- agent-orchestrator: `allowedPrincipals` on the descriptors, read from the
  CRDs by every registry; `callerPrincipal` added to the tool/agent/skill
  query filters; enforced in all three Qdrant stores (query + getByIds) via a
  shared qdrant-abac helper; skill audience derivation extended; graph threads
  the principal through a single `callerFilter(identity)` at every store call.
- Fail-closed and rolling-upgrade safe (explicit `private` flag on upsert).
- ADR 0036 + README/docs; unit tests across stores, registries, derivation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018PGNNomuXvbzwgcaF6gxDb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

User Scoped Skills, Agents and Tools

1 participant