Skip to content

fix(mcp): security hardening for 4 issues (#54, #68, #49, #77) - #1

Closed
shanewas wants to merge 0 commit into
mainfrom
mcp-security-hardening
Closed

fix(mcp): security hardening for 4 issues (#54, #68, #49, #77)#1
shanewas wants to merge 0 commit into
mainfrom
mcp-security-hardening

Conversation

@shanewas

Copy link
Copy Markdown
Owner

Fixes 4 MCP security issues:

NousResearch#54 - MCP stderr logs credentials verbatim with no redaction

  • Add _sanitize_stderr_line() with credential regex + inline secret pattern
  • Add _sanitize_stderr_log_file() for post-hoc log scrubbing
  • Call log sanitizer on server shutdown and in _run_stdio finally block

NousResearch#68 - MCP sampling has no token budget cap

  • Add max_total_tokens config (default 0=unlimited) to SamplingHandler
  • Check budget before each LLM call, return ErrorData when exhausted

NousResearch#49 - MCP tool descriptions pass prompt injection unfiltered

  • Add _sanitize_mcp_description() that strips injection patterns
  • Add sanitize_descriptions config (default True) per server
  • Add _MAX_MCP_DESCRIPTION_LENGTH=4000 for description truncation

NousResearch#77 - Env interpolation resolves from full os.environ leaking secrets

  • Add allowed_keys param to _interpolate_env_vars()
  • Only resolve ${VAR} from safe baseline keys + explicitly configured env keys
  • Update _load_mcp_config() to pass allowed_keys per server config

All 281 existing MCP tests pass.

@shanewas shanewas closed this May 20, 2026
shanewas pushed a commit that referenced this pull request May 22, 2026
Four findings from Copilot's review on PR NousResearch#22891, all in the AX
elements-array cap added by 22fa1ed:

1. The truncation note ("response truncated to N of M elements") was
   appended unconditionally — including in the som/vision multimodal
   path, whose response carries a screenshot rather than an `elements`
   array. The note described a payload field that wasn't present.
   Moved the note into the AX-text branch where the array actually
   appears.

2. `_format_elements(cap.elements)` ran on the full untrimmed list with
   its own `max_lines=40` cap, so a caller passing `max_elements=10`
   would see summary lines referencing `NousResearch#11..NousResearch#40` even though the JSON
   `elements` array only held #1..NousResearch#10. Format on `visible_elements`
   instead so the summary indices always exist in the response.

3. `_coerce_max_elements` enforced a lower bound but no upper bound,
   so `max_elements=10_000_000` silently disabled the safeguard and
   reintroduced the original context-blow-up. Added a hard cap
   (`_MAX_ALLOWED_MAX_ELEMENTS = 1000`) that clamps oversized values.

4. The schema string said "Default 100" but the property carried no
   `default` field, and claimed `max_elements` had no effect on som/
   vision while the image-missing fallback path can still return an
   elements array. Added `"default": 100`, `"maximum": 1000`, and
   clarified the fallback-path wording.

Each finding gets a regression test:

- test_capture_ax_clamps_oversized_max_elements_to_hard_cap
- test_capture_ax_summary_indices_match_returned_elements
- test_capture_multimodal_summary_omits_truncation_note
- test_schema_max_elements_documents_default_and_upper_bound

Verified with `pytest tests/tools/test_computer_use.py` (53 passed,
including the 5 new cases). Confirmed each new test fails on the
pre-fix code path before applying the production change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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