Skip to content

feat(connector-mcp): policy-gate declarative stdio transports — default-deny + host allowlist (ADR-0097 security follow-up) #3055

Description

@os-zhuang

Security follow-up to ADR-0097 / #3017 (#3049), and the precondition for shipping connector-mcp in any default preset (see the companion default-preset issue).

Threat model

A declarative provider: 'mcp' instance may name a stdio transport:

connectors: [{
  name: 'tools',
  provider: 'mcp',
  providerConfig: { transport: { kind: 'stdio', command: 'bash', args: ['-c', '…'] } },
}]

Materialization spawns that command as a local child process (StdioClientTransport). Declarative entries arrive through metadata — including a Studio publish at runtime, which triggers the metadata:reloaded reconcile and materializes new instances on a live server (soft mode skips failures, not successes). Net: anyone who can publish metadata can execute arbitrary commands on the server.

Today the accidental barrier is that the host must consciously install @objectstack/connector-mcp. If the plugin becomes part of the default preset — which the ADR-0097 "expressible and executable as pure metadata" promise wants — that barrier disappears. The gate must become explicit policy instead.

Ask

  • createMcpProviderFactory gains a declarativeStdio policy, default deny:
    • undefined / false → a declarative stdio transport is rejected;
    • string[] → allowlist, matched against transport.command by strict equality;
    • true → allow any command (explicit full trust).
  • ConnectorMcpPlugin plumbs the option through: new ConnectorMcpPlugin({ declarativeStdio: ['npx'] }).
  • A policy violation is a configuration fault — plain throw (fatal at boot, skipped+logged on reload), with an actionable message telling the host exactly how to opt in. It must NOT be classified CONNECTOR_UPSTREAM_UNAVAILABLE (feat(connectors): degrade + retry declarative instances on unreachable upstream (#3017) #3049): a security rejection is not retryable-into-existence.
  • http transports stay unaffected (outbound HTTP is the same trust class as the http node / rest connector).
  • Hand-wired paths stay unaffected (createMcpConnector / plugin instance options): host code wrote that command — a different trust anchor than metadata.
  • Docs: ADR-0097 addendum (+ ADR-0024 §4 cross-ref). Be honest that a command allowlist is a coarse boundary (allowlisting npx ≈ allowing any package); real sandboxing / multi-tenant isolation remains the enterprise tier per ADR-0024 §4.
  • Tests: default-deny (plain throw, not unavailable-coded), allowlist hit/miss, true passthrough, http unaffected, hand-wired path unaffected.

Refs

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions