Skip to content

Add file-size guard on artifact creation via MCP GitHub tools - #523

Open
totto wants to merge 3 commits into
Arvo-AI:mainfrom
totto:bugfix/521-file-size-guard
Open

Add file-size guard on artifact creation via MCP GitHub tools#523
totto wants to merge 3 commits into
Arvo-AI:mainfrom
totto:bugfix/521-file-size-guard

Conversation

@totto

@totto totto commented Jun 17, 2026

Copy link
Copy Markdown

Summary

When the LLM hits its output token limit, create_or_update_file silently pushes a truncated stub to GitHub, destroying the original file (the pattern that caused PR #513 on staging). This adds a pre-flight guard inside the MCP tool wrapper that:

  • Rejects content exceeding a 50 KB absolute cap for both create_or_update_file and push_files (per-entry)
  • For file updates (sha present), fetches the existing file size via get_file_contents and rejects writes smaller than 50% of the original when the original exceeds 10 KB
  • Fail-open: guard failures are logged and the write proceeds

The guard runs after the HITL confirmation gate but before the MCP server call, so it catches both foreground and background writes.

Test plan

  • Verify create_or_update_file with content > 50 KB is rejected
  • Verify create_or_update_file updating a 20 KB file with 5 KB content (sha present) is rejected with ratio error
  • Verify create_or_update_file creating a new file (no sha) is not subject to ratio check
  • Verify push_files with a per-entry > 50 KB is rejected
  • Verify guard failure (e.g., get_file_contents timeout) allows the write through

Closes #521

Summary by CodeRabbit

  • New Features
    • Added a file size safety check for GitHub repository write operations to help prevent truncated or suspiciously sized content from overwriting existing files.
    • When a write is flagged, the operation is blocked and a notification is returned instead of executing the update.

When the LLM hits its output token limit, create_or_update_file silently
pushes a truncated stub to GitHub, destroying the original file (the
pattern that caused PR Arvo-AI#513 on staging).

This adds a pre-flight guard inside the MCP tool wrapper that:

1. Rejects content exceeding a 50 KB absolute cap for both
   create_or_update_file and push_files (per-entry).
2. For file updates (sha present), fetches the existing file size via
   get_file_contents and rejects writes smaller than 50% of the original
   when the original exceeds 10 KB.

The guard runs after the HITL confirmation gate but before the MCP
server call, so it catches both foreground and background writes.
On guard-check failure the write is allowed through (fail-open).

Closes Arvo-AI#521

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@totto
totto requested a review from a team as a code owner June 17, 2026 17:11
@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@totto, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 16 minutes and 53 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 47cd44d9-da7b-4857-8840-c75f4e7c1415

📥 Commits

Reviewing files that changed from the base of the PR and between 55a5e49 and cb6756b.

📒 Files selected for processing (1)
  • server/chat/backend/agent/tools/mcp_tools.py

Walkthrough

Adds a file-size safety guard in mcp_tools.py for GitHub write operations. The guard defines absolute size caps and ratio thresholds, optionally fetches the existing file size from GitHub, and blocks create_or_update_file or push_files calls when content appears truncated. The guard is wired into the MCP-to-LangChain tool wrapper to run before MCP invocation.

Changes

GitHub Write File-Size Guard

Layer / File(s) Summary
Guard constants, size checks, and existing-file fetch
server/chat/backend/agent/tools/mcp_tools.py
Introduces absolute size cap constants, a ratio-based truncation check for create_or_update_file (fetching current file size via get_file_contents), a per-entry hard cap for push_files, and a helper that extracts byte size from get_file_contents responses.
Guard wired into MCP tool wrapper
server/chat/backend/agent/tools/mcp_tools.py
Adds an early guard call in the MCP tool wrapper for create_or_update_file and push_files; emits a "blocked" completion notification when the guard fires, logs and falls through on guard exceptions.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

  • #521 — This PR directly implements the proposed fix: fetching current file size, applying a ratio check (~50% threshold), and adding an absolute content cap, exactly as described in the issue.

Poem

🐇 A tiny stub tried to replace the whole,
But the rabbit cried, "Stop! Check the scroll!"
With ratios and caps, the guard stood tall,
No truncated stub shall corrupt the hall.
The file stays whole — no bits shall fall! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a file-size guard to artifact creation via MCP GitHub tools.
Linked Issues check ✅ Passed The implementation addresses all coding requirements from issue #521: absolute 50KB cap, ratio-based checks for existing files, new file exemption, and fail-open design.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the file-size guard for MCP GitHub tools as specified in the linked issue objectives.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/chat/backend/agent/tools/mcp_tools.py`:
- Around line 1433-1439: The _check_file_size_guard function is being called on
raw kwargs before nested arguments are normalized. The guard needs to operate on
the actual arguments that will be used, but currently the code normalizes nested
arguments (unwrapping kwargs["kwargs"]) later in the execution flow. Normalize
and unwrap any nested arguments before calling _check_file_size_guard to ensure
the guard properly validates the file size and content checks on the correct
argument structure, preventing the guard from being bypassed for nested-call
shapes.
- Around line 164-172: The exception handler wrapping the entire loop causes
premature exit when any content item fails JSON parsing, preventing later valid
items from being processed. Move the try-except block to wrap only the JSON
parsing and data extraction for each individual item (around the _json.loads and
subsequent dictionary checks within the for loop), so that a non-JSON item in
the content_items list will skip to the next item instead of breaking out of the
entire loop. This ensures all content items are evaluated before the function
returns.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: cba62495-b3d2-45d8-80be-1f4de9b78c43

📥 Commits

Reviewing files that changed from the base of the PR and between 659f7ec and 25a66c8.

📒 Files selected for processing (1)
  • server/chat/backend/agent/tools/mcp_tools.py

Comment thread server/chat/backend/agent/tools/mcp_tools.py Outdated
Comment on lines +1433 to +1439
if server_type == "github" and original_tool_name in (
"create_or_update_file", "push_files",
):
try:
guard_msg = _check_file_size_guard(
original_tool_name, kwargs, _mcp_manager, run_async_in_thread,
)

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize nested arguments before invoking the guard.

At Line 1433, the guard runs on raw kwargs, but at Line 1456 the wrapper unwraps legacy nested args (kwargs["kwargs"]). That means the guard can be bypassed for nested-call shape, including content/sha checks.

Suggested fix
                 if server_type == "github" and original_tool_name in (
                     "create_or_update_file", "push_files",
                 ):
                     try:
+                        guard_kwargs = kwargs.get("kwargs") if isinstance(kwargs.get("kwargs"), dict) else kwargs
                         guard_msg = _check_file_size_guard(
-                            original_tool_name, kwargs, _mcp_manager, run_async_in_thread,
+                            original_tool_name, guard_kwargs, _mcp_manager, run_async_in_thread,
                         )
                         if guard_msg:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/chat/backend/agent/tools/mcp_tools.py` around lines 1433 - 1439, The
_check_file_size_guard function is being called on raw kwargs before nested
arguments are normalized. The guard needs to operate on the actual arguments
that will be used, but currently the code normalizes nested arguments
(unwrapping kwargs["kwargs"]) later in the execution flow. Normalize and unwrap
any nested arguments before calling _check_file_size_guard to ensure the guard
properly validates the file size and content checks on the correct argument
structure, preventing the guard from being bypassed for nested-call shapes.

totto and others added 2 commits June 17, 2026 23:33
Move the try/except inside the loop so a JSON parse failure on one
content item does not abort processing of subsequent items.
Also move the `import json` out of the loop body.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Complexity was 33 (max allowed: 15). Extract the create_or_update_file
logic into _guard_create_or_update_file() and the push_files loop into
_guard_push_files(), leaving _check_file_size_guard as a dispatcher.

Complexity after refactor: dispatcher ~2, each helper ≤13.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@beng360

beng360 commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

hey @totto, can you answer the coderabbit comments please? Thanks (same thing for your other PRs)

@beng360 beng360 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.

Good idea, this guard would have prevented the #513 incident. A few issues to fix before merging.

) -> str | None:
"""Return an error string if the write should be blocked, else ``None``."""
if original_tool_name == "create_or_update_file":
return _guard_create_or_update_file(kwargs, manager, run_async)

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.

The guard reads kwargs.get("content") but at invocation time the LLM can pass args nested inside kwargs["kwargs"] (the wrapper unwraps this at line 1326 below). Move the guard after the actual_kwargs extraction, or unwrap before calling _check_file_size_guard. Otherwise the guard silently passes everything on the nested-args shape.

# ---------------------------------------------------------------------------
# File-size guard for GitHub write MCP tools (issue #521)
# ---------------------------------------------------------------------------
# When the LLM's output is silently truncated, create_or_update_file pushes

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.

50 KB cap is too low. Our codebase has legitimate files in the 50-100 KB range (large config YAMLs, generated schemas). A user doing create_or_update_file on those will get blocked every time. Consider 100 KB or 150 KB — the real danger (truncated LLM output) is more like 5-15 KB stubs overwriting 80 KB files, which the ratio check already catches.

)

owner = kwargs.get("owner")
repo = kwargs.get("repo")

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.

get_file_contents MCP call here has no timeout. If the GitHub API is slow this blocks the entire tool execution indefinitely. Pass a timeout to run_async or wrap in asyncio.wait_for. Also — this fires on every update to a file >10 KB, adding latency to the hot path. Consider caching or making the ratio check opt-in via an env var.



def _guard_push_files(kwargs: dict) -> str | None:
"""Size guard for ``push_files``: absolute cap per file entry."""

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.

The import json as _json inside the loop body runs on every iteration. Move it to module-level or at least outside the loop. Also the outer try/except wraps the entire loop — if the first content item is a status text line (not JSON), the json.loads raises and the loop exits without checking remaining items that might have the metadata. Narrow the try/except to the parse line.

@beng360 beng360 added the external contributor PR from an external contributor label Jun 20, 2026
@beng360

beng360 commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Hi! Thanks for your contribution. Before we can merge this, we need you to sign our Contributor License Agreement (CLA) for legal purposes. This is a one-time requirement for external contributors — it ensures that contributions are properly licensed and that both parties are protected.

I'll send the document separately. Once signed, we're good to go on this and any future PRs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

external contributor PR from an external contributor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add file-size guard to mcp_create_or_update_file

2 participants