Skip to content

Security: tomer-bdbd/SOTI-MobiControl-GraphQL-wrapper

Security

docs/SECURITY.md

Security Documentation

This document describes the security measures implemented in the SOTI MobiControl GraphQL Wrapper to protect against common attack vectors.


Table of Contents

  1. Overview
  2. GraphQL Security
  3. Input Validation
  4. Injection Prevention
  5. HTTP Security Headers
  6. CORS Configuration
  7. Logging
  8. Configuration Reference
  9. Security Checklist

Overview

This GraphQL wrapper implements a "Defense in Depth" security architecture with protections at multiple layers:

Layer Protection
Network CORS, Security Headers
GraphQL Introspection control, Depth limiting, Cost analysis
Input Pydantic validation, URL encoding
Output Error masking, Logging sanitization

GraphQL Security

Introspection Control

What it does: Disables GraphQL schema introspection in production environments.

Why it matters: Introspection allows clients to query the entire schema structure (__schema, __type). Attackers can use this to understand the API surface and find potential vulnerabilities.

Implementation: app/security/extensions.py - DisableIntrospection

# Introspection queries like this are blocked in production:
query {
  __schema {
    types { name }
  }
}

Configuration: Automatically enabled when ENVIRONMENT=prod.


Query Depth Limiting

What it does: Limits the nesting depth of GraphQL queries to 6 levels.

Why it matters: Deeply nested queries can cause exponential resource consumption, leading to denial of service.

Implementation: Uses Strawberry's built-in QueryDepthLimiter.

# This query would be rejected (too deep):
query {
  devices {           # Level 1
    items {           # Level 2
      customAttributes { # Level 3
        name          # Level 4
        nested {      # Level 5
          field {     # Level 6
            too {     # Level 7 - REJECTED
              deep
            }
          }
        }
      }
    }
  }
}

Error Response:

{
  "errors": [{
    "message": "Query is too deep (max depth: 6)"
  }]
}

Query Cost Analysis

What it does: Calculates a "cost" for each query and rejects queries that exceed the maximum allowed cost (1000).

Why it matters: Prevents resource exhaustion attacks where attackers craft expensive queries that consume excessive server resources.

Implementation: app/security/extensions.py - QueryCostLimiter

Cost Factors:

  • Number of fields requested
  • Query depth
  • List fields (multiplied by 2)

Error Masking

What it does: Hides internal error details in production, showing only safe error messages to clients.

Why it matters: Stack traces and internal error messages can reveal sensitive information about the system architecture, file paths, and potential vulnerabilities.

Implementation: app/security/extensions.py - ErrorMaskingExtension

Safe errors (shown to clients):

  • Input validation errors (ValueError)
  • Permission errors (PermissionError)
  • Introspection blocked messages
  • Query complexity errors

Masked errors (hidden in production):

  • Database errors
  • Internal exceptions
  • Stack traces

Example:

Development response:

{
  "errors": [{
    "message": "ConnectionRefusedError: [Errno 111] Connection refused",
    "path": ["device"],
    "extensions": {
      "exception": "..."
    }
  }]
}

Production response:

{
  "errors": [{
    "message": "Internal server error"
  }]
}

Input Validation

All user inputs are validated using Pydantic before processing.

Location: app/security/validation.py

Device ID Validation

# Valid: alphanumeric, hyphens, underscores, periods
"device-123"
"abc_def.456"

# Invalid: path traversal, special characters
"../../../etc/passwd"  # REJECTED
"device;DROP TABLE"    # REJECTED

Group ID Validation

# Valid: includes backslashes for Windows-style paths
"\\Root\\MyGroup"

# Invalid: path traversal
"..\\..\\secret"  # REJECTED

Pagination Validation

# Valid ranges:
skip: 0 - 10000
take: 1 - 100

# Invalid values are clamped:
take: 500  -> 100 (max)
skip: -1   -> 0 (min)

Injection Prevention

URL Path Encoding

All path parameters are URL-encoded before being sent to the MobiControl REST API.

Location: app/client/mobicontrol.py

Before (vulnerable):

# Attacker could inject: device_id = "../../../admin"
f"/devices/{device_id}"

After (secure):

# Path traversal is encoded: %2E%2E%2F%2E%2E%2F%2E%2E%2Fadmin
f"/devices/{quote(device_id, safe='')}"

HTTP Security Headers

The following security headers are added to all responses.

Location: app/main.py - SecurityHeadersMiddleware

Header Value Purpose
X-Content-Type-Options nosniff Prevents MIME type sniffing
X-Frame-Options DENY Prevents clickjacking
X-XSS-Protection 1; mode=block Enables XSS filter
Referrer-Policy strict-origin-when-cross-origin Controls referrer info
Strict-Transport-Security max-age=31536000; includeSubDomains Enforces HTTPS (prod only)
Content-Security-Policy default-src 'self'; script-src 'none' Restricts resource loading (prod only)

CORS Configuration

Cross-Origin Resource Sharing is configured based on environment.

Development Mode (ENVIRONMENT=dev)

Permissive CORS for easier development:

allow_origins=["*"]
allow_methods=["*"]
allow_headers=["*"]

Production Mode (ENVIRONMENT=prod)

Strict CORS with allowlist:

allow_origins=settings.cors_origins  # From config
allow_methods=["GET", "POST", "OPTIONS"]
allow_headers=["Authorization", "Content-Type"]

Configuration

Set allowed origins in .env:

CORS_ORIGINS=["https://your-frontend.com","https://admin.your-domain.com"]

Logging

Log Levels by Environment

Environment Log Level Details
dev DEBUG Full debug information
test INFO Standard operational logs
prod INFO Minimal, no sensitive data

What's Logged

  • GraphQL query errors (full details logged internally, masked externally)
  • Security events (introspection attempts, rejected queries)
  • Authentication events (token refresh, failures)

What's NOT Logged

  • Passwords or secrets (masked with ***)
  • Full tokens (only first 8 characters shown)
  • User PII

Configuration Reference

Environment Variables

Variable Default Description
ENVIRONMENT dev Environment: dev, test, prod
DEBUG false Enable debug mode (dev only)
CORS_ORIGINS ["http://localhost:3000"] Allowed CORS origins (JSON array)

Security Defaults

Setting Value Configurable
Max query depth 6 No (code change required)
Max query cost 1000 No (code change required)
Max pagination take 100 No (hardcoded)
Max pagination skip 10000 No (hardcoded)

Security Checklist

Before deploying to production, verify:

  • ENVIRONMENT=prod is set
  • DEBUG=false is set
  • CORS_ORIGINS contains only trusted domains
  • GraphQL introspection returns error
  • Deep queries (>6 levels) are rejected
  • Security headers are present in responses
  • Error responses don't contain stack traces
  • Logs don't contain sensitive data

Testing Security Features

# Test introspection is disabled (should fail in prod)
curl -X POST http://localhost:8000/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{ __schema { types { name } } }"}'

# Test deep query rejection
curl -X POST http://localhost:8000/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{ devices { items { customAttributes { name } } } }"}'

# Verify security headers
curl -I http://localhost:8000/health

Reporting Security Issues

If you discover a security vulnerability, please report it responsibly:

  1. Do not open a public GitHub issue
  2. Email the maintainers directly with details
  3. Allow reasonable time for a fix before disclosure

References

There aren't any published security advisories