-
-
Notifications
You must be signed in to change notification settings - Fork 313
Security Dashboard (Phase 1 Implementation) #5113
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
Conversation
|
π Hi @Nachiket-Roy! This pull request needs a peer review before it can be merged. Please request a review from a team member who is not:
Once a valid peer review is submitted, this check will pass automatically. Thank you! |
WalkthroughAdds a Security Incident feature: new models and migration, serializer and API ViewSet, dashboard and CRUD views with forms/templates (CSV export, filtering, history tracking), a template filter, and URL/router registrations β the router and UI routes are registered twice in the same Changes
Sequence Diagram(s)mermaid Estimated code review effortπ― 4 (Complex) | β±οΈ ~45 minutes
Pre-merge checks and finishing touchesβ Failed checks (1 warning)
β Passed checks (2 passed)
β¨ Finishing touches
π§ͺ Generate unit tests (beta)
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. Comment |
β Pre-commit checks failedThe pre-commit hooks found issues that need to be fixed. Please run the following commands locally to fix them: # Install pre-commit if you haven't already
pip install pre-commit
# Run pre-commit on all files
pre-commit run --all-files
# Or run pre-commit on staged files only
pre-commit runAfter running these commands, the pre-commit hooks will automatically fix most issues. π‘ Tip: You can set up pre-commit to run automatically on every commit by running: pre-commit installPre-commit outputFor more information, see the pre-commit documentation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
π§Ή Nitpick comments (4)
website/serializers.py (1)
17-24: SecurityIncidentSerializer wiring looks correct; tuple-ize Meta fields if you want to appease RuffThe serializer cleanly mirrors the
SecurityIncidentmodel and marks IDs/timestamps readβonly, which is appropriate for the API.If you want to avoid
RUF012on mutable class attributes and make intent clearer, you can switchfields/read_only_fieldsto tuples:class SecurityIncidentSerializer(serializers.ModelSerializer): class Meta: model = SecurityIncident - fields = [ - "id", - "title", - "severity", - "status", - "affected_systems", - "created_at", - "resolved_at", - ] - read_only_fields = ["id", "created_at", "resolved_at"] + fields = ( + "id", + "title", + "severity", + "status", + "affected_systems", + "created_at", + "resolved_at", + ) + read_only_fields = ("id", "created_at", "resolved_at")Also applies to: 299-311
website/templates/security/dashboard.html (1)
4-4: Template is solid; consider a few small UX/maintainability tweaks
- The CSV export link preserves filters but not the current sort. If you want CSV ordering to match the selected sort, you could append
&sort={{ current_sort }}whencurrent_sortis present.- In the βRelated Security Issuesβ section, the link is hardβcoded as
/issue/{{ issue.id }}/. Usingissue.get_absolute_urlor{% url 'issue_view' slug=issue.id %}would be more robust if URL patterns change.- The chart script assumes
severity_chart_dataalways parses to an array. As a defensive guard, you could earlyβreturn if!Array.isArray(rawData)before calling.mapto avoid runtime errors if the context is ever misconfigured.Also applies to: 71-72, 239-259, 269-301
website/models.py (1)
3416-3441: SecurityIncident model is correct; consider Meta ordering, indexes, and strThe model definition matches the migration and how the API/dashboard use it, so itβs functionally fine.
Given you routinely:
- order by
created_at, and- filter/aggregate on
severityandstatus,it would be worth adding a small Meta block and a string repr, e.g.:
class SecurityIncident(models.Model): @@ affected_systems = models.TextField(blank=True) created_at = models.DateTimeField(auto_now_add=True) resolved_at = models.DateTimeField(null=True, blank=True) + + class Meta: + ordering = ["-created_at"] + indexes = [ + models.Index(fields=["created_at"], name="secincident_created_idx"), + models.Index(fields=["severity"], name="secincident_severity_idx"), + models.Index(fields=["status"], name="secincident_status_idx"), + ] + + def __str__(self) -> str: + return f"[{self.get_severity_display()}] {self.title}"That will make admin lists and logs clearer and give you cheap query wins as the table grows.
website/views/security.py (1)
15-27: Review access control for security incidents/dashboard
SecurityDashboardViewusesLoginRequiredMixin, and the corresponding API viewset usesIsAuthenticated, so any loggedβin user can see all security incidents and affected systems.If these incidents are intended to be internal or sensitive, consider tightening this to staff/admins or a dedicated permission group (e.g.
user.is_staffcheck,UserPassesTestMixin, or a custom DRF permission) before rolling this out broadly.
π Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting
π Files selected for processing (7)
blt/urls.py(4 hunks)website/api/views.py(4 hunks)website/migrations/0257_securityincident.py(1 hunks)website/models.py(1 hunks)website/serializers.py(2 hunks)website/templates/security/dashboard.html(1 hunks)website/views/security.py(1 hunks)
π§° Additional context used
𧬠Code graph analysis (4)
website/serializers.py (1)
website/models.py (1)
SecurityIncident(3416-3441)
blt/urls.py (2)
website/api/views.py (1)
SecurityIncidentViewSet(1323-1344)website/views/security.py (1)
SecurityDashboardView(15-133)
website/api/views.py (2)
website/models.py (3)
SecurityIncident(3416-3441)Severity(3417-3421)Status(3423-3426)website/serializers.py (1)
SecurityIncidentSerializer(299-311)
website/views/security.py (1)
website/models.py (3)
SecurityIncident(3416-3441)Severity(3417-3421)Status(3423-3426)
πͺ Ruff (0.14.6)
website/serializers.py
302-310: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
311-311: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
website/api/views.py
1324-1324: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
1326-1326: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
1336-1336: Local variable allowed_statuses is assigned to but never used
Remove assignment to unused variable allowed_statuses
(F841)
website/views/security.py
112-112: Comment contains ambiguous Γ (MULTIPLICATION SIGN). Did you mean x (LATIN SMALL LETTER X)?
(RUF003)
website/migrations/0257_securityincident.py
7-9: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
11-38: Mutable class attributes should be annotated with typing.ClassVar
(RUF012)
π Additional comments (2)
blt/urls.py (1)
40-48: API and dashboard routes for security incidents are correctly wiredThe new router registration and the
security/dashboard/URL are consistent with the view names and the templateβs{% url 'security_dashboard' %}usage. No functional issues spotted here.Also applies to: 301-301, 391-391, 1205-1205
website/migrations/0257_securityincident.py (1)
1-38: Migration matches the SecurityIncident model and looks goodFields, choices, and defaults align with the
SecurityIncidentmodel inwebsite/models.py. This migration is fine to apply asβis; RuffβsRUF012warning on migration class attributes can be safely ignored.
β Pre-commit checks failedThe pre-commit hooks found issues that need to be fixed. Please run the following commands locally to fix them: # Install pre-commit if you haven't already
pip install pre-commit
# Run pre-commit on all files
pre-commit run --all-files
# Or run pre-commit on staged files only
pre-commit runAfter running these commands, the pre-commit hooks will automatically fix most issues. π‘ Tip: You can set up pre-commit to run automatically on every commit by running: pre-commit installPre-commit outputFor more information, see the pre-commit documentation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
π§Ή Nitpick comments (1)
blt/urls.py (1)
393-393: Consider consistent naming for basename.The basename
"securityincident"(no hyphen) differs from the route"security-incidents"(with hyphen). While functional, usingbasename="security-incidents"would maintain consistency when reverse-resolving URLs likereverse('security-incidents-list').Apply this diff for consistency:
-router.register(r"security-incidents", SecurityIncidentViewSet, basename="securityincident") +router.register(r"security-incidents", SecurityIncidentViewSet, basename="security-incidents")
π Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting
π Files selected for processing (2)
blt/urls.py(4 hunks)website/models.py(1 hunks)
π§ Files skipped from review as they are similar to previous changes (1)
- website/models.py
β° Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Run Tests
- GitHub Check: docker-test
π Additional comments (3)
blt/urls.py (3)
40-40: LGTM!The imports for
SecurityIncidentViewSetandSecurityDashboardVieware correctly placed and follow the existing import structure.Also applies to: 303-303
1217-1217: Unfortunately, I'm unable to access the repository to verify the authentication implementation inSecurityDashboardView. The repository clone operation failed, preventing me from examining the actual view code.However, based on the security-sensitive nature of the dashboard and standard Django security practices, the original review comment raises a valid concern that requires manual verification.
Verify that
SecurityDashboardViewimplements proper authentication controls.The Security Dashboard URL pattern shows no visible authentication decorator. Since this view displays sensitive security incident data, confirm that
SecurityDashboardViewimplements required access controls such as:
LoginRequiredMixinor similar mixinspermission_classesattribute (for API views)- Permission checks in
dispatch()orget_queryset()methods- Role-based access verification before data retrieval
393-393: I was unable to directly access the repository code due to a clone failure, and the web search returned only general best practices rather than the specific implementation in the BLT project. Without access to the actualSecurityIncidentViewSetclass definition, I cannot definitively verify whether the permission configuration concerns raised in the review comment are valid or already addressed.To properly verify this review comment, I would need to:
- Access the
website/api/views.pyfile (or whereverSecurityIncidentViewSetis defined)- Check if
permission_classesis configured on the viewset- Verify if object-level permissions are implemented
- Confirm the specific permission classes used
Here is the rewritten review comment:
Verify API viewset permissions are configured on
SecurityIncidentViewSet.The
SecurityIncidentViewSetregistered at line 393 exposes security incident data via the/api/v1/security-incidents/endpoint. Ensure the viewset has appropriatepermission_classesconfigured (e.g.,IsAuthenticated, custom authorization) and implements object-level permission checks to prevent unauthorized access to sensitive incident data.
09c485d to
eb5286f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
π§Ή Nitpick comments (3)
website/templates/security/incidents/history_list.html (3)
488-512: Guardfetch(...).json()withresponse.okfor polling (avoid silent parse errors / masking 500s).function checkForUpdates() { - fetch(`${INCIDENT_API_BASE}history/updates/?since=${lastUpdate}`) - .then(response => response.json()) + fetch(`${INCIDENT_API_BASE}history/updates/?since=${lastUpdate}`) + .then(response => { + if (!response.ok) throw new Error(`HTTP ${response.status}`); + return response.json(); + }) .then(data => { if (data.updates && data.updates.length > 0) { lastUpdate = new Date().toISOString(); showToast('New changes detected', 'info'); ... }) .catch(() => { // Silently fail }); }
437-444: Handle clipboard failures (permission/HTTP context) and toast an error.copyButtons.forEach(button => { button.addEventListener('click', function () { const value = this.dataset.value; - navigator.clipboard.writeText(value).then(() => { - showToast('Value copied to clipboard', 'success'); - }); + navigator.clipboard.writeText(value) + .then(() => showToast('Value copied to clipboard', 'success')) + .catch(() => showToast('Copy failed (clipboard unavailable)', 'error')); }); });
268-331: Remove or wire up unused CSS selectors (.timeline-dot,.history-card,.history-grid) to reduce drift.Right now
.timeline-dotis never applied in markup, and the responsive rules reference classes not present. Either delete these blocks or apply the corresponding classes in the HTML so the intent is preserved.
π Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting
π Files selected for processing (1)
website/templates/security/incidents/history_list.html(1 hunks)
β° Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Run Tests
- GitHub Check: Run pre-commit
- GitHub Check: docker-test
β Tests failedThe Django tests found issues that need to be fixed. Please review the test output below and fix the failing tests. How to run tests locally# Install dependencies
poetry install --with dev
# Run all tests
poetry run python manage.py test
# Run tests with verbose output
poetry run python manage.py test -v 3
# Run a specific test
poetry run python manage.py test app.tests.TestClass.test_methodTest output (last 100 lines)For more information, see the Django testing documentation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
π Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting
π Files selected for processing (1)
website/templates/security/incidents/history_list.html(1 hunks)
β° Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Run Tests
- GitHub Check: docker-test
π Additional comments (2)
website/templates/security/incidents/history_list.html (2)
1-274: Overall: solid history UI + safer export path.
json_scriptforhistory-data, guarded export parsing, feature-flagged revert/polling, andhistory_count|default:0for JS safety are all good improvements.Also applies to: 324-590
1-4: Thereplacefilter is properly defined inwebsite/templatetags/custom_filters.pyand correctly implements the"old|new"format. The filter will successfully replace underscores with spaces as intended in the template.
|
Tip For best results, initiate chat on the files or code changes. π§© Analysis chainπ Script executed: Length of output: 2682 π Script executed: Length of output: 11026 π Script executed: Length of output: 1971 @Pritz395: I've thoroughly verified your review. Here's my detailed assessment: β Verified Claims - CONFIRMED
π Critical Issues - Assessment1. Missing exception handling for DoesNotExist
|
| Category | Status | Blocking? |
|---|---|---|
| Critical issues (1-3) | 1 minor, 2 false | β No |
| High priority (4-6) | All acceptable/false | β No |
| Medium priority (7-10) | 1 needs clarification, rest minor | β No |
| Security concerns (11-13) | 1 false, 1 minor, 1 deferred | β No |
| Code quality (14-15) | All acceptable | β No |
β Conclusion
Your review was thorough, but several concerns are based on misunderstandings:
- History comparison logic is CORRECT (old_instance captured before save)
- CSV sanitizer is OWASP-compliant (only first char matters)
- Race conditions are prevented (select_for_update + transaction.atomic)
- CSRF is present in templates
Minor valid concerns:
- Query efficiency in DetailView (non-blocking)
- Explain custom_filters.py change
- API rate limiting (defer to follow-up)
Verdict: β Production-ready after clarifying the custom_filters.py change. The code is secure, follows Django best practices, and properly implements audit history tracking.
@Nachiket-Roy: Please clarify why custom_filters.py was modified. If the replace filter isn't used in security templates, consider removing it from this PR to keep scope focused.
|
Just clarify the custom_filters.py change, it's been addressed by coderabbit above and also enforce the issue number in the PR description as well. Overall, Great PR! |
|
@Pritz395 Iβve linked the issue in desc but this PR alone does not cover the entire issue. That's why Iβm not using Fixes: or Closes: so that merging this PR does not automatically close the original issue. |
Oh right, this PR dissolves a part of the full scope, thanks for reminding me! Also, consider adding a comment above the custom filter code-logic to help clarify it for others as well. Thanks for clarifying it with me! |
|
Thank you! Please test it out and report any issues |
|
@coderabbitai We need to create a follow-up PR to include tests from this PR. Could you provide some guidance? |
|
Tip For best results, initiate chat on the files or code changes. π§© Analysis chainπ Script executed: Length of output: 10447 @Nachiket-Roy: Great question! Here's comprehensive guidance for writing tests for the Security Dashboard feature, organized by test category and following BLT's existing patterns. π Test File StructureCreate these test files in π§ͺ 1. Model Tests (
|
|
@coderabbitai the test coverage for this PR is submitted in another pr. so moving forwarding what things need to be done in the phase 2? |
|
Tip For best results, initiate chat on the files or code changes. @Nachiket-Roy: Great progress on Phase 1! π Here's my recommendation for Phase 2, designed to be a focused, reviewable PR: π― Recommended Phase 2: User Behavior AnalyticsWhy this component next:
π Phase 2 Deliverables1. Models & Database (2 files)Add to class UserLoginEvent(models.Model):
"""Track user login attempts and patterns"""
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="login_events")
login_time = models.DateTimeField(auto_now_add=True)
ip_address = models.GenericIPAddressField()
user_agent = models.TextField()
location = models.CharField(max_length=100, blank=True) # City, Country
is_successful = models.BooleanField(default=True)
is_suspicious = models.BooleanField(default=False)
class Meta:
ordering = ["-login_time"]
indexes = [
models.Index(fields=["user", "-login_time"]),
models.Index(fields=["is_suspicious", "-login_time"]),
]
class UserAccessLog(models.Model):
"""Track access to sensitive resources"""
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="access_logs")
resource_type = models.CharField(max_length=50) # issue, domain, bounty, security_incident
resource_id = models.IntegerField()
action = models.CharField(max_length=50) # view, edit, delete, export
timestamp = models.DateTimeField(auto_now_add=True)
ip_address = models.GenericIPAddressField()
class Meta:
ordering = ["-timestamp"]
indexes = [
models.Index(fields=["user", "-timestamp"]),
models.Index(fields=["resource_type", "resource_id"]),
]Migration file: 2. Signal Handlers for Auto-Tracking (1 file)Create from django.contrib.auth.signals import user_logged_in, user_login_failed
from django.dispatch import receiver
from website.models import UserLoginEvent
@receiver(user_logged_in)
def log_successful_login(sender, request, user, **kwargs):
"""Log successful login event"""
UserLoginEvent.objects.create(
user=user,
ip_address=get_client_ip(request),
user_agent=request.META.get("HTTP_USER_AGENT", "")[:500],
is_successful=True,
is_suspicious=detect_suspicious_login(user, request)
)
@receiver(user_login_failed)
def log_failed_login(sender, credentials, request, **kwargs):
"""Log failed login attempt"""
# Implementation...
def detect_suspicious_login(user, request):
"""Basic anomaly detection"""
# Check for:
# - New IP address
# - Unusual time of day
# - Multiple failed attempts
# - Different geolocation
return False # PlaceholderRegister in def ready(self):
import website.signals.user_behavior_signals # noqa3. Dashboard Widget View (1 file)Update # Add to SecurityDashboardView.get_context_data()
# User Behavior Analytics section
recent_logins = UserLoginEvent.objects.filter(
is_suspicious=True
).select_related("user").order_by("-login_time")[:10]
failed_login_count = UserLoginEvent.objects.filter(
is_successful=False,
login_time__gte=timezone.now() - timedelta(days=7)
).count()
context.update({
"suspicious_logins": recent_logins,
"failed_login_count": failed_login_count,
"top_active_users": get_top_active_users(), # New helper
})4. Templates (2 files)Create
Update
5. API Endpoints (update 2 files)Update class UserLoginEventViewSet(viewsets.ReadOnlyModelViewSet):
"""API for user login events"""
authentication_classes = [SessionAuthentication, TokenAuthentication]
serializer_class = UserLoginEventSerializer
permission_classes = [IsAdminUser]
def get_queryset(self):
queryset = UserLoginEvent.objects.select_related("user")
if self.request.query_params.get("suspicious") == "true":
queryset = queryset.filter(is_suspicious=True)
return queryset.order_by("-login_time")Update class UserLoginEventSerializer(serializers.ModelSerializer):
username = serializers.CharField(source="user.username", read_only=True)
class Meta:
model = UserLoginEvent
fields = ["id", "username", "login_time", "ip_address", "is_successful", "is_suspicious"]6. URL Routing (1 file)Update router.register(r"user-login-events", UserLoginEventViewSet, basename="userloginevent")π Phase 2 Summary
Total: ~10 files, ~360 new lines β Manageable PR size β πΊοΈ Remaining Components (Post-Phase 2)Phase 3: Complete Vulnerability Management (~6-8 files)
Phase 4: Threat Intelligence Feed (~8-10 files)
Phase 5: Network Traffic Analysis (~10-12 files)
Phase 6: Compliance Monitoring (~10-12 files)
π― My RecommendationFor Phase 2: Focus exclusively on User Behavior Analytics as outlined above. It's:
Defer Threat Intel, Network Traffic, and Compliance to later phases as they're more complex and require external integrations. Would you like me to generate the specific code for any of the Phase 2 components? π |
This pr implements a part of this bigger issue : #1704
New Models
Added foundational security models for future expansion:
SecurityIncident
severity (low/medium/high/critical)
status (open/investigating/resolved)
affected systems
timestamps
Vulnerability
severity
description
affected component
Filtering by:
severity
status
Pagination (page-based)
Sorting by newest/oldest
All serializers and permissions are included.
custom filter - it is used to convert model field names into human-readable labels in templates. For example:
affected_systems β affected systems
resolved_at β resolved at
Security Dashboard Page
New page at:
/security/dashboard/
Includes:
Incident list (with filters + pagination)
Filter by severity
Filter by status
Pagination controls
Sorted newest β oldest
Severity breakdown chart
Counts per severity
Pre-wired for ApexCharts
Auto-updates based on DB values
Status breakdown chart
Counts per status
Dynamically generated
Related Security Issues section
Shows issues where label=4 (Security)
Styled cards
Each links to /issue//
Auto-fallback when no description
Whatβs Left / Future Phases
Phase 2 (Upcoming)
Add API endpoint for:
2 . /api/v1/vulnerabilities/ (list, filter, paginate, CRUD)
Phase 3
Threat Intelligence module
Store threat indicators
Display suspicious IPs/domains
Phase 4
ComplianceRule model
Compliance reports UI
Phase 5
Real-time updates via WebSockets (Django Channels)
Live incident feed
Toast alerts
Phase 6
UI polish, improved cards, severity indicators
Support for searching incidents and vulnerabilities
Summary by CodeRabbit
New Features
Database
βοΈ Tip: You can customize this high-level summary in your review settings.