Skip to content

fix(mcp): העלאה שהשתבשה באחסון הזמני אינה נשמרת (upload_corrupted), ותיאור upload_id בהוספה אומר מה ה-hash מוכיח - #3515

Merged
amirbiron merged 2 commits into
mainfrom
claude/dazzling-hawking-e8bq3c
Oct 1, 2026
Merged

amirbiron merged 2 commits into
mainfrom
claude/dazzling-hawking-e8bq3c

Conversation

@amirbiron

@amirbiron amirbiron commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

✨ תיאור קצר

  • מה היה חסר: content_changed משווה את הטקסט שנשלף מ-mcp_uploads למה שנכתב לקובץ. לכן טקסט שהשתבש באחסון הזמני, בין ההעלאה לשמירה, היה נשמר בשקט, עם content_changed: false: מה שנכתב שווה למה שנשלף, ושניהם כבר משובשים.
  • התיקון: מיד אחרי השליפה, ולפני כל בדיקה אחרת של הכלי, _load_upload משווה את ה-hash של הטקסט שנשלף ל-content_sha256 שנשמר עם ההעלאה כשהגיעה. לא תואם ← upload_corrupted, גם כשהתקרה, file_exists או conflict היו נכשלים: שום דבר לא נכתב, ההעלאה נמחקת (ואם האחסון לא ענה למחיקה, היא פוקעת ב-TTL), והתשובה אומרת להעלות שוב. זה מכסה את שני הכלים, בלי שהסוכן צריך להשוות כלום. (בסבב הראשון הבדיקה רצה רק לפני המחיקה. ראו "סבב 2" למטה.)
  • ובנפרד — תיאור upload_id בהוספה: התיאור היה משותף לשני הכלים, והבטיח גם ב-codekeeper_append_file ש-file.content_sha256 יהיה שווה ל-hash של ההעלאה. בהוספה זה ה-hash של הקובץ כולו. סוכן שבודק לפי התיאור היה מסיק שההוספה נכשלה, מעלה שוב, ומוסיף את הטקסט פעמיים. עכשיו התיאור של ההוספה אומר שמה שמראה שהטקסט נכנס בשלמותו הוא content_changed: false, ושל השמירה נשאר כמו שהיה.

📦 שינויים עיקריים

פירוט:

  • mcp_server/handlers.py: _upload_integrity_problem — "stored_hash_invalid" כשמה שנשמר אינו 64 ספרות הקס (_SHA256_HEX הקיים), "hash_mismatch" כשה-hash של הטקסט שנשלף שונה. הבדיקה רצה ב-_load_upload, מיד אחרי השליפה ולפני כל שער, ועל כישלון שם גם discard_upload. ההשוואה מדויקת, כי הערך נכתב רק מ-hexdigest. _upload_corrupted הוא הסירוב, וה-hint לא טוען שההעלאה נמחקה. _PendingUpload נבנית רק אחרי הבדיקה, ו-_consume_upload הוא השער בלבד.

  • mcp_server/backend.py:

    • find_upload מחזיר גם את content_sha256 השמור, בלי בדיקה. הבדיקה של ה-handler.
    • _delete_live_upload הוא המקום היחיד שמוחק העלאה, ו-consume_upload עובר דרכו.
    • discard_upload משליך העלאה משובשת דרך אותה מחיקה. זה ניקיון ולא השער: שגיאת מסד נרשמת ולא עולה, ובאג עולה כמו שהוא.
    • נרשמת שורת ERROR אחת בלי המזהה ובלי התוכן, עם הסיבה ועם מה שהמחיקה עשתה (deleted: yes / no / unknown). consumed לא נרשם.
  • mcp_server/server.py: _UPLOAD_HASH_AFTER_SAVE ו-_UPLOAD_HASH_AFTER_APPEND, המשפט על ה-hash לכל כלי בנפרד. _upload_id_param_doc מקבל אותו כפרמטר.

  • תיעוד: docs/mcp-server.rst עודכן בכמה מקומות:

    • הפסקה על ה-hash-ים, עכשיו בנפרד לשמירה ולהוספה ובלי ספירה בפרוזה.
    • פסקה חדשה על בדיקת השלמות: מה content_changed מכסה, ומה הבדיקה מכסה.
    • בדיקת השלמות בסדר של "המחיקה היא השער".
    • upload_corrupted בטבלת הסירובים וב"פתרון תקלות".
    • שורת ה-ERROR ב"מה נרשם בלוג".

    בנוסף mcp_server/README.md, docs/whats-new.rst, וה-docstring של create_upload.

🧪 בדיקות

  • Unit
  • Integration — מונגו אמיתי (mongod 8.0.15)
  • Manual

המספרים בסעיף הזה הם של הסבב הראשון (07a6743). המספרים של סבב 2 נמצאים בסעיף שלו, למטה.

הטסטים החדשים על הקוד שלפני התיקון (ב-git worktree נפרד, על 7440abe): 22 נכשלו, ו-146 עברו. הנכשלים: המשפט בתיאור מול מה שהכלי מחזיר (ההוספה), העלאה משובשת בשני הכלים ובחמש צורות שיבוש, מחיקה שלא מוצאת כלום, שני חוטים על העלאה משובשת, אחסון שלא עונה למחיקה, שורת הלוג, ההשלכה ב-backend, החוזה של find_upload, השומר המבני, ושיבוש באוסף האמיתי.

טסטים שעוברים גם על הקוד הישן, ובכוונה, כי הם מקבעים התנהגות שכבר הייתה נכונה:

  • מקרה השמירה בטסט התיאור. זו הבקרה.
  • test_without_upload_id_the_upload_storage_is_not_touched, כלומר שבלי upload_id שום דבר לא משתנה.
  • מטריצת ההוספה במונגו האמיתי: הקובץ כולו, ו-content_changed: false.

כל אחד מהם אומר את זה בגוף שלו.

שומר מבני (בסבב 2 שמו test_every_upload_is_checked_as_it_is_read_and_consumed_at_one_gate): על הקוד הישן הוא נופל רק כי המחיקה עוד לא עברה ל-_delete_live_upload, כלומר שם ולא עקיפה אמיתית. הראיה שהוא מסוגל ליפול היא המוטציות.

11 מוטציות על הקוד הסופי, כולן נתפסו (ב-worktree נפרד; הקוד בלי מוטציה — 168 עברו):

מוטציה כשלים בין הטסטים שנפלו
בלי בדיקת השלמות 18 העלאה משובשת בשני הכלים, שני חוטים, מונגו אמיתי
השוואה אחרי המחיקה 4 מחיקה שלא מוצאת כלום, אחסון שלא עונה, שורת הלוג, שני חוטים (['upload_corrupted', 'upload_not_found'])
save_file צורך בלי העוזר 11 השומר המבני, העלאה משובשת בשמירה
אתר מחיקה שני ב-backend 3 השומר המבני, תרגום שגיאת מסד, הוספה שנשלחה שוב
השלכה שלא מוחקת 17 "ההעלאה נעלמה", שורת הלוג (deleted: no)
השלכה שמעלה שגיאת מסד 2 אחסון שלא עונה למחיקה, ההשלכה ב-backend
תיאור ההוספה חוזר לנוסח הישן 1 המשפט בתיאור מול מה שהכלי מחזיר
find_upload בלי ה-hash השמור 36 כל שמירה מהעלאה נדחית
השלכה שנרשמת כ-consumed 2 שורת הלוג, השומר המבני
הוספה בלי השער 22 השומר המבני, ההוספה מהעלאה, מונגו אמיתי
בדיקת צורה בלי השוואת תוכן 12 טקסט שהשתבש באחסון, בשני הכלים

ריצות:

  • tests/test_mcp_uploads.py ו-tests/test_mcp_content_sha256_real_mongo.py: 168 עברו מול mongod 8.0.15 מקומי. בלעדיו קובץ המונגו מדולג, וב-CI הוא מדולג.
  • כל טסטי ה-MCP והתיעוד (tests/test_mcp_*.py, tests/test_doc*.py, -n 4): 2,129 עברו, אפס דולגו. שני טסטי התיעוד שדורשים docutils רצו אחרי שהותקן בסביבה המקומית.
  • שורת הלוג נבדקה בתהליך נקי, עם הגדרת הלוג של השירות. היא נראית כך:
    ERROR:mcp_server.backend:mcp upload: codekeeper_save_file refused a corrupted upload (hash_mismatch; 20 bytes, 11 chars) for user 4242 — deleted: yes
  • flake8 בבחירה החוסמת של ה-CI: 0. mypy על שלושת המודולים: אותן 16 שגיאות קיימות לפני ואחרי, ושום שגיאה חדשה.
  • docs/mcp-server.rst ו-docs/whats-new.rst נבדקו ב-docutils, עם התפקידים של Sphinx מדומים: אין שגיאות ברמה 3 ומעלה. בנייה מלאה לא הורצה. RTD תופס.

מה לא אומת:

  • בפרודקשן: אי אפשר לייצר שם העלאה משובשת דרך ה-API בלי לגעת במסד.
  • דרך HTTP: הטסטים קוראים לכלים ב-call_tool של שרת ה-MCP האמיתי. השינוי לא נוגע בתעבורה או באימות.
  • אחרי הדיפלוי: שמירה והוספה רגילות דרך upload_id אמורות להתנהג בדיוק כמו קודם, והתיאור החדש אמור להופיע ב-tools/list.

🧪 בדיקות נדרשות ב‑PR

  • 🔍 Code Quality & Security
  • Unit Tests (3.11)
  • Unit Tests (3.12)

📝 סוג שינוי

  • feat: פיצ'ר חדש
  • fix: תיקון באג
  • docs: שינוי תיעוד בלבד
  • refactor: שינוי קוד ללא שינוי התנהגות
  • perf: שיפור ביצועים
  • chore/ci: תשתית/CI
  • breaking change: שינוי שובר תאימות

🔀 סטיות מהתוכנית

  • ההשלכה עוברת ב-discard_upload ולא ב-consume_upload: כך הלוג לא אומר "consumed" על העלאה שלא נשמרה. שתיהן עוברות באותה מחיקה, _delete_live_upload, ולכן יש אתר מחיקה אחד.
  • שני טסטים מעבר לתוכנית: שני חוטים על העלאה משובשת, ואחסון שלא עונה למחיקה. הראשון נוסף כי "התשובה אינה תלויה במחיקה" היא טענת מקביליות, וטענה כזו נבדקת במקביל.
  • "כל טסט נופל על הקוד הנוכחי קודם": נכון לטסטים שבודקים תיקון. שלושת הטסטים שמקבעים התנהגות קיימת לא יכולים ליפול עליו, ומפורטים למעלה.
  • קומיט אחד, לא שניים. תיקון התיאור והבדיקה חולקים קבצים, ויוצאים יחד.

🔁 סבב 2 — שני ממצאים של cubic (b643d80)

מה השתנה למשתמש: העלאה שהשתבשה באחסון הזמני נענית עכשיו upload_corrupted תמיד. זה נכון גם כשהיא הייתה נתקלת קודם בבדיקה אחרת של הכלי, כמו התקרה, file_exists, conflict או not_found. היא נמחקת ונרשמת בשורת ה-ERROR. עד עכשיו היא קיבלה במקרים האלה את הסירוב של הבדיקה האחרת, כלומר החלטה על טקסט שלא נשלח (למשל code_too_large על אורך שאינו של ההעלאה), ונשארה חיה בלי לוג עד שפקעה.

  • ממצא 1 (P2, handlers.py) — תוקן. הבדיקה עברה ל-_load_upload, מיד אחרי השליפה. _PendingUpload נבנית רק שם, אחרי הבדיקה, ו-_consume_upload חזר להיות השער בלבד. סטייה מההצעה: לא השארתי בדיקה שנייה ב-_consume_upload "כחגורת ביטחון". אחרי הבדיקה בשליפה, אף טסט לא יכול להפיל שורה כזו, ובמקומה השומר המבני מקבע שאין מקום אחר שבונה _PendingUpload. זה לא TOCTOU: מה שנשמר הוא הטקסט שנבדק, והשער מכריע רק שההעלאה נצרכת פעם אחת.
  • ממצא 2 (P3, whats-new.rst) — תוקן. "ההעלאה נמחקת (ואם האחסון לא ענה למחיקה, היא פוקעת ב-TTL; התשובה אינה תלויה בזה)". אותו סייג נוסף גם לשורה בטבלת הסירובים, ל"פתרון תקלות" ול-mcp_server/README.md.

בדיקות:

  • טסט חדש לכל שער: תוכן ריק, התקרה, בירור קיום שלא ענה ו-file_exists בשמירה; תוכן ריק, expected_content_sha256 פגום, not_found, conflict והתקרה בהוספה.

    • בקרה: העלאה שלמה מקבלת את סירוב השער ונשארת. כך הטסט אינו יכול לעבור על הכנה שלא מגיעה לשער.
    • העלאה משובשת: upload_corrupted, discard_upload פעם אחת עם הכלי והסיבה, רק היא נמחקה, ושום דבר לא נכתב.
  • על הקוד של הסבב הראשון (07a6743, worktree נפרד): 10 נכשלו. אלה כל מקרי השערים, כל אחד עם סירוב השער שלו בדיוק, והשומר המבני. 108 עברו. טסט שורת הלוג עובר גם על 07a6743, כי הוא מאפיין את השורה ולא את המיקום.

  • 7 מוטציות, כולן נתפסו:

    • בלי הבדיקה: 27 כשלים.
    • סירוב בלי השלכה: 25.
    • השלכה שנרשמת כצריכה: 12.
    • בדיקת צורה בלי השוואה: 21.
    • save_file ששולף בלי _load_upload: 22.
    • שם כלי שגוי בלוג: 5.
    • _PendingUpload שנבנית ב-save_file: השומר המבני.

    "הבדיקה במקומה הקודם" היא הריצה על 07a6743.

  • ריצות:

    • קבצי ההעלאות ומונגו אמיתי (mongod 8.0.15): 177 עברו.
    • כל טסטי ה-MCP והתיעוד: 2,138 עברו, ואפס דולגו.
    • flake8 בבחירה החוסמת: 0. mypy: אותן 16 שגיאות קיימות.
    • docutils על שני עמודי התיעוד: אין שגיאות.

✅ צ'קליסט

  • הקוד עוקב אחרי הסגנון (Black/isort/flake8/mypy)
  • בדיקות רצות ועוברות
  • תיעוד עודכן (README/Docs)
  • אם נוספו ג'ובים חדשים (Background Jobs) — לא רלוונטי
  • אם נוספו/שונו משתני סביבה — לא נוספו ולא שונו
  • אם נוספו/השתנו טוקנים — לא רלוונטי
  • אין סודות/מפתחות בקוד, ושורת הלוג החדשה בלי המזהה ובלי התוכן
  • אין מחיקות מסוכנות/פעולות על root
  • הודעת הקומיט תואמת Conventional Commits
  • עיינתי במסמכי אתר התיעוד:
    • העמודים: AI-MAP.md ← docs/mcp-server.rst (mcp-uploads, mcp-content-sha256, "פתרון תקלות"), docs/doc-authoring.rst, docs/versioning-stable-anchors.rst.
    • המשפט: "אם המספר יכול להשתנות בלי שאף שורת קוד תשתנה — הוא ספירת מופעים, ואין לכתוב אותו". בגללו ירדה הספירה "שלושה hash-ים" מהפסקה ששוכתבה.
    • ב-amir-bug-patterns:
      • K11 ו-bugbot-rules/return-value-failure-unchecked.md
      • U1 ו-bugbot-rules/race-toctou.md
      • U3 ו-bugbot-rules/external-input-isinstance.md
      • R6
      • bugbot-rules/prose-restates-code-fact.md
      • bugbot-rules/derived-field-added-to-one-writer.md
      • bugbot-rules/write-from-cached-read.md
      • bugbot-rules/silent-fallback-to-worse-path.md
      • TESTING-PATTERNS.md (T1–T4) ו-bugbot-rules/widened-exception-scope.md
      • claude-md-snippets/testing.md
      • bugbot-rules/line-number-coupling.md

🧩 השפעות/סיכונים

  • העלאה תקינה: מתנהגת בדיוק כמו קודם. נוסף חישוב SHA-256 אחד על הטקסט בכל קריאה לכלי עם upload_id. הטקסט הוא עד max_code_size() תווים, כי ראוט ההעלאה דוחה טקסט ארוך מזה. זה זניח מול השמירה עצמה.
  • העלאה ששמורה עם hash פגום נדחית עכשיו. הכותב היחיד, create_upload, שומר hash תקין מאז feat(mcp): העלאת תוכן ארוך בלי לעבור דרך המודל — PUT /api/agent/upload ו-upload_id #3502, וכל העלאה פוקעת אחרי UPLOAD_TTL_SECONDS.
  • הפרמטר החדש של _upload_id_param_doc: אם כלי שלישי יקבל את התיאור, הוא חייב לבחור משפט. שכחה נכשלת בזמן הרישום ולא בשקט.

🔗 קישורים

🧯 סיכון / החזרה לאחור (Rollback)

  • revert של ה-PR. אין מיגרציה, ואין שדה חדש במסד.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PLrwvW1zc6nhJfG2Mtr4wi

…ספה אומר מה ה-hash מוכיח

content_changed משווה את הטקסט שנשלף מ-mcp_uploads למה שנכתב לקובץ, ולכן
טקסט שהשתבש בין ההעלאה לשמירה היה נשמר בשקט, עם content_changed: false.

- _consume_upload (handlers.py), העוזר שכל כלי שמקבל upload_id צורך דרכו,
  משווה לפני המחיקה את ה-hash של הטקסט שנשלף ל-content_sha256 שנשמר עם
  ההעלאה כשהגיעה. שונים, או שמה שנשמר אינו 64 ספרות הקס: upload_corrupted,
  שום דבר לא נכתב, וההעלאה נמחקת (discard_upload). התשובה אינה תלויה
  בתוצאת המחיקה.
- backend: find_upload מחזיר גם את ה-hash השמור; _delete_live_upload הוא
  המקום היחיד שמוחק העלאה, ו-discard_upload רושם שורת ERROR אחת בלי המזהה
  ובלי התוכן, ולא consumed.
- תיאור upload_id: המשפט על ה-hash היה משותף לשני הכלים והבטיח גם בהוספה
  ש-file.content_sha256 יהיה שווה ל-hash של ההעלאה. בהוספה הוא של הקובץ
  כולו, ומה שמראה שהטקסט נכנס בשלמותו הוא content_changed: false. עכשיו
  לכל כלי המשפט שלו (_UPLOAD_HASH_AFTER_SAVE / _UPLOAD_HASH_AFTER_APPEND).
- טסטים: המשפט בתיאור מול מה שהכלי מחזיר, העלאה משובשת בשני הכלים (חמש
  צורות שיבוש), מחיקה שלא מוצאת כלום, שני חוטים, אחסון שלא עונה למחיקה,
  שורת הלוג בתהליך נקי, שומר מבני על המקור, ומונגו אמיתי (הוספה ושיבוש).
- תיעוד: mcp-server.rst, mcp_server/README.md, whats-new.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PLrwvW1zc6nhJfG2Mtr4wi
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@sourcery-ai sourcery-ai 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.

Sorry @amirbiron, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 4 days and 17 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@greptile-apps greptile-apps 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.

Your trial has ended. Reactivate Greptile to resume code reviews.

@github-actions

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

🧯 Dangerous deletes guard report

Policy: see .cursorrules — dangerous deletions are blocked unless wrapped safely.

Summary:

  • Flagged findings (blocking): 0
    0
  • Excluded matches (not blocking): 15
  • Total matches (all files): 128

Flagged findings (file:line:snippet):
(none)

Excluded matches (by path pattern)
./node_modules/katex/src/fonts/Makefile:139:	rm -rf pfa ff otf ttf woff woff2
./node_modules/katex/package.json:153:    "build": "rimraf dist/ && mkdirp dist && cp README.md dist && rollup -c --failAfterWarnings && webpack && node update-sri.js package dist/README.md",
./node_modules/mermaid/dist/mermaid.js.map:4:  "sourcesContent": ["/**\n* Default values for dimensions\n*/\nconst defaultIconDimensions = Object.freeze({\n\tleft: 0,\n\ttop: 0,\n\twidth: 16,\n\theight: 16\n});\n/**\n* Default values for tr … [truncated]
./node_modules/mermaid/dist/chunks/mermaid.esm/chunk-2M32CCKP.mjs.map:4:  "sourcesContent": ["{\n  \"name\": \"mermaid\",\n  \"version\": \"11.12.0\",\n  \"description\": \"Markdown-ish syntax for generating flowcharts, mindmaps, sequence d … [truncated]
./node_modules/mermaid/dist/chunks/mermaid.core/chunk-KS23V3DP.mjs.map:4:  "sourcesContent": ["{\n  \"name\": \"mermaid\",\n  \"version\": \"11.12.0\",\n  \"description\": \"Markdown-ish syntax for generating flowcharts, mindmaps, sequence  … [truncated]
./node_modules/mermaid/dist/chunks/mermaid.esm.min/chunk-4HFYJGYH.mjs.map:4:  "sourcesContent": ["{\n  \"name\": \"mermaid\",\n  \"version\": \"11.12.0\",\n  \"description\": \"Markdown-ish syntax for generating flowcharts, mindmaps, sequen … [truncated]
./node_modules/mermaid/dist/chunks/mermaid.esm.min/chunk-4HFYJGYH.mjs:1:var r={name:"mermaid",version:"11.12.0",description:"Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graph … [truncated]
./node_modules/mermaid/dist/mermaid.min.js:1524:`,"getStyles"),c1e=RQe});var h1e={};dr(h1e,{diagram:()=>NQe});var NQe,f1e=N(()=>{"use strict";$ge();a1e();l1e();u1e();NQe={parser:Fge,db:n1e,renderer:o1e,styles:c1e}});var m1e,g1e=N(()=>{"use  … [truncated]
./node_modules/mermaid/dist/mermaid.min.js.map:4:  "sourcesContent": ["/**\n* Default values for dimensions\n*/\nconst defaultIconDimensions = Object.freeze({\n\tleft: 0,\n\ttop: 0,\n\twidth: 16,\n\theight: 16\n});\n/**\n* Default values fo … [truncated]
./README.md:829:find . -name "__pycache__" -exec rm -rf {} +
./webapp/static/js/md_preview.bundle.js.map:4:  "sourcesContent": ["// Markdown-it plugin to render GitHub-style task lists; see\n//\n// https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments\n// https://github.com/blog/1825-t … [truncated]
./Dockerfile:42:    rm -rf /var/lib/apt/lists/*
./Dockerfile:121:    rm -rf /var/lib/apt/lists/*
./docs/DOCUMENTATION_GUIDE.md:453:rm -rf _build
./docs/Makefile:24:	rm -rf $(BUILDDIR)

@sourcery-ai

sourcery-ai Bot commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

The PR closes the temporary-upload integrity gap by validating the fetched text against the SHA-256 recorded at upload time before the atomic consume/delete gate, returning a retryable upload_corrupted response and safely handling cleanup failures. It also fixes append-tool documentation so agents interpret the resulting whole-file hash and content_changed: false correctly, with broad regression and structural coverage plus updated MCP documentation.

Sequence diagram for temporary upload integrity validation

sequenceDiagram
    participant Agent
    participant Tool as MCP tool
    participant Handler as _consume_upload
    participant Backend
    participant Storage as Upload storage

    Agent->>Tool: call_tool(upload_id)
    Tool->>Handler: _load_upload()
    Handler->>Backend: find_upload()
    Backend->>Storage: find_one()
    Storage-->>Backend: text, bytes, content_sha256
    Backend-->>Handler: _PendingUpload
    Handler->>Handler: _upload_integrity_problem()
    alt hash mismatch or invalid stored hash
        Handler->>Backend: discard_upload()
        Backend->>Storage: _delete_live_upload()
        Backend-->>Handler: cleanup result
        Handler-->>Tool: upload_corrupted
        Tool-->>Agent: retryable error, nothing written
    else hash matches
        Handler->>Backend: consume_upload()
        Backend->>Storage: _delete_live_upload()
        Storage-->>Backend: atomic delete result
        Backend-->>Handler: consumed
        Handler-->>Tool: continue file operation
        Tool-->>Agent: save or append result
    end
Loading

Flow diagram for upload consumption integrity gate

flowchart TD
    A[Load upload with find_upload] --> B{_upload_integrity_problem}
    B -->|stored_hash_invalid or hash_mismatch| C[discard_upload]
    C --> D[_delete_live_upload]
    D --> E[Return upload_corrupted]
    E --> F[No file write; upload again]
    B -->|None| G[consume_upload]
    G --> H[_delete_live_upload atomic gate]
    H --> I{Deleted by this request?}
    I -->|yes| J[Perform save or append]
    I -->|no| K[Return upload_not_found]
Loading

File-Level Changes

Change Details Files
Add server-side integrity validation for uploaded content before it can be consumed.
  • Return the persisted upload SHA-256 from find_upload without validating it there.
  • Compare the fetched text hash with the persisted hash in _consume_upload, rejecting invalid or mismatched uploads as upload_corrupted.
  • Discard corrupted uploads through a dedicated cleanup path that preserves the rejection response even if deletion fails.
  • Centralize upload deletion and retain atomic single-use semantics, with sanitized error logging for cleanup outcomes.
mcp_server/handlers.py
mcp_server/backend.py
Correct per-tool documentation of what the upload hash proves after writing.
  • Keep the save tool’s guarantee that the uploaded hash matches file.content_sha256.
  • Document that append produces the hash of the whole resulting file and that content_changed: false proves the uploaded text was applied completely.
  • Pass tool-specific hash descriptions into generated upload_id parameter documentation.
mcp_server/server.py
mcp_server/README.md
mcp_server/backend.py
Expand documentation and regression coverage for corrupted uploads and append hash semantics.
  • Document integrity checks, upload_corrupted, cleanup logging, and per-tool hash behavior across MCP documentation.
  • Add unit, integration, concurrency, cleanup-failure, logging, real-Mongo, and structural-routing tests.
  • Verify corrupted uploads never write or emit push events and that inline-content paths do not access upload storage.
docs/mcp-server.rst
docs/whats-new.rst
mcp_server/README.md
tests/test_mcp_uploads.py
tests/test_mcp_content_sha256_real_mongo.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 81ba9ceb-6596-46ec-9b5b-685a21a09e81

📥 Commits

Reviewing files that changed from the base of the PR and between 07a6743 and b643d80.

📒 Files selected for processing (7)
  • docs/mcp-server.rst
  • docs/whats-new.rst
  • mcp_server/README.md
  • mcp_server/backend.py
  • mcp_server/handlers.py
  • mcp_server/server.py
  • tests/test_mcp_uploads.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • docs/whats-new.rst
  • mcp_server/server.py
  • mcp_server/backend.py

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

העלאות MCP כוללות כעת hash שמור, שנבדק מול הטקסט שנשלף לפני השימוש בו. אם הבדיקה נכשלת, המערכת מחזירה upload_corrupted, אינה כותבת תוכן ומנסה למחוק את ההעלאה. השינוי כולל גם טיפול במחיקה, בדיקות ותיעוד למשמעות ה-hash בשמירה ובהוספה.

Changes

שלמות העלאות MCP

Layer / File(s) Summary
שמירת hash ומחיקה חד-פעמית
mcp_server/backend.py, tests/test_mcp_uploads.py
ה-backend מחזיר את content_sha256 בשליפת העלאה. consume_upload משתמש במחיקה אטומית, ו-discard_upload מטפל בהעלאות שסומנו כפגומות.
בדיקת שלמות לפני צריכה
mcp_server/handlers.py, tests/test_mcp_uploads.py, tests/test_mcp_content_sha256_real_mongo.py, docs/mcp-server.rst, docs/whats-new.rst
_load_upload בודקת את ה-hash מיד לאחר השליפה ולפני בדיקות התוכן. במקרה של hash לא תקין או אי־התאמה, היא מחזירה upload_corrupted בלי לכתוב תוכן ומנסה למחוק את ההעלאה. הבדיקות והתיעוד מכסים גם כשל במחיקה ולוגים.
משמעות ה-hash בשמירה ובהוספה
mcp_server/backend.py, mcp_server/server.py, mcp_server/README.md, docs/mcp-server.rst, tests/test_mcp_uploads.py, tests/test_mcp_content_sha256_real_mongo.py
תיאורי upload_id מבחינים בין שמירה להוספה. בהוספה, ה-hash של הקובץ מתייחס לקובץ כולו, ו-content_changed: false מציין שטקסט ההעלאה נוסף בשלמותו.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant MCPTool
  participant LoadUpload as _load_upload
  participant ProductionBackend
  participant MongoDB
  MCPTool->>LoadUpload: upload_id ושם הכלי
  LoadUpload->>ProductionBackend: find_upload
  ProductionBackend->>MongoDB: שליפת הטקסט וה-hash השמור
  MongoDB-->>ProductionBackend: נתוני ההעלאה
  ProductionBackend-->>LoadUpload: טקסט ו-hash
  alt ה-hash אינו תקין או אינו תואם
    LoadUpload->>ProductionBackend: discard_upload
    ProductionBackend->>MongoDB: ניסיון למחיקת ההעלאה
    LoadUpload-->>MCPTool: upload_corrupted, ללא כתיבה
  else ה-hash תואם
    LoadUpload-->>MCPTool: תוכן מאומת להמשך עיבוד
  end
Loading

Merge Risk: ⚪ Minimal · up to b643d

Upload integrity validation and the documented save/append hash semantics are consistent. No actionable merge-blocking risk was identified; merge after normal checks pass.

Security Architecture Review

Security architecture risk: 🔵 Low · up to b643d

The change strengthens upload integrity checks while preserving write permission, ownership, expiry, and single-use controls. No newly introduced security concern was established. Production interruption and deployment behavior remain unverified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • observed — The changed flow connects a caller's temporary uploads to their persistent file writes. Lookup, consumption, and corruption cleanup all retain the user-and-upload predicate with a fresh expiry check, rather than granting cross-user storage authority.

Trust Boundaries and Controls

  • observed — The handler rejects missing, malformed, or mismatched stored digests before uploaded text enters save or append validation. Final writes use the validated in-memory text, not a later reread of mutable upload storage.
  • inferred — The plain SHA-256 comparison detects inconsistency between stored text and its creation-time digest. Because both values reside in the same document, it does not authenticate content against an actor able to replace both consistently; ownership and write-permission controls remain separate protections.

Resilience and Maintainability Implications

  • observed — Corruption rejection remains independent of cleanup success. Expected storage failures during discard are logged without permitting a write; retained records remain subject to expiry. Corruption logs omit upload identifiers and content, and unrelated programming exceptions are not silently swallowed.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 65.38% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 52 functions across 5 files. (3 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed הכותרת מתארת בצורה ברורה את שני השינויים המרכזיים: דחיית העלאה פגומה ושיפור תיאור ה־upload_id בהוספה. היא קשורה ישירות למטרת ה-PR, אף שהיא מעט ארוכה.
Description check ✅ Passed תיאור ה-PR מקיף ומכסה את השינוי, הסיבה, הבדיקות, הסיכונים, התיעוד, ה-Rollback והמגבלות. הוא מציין במפורש שלא בוצעו בדיקות בפרודקשן או בניית תיעוד מלאה, ולכן מספק את המידע הנדרש להערכת השינוי.
Full details: Docstring Coverage

Explanation

Docstring coverage is 65.38% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 52 functions across 5 files. (3 skipped: 3 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


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

hash נשלף, נבדק מול התוכן
אם אינו תואם, אין כתיבה
העלאה פגומה מסומנת
מחיקה נרשמת בלוג
שמירה והוספה מתועדות
בדיקות מאשרות את המסלול

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

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

⏱️ Performance report

(No performance test durations collected. Mark tests with @pytest.mark.performance.)

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

📖 Documentation Preview

The documentation has been built successfully!

To view locally:

  1. Download the artifacts
  2. Extract the zip file
  3. Open index.html in your browser

@kilo-code-bot

kilo-code-bot Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (7 files)
  • docs/mcp-server.rst
  • docs/whats-new.rst
  • mcp_server/README.md
  • mcp_server/backend.py
  • mcp_server/handlers.py
  • mcp_server/server.py
  • tests/test_mcp_uploads.py
Previous Review Summary (commit 07a6743)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 07a6743)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (8 files)
  • mcp_server/handlers.py
  • mcp_server/backend.py
  • mcp_server/server.py
  • docs/mcp-server.rst
  • docs/whats-new.rst
  • mcp_server/README.md
  • tests/test_mcp_uploads.py
  • tests/test_mcp_content_sha256_real_mongo.py

Reviewed by nemotron-3-ultra-550b-a55b:free · Input: 0 · Output: 0 · Cached: 0

@codecov

codecov Bot commented Oct 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 8 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread mcp_server/handlers.py Outdated
Comment thread docs/whats-new.rst Outdated
… הטקסט

שני ממצאי ריוויו על #3515:

- הבדיקה ישבה רק ב-_consume_upload, אחרי השערים של הכלי. העלאה משובשת
  שפגשה שער — התקרה, file_exists, existence_check_unavailable, conflict,
  not_found, תוכן ריק, expected_content_sha256 פגום — קיבלה את הסירוב של
  השער, החלטה על טקסט שלא נשלח, ונשארה חיה, בלי שורת ERROR, עד שפקעה.
  עכשיו _load_upload, שכל כלי עם upload_id שולף דרכו, משווה את ה-hash מיד
  אחרי השליפה: משובשת ← discard_upload ו-upload_corrupted, לפני כל שער.
  _PendingUpload נבנית רק שם, אחרי הבדיקה, ו-_consume_upload חזר להיות
  השער בלבד. זה לא TOCTOU: מה שנשמר הוא הטקסט שנבדק.
- whats-new אמר "ההעלאה נמחקת" כעובדה. המחיקה של העלאה משובשת היא ניקיון:
  כשהאחסון לא עונה היא פוקעת ב-TTL, והתשובה אינה תלויה בזה. התיקון גם
  ב-mcp-server.rst וב-README.

טסטים: כל שער, עם בקרה (העלאה שלמה מקבלת את סירוב השער ונשארת) ועם
העלאה משובשת (upload_corrupted, השלכה אחת עם הכלי והסיבה, רק היא נמחקה,
שום דבר לא נכתב). על הקוד הקודם (07a6743) כל המקרים ענו את סירוב השער.
השומר המבני נקרא עכשיו test_every_upload_is_checked_as_it_is_read_and_consumed_at_one_gate:
discard_upload רק מ-_load_upload, ו-_PendingUpload לא נבנית בשום מקום אחר.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PLrwvW1zc6nhJfG2Mtr4wi

@greptile-apps greptile-apps 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.

Your trial has ended. Reactivate Greptile to resume code reviews.

@gitar-bot

gitar-bot Bot commented Oct 1, 2026

Copy link
Copy Markdown
Code Review ✅ Approved

🔴 High risk · Hash validation now rejects corrupted uploads before they can be saved as files.

Adds SHA-256 integrity validation before consuming MCP uploads to prevent silently saving corrupted content, and clarifies that upload hashes prove different outcomes for codekeeper_save_file versus codekeeper_append_file. Corrupted uploads are now rejected with upload_corrupted before any write occurs, cleanup is centralized and observable, and tool-specific hash semantics are documented. Comprehensive unit, integration, and concurrency tests validate the fix across normal uploads, concurrent requests, and 11 code mutations. No issues found.

Review coverage

📋 Rules No rules evaluated

🧪 Functional validation Not enabled · Set up

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Counting what did not apply, without listing it.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@amirbiron
amirbiron merged commit d2f9e3b into main Oct 1, 2026
36 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.

2 participants