Skip to content

feat(mcp-gateway): introduce ordered 5-stage request filter pipeline#299

Merged
Agent-Hellboy merged 5 commits into
mainfrom
mcp-gateway/filter_pipeline
Jun 12, 2026
Merged

feat(mcp-gateway): introduce ordered 5-stage request filter pipeline#299
Agent-Hellboy merged 5 commits into
mainfrom
mcp-gateway/filter_pipeline

Conversation

@Agent-Hellboy

Copy link
Copy Markdown
Owner

Replaces the monolithic handleGateway with a Filter/Exchange/Result pipeline (inspect → policy → auth → authz → upstream). Stage 6 audit runs unconditionally from the orchestrator, centralising all emit paths. errPolicyUnavailable sentinel added so authzFilter fails closed with 503 when no valid policy has been loaded.

Adds per-filter unit tests and ordering proofs in pipeline_test.go: Continue/Reject/Respond per stage, auth-before-authz ordering assertion, authorization-input immutability test, and 503 on policy unavailable.

Replaces the monolithic handleGateway with a Filter/Exchange/Result
pipeline (inspect → policy → auth → authz → upstream). Stage 6 audit
runs unconditionally from the orchestrator, centralising all emit
paths. errPolicyUnavailable sentinel added so authzFilter fails closed
with 503 when no valid policy has been loaded.

Adds per-filter unit tests and ordering proofs in pipeline_test.go:
Continue/Reject/Respond per stage, auth-before-authz ordering
assertion, authorization-input immutability test, and 503 on policy
unavailable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request refactors the monolithic handleGateway function into a modular, six-stage request filter pipeline, introducing an Exchange struct to manage request-scoped state and separate files for each filter stage. It also adds comprehensive unit tests to verify pipeline execution and ordering guarantees. Feedback on these changes highlights a high-severity routing inconsistency in upstreamFilter where RawPath is left untrimmed if prefix trimming fails on it but succeeds on Path. Additionally, it is recommended to remove the redundant context.Context parameter from Filter.Handle since the context can be retrieved directly from the request inside the Exchange struct.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +19 to +21
if trimmedRaw, rawOK := trimRequestPathPrefix(ex.R.URL.RawPath, s.stripPrefix); rawOK {
ex.R.URL.RawPath = trimmedRaw
}

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.

security-high high

High Severity: Inconsistent URL state when RawPath trimming fails\n\nIf trimRequestPathPrefix succeeds on Path but fails on RawPath (e.g., due to different escaping or encoding), ex.R.URL.RawPath is left completely untrimmed (retaining the prefix). This creates an inconsistent URL state where Path is stripped of the prefix but RawPath is not.\n\nWhile Go's URL.EscapedPath() may fall back to escaping Path when they are inconsistent, any upstream proxies, routers, or HTTP clients that read URL.RawPath directly will see the untrimmed path (with the prefix still present). This can lead to subtle routing bugs or security bypasses.\n\nTo prevent this, always clear RawPath if trimming fails so that it defaults to a consistent state.

\t\tif trimmedRaw, rawOK := trimRequestPathPrefix(ex.R.URL.RawPath, s.stripPrefix); rawOK {\n\t\t\tex.R.URL.RawPath = trimmedRaw\n\t\t} else {\n\t\t\tex.R.URL.RawPath = ""\n\t\t}

Comment on lines +50 to +53
// Filter is a single stage in the gateway request pipeline.
type Filter interface {
Handle(context.Context, *Exchange) Result
}

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.

medium

Style Guide Violation: Redundant context.Context parameter in Filter.Handle\n\nAccording to the official Go Code Review Comments style guide:\n> When a function or method takes a Request, it should not also take a Context parameter. Instead, it should use the Request's context.\n\nSince Filter.Handle takes *Exchange which contains ex.R *http.Request, passing a separate context.Context is redundant and can lead to out-of-sync contexts. Furthermore, all 5 filter implementations currently ignore this parameter entirely (_ context.Context).\n\nWe should remove the context.Context parameter from Filter.Handle and have the filters use ex.R.Context() directly.

Suggested change
// Filter is a single stage in the gateway request pipeline.
type Filter interface {
Handle(context.Context, *Exchange) Result
}
// Filter is a single stage in the gateway request pipeline.\ntype Filter interface {\n\tHandle(*Exchange) Result\n}
References
  1. When a function or method takes a Request, it should not also take a Context parameter. Instead, it should use the Request's context. (link)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 400b981c1b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if ex.Decision.PolicyVersion == "" {
ex.Decision.PolicyVersion = s.defaultPolicyVersion
}
s.emitAuditFromExchange(ex)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Skip auditing terminal metadata responses

When policyFilter returns Respond, the unconditional finalization here still audits any request whose body inspection found an RPC method. An unauthenticated client can therefore POST a valid tools/call body to /.well-known/oauth-protected-resource; the metadata handler returns 405 (or 404 in header mode), but the gateway emits an audit event with decision=allow and attacker-controlled RPC/tool fields because authentication and authorization never ran. The previous handler returned before audit emission for this path, so terminal non-upstream responses need to be marked or excluded from emitAuditFromExchange.

Useful? React with 👍 / 👎.

Agent-Hellboy and others added 4 commits June 11, 2026 00:46
CVE-2026-42504 affects the Go 1.26.3 stdlib; upgrading the multi-stage
builder to 1.26.4 (latest stable patch) removes the unfixed CVE so the
Trivy operator-image scan no longer fails.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove redundant context.Context from Filter.Handle: all 5 filter
  implementations already ignored the parameter; filters that need the
  request context use ex.R.Context() directly (exchange.go, all filter
  files, proxy.go, pipeline_test.go)

- Clear RawPath when path-prefix trimming succeeds on Path but fails on
  RawPath; previously left an inconsistent URL where the percent-encoded
  form still carried the prefix, which upstream proxies reading RawPath
  directly could route incorrectly (filter_upstream.go)

- Skip audit for terminal pre-auth responses: policyFilter now sets
  Exchange.SkipAudit=true before returning Respond for the OAuth
  metadata early-exit path; emitAuditFromExchange checks the flag so
  no spurious allow-audit is emitted for requests that bypassed
  authentication entirely (exchange.go, filter_policy.go, proxy.go)

- Add TestPolicyFilterSetsSkipAuditForOAuthMetadataPath test case

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Merge origin/main into mcp-gateway/filter_pipeline, resolving proxy.go
conflict by wiring Prometheus request/policy metrics into the pipeline
orchestrator while preserving SkipAudit and filter-stage semantics.

Co-authored-by: Cursor <cursoragent@cursor.com>
Integrate origin/main policy snapshot behavior; unify errPolicyUnavailable
comment for gate filters and validated snapshot readiness.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Agent-Hellboy Agent-Hellboy merged commit a9f3658 into main Jun 12, 2026
5 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.

1 participant