Skip to content

Guard MCP prompt retrieval with DIFC - #14934

Merged
lpcox merged 1 commit into
mainfrom
lpcox-guard-mcp-prompts
Oct 15, 2026
Merged

lpcox merged 1 commit into
mainfrom
lpcox-guard-mcp-prompts

Conversation

@lpcox

@lpcox lpcox commented Oct 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes the private advisory GHSA-4m9j-r78h-2grr by removing the direct prompts/get backend-forwarding path. Prompt retrieval now passes through session initialization, authorization, allowlisting, and the shared DIFC reference-monitor phases before a result reaches the client.

Advisory condition mapping

Advisory condition Remediation Regression coverage
prompts/get skipped session initialization The registered prompt handler calls requireSession before dispatch TestPromptHandlerRunsSessionAndDIFCPipeline verifies the session is created
No prompt allowlist Adds per-server prompts configuration, filters prompts/list, and rejects non-allowed runtime dispatch TestRegisterPromptsFromBackend_RegistersPrompts; TestCallBackendPromptRejectsPromptOutsideAllowlist
No caller authorization Enforces configured per-agent server authorization before backend execution TestCallBackendPromptEnforcesAgentServerAuthorization
Guard session / LabelAgent never initialized callBackendPrompt resolves the session guard and calls ensureGuardInitialized TestPromptHandlerRunsSessionAndDIFCPipeline asserts LabelAgent invocation
No resource labeling or coarse DIFC decision Runs RunPipelinePrePhases before backend execution and returns canonical DIFC denial errors TestCallBackendPromptCoarseDenyPreventsBackendExecution verifies the backend is not called
No response labeling or filtering Runs RunPipelinePhase4 and FilterAndConvertLabeledData; prompt collection labels apply to the messages array TestCallBackendPromptFiltersMessagesWithoutDisclosingDeniedContent verifies inaccessible prompt content is absent
Nil response labels could disclose a coarse-denied read Falls back to the coarse decision when LabelResponse returns nil Shared pipeline behavior is exercised by prompt mediation and existing coarse fallback tests
Propagate semantics skipped Runs RunPipelinePhase6 after successful prompt filtering TestCallBackendPromptPropagateAccumulatesLabels verifies agent secrecy accumulation
Allowed prompt behavior must remain compatible Filtered prompt results are converted back to sdk.GetPromptResult and backend errors remain wrapped TestPromptHandlerRunsSessionAndDIFCPipeline; existing prompt invocation/error tests

Prompt names are passed to the guard as the operation name. Custom guards can therefore add prompt-specific resource and response labeling; guards without prompt-specific rules retain their normal default labeling behavior.

Documentation

  • Documents the prompts allowlist in JSON/TOML configuration.
  • Documents prompt response-labeling semantics.
  • Adds an unreleased security note.

Validation

  • Focused prompt/config regression suites
  • make agent-finished (format, build, lint, full Go tests, 676 Rust guard tests)

Route prompts/get through session initialization, prompt allowlisting, agent authorization, and the shared DIFC reference-monitor phases.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: dfbccdd1-a318-40cd-9460-9709bcdb1f53
Copilot AI max review requested due to automatic review settings October 15, 2026 05:00
@github-actions

Copy link
Copy Markdown
Contributor

🔒 mcpg Read-Only Stress — default

Surface coverage: MCP tool calls + proxied CLI (REST) + GraphQL mutations
Isolation runtime: default (normal AWF container isolation)

Part Surface Op Result Expected Status
A MCP reads (list_issues, list_pull_requests, get_file_contents, list_commits) data returned ALLOWED ✅
B MCP writes (star_repository, issue_write, add_issue_comment) all 3 absent from tool catalog ("unknown tool"); no branch/file/PR tools exposed either BLOCKED ⚠️
C CLI reads (same 4 ops via github CLI proxy) data returned ALLOWED ✅
D CLI REST writes (reaction/star/issue/comment/file) gh unauthenticated (no GH_TOKEN); all 6 refused pre-flight BLOCKED ⚠️
E CLI GraphQL mutations (addReaction/addStar/createIssue) gh unauthenticated; all 3 refused pre-flight BLOCKED ⚠️

Overall: INCONCLUSIVE

No write leaked in any part (0 reactions, stars, issues, comments, branches, files, or PRs created). However:

  • Part B: all targeted write tools (star_repository, issue_write, add_issue_comment, plus create_branch/create_or_update_file/create_pull_request) were absent from the exposed 23-tool catalog entirely — this confirms backend/toolset read-only configuration (per gh-aw's GITHUB_READ_ONLY=1 for tools.github:), not independently mcpg's own DIFC/guard write-blocking layer, per this test's own methodology note.
  • Parts D/E: gh had no GH_TOKEN in this environment, so writes failed at the authentication step rather than exercising the token-scope/gateway boundary.

No security gap found; the inconclusive rows reflect methodology limits of this probe on this runtime, not evidence of a weakened guarantee.

🔒 mcpg read-only stress (default AWF runtime) by Read-Only Stress: default runtime

@lpcox
lpcox merged commit 171e642 into main Oct 15, 2026
39 checks passed
@lpcox
lpcox deleted the lpcox-guard-mcp-prompts branch October 15, 2026 05:11

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.

🟡 Changes recommended

Unresolved security and compatibility issues remain in the prompt response pipeline.

5 open findings
What changed in this PR

Adds DIFC mediation to MCP prompt retrieval, bringing prompts into the gateway’s authorization and response-filtering pipeline.

Changes:

  • Adds session checks, authorization, prompt allowlists, and DIFC processing.
  • Adds prompt-handling and configuration regression tests.
  • Documents prompt configuration, labeling semantics, and the security change.
File Description
internal/​server/​unified.go Initializes prompt allowlist state.
internal/​server/​tool_registry.go Filters discovery and registers guarded handlers.
internal/​server/​tool_registry_helpers.go Builds per-server prompt allowlists.
internal/​server/​register_prompts_test.go Tests discovery filtering and handler behavior.
internal/​server/​prompt_call.go Implements mediated prompt retrieval.
internal/​server/​prompt_call_test.go Tests authorization, filtering, and propagation.
internal/​config/​config_stdin.go Adds JSON prompt allowlist parsing and conversion.
internal/​config/​config_stdin_test.go Tests HTTP and stdio allowlist conversion.
internal/​config/​config_core.go Adds prompt allowlists to server configuration.
docs/​RELEASE_NOTES.md Records the prompt mediation security change.
docs/​GUARD_RESPONSE_LABELING.md Describes prompt response-labeling semantics.
docs/​CONFIGURATION.md Documents the prompt allowlist option.
config.example.toml Adds commented prompt allowlist examples.

🧠 Review effort: Max


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

if err != nil {
return nil, fmt.Errorf("response labeling failed: %w", err)
}
filterResult, err := difc.FilterAndConvertLabeledData(
finalResult := backendResult
if labeledData != nil {
finalResult = filterResult.FinalResult
if _, ok := labeledData.(*difc.CollectionLabeledData); ok {
Comment on lines +138 to +141
filteredMap := make(map[string]interface{}, len(resultMap))
for key, value := range resultMap {
filteredMap[key] = value
}
Tools []string `json:"tools,omitempty"`

// Prompts is an optional list of prompts to filter/expose
Prompts []string `json:"prompts,omitempty"`
Comment on lines +119 to +121
if filterResult.Blocked {
return nil, fmt.Errorf("DIFC policy violation: prompt response contains data that is not accessible to this agent")
}
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.

2 participants