Skip to content

[coordination][security] Supervision must not escalate authority — a delegation lends a goal, never privileges #185

Closed

Description

@serge-ivo

Design: docs/supervision.md. Companion to #183. Security-critical.

The risk

Under configurable multi-layer supervision (#183), if a subordinate executed with its supervisor's tools — or with the union of both — then supervision becomes a consent bypass: wire a low-trust agent beneath a high-trust one and it inherits reach it was never granted.

The platform has a real consent boundary today (write-consent gate, migration 0051; capabilities.tools allowlist gating via toolNamesFor). Multi-layer delegation is a new path that could route straight around it, and it would do so by configuration — no code change, no review.

The rule

A delegation carries the owner's authority plus a goal. It lends nothing else.

  • A subordinate executes with its own consent-gated tools and its own guardrails — never the supervisor's, never a union.
  • Both instances already belong to the same owner (as agent_connections enforces); that ownership is the authority, not the supervisor's tool grants.
  • A supervisor cannot widen a subordinate's reach by delegating to it, and cannot borrow a subordinate's reach by delegating through it.

This is the same boundary #142 draws for creators declaring runtime/workflow capabilities.

Scope

  • Explicit non-inheritance at the delegation boundary: the subordinate's tool resolution ignores the supervisor entirely.
  • Consent checks evaluate against the executing instance, not the originating one.
  • Tests as the contract: a supervisor with a write-consented connector delegating to a subordinate without it must not enable the write.
  • Audit: the trace records which instance's authority each tool call ran under.

Acceptance

  • A subordinate cannot perform an action it lacks consent for, regardless of its supervisor's grants.
  • A supervisor cannot reach a connector it lacks consent for by delegating to a subordinate that has it.
  • Trace attributes every tool call to the instance whose authority ran it.
  • Tests cover both escalation directions (down-lending and up-borrowing).

Activity

  1. added
    backendBackend / Worker / API work
    securitySecurity hardening / audit finding
    coordinationAgent coordination / delegation primitives
    on Aug 3, 2026
  2. serge-ivo commented on Aug 3, 2026

    @serge-ivo
    ContributorAuthor

    Landed in 957ed33.

    Shipped — lib/execution-authority.ts states the rule once, as a named function, so it has a single reviewable/testable home instead of a decision re-made at each future call site:

    consentInstanceOf(authority) // === authority.instanceId, never onBehalfOf

    RegistryToolCtx gains onBehalfOf, documented as audit-only. Both the write-consent gate and connector token-minting now resolve through consentInstanceOf() — the second one matters: instance-resource grants must not follow the asker either, or up-borrowing would work where the consent bypass does not.

    Tested at the gate, not only in the pure module, per the acceptance criteria — the tests assert which instance id the consent lookup was actually performed against:

    • down-lending: a consented supervisor cannot enable a write for an unconsented subordinate
    • up-borrowing: a supervisor gains nothing from a consented subordinate
    • containment is not a blanket denial: the subordinate's OWN consent still works

    Verified by mutation. Changing consentInstanceOf to onBehalfOf ?? instanceId — the exact one-line bypass this exists to prevent — fails 6 tests across both files. The invariant is pinned, not documented.

    Encoded before the path that could violate it exists, which is why #156 still refuses instance targets at dispatch. Remaining: trace attribution of each tool call to its authority (describeAuthority is written and unit-tested but not yet emitted into the trace).

  3. serge-ivo commented on Aug 4, 2026

    @serge-ivo
    ContributorAuthor

    Trace attribution landed in ddd5e0c — the last open item here.

    describeAuthority() was written and unit-tested but never reached the trace, so "whose authority ran this?" was answerable in principle and not in practice. Now emitted from runRegistryTool.

    Scoped to delegated calls only. For ordinary work the authority is trivially the instance already on the event, so logging every tool call would be noise at real cost; when a supervisor asked, "who asked" and "whose permissions applied" are genuinely different questions and both belong in the record.

    Everything on this ticket is now done: the invariant, both enforcement points (consent gate and token minting), the mutation-verified tests, and the audit trail.

  4. serge-ivo commented on Aug 4, 2026

    @serge-ivo
    ContributorAuthor

    Done in 957ed33 (invariant + consent gate + token minting, mutation-verified) and ddd5e0c (trace attribution for delegated calls). Confirmed in production: delegate_goal was refused until the owner granted supervision write consent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    backendBackend / Worker / API workcoordinationAgent coordination / delegation primitivessecuritySecurity hardening / audit finding

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions