Skip to content

[Security] Enforce least-privilege connector permissions and action approval #231

Description

@fettpl

Audit metadata

  • Priority: P1
  • Estimated effort: L
  • Implementation risk: HIGH
  • Category: security
  • Evidence baseline: 07be5be7ce69bea0c3118744ab90d148b010fce0 (origin/main on 2026-07-17)

Dependencies

None.

Description and impact

Adding a catalog connector enables its entire MCP tool surface for autonomous agent use. That can include issue/PR mutations, social posting, remote commands, and filesystem writes. Neither installation nor execution currently creates an enforceable least-privilege grant or a user confirmation boundary.

Current state

  • frontend/src/features/settings/connectors-section.tsx:27-59 catalogs GitHub, X posting, and remote shell/filesystem connectors.
  • frontend/src/features/settings/connectors-section.tsx:175-207 installs and enables connectors without allowTools.
  • services/agent-runtime/src/connector-pool.ts:40-49 interprets absent allowTools as “allow everything.”
  • frontend/src/app/api/agent/connectors/call/route.ts:44-64 invokes any allowed tool immediately.
  • frontend/desktop/resources/pi-extensions/connectors.ts:93-126 registers every inventory tool with a direct executor and no approval exchange.
  • services/agent-runtime/src/connector-contract.ts:18-57 has an optional allowlist but no risk class, approval mode, or deny-by-default representation.
  • MCP annotations originate with the connector process and cannot be treated as trusted policy.

Issue specification

Description

New connectors expose every advertised tool to the agent by default. Mutating and high-impact actions execute without a connector-specific permission grant or per-action user approval.

Steps to reproduce

  1. Install any catalog connector and leave its generated configuration unchanged.
  2. Inspect the connector inventory; every advertised tool is registered.
  3. Invoke a mutating tool, such as a repository mutation, social post, remote command, or file write.
  4. Observe execution without a scoped connector approval.

Solution design

Make absence of a permission grant deny all tools. Before enablement, show the discovered tool inventory and require an explicit allowlist. Maintain a first-party risk policy for catalog tools; unknown tools default high risk, regardless of MCP annotations. Read-only grants may run automatically. Mutating/high-impact calls require a one-use, short-lived approval scoped to session, connector, tool, and a keyed digest over the full canonical Effect Schema-decoded argument object. Generate a random in-memory HMAC key at broker startup and compute a domain-separated HMAC-SHA-256 over version, session, connector, tool, and deterministic canonical JSON arguments. Redaction/summarization happens only after digesting and only for UI/audit presentation; it must never alter the authorization input. Record bounded metadata for approved/denied actions without secrets or raw arguments.

Acceptance criteria

  • A connector with no explicit tool grant exposes and executes no tools.
  • New catalog connectors default to a reviewed read-only/minimal grant.
  • Users see exact tool permissions and risk tiers before enabling.
  • Mutating tools cannot execute without a current, scoped, one-use approval.
  • Changing any argument, including a credential or field hidden/redacted in the UI, invalidates the approval.
  • Approval digests are keyed, cover the complete canonical decoded argument tree, and are never derived from a redacted/sanitized copy.
  • Remote command/filesystem tools receive the highest confirmation tier.
  • Unknown/newly advertised tools remain denied until reviewed.
  • Approval records never persist credentials or raw sensitive arguments.

Related issues

#200 covers Windows MCP process launch/cleanup. audit proposal 038 covers the deprecated GitHub server artifact. audit proposal 039 covers plugin executable trust. None duplicates this authorization boundary.

Verification commands

Purpose Command Expected on success
Service tests bun test tests/frontend/agent-runtime/connectors-service.test.ts tests/frontend/regression/connector-approval.test.ts all permission and approval cases pass
Frontend test npm --prefix frontend run test all script tests pass
Frontend quality npm --prefix frontend run check:quality exit 0
Full repository npm run check exit 0
Integration npm run test:integration all tests pass

Scope

In scope:

  • services/agent-runtime/src/connector-contract.ts
  • services/agent-runtime/src/connectors-service.ts
  • services/agent-runtime/src/connector-pool.ts
  • services/agent-runtime/src/connector-approval.ts (create)
  • services/agent-runtime/src/pi-runtime-helpers.ts
  • frontend/src/app/api/agent/connectors/**
  • frontend/src/features/settings/connectors-section.tsx
  • Connector approval UI/store files under frontend/src/features/agent/
  • frontend/desktop/resources/pi-extensions/connectors.ts
  • tests/frontend/agent-runtime/connectors-service.test.ts
  • tests/frontend/regression/connector-approval.test.ts (create)

Out of scope:

  • Sandboxing arbitrary connector executables.
  • Redesigning connector credential storage; audit proposal 050 owns explicit secret fields.
  • Migrating the GitHub package; audit proposal 038 owns that artifact change.
  • Treating MCP readOnlyHint as authoritative.

Implementation plan

Step 1: Make permission grants explicit and deny by default

Replace optional allow-all semantics with an explicit permission model decoded by Effect Schema. Migrate existing connectors into a disabled, review-required state unless they already have an explicit allowlist. Inventory may show denied tools for review, but connector-pool must reject their execution server-side.

Verify: service tests → omitted grant, empty grant, unknown tool, changed inventory, and explicit read grant all behave deterministically.

Step 2: Add first-party risk classification and grant UI

Define risk tiers for first-party catalog entries and default all unknown tools to high risk. In Settings, inventory tools before enablement and require a user-selected grant. Use existing UI-kit controls and Schema-decoded API responses; add no useEffect.

Verify: frontend tests → install cannot enable until a grant is saved; newly discovered tools stay denied.

Step 3: Add a scoped approval broker

Implement an in-memory, bounded approval broker with pending, approved, denied, expired, consumed, and cancelled states. Decode the complete call argument tree through the Effect Schema-owned call boundary, rejecting non-JSON/unsupported values before approval. Canonicalize the decoded value deterministically by recursively sorting object keys while preserving array order, JSON scalar types, null, and every field; do not omit or replace secret-looking keys. Bind each request with a domain-separated HMAC-SHA-256 over the canonical arguments plus session, connector, and tool using a random per-process key held only by the broker. On execution, recompute and timing-safely compare that digest before consuming the approval. Only after this authorization value is created may a separate redactor produce a UI preview or audit metadata. The call route must independently enforce approval before invoking mutating/high-risk tools. Use Effect for timeouts and cancellation.

Verify: regression tests → missing, wrong-session, wrong-args, expired, reused, and denied approvals all fail; arguments differing only in a redacted credential, nested field, array order, null/omission, or object insertion order behave correctly; one semantically identical canonical call succeeds once.

Step 4: Connect the Pi extension and UI

Pass an explicit agent session ID into the connector extension. On a protected tool call, create a pending request from the full decoded arguments, then display a separately redacted blocking approval in the agent UI and continue only after approval. Ensure abort/session close cancels pending requests. Log only connector/tool/risk/outcome/timestamps; never reuse the UI/audit projection for digest comparison.

Verify: integration fixture → read tool runs under grant; mutating tool waits; denial stops it; approval executes once; disconnect cancels.

Step 5: Run canonical gates and desktop verification

Run all listed gates. Rebuild/reinstall per AGENTS.md and smoke-test a read-only and a denied mutating connector action; confirm desktop health is 200.

Verify: all commands exit 0 and the packaged approval flow behaves as tested.

Test plan

  • Extend connector service tests for migration, allowlists, inventory drift, and deny-by-default behavior.
  • Add pure approval-state tests using virtual time.
  • Add route tests proving server-side enforcement cannot be bypassed by calling the endpoint directly.
  • Add canonicalization/HMAC tests for nested objects, insertion-order equivalence, array-order sensitivity, null versus omission, type differences, and fields normally removed or masked by UI redaction.
  • Prove two calls differing only in a synthetic credential value produce different authorization digests while neither value appears in UI/audit output.
  • Add an end-to-end fake MCP server with one read and one mutation tool.
  • Assert audit output contains no supplied argument values or credentials.

Acceptance criteria

  • if (!connector.allowTools) return tools no longer exists.
  • Every enabled connector has an explicit grant.
  • Mutating/high-risk calls require a one-use approval enforced at the server route.
  • Approval matching uses a timing-safe keyed digest over the full canonical Schema-decoded arguments plus session/connector/tool scope.
  • Redaction is used only for UI/audit output and cannot make distinct calls share approval.
  • Unknown tools default denied/high-risk.
  • Service, frontend, quality, repository, and integration gates pass.
  • Installed desktop health returns 200.

Risks and stop conditions

  • The Pi SDK provides a standard approval primitive that can meet all server-side scoping requirements; stop and redesign around it instead of creating a parallel UX.
  • Tool risk cannot be classified without a maintainer-approved policy for the first-party catalog.
  • Existing connectors cannot be migrated without silently retaining broad write access.
  • Approval enforcement would live only in the renderer or extension and could be bypassed through the HTTP route.
  • A supported argument value cannot be represented losslessly by the canonical Schema-decoded JSON contract; stop and define its wire representation before approving it.
  • The HMAC key would need persistence or exposure to the renderer; keep approvals process-local and redesign the lifecycle instead.

Maintenance considerations

Approval is defense in depth, not a substitute for narrowly scoped provider credentials. Reviewers should verify deny-by-default behavior at the server boundary, full-argument keyed digest coverage before redaction, timing-safe comparison, and that connector-controlled descriptions/annotations never decide authorization.


Generated from Local Studio 2.0 main-branch audit proposal 021.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions