Skip to content

feat(mcp): add secret allowlist to limit agent access#358

Merged
jdx merged 5 commits intomainfrom
feat/mcp-secret-allowlist
Mar 13, 2026
Merged

feat(mcp): add secret allowlist to limit agent access#358
jdx merged 5 commits intomainfrom
feat/mcp-secret-allowlist

Conversation

@jdx
Copy link
Copy Markdown
Owner

@jdx jdx commented Mar 13, 2026

Summary

  • Add mcp.secrets config option to restrict which secrets the MCP server exposes to AI agents
  • When set, only listed secrets are resolved and available via get_secret/exec; unlisted secrets are never resolved (no unnecessary auth prompts)
  • When omitted, all profile secrets are available (current behavior)
  • Warns at startup if allowlist contains names that don't match any configured secret

Example:

[mcp]
secrets = ["GITHUB_TOKEN", "NPM_TOKEN"]  # only these are available

Closes discussion #350

Test plan

  • Manual test: configure multiple secrets, set mcp.secrets to a subset, verify only those are accessible via get_secret
  • Manual test: verify exec only injects allowlisted secrets
  • Manual test: verify warning logged for unknown allowlist entries
  • Manual test: verify omitting mcp.secrets exposes all secrets (backward compat)

🤖 Generated with Claude Code


Note

Medium Risk
Changes how the MCP server selects and resolves secrets, which can affect agent workflows and provider authentication (especially when providers require dependent env secrets). Scope is contained to MCP config parsing/merging plus startup validation and docs/schema updates.

Overview
Adds an optional mcp.secrets allowlist to restrict which profile secrets the MCP server exposes, so get_secret and exec only resolve/inject the listed names (default remains all secrets when omitted).

On fnox mcp startup, it now logs warnings for empty allowlists, unknown secret names, and allowlisted secrets whose provider depends on other configured secrets not included in the allowlist. Config merging is updated so an overlay mcp.secrets replaces the base allowlist (to avoid accidentally re-exposing secrets), with unit tests added for filtering and merge behavior; docs and the public JSON schema are updated accordingly.

Written by Cursor Bugbot for commit 8f71f60. This will update automatically on new commits. Configure here.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant security enhancement by allowing users to define an explicit allowlist for secrets accessible by AI agents through the MCP server. This prevents unintended exposure of sensitive information and streamlines the agent's environment by only providing necessary credentials, thereby reducing unnecessary authentication prompts and improving overall system security posture.

Highlights

  • New Configuration Option: Added mcp.secrets to restrict which secrets the MCP server exposes to AI agents.
  • Secret Access Control: When mcp.secrets is set, only listed secrets are resolved and available via get_secret and exec; unlisted secrets are never resolved.
  • Backward Compatibility: If mcp.secrets is omitted, all profile secrets remain available, maintaining current behavior.
  • Configuration Validation: The system now warns at startup if the allowlist contains secret names that are not configured.
Changelog
  • docs/guide/mcp.md
    • Added a new section detailing the 'Secret allowlist' feature under MCP configuration.
    • Updated the description of the exec tool to reference the new mcp.secrets configuration for limiting access.
  • src/commands/mcp.rs
    • Modified the MCP command to retrieve all secrets and then apply the mcp.secrets allowlist.
    • Implemented a warning mechanism for mcp.secrets entries that do not correspond to any configured secret.
  • src/config.rs
    • Added a secrets field to the McpConfig struct to hold the allowlist of secret names.
    • Implemented the filter_secrets method within McpConfig to filter a map of secrets based on the allowlist.
    • Updated the Config::merge_from method to correctly merge the mcp.secrets configuration from overlays.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

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 introduces a valuable security feature by adding an allowlist for secrets exposed through the MCP server. The implementation is solid, correctly filtering secrets before they reach the server and adding helpful warnings for misconfigurations. I have one minor suggestion to improve the warning logic to avoid duplicate messages. Overall, this is a great addition.

Comment thread src/commands/mcp.rs
Comment on lines +25 to +31
for name in allowlist {
if !all_secrets.contains_key(name) {
tracing::warn!(
"mcp.secrets allowlist contains '{name}' which is not a configured secret"
);
}
}
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

The current implementation iterates through the allowlist directly. If the allowlist contains duplicate secret names that are not configured, it will produce a warning for each occurrence. To improve the user experience by avoiding duplicate warnings, consider checking each name only once. You can achieve this by converting the allowlist to a HashSet before iterating.

            let allowlist_set: std::collections::HashSet<_> =
                allowlist.iter().map(|s| s.as_str()).collect();
            for name in allowlist_set {
                if !all_secrets.contains_key(name) {
                    tracing::warn!(
                        "mcp.secrets allowlist contains '{name}' which is not a configured secret"
                    );
                }
            }

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Mar 13, 2026

Greptile Summary

This PR adds an optional mcp.secrets allowlist to McpConfig that restricts which profile secrets the MCP server resolves and exposes via get_secret/exec. When omitted, all profile secrets remain available (backward-compatible). The implementation is clean and the security boundary is enforced at a single, well-tested point (filter_secrets).

Key changes:

  • McpConfig.secrets: Option<Vec<String>> field with correct serde attributes (default, skip_serializing_if)
  • filter_secrets helper method that returns the map unchanged when secrets is None, or filters to the allowlist when set
  • Startup warnings for: empty allowlist, unknown allowlist entries, and allowlisted secrets whose provider depends on another fnox-managed secret that was excluded from the allowlist
  • Overlay merge replaces rather than appends the allowlist (documented inline), with a dedicated test verifying base is not silently re-exposed
  • FnoxMcpServer receives the pre-filtered profile_secrets, so both get_secret and exec naturally enforce the boundary without additional checks in the server
  • Schema JSON and documentation updated to match

All prior review concerns have been addressed: the empty-allowlist warning was added, the merge semantics are commented, unit tests cover the four filter_secrets variants plus both merge scenarios, and the cross-provider dependency warning loop was implemented.

Confidence Score: 4/5

  • Safe to merge — the allowlist is enforced at a single well-tested point and existing behavior is fully preserved when the field is omitted.
  • Implementation is correct end-to-end: filter_secrets is the sole enforcement gate and is exercised by both get_secret and exec through the pre-filtered profile_secrets. The backward-compatibility path (no allowlist) is trivially safe. Merge semantics for overlays are correctly replace-not-append and are tested. The only deduction is that config.get_providers(&profile) is cloned even when the allowlist is empty, which is a negligible startup-time inefficiency rather than a correctness issue.
  • No files require special attention; src/commands/mcp.rs has a minor startup-path inefficiency (providers map cloned for an empty allowlist) but no correctness issues.

Important Files Changed

Filename Overview
src/commands/mcp.rs Startup warning loop correctly identifies unknown allowlist entries, empty allowlists, and cross-secret provider dependency gaps; filter_secrets is applied to produce profile_secrets before MCP server construction. Minor: get_providers is cloned even when the allowlist is empty (inside the if allowlist.is_empty() branch, the for name in allowlist loop never runs, making providers unused).
src/config.rs New McpConfig.secrets field, filter_secrets helper, and overlay-merge logic are all correct. Replace-not-append merge semantics are now documented with an inline comment. Tests cover the four filter_secrets variants plus the two security-critical merge scenarios (replace and preserve-base).
docs/guide/mcp.md Documentation accurately describes the allowlist semantics for both get_secret and exec, plus the backward-compatible default (omit = all secrets visible).
docs/public/schema.json Schema correctly models secrets as a nullable array of string items with an accurate description matching the implementation.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[fnox mcp startup] --> B[get_secrets for profile]
    B --> C{mcp.secrets set?}
    C -- None --> D[all secrets available]
    C -- Some allowlist --> E{allowlist empty?}
    E -- Yes --> F[warn: no secrets will be available]
    E -- No --> G[warn: unknown entries in allowlist]
    G --> H[warn: missing provider dependency secrets]
    F --> I[filter_secrets: keep only allowlisted]
    H --> I
    D --> I
    I --> J[profile_secrets passed to FnoxMcpServer]
    J --> K{MCP tool called}
    K -- get_secret --> L{name in profile_secrets?}
    L -- No --> M[return not found]
    L -- Yes --> N[resolve and return value]
    K -- exec --> O[ensure_resolved: batch resolve env=true secrets]
    O --> P[inject only allowlisted env=true vars into subprocess]
Loading

Fix All in Claude Code

Last reviewed commit: 8f71f60

Comment thread src/commands/mcp.rs
Comment thread src/config.rs
Comment thread src/config.rs
Comment thread src/config.rs
Comment thread src/commands/mcp.rs
jdx and others added 5 commits March 13, 2026 15:16
Add `mcp.secrets` config option that restricts which secrets the MCP
server exposes. When set, only listed secrets are resolved and available
via get_secret/exec. Unlisted secrets are never resolved, avoiding
unnecessary auth prompts and reducing exposure to AI agents.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a warning when mcp.secrets is set to an empty list, since this
silently disables all secrets for the MCP server. Also add a comment
explaining why overlay replaces (not appends) the allowlist.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cover the security-critical filtering logic: no allowlist returns all
secrets, empty allowlist returns empty, subset filtering, and unknown
allowlist entries are silently ignored.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add tests verifying that overlay mcp.secrets replaces (not appends) the
base allowlist, and that omitting secrets in the overlay preserves the
base.

Also warn at startup when an allowlisted secret's provider depends on
another fnox-managed secret that is excluded from the allowlist (e.g.,
OP_SERVICE_ACCOUNT_TOKEN needed by 1Password but missing from
mcp.secrets), which would cause silent auth failure at runtime.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jdx jdx force-pushed the feat/mcp-secret-allowlist branch from e2eacd6 to 8f71f60 Compare March 13, 2026 14:17
@jdx jdx enabled auto-merge (squash) March 13, 2026 14:27
@jdx jdx merged commit af5c9cd into main Mar 13, 2026
16 checks passed
@jdx jdx deleted the feat/mcp-secret-allowlist branch March 13, 2026 14:28
@greptile-apps greptile-apps Bot mentioned this pull request Mar 13, 2026
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