SentinelDesk
A Secure-by-Design SOAR (Security Orchestration, Automation, and Response) platform demonstrating enterprise security patterns through real implementation.
Overview
SentinelDesk is a Security Orchestration, Automation, and Response (SOAR) platform designed to demonstrate modern DevSecOps principles. Unlike typical CRUD apps, it features a built-in Threat Detection Engine that monitors user activity in real-time to identify anomalies like impossible travel, brute force attempts, and privilege escalation.
The project includes a "Simulation Mode" that generates realistic attack traffic, allowing users to watch the detection logic trigger alerts and automate incident response workflows.
Demo Screenshots
*Click images to view the specific security controls and detection dashboards.*
1. Identity & Access Management (IAM)
Authentication is handled through Microsoft Entra ID (formerly Azure AD) using the OIDC protocol. This provides enterprise-grade SSO without managing passwords directly.
Key Implementation Details
- OIDC Flow: Modern authentication with ID tokens and access tokens
- JWT Validation: Backend verifies token signatures, issuers, and audiences
- No Blind Trust: Every request is validated—no session-based assumptions
# FastAPI backend - JWT token validation
async def verify_bearer_token(request: Request) -> Dict[str, Any]:
auth = request.headers.get("authorization")
if not auth or not auth.lower().startswith("bearer "):
raise HTTPException(status_code=401, detail="Missing bearer token")
token = auth.split(" ", 1)[1].strip()
jwks = await _get_jwks()
try:
# Microsoft Entra ID token validation
# Verify signature, issuer, and audience against JWKS
claims = jwt.decode(
token,
jwks,
algorithms=["RS256"],
audience=settings.api_audience,
)
# Manual issuer check against valid Entra ID endpoints
token_issuer = claims.get("iss", "")
if token_issuer not in valid_issuers:
raise HTTPException(status_code=401, detail="Invalid issuer")
return claims
except JWTError as e:
raise HTTPException(status_code=401, detail="Invalid token") 2. Role-Based Access Control (RBAC)
The platform implements least-privilege access with three distinct roles:
RBAC Matrix
| Action | Viewer | Analyst | Admin |
|---|---|---|---|
| View own incidents | ✓ | ✓ | ✓ |
| View all incidents | ✗ | ✓ | ✓ |
| Update incidents | ✗ | ✓ | ✓ |
| Delete incidents | ✗ | ✗ | ✓ |
| View security alerts | ✗ | ✓ | ✓ |
| Manage users | ✗ | ✗ | ✓ |
# Granular RBAC Implementation
ROLE_PERMS: Dict[str, Set[str]] = {
"viewer": {"tickets:read", "tickets:create", "tickets:comment"},
"analyst": {"tickets:read", "tickets:create", "tickets:comment", "tickets:update"},
"admin": {"tickets:read", "tickets:create", "tickets:update", "tickets:delete", "admin:export_audit"},
}
def require_perm(claims: dict, perm: str) -> None:
"""Check if user has a role that includes the required permission."""
roles = roles_from_claims(claims) # Maps Entra Group IDs to roles
allowed = set()
for r in roles:
allowed |= ROLE_PERMS.get(r, set())
if perm not in allowed:
# This triggers an "authz:denied" audit event in the caller
raise HTTPException(status_code=403, detail=f"Missing permission: {perm}") 3. Threat Detection Engine
The platform includes real-time detection for common attack patterns:
Impossible Travel
Detects logins from geographically distant IPs within short timeframes. If you can't physically travel that fast, it's likely compromised credentials.
Privilege Escalation Attempts
Flags repeated 403 Forbidden responses on sensitive endpoints. Pattern of access denials may indicate an attacker probing for vulnerabilities.
IDOR Protection
Explicit ownership checks on every resource access. Changing an ID in the URL won't give you access to someone else's data.
# Real-time Threat Detection Logic
def run_detections_for_event(db: Session, event: AuditEvent):
"""Run detection rules against every new audit event."""
# Rule 1: Auth failure burst (Brute Force / Credential Stuffing)
if event.action == "auth:token_invalid" and event.ip:
recent = count_recent_events(action="auth:token_invalid", ip=event.ip, minutes=5)
if recent >= 10:
_create_alert(db, "AUTH_FAIL_BURST", "high", {"ip": event.ip, "count": recent})
# Rule 2: Impossible Travel (Same user, different IPs < 5 mins)
if event.action == "auth:login" and event.actor_sub:
unique_ips = get_recent_distinct_ips(event.actor_sub, minutes=5)
if len(unique_ips) >= 2:
_create_alert(db, "IMPOSSIBLE_TRAVEL", "high", {"ips": unique_ips})
# Rule 3: Privilege Escalation (Repeated Admin Access Denials)
if event.action == "authz:denied" and "admin:" in event.target:
denials = count_recent_denials(event.actor_sub, target_match="admin:", minutes=10)
if denials >= 3:
_create_alert(db, "PRIVILEGE_ESCALATION_ATTEMPT", "high", {"count": denials}) 4. Vulnerability Lab: IDOR Demo
To demonstrate the importance of secure coding, the project features a "Vulnerability Lab" with two parallel endpoints for education:
-
GET /tickets/insecure/{id}: A naive implementation vulnerable to IDOR. Any authenticated user can view any ticket by changing the ID. -
GET /tickets/{id}: The secure implementation. It explicitly checksif ticket.owner_sub == current_user.subbefore returning data.
Attempting to exploit the secure endpoint triggers a "BLOCKED_IDOR_ATTEMPT" alert in the system.
# VULNERABILITY LAB DEMO
# ❌ INSECURE ENDPOINT (Vulnerable to IDOR)
@router.get("/tickets/insecure/{ticket_id}")
async def get_ticket_insecure(ticket_id: int):
# DANGEROUS: No ownership check! ANY authenticated user can view this.
return db.query(Ticket).filter(Ticket.id == ticket_id).first()
# ✅ SECURE ENDPOINT
@router.get("/tickets/{ticket_id}")
async def get_ticket_secure(ticket_id: int, claims: dict = Depends(verify_bearer_token)):
ticket = db.query(Ticket).filter(Ticket.id == ticket_id).first()
# Secure ownership check
current_user_sub = claims["sub"]
if ticket.owner_sub != current_user_sub:
# Trigger high-severity alert for immediate response
_create_alert(db, "BLOCKED_IDOR_ATTEMPT", "medium", {"target": ticket_id})
raise HTTPException(status_code=403, detail="Forbidden")
return ticket 5. Attack Simulation & Audit Logging
A key feature of the platform is the Attack Simulator, capable of generating realistic threat patterns to test the detection engine.
The simulator programmatically generates audit events (e.g., login from New York followed by Login from Tokyo) to verify that "Impossible Travel" alerts are triggered correctly. This closes the feedback loop, ensuring detections actually work in practice.
6. DevSecOps Pipeline
Security isn't just about the code—it's about the entire development lifecycle.
Static analysis for Python security issues
Dynamic scanning for runtime vulnerabilities
Infrastructure Security
- Multi-stage Docker builds: Minimal attack surface in production images
- Rate limiting: 100 requests/minute to prevent abuse
- Security headers: CSP, HSTS, X-Frame-Options configured
- Audit logging: Every security-relevant action is logged
Key Takeaways
Security is not a feature—it's a property of how the system is designed and implemented.
SentinelDesk demonstrates that security can be built in from day one without sacrificing developer experience or functionality. The patterns shown here—OIDC authentication, server-side RBAC, threat detection, and DevSecOps—are directly applicable to enterprise environments.