feat(security): Enforce Content Size Limits for resources and prompts (fixes #538 US-1)#3251
Conversation
There was a problem hiding this comment.
Linked Issues — Completion Status
| Issue | Title | Status | Notes |
|---|---|---|---|
| #538 (US-1) | Security - Enforce Content Size Limits | Core validation logic implemented; 2 bugs and missing deny-path tests (see Security Findings) |
Acceptance Criteria Detail
| Issue | Criterion | Status | Notes |
|---|---|---|---|
| #538 US-1 | Resource content exceeding 100KB returns 413 | admin_edit_resource returns 409 instead of 413 (admin.py L11792) | |
| #538 US-1 | Prompt template exceeding 10KB returns 413 | ✅ Done | Verified across all 4 handlers |
| #538 US-1 | Content within limits succeeds | ✅ Done | Tests cover exact-limit and under-limit cases |
| #538 US-1 | Error responses include size limit information | main.py handlers return {"detail": str(e)} (flat string), but tests expect structured dict with actual_size, max_size, error, message keys — these tests will fail |
|
| #538 US-1 | Validation on both create and update | ✅ Done | Both paths validated in services and tested |
| #538 US-1 | Security violations logged with user context | ✅ Done | logger.warning() with user/IP context in content_security.py |
Overall Issue Coverage: 4 of 6 criteria fully addressed, 2 partially addressed.
# Run - fails the test
uv run pytest tests/integration/test_content_size_limits.py --with-integrationSecurity Findings
Findings Summary
| # | File | Line | Severity | CWE | Description |
|---|---|---|---|---|---|
| 1 | admin.py |
11792 | High | CWE-436 | Inconsistent HTTP status: admin_edit_resource returns 409 for ContentSizeError while all other handlers return 413 |
| 2 | main.py |
4501, 4685, 5007, 5202 | Medium | CWE-209 | Response format mismatch: Handlers return {"detail": str(e)} (string) but tests expect structured dict {"detail": {"actual_size":..., "max_size":..., "error":..., "message":...}} — tests will fail |
| 3 | content_security.py |
91, 122 | Medium | CWE-117 | Log injection via f-strings: uri, user_email, ip_address are user-controlled values interpolated in logger.warning() f-strings |
| 4 | content_security.py |
91, 122 | Medium | CWE-532 | PII in logs: user_email and ip_address logged at WARNING level on every size violation |
| 5 | content_security.py |
132–147 | Medium | CWE-362 | Non-thread-safe singleton: get_content_security_service() uses check-then-set without locking |
| 6 | resource_service.py |
434 | Low | CWE-20 | Silent byte coercion: errors="ignore" drops non-UTF-8 bytes, potentially shrinking measured size below actual storage size |
| 7 | main.py |
4501, 4685, 5007, 5202 | Low | CWE-755 | ORJSONResponse vs HTTPException: Returning ORJSONResponse bypasses FastAPI's exception middleware (CORS, OpenAPI, centralized logging) |
| 8 | main.py |
5201 | Low | — | Typo: "Content size exceeded in in updating prompt" (double "in") |
Suggestions
-
Finding 1 — Change
status_code=409tostatus_code=413inadmin.py:11792. One-line fix. Effort: trivial. -
Finding 2 — Update the 4
ContentSizeErrorhandlers inmain.pyto return structured responses matching the test expectations. Effort: low. (OWASP: Consistent error responses — A09:2021) -
Finding 3 — Use
%sformat placeholders instead of f-strings inlogger.warning()calls to prevent log injection. Effort: trivial. (CWE-117, RFC 5424) -
Finding 4 — Replace
user_emailwithuser_idin all log statements — email addresses are strong PII identifiers and must not be logged directly. Forip_address, note that under GDPR (Article 4(1)), IP addresses are classified as personal data by default (confirmed by the ECJ); logging raw IPs is non-compliant unless a lawful basis is documented. Prefer one of the following approaches:- Anonymise the IP before logging (e.g. zero out the last octet:
192.168.1.123→192.168.1.0), or - Hash it (e.g. SHA-256 with a rotating salt) to enable correlation without storing the raw value, or
- Drop it entirely if the IP adds no actionable security value for this log event.
Additionally, move any remaining identifiers to
DEBUGlevel or use structured loggerextra={}fields so they can be filtered or masked by the log pipeline before reaching external log stores. Effort: low–medium. (GDPR Article 4(1), Article 5(1)(c) — data minimisation; CWE-532) - Anonymise the IP before logging (e.g. zero out the last octet:
-
Finding 5 — Add
threading.Lockto the singleton getter, or use module-level initialization. Effort: trivial. -
Finding 6 — Pass
resource.contentas bytes directly tovalidate_resource_size()instead of decoding witherrors="ignore". The method already acceptsUnion[str, bytes]. Effort: trivial. -
Finding 7 — Consider using
raise HTTPException(status_code=413, detail=...)for consistency with existing error handlers. Effort: low. -
Finding 8 — Fix typo:
"in in"→"in". Effort: trivial.
|
Hi @Lang-Akshay ✅ All Issues Fixed - PR #3251 Complete with Comprehensive Security FixesSummary of All Implemented FixesOriginal PR #3251 Findings (All Fixed)
Security Issues Fixed (Critical)
Files Modified (7 total)
Security Improvements Summary
Testing Performed✅ PII Sanitization:
✅ Thread Safety:
✅ Size Validation Bypass Prevention:
✅ Middleware Flow:
Code Quality
Next Steps
Additional NotesCodebase-wide Issue: Log injection vulnerability affects 115 locations across the codebase. Consider creating a follow-up PR to address remaining instances in other services. PR #3251 is now complete with:
|
4d2ee6a to
4310915
Compare
|
Thanks @msureshkumar88. The Content Security Service approach with configurable limits and 413 responses is well-structured. Please address the requested changes and we'll take another look. |
Implement configurable content size validation to prevent DoS attacks via oversized content submissions. Resources are limited to 100KB and prompt templates to 10KB by default (configurable via environment variables CONTENT_MAX_RESOURCE_SIZE and CONTENT_MAX_PROMPT_SIZE). Validation occurs at the service layer before database operations, returning 413 Payload Too Large with structured error details. Closes #538 Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
- Fix ResourceCreate.validate_content to remove MAX_CONTENT_LENGTH schema-level check (now handled by service layer, consistent with ResourceUpdate) - Fix broken test assertions checking raw bytes in formatted messages (TestErrorMessageClarity was asserting "200000" in human-readable "195.3 KB" strings) - Fix broken test_update_prompt_validation_and_integrity_errors which had its body accidentally deleted by the original PR - Restore accidentally deleted assertions in test_list_resources and test_create_resource_endpoint - Add global exception handler for ContentSizeError (defense-in-depth, consistent with existing handlers for ValidationError/IntegrityError) - Add reset_content_security_service() for test isolation - Remove "Made with Bob" watermark from test file - Remove duplicate pytest import in integration tests - Fix broken README link to non-existent migration guide - Fix double-commented lines in .env.example Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
- Move noqa:DUO138 to the line flake8 reports (validators.py) - Add Args/Returns to content_size_exception_handler docstring (DAR101/DAR201) Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Content size validation moved from Pydantic schema to service layer (returns 413 instead of 422). Update test_resource_create_content_validation and test_zip_bomb_prevention to reflect that ResourceCreate no longer rejects oversized content at schema level. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
1. Remove MAX_TEMPLATE_LENGTH check from SecurityValidator.validate_template so the configurable CONTENT_MAX_PROMPT_SIZE is not silently capped at 64KB by the schema-level validator (finding #1) 2. Add db.rollback() to ContentSizeError handlers in resource_service to prevent partial metadata mutations from being committed when content size validation fails after ORM fields are already updated (finding #2) 3. Include actual_size and max_size in admin route 413 responses to match the structured error contract used by API routes (finding #3) 4. Update security and validator tests that expected schema-level length rejection — size enforcement now happens at service layer Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
3231768 to
33ada94
Compare
…and Helm values - docs/docs/manage/configuration.md: add Content Security section with env var table, scope note, and error response example - docs/docs/manage/securing.md: add section 9 (Content Size Limits) to production security checklist, renumber subsequent sections - charts/mcp-stack/values.yaml: add CONTENT_MAX_RESOURCE_SIZE and CONTENT_MAX_PROMPT_SIZE to gateway env config Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
🔗 Related Issue
Associated with #538 US-1
📝 Summary
What does this PR do and why?
Implements User Story 1 (US-1) from issue #538: Security - Enforce Content Size Limits. This PR adds configurable content size validation to prevent DoS attacks, resource exhaustion, and ensure data quality.
🔒 Security Impact
DoS Prevention: Blocks oversized content submissions that could exhaust memory/storage
Resource Management: Enforces configurable limits on resource content (100KB default) and prompt templates (10KB default)
Audit Trail: Logs all size limit violations with user context for security monitoring
✨ Changes Made
Core Implementation
Content Security Service (
mcpgateway/services/content_security.py)ContentSecurityServiceclass for centralized validationvalidate_resource_size()- validates resource content against configured limitsvalidate_prompt_size()- validates prompt templates against configured limitsContentSizeErrorexception with detailed size informationget_content_security_service()Configuration (
mcpgateway/config.py)content_max_resource_size: 100KB default (configurable 1KB-10MB)content_max_prompt_size: 10KB default (configurable 512B-1MB)Service Integration
resource_service.py: Size validation before database operationsprompt_service.py: Size validation before template processingError Handling (
main.py,admin.py)actual_size,max_size,error, andmessagefieldsTesting
Comprehensive integration tests (
tests/integration/test_content_size_limits.py):Reproduce
(e.g. make dev or python -m mcpgateway.main)
🏷️ Type of Change
📋 Acceptance Criteria Met
From issue #538 US-1:
🔧 Configuration
🚀 Usage Example
📊 Impact
🧪 Verification
make lintmake testmake coverage✅ Checklist
make black isort pre-commit)📓 Notes (optional)
Screenshots, design decisions, or additional context.