Skip to content

fix(http): make server lockdown mode an upper bound over requests - #3112

Merged
SamMorrowDrums merged 4 commits into
mainfrom
sammorrowdrums-lockdown-mode-upper-bound-http
Aug 19, 2026
Merged

fix(http): make server lockdown mode an upper bound over requests#3112
SamMorrowDrums merged 4 commits into
mainfrom
sammorrowdrums-lockdown-mode-upper-bound-http

Conversation

@SamMorrowDrums

Copy link
Copy Markdown
Collaborator

Summary

In HTTP mode, lockdown mode was only active when both the server operator's --lockdown-mode / GITHUB_LOCKDOWN_MODE configuration and the per-request X-MCP-Lockdown header enabled it (AND logic). This let any request that simply omitted the header silently disable lockdown mode, even when the operator had explicitly enabled it server-wide.

This PR changes the combination to OR logic: serverEnabled || requestEnabled. Server configuration is now an upper bound — the operator's setting can never be relaxed by a request — while a request may still opt itself into lockdown mode when the operator has not already enabled it. This mirrors the existing upper-bound pattern already used for read-only mode in HTTP mode (pkg/http/handler.go: "Static read-only is an upper bound — enforce before request filters").

RequestDeps.GetRepoAccessCache's cache-construction condition was also aligned with the same effective-lockdown check, so request-only lockdown mode gets a working cache instead of failing closed with a "lockdown cache is not configured" error.

Lockdown mode remains a best-effort content filter, not a security boundary — this change only fixes the activation/precedence semantics, not the filtering guarantees.

Changes

  • pkg/github/dependencies.go: added RequestDeps.effectiveLockdownMode(ctx) helper (d.lockdownMode || ghcontext.IsLockdownMode(ctx)), used by both GetFlags and GetRepoAccessCache.
  • pkg/github/dependencies_test.go: added focused tests for the four configuration combinations (neither / server-only / request-only / both) plus a dedicated regression test for the reported bug (server-enabled lockdown surviving a request that omits the header).
  • Docs (README.md, docs/server-configuration.md, docs/remote-server.md): documented the new upper-bound semantics and reiterated that lockdown mode is a best-effort content filter, not a security boundary.

Testing

  • script/lint — 0 issues
  • script/test — all packages pass
  • New tests: TestRequestDepsLockdownModeIsUpperBound (table-driven over all 4 combos) and TestRequestDepsLockdownModeCannotBeDisabledByOmittingHeader (direct regression test)

Fixes #3104

In HTTP mode, RequestDeps.GetFlags previously combined the server
operator's --lockdown-mode / GITHUB_LOCKDOWN_MODE configuration with
the per-request X-MCP-Lockdown header using AND logic. This meant a
request that omitted the header silently disabled lockdown mode even
when the operator had explicitly enabled it server-wide.

Change the combination to OR (server-enabled OR request-enabled), so
server configuration is an upper bound: the operator's setting can
never be relaxed by a request, while a request may still opt itself
into lockdown mode when the operator has not already enabled it. This
mirrors the existing upper-bound pattern already used for read-only
mode in HTTP mode. Also align GetRepoAccessCache's cache-construction
condition with the same effective-lockdown check so request-only
lockdown mode has a working cache instead of failing closed with a
"lockdown cache is not configured" error.

Add focused tests covering server-only, request-only, both, and
neither lockdown configurations, plus a regression test for the
specific bug (server-enabled lockdown surviving a request that omits
the header). Update README and the server-configuration/remote-server
docs to describe the upper-bound semantics and reiterate that lockdown
mode is a best-effort content filter, not a security boundary.

Fixes #3104

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@SamMorrowDrums
SamMorrowDrums requested a review from a team as a code owner August 19, 2026 12:21
Copilot AI balanced review requested due to automatic review settings August 19, 2026 12:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes HTTP lockdown precedence so requests cannot relax server-enforced lockdown.

Changes:

  • Uses effective lockdown (server || request) for flags and cache creation.
  • Adds coverage for all configuration combinations and the regression.
  • Documents precedence and best-effort filtering semantics.
Show a summary per file
File Description
README.md Documents lockdown precedence and limitations.
pkg/github/dependencies.go Centralizes effective lockdown logic.
pkg/github/dependencies_test.go Tests precedence and cache creation.
docs/server-configuration.md Explains HTTP configuration semantics.
docs/remote-server.md Clarifies header behavior.

Review details

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 5/5 changed files
  • Comments generated: 0
  • Review effort level: Balanced

SamMorrowDrums and others added 3 commits August 19, 2026 14:52
…ests

httptest.Server.Close closes the idle connections of the process-global
http.DefaultTransport, whichever server is being shut down. Every test in
this package runs in parallel, issues a live request through
http.DefaultTransport and shuts down its own server, so one test's cleanup
could break another test's in-flight request with:

  net/http: HTTP/1.x transport connection broken: http: CloseIdleConnections called

That flake failed build (ubuntu-latest) on this branch, and reproduces on
unmodified main under `go test ./pkg/http/transport -race -count=800`,
hitting both bearer_test.go and graphql_features_test.go. It is unrelated
to the lockdown change in this PR.

Give each test its own http.Transport so the global side effect cannot
reach it. TestGraphQLFeaturesTransport_NilTransport asserts the documented
nil to http.DefaultTransport fallback, so it keeps using that global and
now runs serially instead.

Assertions and coverage are unchanged. The same stress run that reproduced
the failure now passes 2500 iterations under -race.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Brings in 769340d, which added a paragraph describing lockdown mode as a
best-effort content filter rather than an authorization boundary.

Git merged the docs cleanly but the result was semantically redundant: this
branch had appended "Lockdown mode is a best-effort content filter, not a
security boundary." to the paragraph immediately preceding the new one, so
the same point was made twice in a row in both README.md and
docs/server-configuration.md. Dropped the appended sentence in those two
places; the incoming paragraph states it more fully. The composability note
in docs/server-configuration.md keeps its mention, as it has no adjacent
duplicate.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Condense multi-line narration into short, focused comments. Keep
only the non-obvious invariants (lockdown precedence, isolated
transport rationale) and drop restated code/step-by-step prose.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@SamMorrowDrums
SamMorrowDrums merged commit 3bad3bc into main Aug 19, 2026
19 checks passed
@SamMorrowDrums
SamMorrowDrums deleted the sammorrowdrums-lockdown-mode-upper-bound-http branch August 19, 2026 13:20
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.

Make server lockdown configuration an upper bound in HTTP mode

2 participants