Skip to content

Grant pods/attach only for stdio transport - #6841

Merged
JAORMX merged 2 commits into
stacklok:mainfrom
hellouz818:fix/6230-scope-pods-attach-to-stdio
Oct 16, 2026
Merged

JAORMX merged 2 commits into
stacklok:mainfrom
hellouz818:fix/6230-scope-pods-attach-to-stdio

Conversation

@hellouz818

@hellouz818 hellouz818 commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The operator currently grants pods/attach (create, get) to every MCPServer proxy-runner Role. Only stdio transport needs this permission; streamable-http and sse communicate with the backend over the network. Unnecessary attach permissions can trigger security audit findings, as reported with Polaris.

This change separates the attach rule from the shared RBAC rules and includes it only for stdio transport. An empty transport is treated as stdio, consistent with the CRD default.

Closes #6230

Type of change

  • Bug fix
  • New feature
  • Refactoring (no behavior change)
  • Dependency update
  • Documentation
  • Other (describe):

Test plan

  • Unit tests (task test)
  • E2E tests (task test-e2e)
  • Linting (task lint)
  • Manual testing (describe below)

Also ran task docs-verify, task operator-manifests, and task operator-generate; all completed without generated artifact changes.

New coverage:

  • TestEnsureRBACResources_Role_PodsAttachScopedToStdio checks Role creation for stdio, streamable-http, sse, and an empty transport, verifying the expected attach rule and preservation of shared rules.
  • TestRBACRulesForTransport_ReturnsFreshSlice checks that replacing an element in a returned slice does not affect subsequent calls.

Restoring unconditional attach permissions causes both new tests to fail, while the pre-existing RBAC tests still pass.

The new tests cover Role creation and slice isolation. They do not directly test transport changes on an existing Role.

API Compatibility

  • This PR does not break the v1beta1 API, OR the api-break-allowed label is applied and the migration guidance is described above.

No CRD schema changes; this changes reconcile-time RBAC construction only.

Does this introduce a user-facing change?

Yes, for operators.

Before: Every MCPServer proxy-runner Role grants pods/attach (create, get).

After: The rule is included only when spec.transport is stdio or empty. Shared permissions remain unchanged for all transports.

Existing Roles are updated on the next reconcile: the RBAC helper uses CreateOrUpdate to replace the Role's rules with the desired set. HTTP/SSE servers therefore lose the attach permission without requiring a workload restart. Changing the transport to stdio restores it on reconciliation.

Special notes for reviewers

  • The operator's own +kubebuilder:rbac marker for pods/attach remains unchanged because the operator still needs permission to grant it to stdio servers.
  • Empty transport retains the attach rule as defensive handling consistent with the CRD's stdio default.
  • slices.Clone gives each call its own outer slice, so replacing or appending rules does not modify the shared rule slice. Nested PolicyRule fields remain shared and are treated as immutable.
  • Thanks to @lucianstancu for the report and for handing the fix over.

The proxy-runner Role created for every MCPServer carried
pods/attach (create, get) regardless of transport. Only stdio
uses it: the runner reaches a stdio MCP server by attaching to
its pod through Client.AttachToWorkload, which is called only
from pkg/transport/stdio.go. A streamable-http or sse server
talks to its pod over the network and never attaches.

Security auditors flag pods/attach as equivalent to pod exec,
so clusters with strict policies saw a finding on every
MCPServer deployment for a permission that could not be used.

Split the rule out of defaultRBACRules into stdioRBACRules and
choose between them in rbacRulesForTransport. An absent
transport is treated as stdio, matching the CRD default, so an
object stored before that default existed keeps working.

Already-deployed servers are corrected without operator action:
EnsureRBACResources replaces Rules wholesale, so the next
reconcile rewrites the Role.

Closes stacklok#6230

Signed-off-by: hellouz818 <hellouz818@gmail.com>
@codecov

codecov Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.67%. Comparing base (87ed79d) to head (a62fa9d).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6841      +/-   ##
==========================================
- Coverage   79.69%   79.67%   -0.02%     
==========================================
  Files         817      817              
  Lines       83421    83425       +4     
==========================================
- Hits        66480    66467      -13     
- Misses      16936    16953      +17     
  Partials        5        5              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

The chainsaw operator E2E suites asserted one shared
proxyrunner-role.yaml for the stdio, sse, streamable-http and
stdio-streamable-http scenarios. That stopped holding once
pods/attach became transport-dependent: chainsaw matches the
rules array positionally and on length, so the stdio scenarios
failed on the rule having moved to the end, and the HTTP ones
would fail on it being absent.

Replace the shared file with proxyrunner-role-stdio.yaml (six
rules, pods/attach last) and proxyrunner-role-http.yaml (the
five shared rules), and point each scenario at the one matching
the transport in its mcpserver.yaml. Both files still spell out
the complete expected Role, so an over-granted rule fails the
assertion rather than slipping through.

Only the multi-tenancy run reached CI: chainsaw runs with
FailFast and both suites share one shell step, so the
single-tenancy scenarios never executed.

Refs stacklok#6230

Signed-off-by: hellouz818 <hellouz818@gmail.com>
@JAORMX
JAORMX merged commit 60bfed8 into stacklok:main Oct 16, 2026
43 checks passed
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.

Scope pods/attach Role rule to stdio transport only

2 participants