Hasura GraphQL Engine Security Testing: Admin Secret Bypass, Introspection, and Role Misconfiguration

Hasura GraphQL Engine automatically generates a real-time GraphQL API on top of PostgreSQL, MySQL, SQL Server, and other databases, making it extremely popular for rapid API development. Its security model requires careful assessment: if HASURA_GRAPHQL_ADMIN_SECRET is not set, Hasura exposes the entire database schema as a GraphQL API with no authentication required — any unauthenticated request can query all tables; the admin secret, when set, should never be leaked to client applications as it bypasses all row-level security and permission rules; Hasura's permission system relies on JWT claims or webhook authentication, but the session variable headers x-hasura-role and x-hasura-user-id must be extracted from a verified JWT — if the application forwards user-controlled headers directly to Hasura, privilege escalation is possible; GraphQL introspection is enabled by default in Hasura and reveals the complete schema; event triggers and remote schema integrations create SSRF surfaces when Hasura makes outbound requests to configured webhook endpoints; and the /v1/metadata endpoint exports the entire permission configuration when admin secret is known. This guide covers systematic Hasura security assessment.

Table of Contents

  1. Hasura Discovery and Version Detection
  2. Missing Admin Secret Testing
  3. Schema Introspection
  4. Role Header Injection
  5. Event Trigger SSRF
  6. Hasura Security Hardening

Hasura Discovery and Version Detection

# Hasura default port: 8080 (also commonly deployed on 443/80 behind a proxy)
# Common paths: /v1/graphql, /v2/query, /healthz, /v1/version

# Check Hasura health and version
curl -s http://hasura.example.com:8080/healthz 2>/dev/null
# Returns: {"status":"OK"} if Hasura is running

curl -s http://hasura.example.com:8080/v1/version 2>/dev/null | \
  python3 -c "
import json,sys
data = json.load(sys.stdin)
print(f\"Hasura {data.get('version','?')} ({data.get('edition','?')})\")
" 2>/dev/null

# Check if Hasura console is accessible (reveals admin access)
curl -s -o /dev/null -w "%{http_code}" \
  http://hasura.example.com:8080/console 2>/dev/null

Missing Admin Secret Testing

# Without HASURA_GRAPHQL_ADMIN_SECRET set, all requests are admin by default
# Test by querying any table — if it returns data, no auth is configured

# Check if admin secret is required
curl -s -X POST http://hasura.example.com:8080/v1/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{ __typename }"}' 2>/dev/null | \
  python3 -c "
import json,sys
data = json.load(sys.stdin)
errors = data.get('errors', [])
if errors:
    for e in errors:
        print(f\"Auth required: {e.get('message','?')}\")
else:
    print('NO AUTH REQUIRED — Hasura accessible without admin secret')
    print(f'Response: {str(data)[:200]}')
" 2>/dev/null

# If auth is not required, enumerate all tables via introspection
curl -s -X POST http://hasura.example.com:8080/v1/graphql \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ __schema { queryType { fields { name description } } } }"
  }' 2>/dev/null | \
  python3 -c "
import json,sys
data = json.load(sys.stdin)
fields = data.get('data',{}).get('__schema',{}).get('queryType',{}).get('fields',[])
print(f'Query fields: {len(fields)}')
# Each field corresponds to a database table
for f in fields[:20]:
    print(f\"  {f.get('name','?')}\")
if len(fields) > 20:
    print(f'  ... and {len(fields)-20} more')
" 2>/dev/null

Role Header Injection

# Hasura trusts x-hasura-role and x-hasura-user-id headers when passed with a valid JWT
# Vulnerability: some integrations forward user-controlled headers directly to Hasura
# OR: JWT validation is weak allowing forged tokens

# Test if x-hasura-role: admin is trusted without a valid JWT
# (This should be blocked — Hasura should require admin secret for admin role without JWT)
curl -s -X POST http://hasura.example.com:8080/v1/graphql \
  -H "Content-Type: application/json" \
  -H "x-hasura-role: admin" \
  -H "x-hasura-user-id: 1" \
  -d '{"query": "{ __typename }"}' 2>/dev/null | \
  python3 -c "
import json,sys
data = json.load(sys.stdin)
errors = data.get('errors', [])
if not errors:
    print('VULNERABILITY: x-hasura-role header accepted without admin secret or JWT')
else:
    print('Headers correctly rejected without valid auth')
" 2>/dev/null

# Test JWT with injected role claims (if using JWT auth mode)
# A valid JWT with x-hasura-role: admin in claims bypasses all permissions
# This tests whether the application generates JWTs with insufficient claim validation

Hasura Security Hardening

Hasura Security Hardening Checklist:
Security TestMethodRisk
Admin secret not configuredPOST /v1/graphql without credentials — 200 with data means full database access without authCritical
Admin secret leaked in client applicationReview JS bundles, mobile apps, browser devtools — admin secret grants full database bypassCritical
Introspection reveals full schemaPOST __schema query — enumerates all tables, relationships, and permission-restricted fieldsHigh
x-hasura-role header injectionSend x-hasura-role: admin without admin secret — tests if role bypass is possibleHigh
Excessive permissions per roleAuthenticated query as non-admin role — test what tables and operations are accessible vs expectedHigh
Metadata API exposes permission rulesPOST /v1/metadata with admin secret — exports all table permissions and RLS configurationMedium

Automate Hasura Security Testing

Ironimo tests Hasura deployments for missing admin secret giving full unauthenticated database access, GraphQL introspection enabled in production revealing complete schema and table structure, x-hasura-role header injection bypassing row-level security permissions, console and developer mode accessible in production environments, event trigger webhook endpoints reachable from the internet enabling SSRF, and admin secret exposure in client application bundles or API responses.

Start free scan