-
-
Notifications
You must be signed in to change notification settings - Fork 118
Fix some minor security flags #800
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Conducted exhaustive security audit of the OpenContracts codebase covering: - IDOR vulnerabilities in GraphQL mutations (6 critical) - XSS risks in Django admin format_html usage - Hardcoded secrets and credential exposure - File upload security gaps (base64 validation, temp file leaks) - SSRF risks in GremlinEngine and pipeline endpoints - SQL injection assessment (1 low-risk dynamic ORM filter) - Command injection review (none found) - Session/CSRF configuration gaps - Dependency security analysis - Authentication flow review The report includes severity ratings, affected file locations with line numbers, attack vectors, and prioritized remediation guidance.
Corrections after review: - PostHog API key: False positive (intentionally public ingest key) - ZIP path validation: False positive (in-memory processing, no filesystem extraction) - Django Admin XSS: Downgraded to LOW (staff-only, requires authenticated attacker) - SSRF/GremlinEngine: Removed (admin-only configuration via Django admin) - Pipeline URLs: Removed (deployment-time env var config, not user input) Actual findings: - HIGH: IDOR in 6 GraphQL mutations (error message differentiation) - MEDIUM: Base64 size validation, temp file cleanup, session cookie config - LOW: Defense-in-depth improvements (admin escaping, filter whitelist) Overall assessment: Well-architected security posture with one actionable high-priority fix (IDOR) and several defense-in-depth improvements.
Security fix: Updated 6 mutations to return unified error messages for both "not found" and "permission denied" cases, preventing attackers from enumerating valid object IDs. Mutations fixed: - VoteMessageMutation (voting_mutations.py) - RemoveVoteMutation (voting_mutations.py) - RemoveAnnotation (mutations.py) - RejectAnnotation (mutations.py) - ApproveAnnotation (mutations.py) - RemoveRelationship (mutations.py) Pattern used: visible_to_user(user).get(pk=pk) which returns DoesNotExist for both non-existent and inaccessible objects, plus unified error message for any subsequent permission checks. Also added IDOR protection tests to verify the fix: - test_voting_mutations_graphql.py: 2 new IDOR tests - test_permission_fixes.py: 4 new IDOR tests Removed security assessment report as requested.
Code Review SummaryThis PR addresses IDOR (Insecure Direct Object Reference) vulnerabilities in 6 GraphQL mutations by unifying error messages for "not found" and "permission denied" cases. This prevents attackers from enumerating valid object IDs by observing different error responses. ✅ Strengths
🔍 Issues FoundCritical Issue: Missing CHANGELOG EntrySeverity: 🔴 CRITICAL (Per CLAUDE.md requirements) The CHANGELOG.md was not updated for these security fixes. According to CLAUDE.md:
Required Fix: Add a section like this to CHANGELOG.md under the most recent Unreleased heading: ### Security
#### IDOR Vulnerabilities Fixed in 6 GraphQL Mutations (PR #800)
- **HIGH**: Fixed information leakage allowing object ID enumeration via different error messages
- `RemoveAnnotation` (`config/graphql/mutations.py:2173-2209`)
- `RejectAnnotation` (`config/graphql/mutations.py:2226-2266`)
- `ApproveAnnotation` (`config/graphql/mutations.py:2289-2329`)
- `RemoveRelationship` (`config/graphql/mutations.py:2450-2490`)
- `VoteMessageMutation` (`config/graphql/voting_mutations.py:68-110`)
- `RemoveVoteMutation` (`config/graphql/voting_mutations.py:148-175`)
- **Attack Vector**: Unauthorized users could distinguish between "object doesn't exist" and "object exists but you can't access it" by observing different error messages
- **Impact**: Allowed enumeration of valid annotation/relationship/message IDs
- **Solution**: All mutations now use `visible_to_user()` pattern with unified error messages
- **Test Coverage**: Added 376 lines of IDOR protection tests in `test_permission_fixes.py` and `test_voting_mutations_graphql.py`Minor Issue: Test File Docstring Claims 7 VulnerabilitiesFile: The docstring states:
But this PR only fixes 6 mutations (as stated in the commit message). The 7th mentioned in REMEDIATION_GUIDE.md is Recommendation: Update the docstring to be more accurate: """
Tests for IDOR protection fixes in GraphQL mutations.
This test suite verifies IDOR vulnerabilities have been properly fixed
by ensuring identical error messages for 'not found' vs 'no permission' cases.
Tests cover mutations fixed in PR #800:
1. RemoveAnnotation - Cannot delete annotations without permission
2. RejectAnnotation - Cannot reject annotations without permission
3. ApproveAnnotation - Cannot approve annotations without permission
4. RemoveRelationship - Cannot delete relationships without permission
5. VoteMessage - Cannot vote on inaccessible messages
6. RemoveVote - Cannot remove votes on inaccessible messages
Also includes tests for previous security fixes (badges, moderation, etc.)
"""🎯 Code Quality Observations1. Voting Mutations: Permission Check SimplificationFiles: The refactor removed redundant permission checks, which is good: Before: message = ChatMessage.objects.get(pk=pk) # Get message
conversation = message.conversation
if not user_has_permission_for_obj(user, conversation, PermissionTypes.READ): # Check conversation
return errorAfter: message = ChatMessage.objects.visible_to_user(user).get(pk=pk) # Automatically checks conversation permissionsThis is cleaner because 2. Nested Try-Except PatternFiles: The nested try-except blocks work correctly but could be slightly more elegant: try:
user = info.context.user
annotation_pk = from_global_id(annotation_id)[1]
try: # Inner try for visible_to_user
annotation_obj = Annotation.objects.visible_to_user(user).get(pk=annotation_pk)
except Annotation.DoesNotExist:
return RemoveAnnotation(...)
# ... rest of logic
except Exception as e: # Outer try for unexpected errors
logger.error(...)Alternative pattern (used in other mutations like try:
annotation = Annotation.objects.visible_to_user(user).get(pk=pk)
except (ObjectDoesNotExist, Annotation.DoesNotExist):
return RejectAnnotation(...)Both patterns are functionally equivalent. The dual-exception catch is slightly more defensive (catches both generic and model-specific DoesNotExist). 3. Test Quality: Excellent Assertion MessagesThe new tests include excellent descriptive messages: self.assertEqual(
error_msg_no_perm,
error_msg_not_found,
"IDOR vulnerability: Different error messages allow enumeration of valid message IDs",
)This makes debugging test failures very clear. 👍 🛡️ Security AnalysisIDOR Protection: ✅ Correct ImplementationThe pattern used is the correct approach per OpenContracts' security standards:
Edge Case: What if
|
Coupled return messages were leaking obj existence